mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-05 20:26:53 +08:00
Merge pull request #51 from ThakeeNathees/core-attrib
Core attributes refactored and added tests
This commit is contained in:
commit
484aa5d88f
@ -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)
|
||||
|
||||
|
@ -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 */
|
||||
/*****************************************************************************/
|
||||
|
@ -3,16 +3,14 @@
|
||||
* Distributed Under The MIT License
|
||||
*/
|
||||
|
||||
|
||||
// The REPL (Read Evaluate Print Loop) implementation.
|
||||
// https://en.wikipedia.org/wiki/Read–eval–print_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;
|
||||
|
@ -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;
|
||||
|
@ -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.
|
||||
|
@ -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).
|
||||
|
@ -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.
|
||||
|
@ -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) { \
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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
|
||||
|
432
src/pk_core.c
432
src/pk_core.c
@ -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;
|
||||
}
|
||||
|
@ -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:
|
||||
|
@ -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)
|
||||
|
||||
|
93
src/pk_var.c
93
src/pk_var.c
@ -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.
|
||||
|
18
src/pk_var.h
18
src/pk_var.h
@ -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);
|
||||
|
87
test/run.py
87
test/run.py
@ -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
127
tests/check.py
Normal 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
32
tests/lang/core.pk
Normal 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)
|
||||
|
@ -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
90
tests/tests.py
Normal 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()
|
Loading…
Reference in New Issue
Block a user