Merge pull request #51 from ThakeeNathees/core-attrib

Core attributes refactored and added tests
This commit is contained in:
Thakee Nathees 2021-06-10 23:55:05 +05:30 committed by GitHub
commit 484aa5d88f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
55 changed files with 683 additions and 324 deletions

View File

@ -20,7 +20,7 @@
"Free and open source software under the terms of the MIT license.\n"
// Note that the cli itself is not a part of the pocketlang compiler, instead
// its a host application to run pocketlang from the command line. We're
// it's a host application to run pocketlang from the command line. We're
// embedding the pocketlang VM and we can only use its public APIs, not any
// internals of it, including assertion macros. So we're re-defining those
// macros here (like if it's a new project).
@ -34,8 +34,7 @@
// The internal assertion macro, this will print error and break regardless of
// the build target (debug or release). Use ASSERT() for debug assertion and
// use __ASSERT() for TODOs and assetions in public methods (to indicate that
// the host application did something wrong).
// use __ASSERT() for TODOs.
#define __ASSERT(condition, message) \
do { \
if (!(condition)) { \
@ -65,6 +64,7 @@
do { \
fprintf(stderr, "Execution reached an unreachable path\n" \
"\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \
DEBUG_BREAK(); \
abort(); \
} while (false)

View File

@ -3,7 +3,6 @@
* Distributed Under The MIT License
*/
#include <errno.h>
#include <pocketlang.h>
#include <stdio.h> /* defines FILENAME_MAX */
@ -29,7 +28,6 @@
// TODO: No error is handled below. I should check for path with size more than
// FILENAME_MAX.
// TODO: this macros should be moved to a general place of in cli.
#define TOSTRING(x) #x
@ -64,7 +62,6 @@ size_t pathJoin(const char* path_a, const char* path_b, char* buffer,
return cwk_path_join(path_a, path_b, buffer, buff_size);
}
/*****************************************************************************/
/* INTERNAL FUNCTIONS */
/*****************************************************************************/

View File

@ -3,16 +3,14 @@
* Distributed Under The MIT License
*/
// The REPL (Read Evaluate Print Loop) implementation.
// https://en.wikipedia.org/wiki/Readevalprint_loop.
// https://en.wikipedia.org/wiki/Read-eval-print_loop.
#include "common.h"
#include <ctype.h> // isspace
#include "utils.h"
// FIXME: use fgetc char by char till reach a new line.
//
// Read a line from stdin and returns it without the line ending. Accepting
@ -55,8 +53,6 @@ int repl(PKVM* vm, const PkCompileOptions* options) {
// The main module that'll be used to compile and execute the input source.
PkHandle* module = pkNewModule(vm, "$(REPL)");
// FIXME: Again it's temp for testing.
// A buffer to store lines read from stdin.
ByteBuffer lines;
byteBufferInit(&lines);
@ -120,6 +116,7 @@ int repl(PKVM* vm, const PkCompileOptions* options) {
} while (!done);
byteBufferClear(&lines);
pkReleaseHandle(vm, module);
return 0;

View File

@ -26,7 +26,6 @@ static inline int powerOf2Ceil(int n) {
/* BYTE BUFFER IMPLEMENTATION */
/*****************************************************************************/
void byteBufferInit(ByteBuffer* buffer) {
buffer->data = NULL;
buffer->count = 0;
@ -34,7 +33,7 @@ void byteBufferInit(ByteBuffer* buffer) {
}
void byteBufferClear(ByteBuffer* buffer) {
buffer->data = realloc(buffer->data, 0);
free(buffer->data);
buffer->data = NULL;
buffer->count = 0;
buffer->capacity = 0;

View File

@ -11,12 +11,11 @@ typedef struct {
uint32_t capacity;
} ByteBuffer;
/*****************************************************************************/
/* BYTE BUFFER */
/*****************************************************************************/
// Initialize a new buffer int instance.
// Initialize a new buffer int instance.
void byteBufferInit(ByteBuffer* buffer);
// Clears the allocated elements from the VM's realloc function.

View File

@ -7,9 +7,13 @@
- check for tabs and trailing white space in source.
they're not allowed.
- Hex, binary literals and floats like ".5".
- change or add => to_string() to value.as_string
and add as_repr, as_bool.
str_lower("UPPER") to "UPPER".lower
- Make bool and num are incompatible
1 + true - make it not allowed.
- support new line just after '=' (and other places)
@ -25,7 +29,6 @@
- Implement utf8 support.
- Implement gdb like debugger (add color print for readability).
- Initialize imported scripts (require fiber based vm).
- Hex, binary literals and floats like ".5".
- Complete all the TODO; macros.
- implement MAX_ARGC checks (would cause a buffer overflow if not)
when compiling and calling a function (also in fibers).

View File

@ -57,13 +57,14 @@ extern "C" {
// A convinent macro to define documentation of funcions. Use it to document
// your native functions.
//
// PK_DOC(foo,
// "The function will print 'foo' on the console.") {
// PK_DOC(
// "The function will print 'foo' on the console.",
// static void foo()) {
// printf("foo\n");
// }
//
#define PK_DOC(func, doc) \
/* TODO: static char __pkdoc__##func[] = doc;*/ static void func(PKVM* vm)
#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.

View File

@ -43,7 +43,6 @@
void pk##m_name##BufferWrite(pk##m_name##Buffer* self, \
PKVM* vm, m_type data); \
// The buffer "template" implementation of diferent types.
#define DEFINE_BUFFER(m_name, m_type) \
void pk##m_name##BufferInit(pk##m_name##Buffer* self) { \

View File

@ -113,6 +113,7 @@
do { \
fprintf(stderr, "Execution reached an unreachable path\n" \
"\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \
DEBUG_BREAK(); \
abort(); \
} while (false)
@ -141,7 +142,7 @@
// Using __ASSERT() for make it crash in release binary too.
#define TODO __ASSERT(false, "TODO: It hasn't implemented yet.")
#define OOPS "Oops a bug!! report plese."
#define OOPS "Oops a bug!! report please."
#define TOSTRING(x) #x
#define STRINGIFY(x) TOSTRING(x)
@ -149,6 +150,7 @@
// 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).
// Reference: https://www.cplusplus.com/reference/cstdio/printf/
#define DOUBLE_FMT "%.16g"
// Double number to string buffer size, used in sprintf with DOUBLE_FMT.
@ -169,4 +171,23 @@
// + 1 for null byte '\0'
#define STR_INT_BUFF_SIZE 12
// Integer number (double) to hex string buffer size.
// The maximum value an unsigned 64 bit integer can get is
// 0xffffffffffffffff which is 16 characters.
// + 16 for hex digits
// + 1 for sign '-'
// + 2 for '0x' prefix
// + 1 for null byte '\0'
#define STR_HEX_BUFF_SIZE 20
// Integer number (double) to bin string buffer size.
// The maximum value an unsigned 64 bit integer can get is
// 0b1111111111111111111111111111111111111111111111111111111111111111
// which is 64 characters.
// + 64 for bin digits
// + 1 for sign '-'
// + 2 for '0b' prefix
// + 1 for null byte '\0'
#define STR_BIN_BUFF_SIZE 68
#endif //PK_COMMON_H

View File

@ -194,7 +194,6 @@ static _Keyword _keywords[] = {
{ NULL, 0, (TokenType)(0) }, // Sentinal to mark the end of the array
};
/*****************************************************************************
* COMPILIER INTERNAL TYPES *
*****************************************************************************/
@ -302,7 +301,7 @@ typedef struct sForwardName {
typedef struct sFunc {
// Scope of the function. -2 for script body, -1 for top level function and
// Scope of the function. -2 for script body, -1 for top level function and
// literal functions will have the scope where it declared.
int depth;
@ -354,7 +353,7 @@ struct Compiler {
int forwards_count;
// True if the last statement is a new local variable assignment. Because
// the assignment is different than reqular assignment and use this boolean
// the assignment is different than reqular assignment and use this boolean
// to tell the compiler that dont pop it's assigned value because the value
// itself is the local.
bool new_local;
@ -409,8 +408,8 @@ static void reportError(Compiler* compiler, const char* file, int line,
char message[ERROR_MESSAGE_SIZE];
int length = vsprintf(message, fmt, args);
__ASSERT(length < ERROR_MESSAGE_SIZE, "Error message buffer should not exceed "
"the buffer");
__ASSERT(length < ERROR_MESSAGE_SIZE, "Error message buffer should not "
"exceed the buffer");
vm->config.error_fn(vm, PK_ERROR_COMPILE, file, line, message);
}
@ -917,7 +916,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
result.index = index;
return result;
}
// Search through functions.
index = scriptGetFunc(compiler->script, name, length);
if (index != -1) {
@ -1194,16 +1193,16 @@ static void exprName(Compiler* compiler) {
}
/* a or b: | a and b:
|
|
(...) | (...)
.-- jump_if [offset] | .-- jump_if_not [offset]
| (...) | | (...)
| (...) | | (...)
|-- jump_if [offset] | |-- jump_if_not [offset]
| push false | | push true
| push false | | push true
.--+-- jump [offset] | .--+-- jump [offset]
| '-> push true | | '-> push false
'----> (...) | '----> (...)
*/
*/
void exprOr(Compiler* compiler) {
emitOpcode(compiler, OP_JUMP_IF);
@ -1214,7 +1213,7 @@ void exprOr(Compiler* compiler) {
int true_offset_b = emitShort(compiler, 0xffff); //< Will be patched.
emitOpcode(compiler, OP_PUSH_FALSE);
emitOpcode(compiler, OP_JUMP);
emitOpcode(compiler, OP_JUMP);
int end_offset = emitShort(compiler, 0xffff); //< Will be patched.
patchJump(compiler, true_offset_a);
@ -1486,7 +1485,7 @@ static void parsePrecedence(Compiler* compiler, Precedence precedence) {
static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
Script* script, const PkCompileOptions* options) {
compiler->vm = vm;
compiler->next_compiler = NULL;
@ -1559,10 +1558,10 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
// Add the variable and return it's index.
if (compiler->scope_depth == DEPTH_GLOBAL) {
uint32_t name_index = scriptAddName(compiler->script, compiler->vm,
name, length);
pkUintBufferWrite(&compiler->script->global_names, compiler->vm, name_index);
pkVarBufferWrite(&compiler->script->globals, compiler->vm, VAR_NULL);
Script* script = compiler->script;
uint32_t name_index = scriptAddName(script, compiler->vm, name, length);
pkUintBufferWrite(&script->global_names, compiler->vm, name_index);
pkVarBufferWrite(&script->globals, compiler->vm, VAR_NULL);
return compiler->script->globals.count - 1;
} else {
@ -1726,7 +1725,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
name = LITERAL_FN_NAME;
name_length = (int)strlen(name);
}
Function* func = newFunction(compiler->vm, name, name_length,
compiler->script, fn_type == FN_NATIVE);
int fn_index = (int)compiler->script->functions.count - 1;
@ -1784,7 +1783,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
if (fn_type != FN_NATIVE) {
compileBlockBody(compiler, BLOCK_FUNC);
consume(compiler, TK_END, "Expected 'end' after function definition end.");
// TODO: This is the function end return, if we pop all the parameters the
// below push_null is redundent (because we always have a null at the rbp
// of the call frame. (for i in argc : emit(pop)) emit(return); but this
@ -2058,22 +2057,22 @@ static void compileFromImport(Compiler* compiler) {
const char* name = compiler->previous.start;
int length = compiler->previous.length;
int line = compiler->previous.line;
// Add the name of the symbol to the names buffer.
int name_index = (int)scriptAddName(compiler->script, compiler->vm,
name, length);
// Don't pop the lib since it'll be used for the next entry.
emitOpcode(compiler, OP_GET_ATTRIB_KEEP);
emitShort(compiler, name_index); //< Name of the attrib.
// Check if it has an alias.
if (match(compiler, TK_AS)) {
// Consuming it'll update the previous token which would be the name of
// the binding variable.
consume(compiler, TK_NAME, "Expected a name after 'as'.");
}
// Get the variable to bind the imported symbol, if we already have a
// variable with that name override it, otherwise use a new variable.
const char* name_start = compiler->previous.start;
@ -2085,7 +2084,7 @@ static void compileFromImport(Compiler* compiler) {
emitStoreVariable(compiler, var_index, true);
emitOpcode(compiler, OP_POP);
} while (match(compiler, TK_COMMA) && (skipNewLines(compiler), true));
}
@ -2465,7 +2464,7 @@ PkResult compile(PKVM* vm, Script* script, const char* source,
skipNewLines(compiler);
}
// Already a null at the stack top, added when the fiber for the function created.
emitOpcode(compiler, OP_PUSH_NULL);
emitOpcode(compiler, OP_RETURN);
emitOpcode(compiler, OP_END);
@ -2498,7 +2497,7 @@ PkResult compile(PKVM* vm, Script* script, const char* source,
#if DEBUG_DUMP_COMPILED_CODE
dumpFunctionCode(vm, script->body);
#endif
// Return the compilation result.
if (compiler->has_errors) {
if (compiler->options && compiler->options->repl_mode &&
@ -2523,7 +2522,7 @@ PkResult pkCompileModule(PKVM* vm, PkHandle* module, PkStringPtr source,
}
void compilerMarkObjects(PKVM* vm, Compiler* compiler) {
// Mark the script which is currently being compiled.
grayObject(vm, &compiler->script->_super);

View File

@ -19,9 +19,10 @@ typedef enum {
// doesn't go through the basic compilation pipeline such as lexing, parsing
// (AST), analyzing, intermediate code generation, and target codegeneration
// one by one. Instead it'll generate the target code as it reads the source
// (directly from lexing to codegen). Despite it faster than multipass compilers,
// we're restricted syntax-wise and from compile-time optimizations, yet we support
// "forward names" to call functions before they defined (unlike C/Python).
// (directly from lexing to codegen). Despite it's faster than multipass
// compilers, we're restricted syntax-wise and from compile-time optimizations.
// Yet we support "forward names" to call functions before they defined
// (unlike C/Python).
typedef struct Compiler Compiler;
// This will take source code as a cstring, compiles it to pocketlang bytecodes

View File

@ -50,13 +50,13 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
// TODO: Currently it's O(n) and could be optimized to O(log(n)) but does it
// worth it?
//
//
// 'function_names' buffer is un-necessary since the function itself has the
// reference to the function name and it can be refactored into a index buffer
// in an "increasing-name" order which can be used to binary search. Similer
// for 'global_names' refactor them from VarBuffer to GlobalVarBuffer where
// GlobalVar is struct { const char* name, Var value };
//
// reference to the function name and it can be refactored into a index
// buffer in an "increasing-name" order which can be used to binary search.
// Similer for 'global_names' refactor them from VarBuffer to GlobalVarBuffer
// where GlobalVar is struct { const char* name, Var value };
//
// "increasing-name" order index buffer:
// A buffer of int where each is an index in the function buffer and each
// points to different functions in an "increasing-name" (could be hash
@ -72,14 +72,10 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
return NULL;
}
// A convenient macro to get the nth (1 based) argument of the current function.
// A convenient macro to get the nth (1 based) argument of the current
// function.
#define ARG(n) (vm->fiber->ret[n])
// Convenient macros to get the 1st, 2nd, 3rd arguments.
#define ARG1 ARG(1)
#define ARG2 ARG(2)
#define ARG3 ARG(3)
// Evaluates to the current function's argument count.
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
@ -102,7 +98,7 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
__ASSERT(vm->fiber != NULL, \
"This function can only be called at runtime."); \
__ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index."); \
__ASSERT(value != NULL, "Parameter [value] was NULL."); \
__ASSERT(value != NULL, "Argument [value] was NULL."); \
} while (false)
// Set error for incompatible type provided as an argument.
@ -265,26 +261,29 @@ static inline bool validateNumeric(PKVM* vm, Var var, double* value,
return false;
}
// Check if [var] is integer. If not set error and return false.
static inline bool validateInteger(PKVM* vm, Var var, int32_t* value,
// Check if [var] is 32 bit integer. If not set error and return false.
static inline bool validateInteger(PKVM* vm, Var var, int64_t* value,
const char* name) {
double number;
if (isNumeric(var, &number)) {
double truncated = floor(number);
if (truncated == number) {
*value = (int32_t)(truncated);
// TODO: check if the number is larger for a 64 bit integer.
double floor_val = floor(number);
if (floor_val == number) {
*value = (int64_t)(floor_val);
return true;
}
}
vm->fiber->error = stringFormat(vm, "$ must be an integer.", name);
vm->fiber->error = stringFormat(vm, "$ must be a whole number.", name);
return false;
}
static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size,
const char* container) {
// Index is could be larger than 32 bit integer, but the size in pocketlang
// limited to 32 unsigned bit integer
static inline bool validateIndex(PKVM* vm, int64_t index, uint32_t size,
const char* container) {
if (index < 0 || size <= index) {
vm->fiber->error = stringFormat(vm, "$ index out of range.", container);
vm->fiber->error = stringFormat(vm, "$ index out of bound.", container);
return false;
}
return true;
@ -351,12 +350,12 @@ Script* getCoreLib(const PKVM* vm, String* name) {
#define FN_IS_PRIMITE_TYPE(name, check) \
static void coreIs##name(PKVM* vm) { \
RET(VAR_BOOL(check(ARG1))); \
RET(VAR_BOOL(check(ARG(1)))); \
}
#define FN_IS_OBJ_TYPE(name, _enum) \
static void coreIs##name(PKVM* vm) { \
Var arg1 = ARG1; \
Var arg1 = ARG(1); \
if (IS_OBJ_TYPE(arg1, _enum)) { \
RET(VAR_TRUE); \
} else { \
@ -376,29 +375,73 @@ FN_IS_OBJ_TYPE(Function, OBJ_FUNC)
FN_IS_OBJ_TYPE(Script, OBJ_SCRIPT)
FN_IS_OBJ_TYPE(UserObj, OBJ_USER)
PK_DOC(coreTypeName,
PK_DOC(
"type_name(value:var) -> string\n"
"Returns the type name of the of the value.") {
RET(VAR_OBJ(newString(vm, varTypeName(ARG1))));
"Returns the type name of the of the value.",
static void coreTypeName(PKVM* vm)) {
RET(VAR_OBJ(newString(vm, varTypeName(ARG(1)))));
}
PK_DOC(coreAssert,
// TODO: Complete this and register it.
PK_DOC(
"bin(value:num) -> string\n"
"Returns as a binary value string with '0x' prefix.",
static void coreBin(PKVM* vm)) {
int64_t value;
if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return;
char buff[STR_BIN_BUFF_SIZE];
char* ptr = buff;
if (value < 0) *ptr++ = '-';
*ptr++ = '0'; *ptr++ = 'b';
TODO; // sprintf(ptr, "%b");
}
PK_DOC(
"hex(value:num) -> string\n"
"Returns as a hexadecimal value string with '0x' prefix.",
static void coreHex(PKVM* vm)) {
int64_t value;
if (!validateInteger(vm, ARG(1), &value, "Argument 1")) return;
char buff[STR_HEX_BUFF_SIZE];
char* ptr = buff;
if (value < 0) *ptr++ = '-';
*ptr++ = '0'; *ptr++ = 'x';
if (value > UINT32_MAX || value < -(int64_t)(UINT32_MAX)) {
vm->fiber->error = newString(vm, "Integer is too large.");
RET(VAR_NULL);
}
uint32_t _x = (uint32_t)((value < 0) ? -value : value);
int length = sprintf(ptr, "%x", _x);
RET(VAR_OBJ(newStringLength(vm, 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") {
"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(ARG1)) {
if (!toBool(ARG(1))) {
String* msg = NULL;
if (argc == 2) {
if (AS_OBJ(ARG2)->type != OBJ_STRING) {
msg = toString(vm, ARG2);
if (AS_OBJ(ARG(2))->type != OBJ_STRING) {
msg = toString(vm, ARG(2));
} else {
msg = (String*)AS_OBJ(ARG2);
msg = (String*)AS_OBJ(ARG(2));
}
vmPushTempRef(vm, &msg->_super);
vm->fiber->error = stringFormat(vm, "Assertion failed: '@'.", msg);
@ -409,31 +452,34 @@ PK_DOC(coreAssert,
}
}
PK_DOC(coreYield,
PK_DOC(
"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.") {
"the yield() would be that value otherwise null.",
static void coreYield(PKVM* vm)) {
int argc = ARGC;
if (argc > 1) { // yield() or yield(val).
RET_ERR(newString(vm, "Invalid argument count."));
}
vmYieldFiber(vm, (argc == 1) ? &ARG1 : NULL);
vmYieldFiber(vm, (argc == 1) ? &ARG(1) : NULL);
}
PK_DOC(coreToString,
PK_DOC(
"to_string(value:var) -> string\n"
"Returns the string representation of the value.") {
RET(VAR_OBJ(toString(vm, ARG1)));
"Returns the string representation of the value.",
static void coreToString(PKVM* vm)) {
RET(VAR_OBJ(toString(vm, ARG(1))));
}
PK_DOC(corePrint,
PK_DOC(
"print(...) -> void\n"
"Write each argument as comma seperated to the stdout and ends with a "
"newline.") {
"newline.",
static void corePrint(PKVM* vm)) {
// If the host appliaction donesn't provide any write function, discard the
// output.
if (vm->config.write_fn == NULL) return;
@ -446,10 +492,11 @@ PK_DOC(corePrint,
vm->config.write_fn(vm, "\n");
}
PK_DOC(coreInput,
PK_DOC(
"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.") {
"an optional argument [msg] and prints it before reading.",
static void coreInput(PKVM* vm)) {
int argc = ARGC;
if (argc != 1 && argc != 2) {
RET_ERR(newString(vm, "Invalid argument count."));
@ -459,7 +506,7 @@ PK_DOC(coreInput,
if (vm->config.read_fn == NULL) return;
if (argc == 1) {
vm->config.write_fn(vm, toString(vm, ARG1)->data);
vm->config.write_fn(vm, toString(vm, ARG(1))->data);
}
PkStringPtr result = vm->config.read_fn(vm);
@ -470,66 +517,26 @@ PK_DOC(coreInput,
// String functions.
// -----------------
PK_DOC(coreStrLower,
"str_lower(value:string) -> string\n"
"Returns a lower-case version of the given string.") {
String* str;
if (!validateArgString(vm, 1, &str)) return;
String* result = newStringLength(vm, str->data, str->length);
char* data = result->data;
for (; *data; ++data) *data = (char)tolower(*data);
// Since the string is modified re-hash it.
result->hash = utilHashString(result->data);
// TODO: substring.
RET(VAR_OBJ(result));
}
PK_DOC(coreStrUpper,
"str_upper(value:string) -> string\n"
"Returns a upper-case version of the given string.") {
String* str;
if (!validateArgString(vm, 1, &str)) return;
String* result = newStringLength(vm, str->data, str->length);
char* data = result->data;
for (; *data; ++data) *data = (char)toupper(*data);
// Since the string is modified re-hash it.
result->hash = utilHashString(result->data);
RET(VAR_OBJ(result));
}
PK_DOC(coreStrStrip,
"str_strip(value:string) -> string\n"
"Returns a copy of the string as the leading and trailing white spaces are"
"trimed.") {
String* str;
if (!validateArgString(vm, 1, &str)) return;
const char* start = str->data;
while (*start && isspace(*start)) start++;
if (*start == '\0') RET(VAR_OBJ(newStringLength(vm, NULL, 0)));
const char* end = str->data + str->length - 1;
while (isspace(*end)) end--;
RET(VAR_OBJ(newStringLength(vm, start, (uint32_t)(end - start + 1))));
}
PK_DOC(coreStrChr,
PK_DOC(
"str_chr(value:number) -> string\n"
"Returns the ASCII string value of the integer argument.") {
int32_t num;
if (!validateInteger(vm, ARG1, &num, "Argument 1")) return;
"Returns the ASCII string value of the integer argument.",
static void coreStrChr(PKVM* vm)) {
int64_t num;
if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return;
// TODO: validate num is a byte.
char c = (char)num;
RET(VAR_OBJ(newStringLength(vm, &c, 1)));
}
PK_DOC(coreStrOrd,
PK_DOC(
"str_ord(value:string) -> number\n"
"Returns integer value of the given ASCII character.") {
"Returns integer value of the given ASCII character.",
static void coreStrOrd(PKVM* vm)) {
String* c;
if (!validateArgString(vm, 1, &c)) return;
if (c->length != 1) {
@ -543,9 +550,10 @@ PK_DOC(coreStrOrd,
// List functions.
// ---------------
PK_DOC(coreListAppend,
PK_DOC(
"list_append(self:List, value:var) -> List\n"
"Append the [value] to the list [self] and return the list.") {
"Append the [value] to the list [self] and return the list.",
static void coreListAppend(PKVM* vm)) {
List* list;
if (!validateArgList(vm, 1, &list)) return;
Var elem = ARG(2);
@ -557,10 +565,11 @@ PK_DOC(coreListAppend,
// Map functions.
// --------------
PK_DOC(coreMapRemove,
PK_DOC(
"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.") {
"exists, otherwise it'll return null.",
static void coreMapRemove(PKVM* vm)) {
Map* map;
if (!validateArgMap(vm, 1, &map)) return;
Var key = ARG(2);
@ -571,35 +580,39 @@ PK_DOC(coreMapRemove,
// Fiber functions.
// ----------------
PK_DOC(coreFiberNew,
PK_DOC(
"fiber_new(fn:function) -> fiber\n"
"Create and return a new fiber from the given function [fn].") {
"Create and return a new fiber from the given function [fn].",
static void coreFiberNew(PKVM* vm)) {
Function* fn;
if (!validateArgFunction(vm, 1, &fn)) return;
RET(VAR_OBJ(newFiber(vm, fn)));
}
PK_DOC(coreFiberGetFunc,
PK_DOC(
"fiber_get_func(fb:fiber) -> function\n"
"Retruns the fiber's functions. Which is usefull if you wan't to re-run the "
"fiber, you can get the function and crate a new fiber.") {
"fiber, you can get the function and crate a new fiber.",
static void coreFiberGetFunc(PKVM* vm)) {
Fiber* fb;
if (!validateArgFiber(vm, 1, &fb)) return;
RET(VAR_OBJ(fb->func));
}
PK_DOC(coreFiberIsDone,
PK_DOC(
"fiber_is_done(fb:fiber) -> bool\n"
"Returns true if the fiber [fb] is done running and can no more resumed.") {
"Returns true if the fiber [fb] is done running and can no more resumed.",
static void coreFiberIsDone(PKVM* vm)) {
Fiber* fb;
if (!validateArgFiber(vm, 1, &fb)) return;
RET(VAR_BOOL(fb->state == FIBER_DONE));
}
PK_DOC(coreFiberRun,
PK_DOC(
"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.") {
"return value or the yielded value if it's yielded.",
static void coreFiberRun(PKVM* vm)) {
int argc = ARGC;
if (argc == 0) // Missing the fiber argument.
@ -623,10 +636,11 @@ PK_DOC(coreFiberRun,
}
}
PK_DOC(coreFiberResume,
PK_DOC(
"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." ) {
"Return it's return value or the yielded value if it's yielded.",
static void coreFiberResume(PKVM* vm)) {
int argc = ARGC;
if (argc == 0) // Missing the fiber argument.
@ -661,7 +675,7 @@ static Script* newModuleInternal(PKVM* vm, const char* name) {
// hosting application.
if (!IS_UNDEF(mapGet(vm->core_libs, VAR_OBJ(_name)))) {
vmPopTempRef(vm); // _name
__ASSERT(false, stringFormat(vm,
__ASSERT(false, stringFormat(vm,
"A module named '$' already exists", name)->data);
}
@ -749,60 +763,61 @@ void stdLangWrite(PKVM* vm) {
void stdMathFloor(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
RET(VAR_NUM(floor(num)));
}
void stdMathCeil(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
RET(VAR_NUM(ceil(num)));
}
void stdMathPow(PKVM* vm) {
double num, ex;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (!validateNumeric(vm, ARG2, &ex, "Parameter 2")) return;
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) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
RET(VAR_NUM(sqrt(num)));
}
void stdMathAbs(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
if (num < 0) num = -num;
RET(VAR_NUM(num));
}
void stdMathSign(PKVM* vm) {
double num;
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
if (num < 0) num = -1;
else if (num > 0) num = +1;
else num = 0;
RET(VAR_NUM(num));
}
PK_DOC(stdMathHash,
PK_DOC(
"hash(value:var) -> num\n"
"Return the hash value of the variable, if it's not hashable it'll "
"return null.");
"return null.",
static void stdMathHash(PKVM* vm));
void stdMathHash(PKVM* vm) {
if (IS_OBJ(ARG1)) {
if (!isObjectHashable(AS_OBJ(ARG1)->type)) {
if (IS_OBJ(ARG(1))) {
if (!isObjectHashable(AS_OBJ(ARG(1))->type)) {
RET(VAR_NULL);
}
}
RET(VAR_NUM((double)varHashValue(ARG1)));
RET(VAR_NUM((double)varHashValue(ARG(1))));
}
/*****************************************************************************/
@ -837,7 +852,7 @@ void initializeCore(PKVM* vm) {
INITALIZE_BUILTIN_FN("is_null", coreIsNull, 1);
INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1);
INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1);
INITALIZE_BUILTIN_FN("is_string", coreIsString, 1);
INITALIZE_BUILTIN_FN("is_list", coreIsList, 1);
INITALIZE_BUILTIN_FN("is_map", coreIsMap, 1);
@ -845,7 +860,8 @@ void initializeCore(PKVM* vm) {
INITALIZE_BUILTIN_FN("is_function", coreIsFunction, 1);
INITALIZE_BUILTIN_FN("is_script", coreIsScript, 1);
INITALIZE_BUILTIN_FN("is_userobj", coreIsUserObj, 1);
INITALIZE_BUILTIN_FN("hex", coreHex, 1);
INITALIZE_BUILTIN_FN("assert", coreAssert, -1);
INITALIZE_BUILTIN_FN("yield", coreYield, -1);
INITALIZE_BUILTIN_FN("to_string", coreToString, 1);
@ -853,9 +869,6 @@ void initializeCore(PKVM* vm) {
INITALIZE_BUILTIN_FN("input", coreInput, -1);
// String functions.
INITALIZE_BUILTIN_FN("str_lower", coreStrLower, 1);
INITALIZE_BUILTIN_FN("str_upper", coreStrUpper, 1);
INITALIZE_BUILTIN_FN("str_strip", coreStrStrip, 1);
INITALIZE_BUILTIN_FN("str_chr", coreStrChr, 1);
INITALIZE_BUILTIN_FN("str_ord", coreStrOrd, 1);
@ -1022,13 +1035,31 @@ bool varLesser(Var v1, Var v2) {
return false;
}
// A convinent convenient macro used in varGetAttrib and varSetAttrib.
#define IS_ATTRIB(name) \
(attrib->length == strlen(name) && strcmp(name, attrib->data) == 0)
// Here we're switching the FNV-1a hash value of the name (cstring). Which is
// an efficient way than having multiple if (attrib == "name"). From O(n) * k
// to O(1) where n is the length of the string and k is the number of string
// comparison.
//
// ex:
// SWITCH_ATTRIB(str) { // str = "length"
// CASE_ATTRIB("length", 0x83d03615) : { return string->length; }
// }
//
// In C++11 this can be achieved (in a better way) with user defined literals
// and constexpr. (Reference from my previous compiler written in C++).
// https://github.com/ThakeeNathees/carbon/blob/89b11800132cbfeedcac0c992593afb5f0357236/include/core/internal.h#L174-L180
// https://github.com/ThakeeNathees/carbon/blob/454d087f85f7fb9408eb0bc10ae702b8de844648/src/var/_string.cpp#L60-L77
//
// However there is a python script that's matching the CASE_ATTRIB() macro
// calls and validate if the string and the hash values are matching.
// TODO: port it to the CI/CD process at github actions.
//
#define SWITCH_ATTRIB(name) switch (utilHashString(name))
#define CASE_ATTRIB(name, hash) case hash
// Set error for accessing non-existed attribute.
#define ERR_NO_ATTRIB() \
vm->fiber->error = stringFormat(vm, "'$' objects has no attribute " \
#define ERR_NO_ATTRIB(on) \
vm->fiber->error = stringFormat(vm, "'$' object has no attribute " \
"named '$'", \
varTypeName(on), attrib->data);
@ -1044,57 +1075,68 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
switch (obj->type) {
case OBJ_STRING:
{
if (IS_ATTRIB("length")) {
size_t length = ((String*)obj)->length;
return VAR_NUM((double)length);
String* str = (String*)obj;
SWITCH_ATTRIB(attrib->data) {
CASE_ATTRIB("length", 0x83d03615) :
return VAR_NUM((double)(str->length));
CASE_ATTRIB("lower", 0xb51d04ba) :
return VAR_OBJ(stringLower(vm, str));
CASE_ATTRIB("upper", 0xa8c6a47) :
return VAR_OBJ(stringUpper(vm, str));
CASE_ATTRIB("strip", 0xfd1b18d1) :
return VAR_OBJ(stringStrip(vm, str));
default:
ERR_NO_ATTRIB(on);
return VAR_NULL;
}
ERR_NO_ATTRIB();
return VAR_NULL;
UNREACHABLE();
}
case OBJ_LIST:
{
if (IS_ATTRIB("length")) {
size_t length = ((List*)obj)->elements.count;
return VAR_NUM((double)length);
List* list = (List*)obj;
SWITCH_ATTRIB(attrib->data) {
CASE_ATTRIB("length", 0x83d03615) :
return VAR_NUM((double)(list->elements.count));
default:
ERR_NO_ATTRIB(on);
return VAR_NULL;
}
ERR_NO_ATTRIB();
return VAR_NULL;
UNREACHABLE();
}
case OBJ_MAP:
{
TODO; // Not sure should I allow this(below).
//Var value = mapGet((Map*)obj, VAR_OBJ(attrib));
//if (IS_UNDEF(value)) {
// vm->fiber->error = stringFormat(vm, "Key (\"@\") not exists.",
// attrib);
// return VAR_NULL;
//}
//return value;
// Not sure should I allow string values could be accessed with
// this way. ex:
// map = { "foo" : 42, "can't access" : 32 }
// val = map.foo ## 42
TODO;
}
case OBJ_RANGE:
{
Range* range = (Range*)obj;
SWITCH_ATTRIB(attrib->data) {
if (IS_ATTRIB("as_list")) {
List* list;
if (range->from < range->to) {
list = newList(vm, (uint32_t)(range->to - range->from));
for (double i = range->from; i < range->to; i++) {
pkVarBufferWrite(&list->elements, vm, VAR_NUM(i));
}
} else {
list = newList(vm, 0);
}
return VAR_OBJ(list);
CASE_ATTRIB("as_list", 0x1562c22) :
return VAR_OBJ(rangeAsList(vm, range));
default:
ERR_NO_ATTRIB(on);
return VAR_NULL;
}
ERR_NO_ATTRIB();
return VAR_NULL;
UNREACHABLE();
}
case OBJ_SCRIPT: {
@ -1114,11 +1156,28 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
return scr->globals.data[index];
}
ERR_NO_ATTRIB();
ERR_NO_ATTRIB(on);
return VAR_NULL;
}
case OBJ_FUNC:
{
Function* fn = (Function*)obj;
SWITCH_ATTRIB(attrib->data) {
CASE_ATTRIB("arity", 0x3e96bd7a) :
return VAR_NUM((double)(fn->arity));
CASE_ATTRIB("name", 0x8d39bde6) :
return VAR_OBJ(newString(vm, fn->name));
default:
ERR_NO_ATTRIB(on);
return VAR_NULL;
}
UNREACHABLE();
}
case OBJ_FIBER:
case OBJ_USER:
TODO;
@ -1133,10 +1192,10 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
#define ATTRIB_IMMUTABLE(prop) \
#define ATTRIB_IMMUTABLE(name) \
do { \
if (IS_ATTRIB(prop)) { \
vm->fiber->error = stringFormat(vm, "'$' attribute is immutable.", prop); \
if ((attrib->length == strlen(name) && strcmp(name, attrib->data) == 0)) { \
vm->fiber->error = stringFormat(vm, "'$' attribute is immutable.", name); \
return; \
} \
} while (false)
@ -1151,21 +1210,30 @@ do { \
switch (obj->type) {
case OBJ_STRING:
ATTRIB_IMMUTABLE("length");
ERR_NO_ATTRIB();
ATTRIB_IMMUTABLE("lower");
ATTRIB_IMMUTABLE("upper");
ATTRIB_IMMUTABLE("strip");
ERR_NO_ATTRIB(on);
return;
case OBJ_LIST:
ATTRIB_IMMUTABLE("length");
ERR_NO_ATTRIB();
ERR_NO_ATTRIB(on);
return;
case OBJ_MAP:
// Not sure should I allow string values could be accessed with
// this way. ex:
// map = { "foo" : 42, "can't access" : 32 }
// map.foo = 'bar'
TODO;
ERR_NO_ATTRIB();
ERR_NO_ATTRIB(on);
return;
case OBJ_RANGE:
ERR_NO_ATTRIB();
ATTRIB_IMMUTABLE("as_list");
ERR_NO_ATTRIB(on);
return;
case OBJ_SCRIPT: {
@ -1187,20 +1255,22 @@ do { \
return;
}
ERR_NO_ATTRIB();
ERR_NO_ATTRIB(on);
return;
}
case OBJ_FUNC:
ERR_NO_ATTRIB();
ATTRIB_IMMUTABLE("arity");
ATTRIB_IMMUTABLE("name");
ERR_NO_ATTRIB(on);
return;
case OBJ_FIBER:
ERR_NO_ATTRIB();
ERR_NO_ATTRIB(on);
return;
case OBJ_USER:
TODO; //ERR_NO_ATTRIB();
TODO; //ERR_NO_ATTRIB(on);
return;
default:
@ -1208,6 +1278,8 @@ do { \
}
CHECK_MISSING_OBJ_TYPE(7);
UNREACHABLE();
#undef ATTRIB_IMMUTABLE
}
Var varGetSubscript(PKVM* vm, Var on, Var key) {
@ -1221,7 +1293,7 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
switch (obj->type) {
case OBJ_STRING:
{
int32_t index;
int64_t index;
String* str = ((String*)obj);
if (!validateInteger(vm, key, &index, "List index")) {
return VAR_NULL;
@ -1235,12 +1307,12 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
case OBJ_LIST:
{
int32_t index;
int64_t index;
pkVarBuffer* elems = &((List*)obj)->elements;
if (!validateInteger(vm, key, &index, "List index")) {
return VAR_NULL;
}
if (!validateIndex(vm, index, (int)elems->count, "List")) {
if (!validateIndex(vm, index, elems->count, "List")) {
return VAR_NULL;
}
return elems->data[index];
@ -1293,10 +1365,10 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
case OBJ_LIST:
{
int32_t index;
int64_t index;
pkVarBuffer* elems = &((List*)obj)->elements;
if (!validateInteger(vm, key, &index, "List index")) return;
if (!validateIndex(vm, index, (int)elems->count, "List")) return;
if (!validateIndex(vm, index, elems->count, "List")) return;
elems->data[index] = value;
return;
}

View File

@ -185,7 +185,6 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
case OP_LIST_APPEND: NO_ARGS(); break;
case OP_MAP_INSERT: NO_ARGS(); break;
case OP_PUSH_LOCAL_0:
case OP_PUSH_LOCAL_1:
case OP_PUSH_LOCAL_2:

View File

@ -153,7 +153,7 @@ OPCODE(SET_ATTRIB, 2, -1)
// Pop var, key, get value and push the result.
OPCODE(GET_SUBSCRIPT, 0, -1)
// Get subscript to perform assignment operation before store it, so it won't
// Get subscript to perform assignment operation before store it, so it won't
// pop the var and the key. (ex: map[key] += value).
OPCODE(GET_SUBSCRIPT_KEEP, 0, 1)

View File

@ -6,6 +6,8 @@
#include "pk_var.h"
#include <math.h>
#include <ctype.h>
#include "pk_utils.h"
#include "pk_vm.h"
@ -424,6 +426,97 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
return fiber;
}
List* rangeAsList(PKVM* vm, Range* self) {
List* list;
if (self->from < self->to) {
list = newList(vm, (uint32_t)(self->to - self->from));
for (double i = self->from; i < self->to; i++) {
pkVarBufferWrite(&list->elements, vm, VAR_NUM(i));
}
return list;
} else {
list = newList(vm, 0);
}
return list;
}
String* stringLower(PKVM* vm, String* self) {
// If the string itself is already lower don't allocate new string.
uint32_t index = 0;
for (const char* c = self->data; *c != '\0'; c++, index++) {
if (isupper(*c)) {
// It contain upper case letters, allocate new lower case string .
String* lower = newStringLength(vm, self->data, self->length);
// Start where the first upper case letter found.
char* _c = lower->data + (c - self->data);
for (; *_c != '\0'; _c++) *_c = (char)tolower(*_c);
// Since the string is modified re-hash it.
lower->hash = utilHashString(lower->data);
return lower;
}
}
// If we reached here the string itself is lower, return it.
return self;
}
String* stringUpper(PKVM* vm, String* self) {
// If the string itself is already upper don't allocate new string.
uint32_t index = 0;
for (const char* c = self->data; *c != '\0'; c++, index++) {
if (islower(*c)) {
// It contain lower case letters, allocate new upper case string .
String* upper = newStringLength(vm, self->data, self->length);
// Start where the first lower case letter found.
char* _c = upper->data + (c - self->data);
for (; *_c != '\0'; _c++) *_c = (char)toupper(*_c);
// Since the string is modified re-hash it.
upper->hash = utilHashString(upper->data);
return upper;
}
}
// If we reached here the string itself is lower, return it.
return self;
}
String* stringStrip(PKVM* vm, String* self) {
// Implementation:
//
// " a string with leading and trailing white space "
// ^start >> << end^
//
// These 'start' and 'end' pointers will move respectively right and left
// while it's a white space and return an allocated string from 'start' with
// length of (end - start + 1). For already trimed string it'll not allocate
// a new string, instead returns the same string provided.
const char* start = self->data;
while (*start && isspace(*start)) start++;
// If we reached the end of the string, it's all white space, return
// an empty string.
if (*start == '\0') {
return newStringLength(vm, NULL, 0);
}
const char* end = self->data + self->length - 1;
while (isspace(*end)) end--;
// If the string is already trimed, return the same string.
if (start == self->data && end == self->data + self->length - 1) {
return self;
}
return newStringLength(vm, start, (uint32_t)(end - start + 1));
}
void listInsert(PKVM* vm, List* self, uint32_t index, Var value) {
// Add an empty slot at the end of the buffer.

View File

@ -156,7 +156,6 @@
// TODO: Union tagging implementation of all the above macros ignore macros
// starts with an underscore.
typedef enum {
VAR_UNDEFINED, //< Internal type for exceptions.
VAR_NULL, //< Null pointer type.
@ -450,6 +449,23 @@ void grayFunctionBuffer(PKVM* vm, pkFunctionBuffer* self);
// working list to traverse and update the vm's [bytes_allocated] value.
void blackenObjects(PKVM* vm);
// Returns a number list from the range. starts with range.from and ends with
// (range.to - 1) increase by 1. Note that if the range is reversed
// (ie. range.from > range.to) It'll return an empty list ([]).
List* rangeAsList(PKVM* vm, Range* self);
// Returns a lower case version of the given string. If the string is
// already lower it'll return the same string.
String* stringLower(PKVM* vm, String* self);
// Returns a upper case version of the given string. If the string is
// already upper it'll return the same string.
String* stringUpper(PKVM* vm, String* self);
// Returns string with the leading and trailing white spaces are trimed.
// If the string is already trimed it'll return the same string.
String* stringStrip(PKVM* vm, String* self);
// Insert [value] to the list at [index] and shift down the rest of the
// elements.
void listInsert(PKVM* vm, List* self, uint32_t index, Var value);

View File

@ -1,87 +0,0 @@
import subprocess, os, sys
import json, re
from os.path import join
## All the test files.
test_files = [
"lang/basics.pk",
"lang/functions.pk",
"lang/controlflow.pk",
"lang/fibers.pk",
"lang/import.pk",
"examples/helloworld.pk",
"examples/brainfuck.pk",
"examples/fib.pk",
"examples/prime.pk",
"examples/fizzbuzz.pk",
"examples/pi.pk",
]
## All benchmark files. ## TODO: pass args for iterations.
benchmarks = {
"factors" : ['.pk', '.py', '.rb', '.wren'],
"fib" : ['.pk', '.py', '.rb', '.wren'],
"list" : ['.pk', '.py', '.rb'],
"loop" : ['.pk', '.py', '.rb', ".wren"],
"primes" : ['.pk', '.py', '.rb', ".wren"],
}
def main():
run_all_tests()
run_all_benchmarks()
def run_all_benchmarks():
print_title("BENCHMARKS")
def get_interpreter(file):
if file.endswith('.pk' ) : return 'pocket'
if file.endswith('.py' ) : return 'python'
if file.endswith('.rb' ) : return 'ruby'
if file.endswith('.wren') : return 'wren'
assert False
for bm_name in benchmarks:
print(bm_name + ":")
for ext in benchmarks[bm_name]:
file = join('benchmark', bm_name, bm_name + ext)
interpreter = get_interpreter(file)
print(' %10s: '%interpreter, end=''); sys.stdout.flush()
result = run_command([interpreter, file])
time = re.findall(r'elapsed:\s*([0-9\.]+)\s*s',
result.stdout.decode('utf8'),
re.MULTILINE)
assert len(time) == 1, r'elapsed:\s*([0-9\.]+)\s*s --> no mach found.'
print('%10ss'%time[0])
def run_all_tests():
print_title("TESTS")
FMT_PATH = "%-25s"
INDENTATION = ' | '
for path in test_files:
print(FMT_PATH % path, end='')
result = run_command(['pocket', path])
if result.returncode != 0:
print('-- Failed')
err = INDENTATION + result.stderr \
.decode('utf8') \
.replace('\n', '\n' + INDENTATION)
print(err)
else:
print('-- OK')
def run_command(command):
return subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def print_title(title):
print("--------------------------------")
print(" %s " % title)
print("--------------------------------")
if __name__ == '__main__':
main()

127
tests/check.py Normal file
View File

@ -0,0 +1,127 @@
#!python
## Copyright (c) 2020-2021 Thakee Nathees
## Distributed Under The MIT License
## This will run a static checks on the source files, for line length,
## uses of tabs and trailing white spaces, etc.
##
## Run the file at the top level of the repository "python3 tests/check.py".
import os, sys, re
from os.path import join
from os import listdir
## A list of source files, to check if the fnv1a hash values match it's
## corresponding cstring in the CASE_ATTRIB(name, hash) macro calls.
HASH_CHECK_LIST = [
"src/pk_core.c",
]
## A list of directory, contains C source files to perform static checks.
## This will include both '.c' and '.h' files.
C_SOURCE_DIRS = [
"src/",
"cli/",
"cli/modules/",
]
## This global variable will be set to true if any check failed.
checks_failed = False
def main():
dir = os.path.dirname(os.path.realpath(__file__))
if dir == os.getcwd():
print("Run the file from the top level of the repository", file=sys.stderr)
sys.exit(1)
check_fnv1_hash(HASH_CHECK_LIST)
check_static(C_SOURCE_DIRS)
if checks_failed:
sys.exit(1)
print("Static checks were passed.")
def check_fnv1_hash(sources):
PATTERN = r'CASE_ATTRIB\(\s*"([A-Za-z0-9_]+)"\s*,\s*(0x[0-9abcdef]+)\)'
for file in sources:
fp = open(file, 'r')
line_no = 0
for line in fp.readlines():
line_no += 1
match = re.findall(PATTERN, line)
if len(match) == 0: continue
name, val = match[0]
hash = hex(fnv1a_hash(name))
if val == hash: continue
report_error(f"{location(file, line_no)} - hash mismatch. "
f"hash('{name}') = {hash} not {val}")
fp.close()
## Check each source file ('.c', '.h', '.py') in the [dirs] contains tabs,
## more than 79 characters and trailing white space.
def check_static(dirs):
valid_ext = ('.c', '.h', '.py', '.pk')
for dir in dirs:
for file in listdir(dir):
if not file.endswith(valid_ext): continue
if os.path.isdir(join(dir, file)): continue
fp = open(join(dir, file), 'r')
## This will be set to true if the last line is empty.
is_last_empty = False; line_no = 0
for line in fp.readlines():
line_no += 1; line = line[:-1] # remove the line ending.
_location = location(join(dir, file), line_no)
## Check if the line contains any tabs.
if '\t' in line:
report_error(f"{_location} - contains tab(s) ({repr(line)}).")
if len(line) >= 80:
if 'http://' in line or 'https://' in line: continue
report_error(
f"{_location} - contains {len(line)} (> 79) characters.")
if line.endswith(' '):
report_error(f"{_location} - contains trailing white space.")
if line == '':
if is_last_empty:
report_error(f"{_location} - consequent empty lines.")
is_last_empty = True
else:
is_last_empty = False
fp.close()
## Returns a formated string of the error location.
def location(file, line):
return f"{'%-17s'%file} : {'%4s'%line}"
## FNV-1a hash. See: http://www.isthe.com/chongo/tech/comp/fnv/
def fnv1a_hash(string):
FNV_prime_32_bit = 16777619
FNV_offset_basis_32_bit = 2166136261
hash = FNV_offset_basis_32_bit
for c in string:
hash ^= ord(c)
hash *= FNV_prime_32_bit
hash &= 0xffffffff ## intentional 32 bit overflow.
return hash
def report_error(msg):
global checks_failed
checks_failed = True
print(msg, file=sys.stderr)
if __name__ == '__main__':
main()

32
tests/lang/core.pk Normal file
View File

@ -0,0 +1,32 @@
## Core builtin functions and attribute tests.
assert(hex(12648430) == '0xc0ffee')
assert(hex(255) == '0xff' and hex(10597059) == '0xa1b2c3')
assert(hex(-4294967295) == '-0xffffffff') ## the largest.
## string attributes.
assert(''.length == 0)
assert('test'.length == 4)
assert(''.lower == '' and ''.upper == '')
assert('already+lower '.lower == 'already+lower ')
assert('ALREADY+UPPER '.upper == 'ALREADY+UPPER ')
assert('tEST+InG'.lower == 'test+ing')
assert('tEST+InG'.upper == 'TEST+ING')
assert(' trim '.strip == 'trim')
assert(''.strip == '')
## List attribute
assert([].length == 0)
assert([1, 2, 3].length == 3)
## Function
assert(print.arity == -1)
assert(hex.arity == 1)
assert(func(a, b)end .arity == 2)
assert(print.name == "print")
def fn(p1, p2, p3) end
assert(fn.name == "fn")
assert(fn.arity == 3)

View File

@ -22,5 +22,6 @@ def fn3(data) return '[fn3:' + data + ']' end
result = 'data' -> fn1 -> fn2{'suff'} -> fn3
assert(result == '[fn3:[fn2:[fn1:data]|suff]]')
result = ' tEST+InG ' -> str_strip -> str_lower
assert(result == 'test+ing')
# str_lower(s) function refactored to s.lower attribute.
#result = ' tEST+InG ' -> str_strip -> str_lower
#assert(result == 'test+ing')

90
tests/tests.py Normal file
View File

@ -0,0 +1,90 @@
import subprocess, os, sys
import json, re
from os.path import join
## TODO: Re write this in doctest (https://github.com/onqtam/doctest)
## All the test files.
test_files = [
"lang/basics.pk",
"lang/core.pk",
"lang/controlflow.pk",
"lang/fibers.pk",
"lang/functions.pk",
"lang/import.pk",
"examples/brainfuck.pk",
"examples/fib.pk",
"examples/fizzbuzz.pk",
"examples/helloworld.pk",
"examples/pi.pk",
"examples/prime.pk",
]
## All benchmark files. ## TODO: pass args for iterations.
benchmarks = {
"factors" : ['.pk', '.py', '.rb', '.wren'],
"fib" : ['.pk', '.py', '.rb', '.wren'],
"list" : ['.pk', '.py', '.rb'],
"loop" : ['.pk', '.py', '.rb', ".wren"],
"primes" : ['.pk', '.py', '.rb', ".wren"],
}
def main():
run_all_tests()
#run_all_benchmarks()
def run_all_benchmarks():
print_title("BENCHMARKS")
def get_interpreter(file):
if file.endswith('.pk' ) : return 'pocket'
if file.endswith('.py' ) : return 'python'
if file.endswith('.rb' ) : return 'ruby'
if file.endswith('.wren') : return 'wren'
assert False
for bm_name in benchmarks:
print(bm_name + ":")
for ext in benchmarks[bm_name]:
file = join('benchmark', bm_name, bm_name + ext)
interpreter = get_interpreter(file)
print(' %10s: '%interpreter, end=''); sys.stdout.flush()
result = run_command([interpreter, file])
time = re.findall(r'elapsed:\s*([0-9\.]+)\s*s',
result.stdout.decode('utf8'),
re.MULTILINE)
assert len(time) == 1, r'elapsed:\s*([0-9\.]+)\s*s --> no mach found.'
print('%10ss'%time[0])
def run_all_tests():
print_title("TESTS")
FMT_PATH = "%-25s"
INDENTATION = ' | '
for path in test_files:
print(FMT_PATH % path, end='')
result = run_command(['pocket', path])
if result.returncode != 0:
print('-- Failed')
err = INDENTATION + result.stderr \
.decode('utf8') \
.replace('\n', '\n' + INDENTATION)
print(err)
else:
print('-- OK')
def run_command(command):
return subprocess.run(command,
stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
def print_title(title):
print("--------------------------------")
print(" %s " % title)
print("--------------------------------")
if __name__ == '__main__':
main()