mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-11 07:00:58 +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"
|
"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
|
// 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
|
// 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
|
// internals of it, including assertion macros. So we're re-defining those
|
||||||
// macros here (like if it's a new project).
|
// 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 internal assertion macro, this will print error and break regardless of
|
||||||
// the build target (debug or release). Use ASSERT() for debug assertion and
|
// the build target (debug or release). Use ASSERT() for debug assertion and
|
||||||
// use __ASSERT() for TODOs and assetions in public methods (to indicate that
|
// use __ASSERT() for TODOs.
|
||||||
// the host application did something wrong).
|
|
||||||
#define __ASSERT(condition, message) \
|
#define __ASSERT(condition, message) \
|
||||||
do { \
|
do { \
|
||||||
if (!(condition)) { \
|
if (!(condition)) { \
|
||||||
@ -65,6 +64,7 @@
|
|||||||
do { \
|
do { \
|
||||||
fprintf(stderr, "Execution reached an unreachable path\n" \
|
fprintf(stderr, "Execution reached an unreachable path\n" \
|
||||||
"\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \
|
"\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \
|
||||||
|
DEBUG_BREAK(); \
|
||||||
abort(); \
|
abort(); \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
* Distributed Under The MIT License
|
* Distributed Under The MIT License
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
#include <errno.h>
|
#include <errno.h>
|
||||||
#include <pocketlang.h>
|
#include <pocketlang.h>
|
||||||
#include <stdio.h> /* defines FILENAME_MAX */
|
#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
|
// TODO: No error is handled below. I should check for path with size more than
|
||||||
// FILENAME_MAX.
|
// FILENAME_MAX.
|
||||||
|
|
||||||
|
|
||||||
// TODO: this macros should be moved to a general place of in cli.
|
// TODO: this macros should be moved to a general place of in cli.
|
||||||
#define TOSTRING(x) #x
|
#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);
|
return cwk_path_join(path_a, path_b, buffer, buff_size);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
/* INTERNAL FUNCTIONS */
|
/* INTERNAL FUNCTIONS */
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
@ -3,16 +3,14 @@
|
|||||||
* Distributed Under The MIT License
|
* Distributed Under The MIT License
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
|
||||||
// The REPL (Read Evaluate Print Loop) implementation.
|
// 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 "common.h"
|
||||||
|
|
||||||
#include <ctype.h> // isspace
|
#include <ctype.h> // isspace
|
||||||
#include "utils.h"
|
#include "utils.h"
|
||||||
|
|
||||||
|
|
||||||
// FIXME: use fgetc char by char till reach a new line.
|
// FIXME: use fgetc char by char till reach a new line.
|
||||||
//
|
//
|
||||||
// Read a line from stdin and returns it without the line ending. Accepting
|
// 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.
|
// The main module that'll be used to compile and execute the input source.
|
||||||
PkHandle* module = pkNewModule(vm, "$(REPL)");
|
PkHandle* module = pkNewModule(vm, "$(REPL)");
|
||||||
|
|
||||||
// FIXME: Again it's temp for testing.
|
|
||||||
|
|
||||||
// A buffer to store lines read from stdin.
|
// A buffer to store lines read from stdin.
|
||||||
ByteBuffer lines;
|
ByteBuffer lines;
|
||||||
byteBufferInit(&lines);
|
byteBufferInit(&lines);
|
||||||
@ -120,6 +116,7 @@ int repl(PKVM* vm, const PkCompileOptions* options) {
|
|||||||
|
|
||||||
} while (!done);
|
} while (!done);
|
||||||
|
|
||||||
|
byteBufferClear(&lines);
|
||||||
pkReleaseHandle(vm, module);
|
pkReleaseHandle(vm, module);
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
|
@ -26,7 +26,6 @@ static inline int powerOf2Ceil(int n) {
|
|||||||
/* BYTE BUFFER IMPLEMENTATION */
|
/* BYTE BUFFER IMPLEMENTATION */
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
|
|
||||||
void byteBufferInit(ByteBuffer* buffer) {
|
void byteBufferInit(ByteBuffer* buffer) {
|
||||||
buffer->data = NULL;
|
buffer->data = NULL;
|
||||||
buffer->count = 0;
|
buffer->count = 0;
|
||||||
@ -34,7 +33,7 @@ void byteBufferInit(ByteBuffer* buffer) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void byteBufferClear(ByteBuffer* buffer) {
|
void byteBufferClear(ByteBuffer* buffer) {
|
||||||
buffer->data = realloc(buffer->data, 0);
|
free(buffer->data);
|
||||||
buffer->data = NULL;
|
buffer->data = NULL;
|
||||||
buffer->count = 0;
|
buffer->count = 0;
|
||||||
buffer->capacity = 0;
|
buffer->capacity = 0;
|
||||||
|
@ -11,12 +11,11 @@ typedef struct {
|
|||||||
uint32_t capacity;
|
uint32_t capacity;
|
||||||
} ByteBuffer;
|
} ByteBuffer;
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
/* BYTE BUFFER */
|
/* BYTE BUFFER */
|
||||||
/*****************************************************************************/
|
/*****************************************************************************/
|
||||||
|
|
||||||
// Initialize a new buffer int instance.
|
// Initialize a new buffer int instance.
|
||||||
void byteBufferInit(ByteBuffer* buffer);
|
void byteBufferInit(ByteBuffer* buffer);
|
||||||
|
|
||||||
// Clears the allocated elements from the VM's realloc function.
|
// Clears the allocated elements from the VM's realloc function.
|
||||||
|
@ -7,9 +7,13 @@
|
|||||||
- check for tabs and trailing white space in source.
|
- check for tabs and trailing white space in source.
|
||||||
they're not allowed.
|
they're not allowed.
|
||||||
|
|
||||||
|
- Hex, binary literals and floats like ".5".
|
||||||
|
|
||||||
- change or add => to_string() to value.as_string
|
- change or add => to_string() to value.as_string
|
||||||
and add as_repr, as_bool.
|
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)
|
- support new line just after '=' (and other places)
|
||||||
|
|
||||||
@ -25,7 +29,6 @@
|
|||||||
- Implement utf8 support.
|
- Implement utf8 support.
|
||||||
- Implement gdb like debugger (add color print for readability).
|
- Implement gdb like debugger (add color print for readability).
|
||||||
- Initialize imported scripts (require fiber based vm).
|
- Initialize imported scripts (require fiber based vm).
|
||||||
- Hex, binary literals and floats like ".5".
|
|
||||||
- Complete all the TODO; macros.
|
- Complete all the TODO; macros.
|
||||||
- implement MAX_ARGC checks (would cause a buffer overflow if not)
|
- implement MAX_ARGC checks (would cause a buffer overflow if not)
|
||||||
when compiling and calling a function (also in fibers).
|
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
|
// A convinent macro to define documentation of funcions. Use it to document
|
||||||
// your native functions.
|
// your native functions.
|
||||||
//
|
//
|
||||||
// PK_DOC(foo,
|
// PK_DOC(
|
||||||
// "The function will print 'foo' on the console.") {
|
// "The function will print 'foo' on the console.",
|
||||||
|
// static void foo()) {
|
||||||
// printf("foo\n");
|
// printf("foo\n");
|
||||||
// }
|
// }
|
||||||
//
|
//
|
||||||
#define PK_DOC(func, doc) \
|
#define PK_DOC(doc, func) \
|
||||||
/* TODO: static char __pkdoc__##func[] = doc;*/ static void func(PKVM* vm)
|
/* TODO: static char __pkdoc__##func[] = doc;*/ func
|
||||||
|
|
||||||
// Name of the implicit function for a module. When a module is parsed all of
|
// 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.
|
// 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, \
|
void pk##m_name##BufferWrite(pk##m_name##Buffer* self, \
|
||||||
PKVM* vm, m_type data); \
|
PKVM* vm, m_type data); \
|
||||||
|
|
||||||
|
|
||||||
// The buffer "template" implementation of diferent types.
|
// The buffer "template" implementation of diferent types.
|
||||||
#define DEFINE_BUFFER(m_name, m_type) \
|
#define DEFINE_BUFFER(m_name, m_type) \
|
||||||
void pk##m_name##BufferInit(pk##m_name##Buffer* self) { \
|
void pk##m_name##BufferInit(pk##m_name##Buffer* self) { \
|
||||||
|
@ -113,6 +113,7 @@
|
|||||||
do { \
|
do { \
|
||||||
fprintf(stderr, "Execution reached an unreachable path\n" \
|
fprintf(stderr, "Execution reached an unreachable path\n" \
|
||||||
"\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \
|
"\tat %s() (%s:%i)\n", __func__, __FILE__, __LINE__); \
|
||||||
|
DEBUG_BREAK(); \
|
||||||
abort(); \
|
abort(); \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
@ -141,7 +142,7 @@
|
|||||||
|
|
||||||
// Using __ASSERT() for make it crash in release binary too.
|
// Using __ASSERT() for make it crash in release binary too.
|
||||||
#define TODO __ASSERT(false, "TODO: It hasn't implemented yet.")
|
#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 TOSTRING(x) #x
|
||||||
#define STRINGIFY(x) TOSTRING(x)
|
#define STRINGIFY(x) TOSTRING(x)
|
||||||
@ -149,6 +150,7 @@
|
|||||||
// The formated string to convert double to string. It'll be with the minimum
|
// 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
|
// length string representation of either a regular float or a scientific
|
||||||
// notation (at most 15 decimal points).
|
// notation (at most 15 decimal points).
|
||||||
|
// Reference: https://www.cplusplus.com/reference/cstdio/printf/
|
||||||
#define DOUBLE_FMT "%.16g"
|
#define DOUBLE_FMT "%.16g"
|
||||||
|
|
||||||
// Double number to string buffer size, used in sprintf with DOUBLE_FMT.
|
// Double number to string buffer size, used in sprintf with DOUBLE_FMT.
|
||||||
@ -169,4 +171,23 @@
|
|||||||
// + 1 for null byte '\0'
|
// + 1 for null byte '\0'
|
||||||
#define STR_INT_BUFF_SIZE 12
|
#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
|
#endif //PK_COMMON_H
|
||||||
|
@ -194,7 +194,6 @@ static _Keyword _keywords[] = {
|
|||||||
{ NULL, 0, (TokenType)(0) }, // Sentinal to mark the end of the array
|
{ NULL, 0, (TokenType)(0) }, // Sentinal to mark the end of the array
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
/*****************************************************************************
|
/*****************************************************************************
|
||||||
* COMPILIER INTERNAL TYPES *
|
* COMPILIER INTERNAL TYPES *
|
||||||
*****************************************************************************/
|
*****************************************************************************/
|
||||||
@ -302,7 +301,7 @@ typedef struct sForwardName {
|
|||||||
|
|
||||||
typedef struct sFunc {
|
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.
|
// literal functions will have the scope where it declared.
|
||||||
int depth;
|
int depth;
|
||||||
|
|
||||||
@ -354,7 +353,7 @@ struct Compiler {
|
|||||||
int forwards_count;
|
int forwards_count;
|
||||||
|
|
||||||
// True if the last statement is a new local variable assignment. Because
|
// 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
|
// to tell the compiler that dont pop it's assigned value because the value
|
||||||
// itself is the local.
|
// itself is the local.
|
||||||
bool new_local;
|
bool new_local;
|
||||||
@ -409,8 +408,8 @@ static void reportError(Compiler* compiler, const char* file, int line,
|
|||||||
|
|
||||||
char message[ERROR_MESSAGE_SIZE];
|
char message[ERROR_MESSAGE_SIZE];
|
||||||
int length = vsprintf(message, fmt, args);
|
int length = vsprintf(message, fmt, args);
|
||||||
__ASSERT(length < ERROR_MESSAGE_SIZE, "Error message buffer should not exceed "
|
__ASSERT(length < ERROR_MESSAGE_SIZE, "Error message buffer should not "
|
||||||
"the buffer");
|
"exceed the buffer");
|
||||||
vm->config.error_fn(vm, PK_ERROR_COMPILE, file, line, message);
|
vm->config.error_fn(vm, PK_ERROR_COMPILE, file, line, message);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -917,7 +916,7 @@ static NameSearchResult compilerSearchName(Compiler* compiler,
|
|||||||
result.index = index;
|
result.index = index;
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search through functions.
|
// Search through functions.
|
||||||
index = scriptGetFunc(compiler->script, name, length);
|
index = scriptGetFunc(compiler->script, name, length);
|
||||||
if (index != -1) {
|
if (index != -1) {
|
||||||
@ -1194,16 +1193,16 @@ static void exprName(Compiler* compiler) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/* a or b: | a and b:
|
/* a or b: | a and b:
|
||||||
|
|
|
|
||||||
(...) | (...)
|
(...) | (...)
|
||||||
.-- jump_if [offset] | .-- jump_if_not [offset]
|
.-- jump_if [offset] | .-- jump_if_not [offset]
|
||||||
| (...) | | (...)
|
| (...) | | (...)
|
||||||
|-- 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]
|
.--+-- jump [offset] | .--+-- jump [offset]
|
||||||
| '-> push true | | '-> push false
|
| '-> push true | | '-> push false
|
||||||
'----> (...) | '----> (...)
|
'----> (...) | '----> (...)
|
||||||
*/
|
*/
|
||||||
|
|
||||||
void exprOr(Compiler* compiler) {
|
void exprOr(Compiler* compiler) {
|
||||||
emitOpcode(compiler, OP_JUMP_IF);
|
emitOpcode(compiler, OP_JUMP_IF);
|
||||||
@ -1214,7 +1213,7 @@ void exprOr(Compiler* compiler) {
|
|||||||
int true_offset_b = emitShort(compiler, 0xffff); //< Will be patched.
|
int true_offset_b = emitShort(compiler, 0xffff); //< Will be patched.
|
||||||
|
|
||||||
emitOpcode(compiler, OP_PUSH_FALSE);
|
emitOpcode(compiler, OP_PUSH_FALSE);
|
||||||
emitOpcode(compiler, OP_JUMP);
|
emitOpcode(compiler, OP_JUMP);
|
||||||
int end_offset = emitShort(compiler, 0xffff); //< Will be patched.
|
int end_offset = emitShort(compiler, 0xffff); //< Will be patched.
|
||||||
|
|
||||||
patchJump(compiler, true_offset_a);
|
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,
|
static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
|
||||||
Script* script, const PkCompileOptions* options) {
|
Script* script, const PkCompileOptions* options) {
|
||||||
|
|
||||||
compiler->vm = vm;
|
compiler->vm = vm;
|
||||||
compiler->next_compiler = NULL;
|
compiler->next_compiler = NULL;
|
||||||
|
|
||||||
@ -1559,10 +1558,10 @@ static int compilerAddVariable(Compiler* compiler, const char* name,
|
|||||||
// Add the variable and return it's index.
|
// Add the variable and return it's index.
|
||||||
|
|
||||||
if (compiler->scope_depth == DEPTH_GLOBAL) {
|
if (compiler->scope_depth == DEPTH_GLOBAL) {
|
||||||
uint32_t name_index = scriptAddName(compiler->script, compiler->vm,
|
Script* script = compiler->script;
|
||||||
name, length);
|
uint32_t name_index = scriptAddName(script, compiler->vm, name, length);
|
||||||
pkUintBufferWrite(&compiler->script->global_names, compiler->vm, name_index);
|
pkUintBufferWrite(&script->global_names, compiler->vm, name_index);
|
||||||
pkVarBufferWrite(&compiler->script->globals, compiler->vm, VAR_NULL);
|
pkVarBufferWrite(&script->globals, compiler->vm, VAR_NULL);
|
||||||
return compiler->script->globals.count - 1;
|
return compiler->script->globals.count - 1;
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
@ -1726,7 +1725,7 @@ static int compileFunction(Compiler* compiler, FuncType fn_type) {
|
|||||||
name = LITERAL_FN_NAME;
|
name = LITERAL_FN_NAME;
|
||||||
name_length = (int)strlen(name);
|
name_length = (int)strlen(name);
|
||||||
}
|
}
|
||||||
|
|
||||||
Function* func = newFunction(compiler->vm, name, name_length,
|
Function* func = newFunction(compiler->vm, name, name_length,
|
||||||
compiler->script, fn_type == FN_NATIVE);
|
compiler->script, fn_type == FN_NATIVE);
|
||||||
int fn_index = (int)compiler->script->functions.count - 1;
|
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) {
|
if (fn_type != FN_NATIVE) {
|
||||||
compileBlockBody(compiler, BLOCK_FUNC);
|
compileBlockBody(compiler, BLOCK_FUNC);
|
||||||
consume(compiler, TK_END, "Expected 'end' after function definition end.");
|
consume(compiler, TK_END, "Expected 'end' after function definition end.");
|
||||||
|
|
||||||
// TODO: This is the function end return, if we pop all the parameters the
|
// 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
|
// 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
|
// 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;
|
const char* name = compiler->previous.start;
|
||||||
int length = compiler->previous.length;
|
int length = compiler->previous.length;
|
||||||
int line = compiler->previous.line;
|
int line = compiler->previous.line;
|
||||||
|
|
||||||
// Add the name of the symbol to the names buffer.
|
// Add the name of the symbol to the names buffer.
|
||||||
int name_index = (int)scriptAddName(compiler->script, compiler->vm,
|
int name_index = (int)scriptAddName(compiler->script, compiler->vm,
|
||||||
name, length);
|
name, length);
|
||||||
|
|
||||||
// Don't pop the lib since it'll be used for the next entry.
|
// Don't pop the lib since it'll be used for the next entry.
|
||||||
emitOpcode(compiler, OP_GET_ATTRIB_KEEP);
|
emitOpcode(compiler, OP_GET_ATTRIB_KEEP);
|
||||||
emitShort(compiler, name_index); //< Name of the attrib.
|
emitShort(compiler, name_index); //< Name of the attrib.
|
||||||
|
|
||||||
// Check if it has an alias.
|
// Check if it has an alias.
|
||||||
if (match(compiler, TK_AS)) {
|
if (match(compiler, TK_AS)) {
|
||||||
// Consuming it'll update the previous token which would be the name of
|
// Consuming it'll update the previous token which would be the name of
|
||||||
// the binding variable.
|
// the binding variable.
|
||||||
consume(compiler, TK_NAME, "Expected a name after 'as'.");
|
consume(compiler, TK_NAME, "Expected a name after 'as'.");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the variable to bind the imported symbol, if we already have a
|
// Get the variable to bind the imported symbol, if we already have a
|
||||||
// variable with that name override it, otherwise use a new variable.
|
// variable with that name override it, otherwise use a new variable.
|
||||||
const char* name_start = compiler->previous.start;
|
const char* name_start = compiler->previous.start;
|
||||||
@ -2085,7 +2084,7 @@ static void compileFromImport(Compiler* compiler) {
|
|||||||
|
|
||||||
emitStoreVariable(compiler, var_index, true);
|
emitStoreVariable(compiler, var_index, true);
|
||||||
emitOpcode(compiler, OP_POP);
|
emitOpcode(compiler, OP_POP);
|
||||||
|
|
||||||
} while (match(compiler, TK_COMMA) && (skipNewLines(compiler), true));
|
} while (match(compiler, TK_COMMA) && (skipNewLines(compiler), true));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2465,7 +2464,7 @@ PkResult compile(PKVM* vm, Script* script, const char* source,
|
|||||||
skipNewLines(compiler);
|
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_RETURN);
|
||||||
emitOpcode(compiler, OP_END);
|
emitOpcode(compiler, OP_END);
|
||||||
|
|
||||||
@ -2498,7 +2497,7 @@ PkResult compile(PKVM* vm, Script* script, const char* source,
|
|||||||
#if DEBUG_DUMP_COMPILED_CODE
|
#if DEBUG_DUMP_COMPILED_CODE
|
||||||
dumpFunctionCode(vm, script->body);
|
dumpFunctionCode(vm, script->body);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Return the compilation result.
|
// Return the compilation result.
|
||||||
if (compiler->has_errors) {
|
if (compiler->has_errors) {
|
||||||
if (compiler->options && compiler->options->repl_mode &&
|
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) {
|
void compilerMarkObjects(PKVM* vm, Compiler* compiler) {
|
||||||
|
|
||||||
// Mark the script which is currently being compiled.
|
// Mark the script which is currently being compiled.
|
||||||
grayObject(vm, &compiler->script->_super);
|
grayObject(vm, &compiler->script->_super);
|
||||||
|
|
||||||
|
@ -19,9 +19,10 @@ typedef enum {
|
|||||||
// doesn't go through the basic compilation pipeline such as lexing, parsing
|
// doesn't go through the basic compilation pipeline such as lexing, parsing
|
||||||
// (AST), analyzing, intermediate code generation, and target codegeneration
|
// (AST), analyzing, intermediate code generation, and target codegeneration
|
||||||
// one by one. Instead it'll generate the target code as it reads the source
|
// 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,
|
// (directly from lexing to codegen). Despite it's faster than multipass
|
||||||
// we're restricted syntax-wise and from compile-time optimizations, yet we support
|
// compilers, we're restricted syntax-wise and from compile-time optimizations.
|
||||||
// "forward names" to call functions before they defined (unlike C/Python).
|
// Yet we support "forward names" to call functions before they defined
|
||||||
|
// (unlike C/Python).
|
||||||
typedef struct Compiler Compiler;
|
typedef struct Compiler Compiler;
|
||||||
|
|
||||||
// This will take source code as a cstring, compiles it to pocketlang bytecodes
|
// 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
|
// TODO: Currently it's O(n) and could be optimized to O(log(n)) but does it
|
||||||
// worth it?
|
// worth it?
|
||||||
//
|
//
|
||||||
// 'function_names' buffer is un-necessary since the function itself has the
|
// '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
|
// reference to the function name and it can be refactored into a index
|
||||||
// in an "increasing-name" order which can be used to binary search. Similer
|
// buffer in an "increasing-name" order which can be used to binary search.
|
||||||
// for 'global_names' refactor them from VarBuffer to GlobalVarBuffer where
|
// Similer for 'global_names' refactor them from VarBuffer to GlobalVarBuffer
|
||||||
// GlobalVar is struct { const char* name, Var value };
|
// where GlobalVar is struct { const char* name, Var value };
|
||||||
//
|
//
|
||||||
// "increasing-name" order index buffer:
|
// "increasing-name" order index buffer:
|
||||||
// A buffer of int where each is an index in the function buffer and each
|
// 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
|
// points to different functions in an "increasing-name" (could be hash
|
||||||
@ -72,14 +72,10 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
|
|||||||
return NULL;
|
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])
|
#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.
|
// Evaluates to the current function's argument count.
|
||||||
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
|
#define ARGC ((int)(vm->fiber->sp - vm->fiber->ret) - 1)
|
||||||
|
|
||||||
@ -102,7 +98,7 @@ PkHandle* pkGetFunction(PKVM* vm, PkHandle* module,
|
|||||||
__ASSERT(vm->fiber != NULL, \
|
__ASSERT(vm->fiber != NULL, \
|
||||||
"This function can only be called at runtime."); \
|
"This function can only be called at runtime."); \
|
||||||
__ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index."); \
|
__ASSERT(arg > 0 || arg <= ARGC, "Invalid argument index."); \
|
||||||
__ASSERT(value != NULL, "Parameter [value] was NULL."); \
|
__ASSERT(value != NULL, "Argument [value] was NULL."); \
|
||||||
} while (false)
|
} while (false)
|
||||||
|
|
||||||
// Set error for incompatible type provided as an argument.
|
// 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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if [var] is integer. If not set error and return false.
|
// Check if [var] is 32 bit integer. If not set error and return false.
|
||||||
static inline bool validateInteger(PKVM* vm, Var var, int32_t* value,
|
static inline bool validateInteger(PKVM* vm, Var var, int64_t* value,
|
||||||
const char* name) {
|
const char* name) {
|
||||||
double number;
|
double number;
|
||||||
if (isNumeric(var, &number)) {
|
if (isNumeric(var, &number)) {
|
||||||
double truncated = floor(number);
|
// TODO: check if the number is larger for a 64 bit integer.
|
||||||
if (truncated == number) {
|
double floor_val = floor(number);
|
||||||
*value = (int32_t)(truncated);
|
if (floor_val == number) {
|
||||||
|
*value = (int64_t)(floor_val);
|
||||||
return true;
|
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;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
static inline bool validateIndex(PKVM* vm, int32_t index, int32_t size,
|
// Index is could be larger than 32 bit integer, but the size in pocketlang
|
||||||
const char* container) {
|
// 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) {
|
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 false;
|
||||||
}
|
}
|
||||||
return true;
|
return true;
|
||||||
@ -351,12 +350,12 @@ Script* getCoreLib(const PKVM* vm, String* name) {
|
|||||||
|
|
||||||
#define FN_IS_PRIMITE_TYPE(name, check) \
|
#define FN_IS_PRIMITE_TYPE(name, check) \
|
||||||
static void coreIs##name(PKVM* vm) { \
|
static void coreIs##name(PKVM* vm) { \
|
||||||
RET(VAR_BOOL(check(ARG1))); \
|
RET(VAR_BOOL(check(ARG(1)))); \
|
||||||
}
|
}
|
||||||
|
|
||||||
#define FN_IS_OBJ_TYPE(name, _enum) \
|
#define FN_IS_OBJ_TYPE(name, _enum) \
|
||||||
static void coreIs##name(PKVM* vm) { \
|
static void coreIs##name(PKVM* vm) { \
|
||||||
Var arg1 = ARG1; \
|
Var arg1 = ARG(1); \
|
||||||
if (IS_OBJ_TYPE(arg1, _enum)) { \
|
if (IS_OBJ_TYPE(arg1, _enum)) { \
|
||||||
RET(VAR_TRUE); \
|
RET(VAR_TRUE); \
|
||||||
} else { \
|
} else { \
|
||||||
@ -376,29 +375,73 @@ FN_IS_OBJ_TYPE(Function, OBJ_FUNC)
|
|||||||
FN_IS_OBJ_TYPE(Script, OBJ_SCRIPT)
|
FN_IS_OBJ_TYPE(Script, OBJ_SCRIPT)
|
||||||
FN_IS_OBJ_TYPE(UserObj, OBJ_USER)
|
FN_IS_OBJ_TYPE(UserObj, OBJ_USER)
|
||||||
|
|
||||||
PK_DOC(coreTypeName,
|
PK_DOC(
|
||||||
"type_name(value:var) -> string\n"
|
"type_name(value:var) -> string\n"
|
||||||
"Returns the type name of the of the value.") {
|
"Returns the type name of the of the value.",
|
||||||
RET(VAR_OBJ(newString(vm, varTypeName(ARG1))));
|
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"
|
"assert(condition:bool [, msg:string]) -> void\n"
|
||||||
"If the condition is false it'll terminate the current fiber with the "
|
"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;
|
int argc = ARGC;
|
||||||
if (argc != 1 && argc != 2) {
|
if (argc != 1 && argc != 2) {
|
||||||
RET_ERR(newString(vm, "Invalid argument count."));
|
RET_ERR(newString(vm, "Invalid argument count."));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!toBool(ARG1)) {
|
if (!toBool(ARG(1))) {
|
||||||
String* msg = NULL;
|
String* msg = NULL;
|
||||||
|
|
||||||
if (argc == 2) {
|
if (argc == 2) {
|
||||||
if (AS_OBJ(ARG2)->type != OBJ_STRING) {
|
if (AS_OBJ(ARG(2))->type != OBJ_STRING) {
|
||||||
msg = toString(vm, ARG2);
|
msg = toString(vm, ARG(2));
|
||||||
} else {
|
} else {
|
||||||
msg = (String*)AS_OBJ(ARG2);
|
msg = (String*)AS_OBJ(ARG(2));
|
||||||
}
|
}
|
||||||
vmPushTempRef(vm, &msg->_super);
|
vmPushTempRef(vm, &msg->_super);
|
||||||
vm->fiber->error = stringFormat(vm, "Assertion failed: '@'.", msg);
|
vm->fiber->error = stringFormat(vm, "Assertion failed: '@'.", msg);
|
||||||
@ -409,31 +452,34 @@ PK_DOC(coreAssert,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(coreYield,
|
PK_DOC(
|
||||||
"yield([value]) -> var\n"
|
"yield([value]) -> var\n"
|
||||||
"Return the current function with the yield [value] to current running "
|
"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 "
|
"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 "
|
"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;
|
int argc = ARGC;
|
||||||
if (argc > 1) { // yield() or yield(val).
|
if (argc > 1) { // yield() or yield(val).
|
||||||
RET_ERR(newString(vm, "Invalid argument count."));
|
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"
|
"to_string(value:var) -> string\n"
|
||||||
"Returns the string representation of the value.") {
|
"Returns the string representation of the value.",
|
||||||
RET(VAR_OBJ(toString(vm, ARG1)));
|
static void coreToString(PKVM* vm)) {
|
||||||
|
RET(VAR_OBJ(toString(vm, ARG(1))));
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(corePrint,
|
PK_DOC(
|
||||||
"print(...) -> void\n"
|
"print(...) -> void\n"
|
||||||
"Write each argument as comma seperated to the stdout and ends with a "
|
"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
|
// If the host appliaction donesn't provide any write function, discard the
|
||||||
// output.
|
// output.
|
||||||
if (vm->config.write_fn == NULL) return;
|
if (vm->config.write_fn == NULL) return;
|
||||||
@ -446,10 +492,11 @@ PK_DOC(corePrint,
|
|||||||
vm->config.write_fn(vm, "\n");
|
vm->config.write_fn(vm, "\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(coreInput,
|
PK_DOC(
|
||||||
"input([msg:var]) -> string\n"
|
"input([msg:var]) -> string\n"
|
||||||
"Read a line from stdin and returns it without the line ending. Accepting "
|
"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;
|
int argc = ARGC;
|
||||||
if (argc != 1 && argc != 2) {
|
if (argc != 1 && argc != 2) {
|
||||||
RET_ERR(newString(vm, "Invalid argument count."));
|
RET_ERR(newString(vm, "Invalid argument count."));
|
||||||
@ -459,7 +506,7 @@ PK_DOC(coreInput,
|
|||||||
if (vm->config.read_fn == NULL) return;
|
if (vm->config.read_fn == NULL) return;
|
||||||
|
|
||||||
if (argc == 1) {
|
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);
|
PkStringPtr result = vm->config.read_fn(vm);
|
||||||
@ -470,66 +517,26 @@ PK_DOC(coreInput,
|
|||||||
|
|
||||||
// String functions.
|
// 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);
|
// TODO: substring.
|
||||||
char* data = result->data;
|
|
||||||
for (; *data; ++data) *data = (char)tolower(*data);
|
|
||||||
// Since the string is modified re-hash it.
|
|
||||||
result->hash = utilHashString(result->data);
|
|
||||||
|
|
||||||
RET(VAR_OBJ(result));
|
PK_DOC(
|
||||||
}
|
|
||||||
|
|
||||||
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,
|
|
||||||
"str_chr(value:number) -> string\n"
|
"str_chr(value:number) -> string\n"
|
||||||
"Returns the ASCII string value of the integer argument.") {
|
"Returns the ASCII string value of the integer argument.",
|
||||||
int32_t num;
|
static void coreStrChr(PKVM* vm)) {
|
||||||
if (!validateInteger(vm, ARG1, &num, "Argument 1")) return;
|
int64_t num;
|
||||||
|
if (!validateInteger(vm, ARG(1), &num, "Argument 1")) return;
|
||||||
|
|
||||||
|
// TODO: validate num is a byte.
|
||||||
|
|
||||||
char c = (char)num;
|
char c = (char)num;
|
||||||
RET(VAR_OBJ(newStringLength(vm, &c, 1)));
|
RET(VAR_OBJ(newStringLength(vm, &c, 1)));
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(coreStrOrd,
|
PK_DOC(
|
||||||
"str_ord(value:string) -> number\n"
|
"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;
|
String* c;
|
||||||
if (!validateArgString(vm, 1, &c)) return;
|
if (!validateArgString(vm, 1, &c)) return;
|
||||||
if (c->length != 1) {
|
if (c->length != 1) {
|
||||||
@ -543,9 +550,10 @@ PK_DOC(coreStrOrd,
|
|||||||
// List functions.
|
// List functions.
|
||||||
// ---------------
|
// ---------------
|
||||||
|
|
||||||
PK_DOC(coreListAppend,
|
PK_DOC(
|
||||||
"list_append(self:List, value:var) -> List\n"
|
"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;
|
List* list;
|
||||||
if (!validateArgList(vm, 1, &list)) return;
|
if (!validateArgList(vm, 1, &list)) return;
|
||||||
Var elem = ARG(2);
|
Var elem = ARG(2);
|
||||||
@ -557,10 +565,11 @@ PK_DOC(coreListAppend,
|
|||||||
// Map functions.
|
// Map functions.
|
||||||
// --------------
|
// --------------
|
||||||
|
|
||||||
PK_DOC(coreMapRemove,
|
PK_DOC(
|
||||||
"map_remove(self:map, key:var) -> var\n"
|
"map_remove(self:map, key:var) -> var\n"
|
||||||
"Remove the [key] from the map [self] and return it's value if the key "
|
"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;
|
Map* map;
|
||||||
if (!validateArgMap(vm, 1, &map)) return;
|
if (!validateArgMap(vm, 1, &map)) return;
|
||||||
Var key = ARG(2);
|
Var key = ARG(2);
|
||||||
@ -571,35 +580,39 @@ PK_DOC(coreMapRemove,
|
|||||||
// Fiber functions.
|
// Fiber functions.
|
||||||
// ----------------
|
// ----------------
|
||||||
|
|
||||||
PK_DOC(coreFiberNew,
|
PK_DOC(
|
||||||
"fiber_new(fn:function) -> fiber\n"
|
"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;
|
Function* fn;
|
||||||
if (!validateArgFunction(vm, 1, &fn)) return;
|
if (!validateArgFunction(vm, 1, &fn)) return;
|
||||||
RET(VAR_OBJ(newFiber(vm, fn)));
|
RET(VAR_OBJ(newFiber(vm, fn)));
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(coreFiberGetFunc,
|
PK_DOC(
|
||||||
"fiber_get_func(fb:fiber) -> function\n"
|
"fiber_get_func(fb:fiber) -> function\n"
|
||||||
"Retruns the fiber's functions. Which is usefull if you wan't to re-run the "
|
"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;
|
Fiber* fb;
|
||||||
if (!validateArgFiber(vm, 1, &fb)) return;
|
if (!validateArgFiber(vm, 1, &fb)) return;
|
||||||
RET(VAR_OBJ(fb->func));
|
RET(VAR_OBJ(fb->func));
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(coreFiberIsDone,
|
PK_DOC(
|
||||||
"fiber_is_done(fb:fiber) -> bool\n"
|
"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;
|
Fiber* fb;
|
||||||
if (!validateArgFiber(vm, 1, &fb)) return;
|
if (!validateArgFiber(vm, 1, &fb)) return;
|
||||||
RET(VAR_BOOL(fb->state == FIBER_DONE));
|
RET(VAR_BOOL(fb->state == FIBER_DONE));
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(coreFiberRun,
|
PK_DOC(
|
||||||
"fiber_run(fb:fiber, ...) -> var\n"
|
"fiber_run(fb:fiber, ...) -> var\n"
|
||||||
"Runs the fiber's function with the provided arguments and returns it's "
|
"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;
|
int argc = ARGC;
|
||||||
if (argc == 0) // Missing the fiber argument.
|
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"
|
"fiber_resume(fb:fiber) -> var\n"
|
||||||
"Resumes a yielded function from a previous call of fiber_run() function. "
|
"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;
|
int argc = ARGC;
|
||||||
if (argc == 0) // Missing the fiber argument.
|
if (argc == 0) // Missing the fiber argument.
|
||||||
@ -661,7 +675,7 @@ static Script* newModuleInternal(PKVM* vm, const char* name) {
|
|||||||
// hosting application.
|
// hosting application.
|
||||||
if (!IS_UNDEF(mapGet(vm->core_libs, VAR_OBJ(_name)))) {
|
if (!IS_UNDEF(mapGet(vm->core_libs, VAR_OBJ(_name)))) {
|
||||||
vmPopTempRef(vm); // _name
|
vmPopTempRef(vm); // _name
|
||||||
__ASSERT(false, stringFormat(vm,
|
__ASSERT(false, stringFormat(vm,
|
||||||
"A module named '$' already exists", name)->data);
|
"A module named '$' already exists", name)->data);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -749,60 +763,61 @@ void stdLangWrite(PKVM* vm) {
|
|||||||
|
|
||||||
void stdMathFloor(PKVM* vm) {
|
void stdMathFloor(PKVM* vm) {
|
||||||
double num;
|
double num;
|
||||||
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
|
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||||
|
|
||||||
RET(VAR_NUM(floor(num)));
|
RET(VAR_NUM(floor(num)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void stdMathCeil(PKVM* vm) {
|
void stdMathCeil(PKVM* vm) {
|
||||||
double num;
|
double num;
|
||||||
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
|
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||||
|
|
||||||
RET(VAR_NUM(ceil(num)));
|
RET(VAR_NUM(ceil(num)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void stdMathPow(PKVM* vm) {
|
void stdMathPow(PKVM* vm) {
|
||||||
double num, ex;
|
double num, ex;
|
||||||
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
|
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||||
if (!validateNumeric(vm, ARG2, &ex, "Parameter 2")) return;
|
if (!validateNumeric(vm, ARG(2), &ex, "Argument 2")) return;
|
||||||
|
|
||||||
RET(VAR_NUM(pow(num, ex)));
|
RET(VAR_NUM(pow(num, ex)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void stdMathSqrt(PKVM* vm) {
|
void stdMathSqrt(PKVM* vm) {
|
||||||
double num;
|
double num;
|
||||||
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
|
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||||
|
|
||||||
RET(VAR_NUM(sqrt(num)));
|
RET(VAR_NUM(sqrt(num)));
|
||||||
}
|
}
|
||||||
|
|
||||||
void stdMathAbs(PKVM* vm) {
|
void stdMathAbs(PKVM* vm) {
|
||||||
double num;
|
double num;
|
||||||
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
|
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||||
if (num < 0) num = -num;
|
if (num < 0) num = -num;
|
||||||
RET(VAR_NUM(num));
|
RET(VAR_NUM(num));
|
||||||
}
|
}
|
||||||
|
|
||||||
void stdMathSign(PKVM* vm) {
|
void stdMathSign(PKVM* vm) {
|
||||||
double num;
|
double num;
|
||||||
if (!validateNumeric(vm, ARG1, &num, "Parameter 1")) return;
|
if (!validateNumeric(vm, ARG(1), &num, "Argument 1")) return;
|
||||||
if (num < 0) num = -1;
|
if (num < 0) num = -1;
|
||||||
else if (num > 0) num = +1;
|
else if (num > 0) num = +1;
|
||||||
else num = 0;
|
else num = 0;
|
||||||
RET(VAR_NUM(num));
|
RET(VAR_NUM(num));
|
||||||
}
|
}
|
||||||
|
|
||||||
PK_DOC(stdMathHash,
|
PK_DOC(
|
||||||
"hash(value:var) -> num\n"
|
"hash(value:var) -> num\n"
|
||||||
"Return the hash value of the variable, if it's not hashable it'll "
|
"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) {
|
void stdMathHash(PKVM* vm) {
|
||||||
if (IS_OBJ(ARG1)) {
|
if (IS_OBJ(ARG(1))) {
|
||||||
if (!isObjectHashable(AS_OBJ(ARG1)->type)) {
|
if (!isObjectHashable(AS_OBJ(ARG(1))->type)) {
|
||||||
RET(VAR_NULL);
|
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_null", coreIsNull, 1);
|
||||||
INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1);
|
INITALIZE_BUILTIN_FN("is_bool", coreIsBool, 1);
|
||||||
INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1);
|
INITALIZE_BUILTIN_FN("is_num", coreIsNum, 1);
|
||||||
|
|
||||||
INITALIZE_BUILTIN_FN("is_string", coreIsString, 1);
|
INITALIZE_BUILTIN_FN("is_string", coreIsString, 1);
|
||||||
INITALIZE_BUILTIN_FN("is_list", coreIsList, 1);
|
INITALIZE_BUILTIN_FN("is_list", coreIsList, 1);
|
||||||
INITALIZE_BUILTIN_FN("is_map", coreIsMap, 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_function", coreIsFunction, 1);
|
||||||
INITALIZE_BUILTIN_FN("is_script", coreIsScript, 1);
|
INITALIZE_BUILTIN_FN("is_script", coreIsScript, 1);
|
||||||
INITALIZE_BUILTIN_FN("is_userobj", coreIsUserObj, 1);
|
INITALIZE_BUILTIN_FN("is_userobj", coreIsUserObj, 1);
|
||||||
|
|
||||||
|
INITALIZE_BUILTIN_FN("hex", coreHex, 1);
|
||||||
INITALIZE_BUILTIN_FN("assert", coreAssert, -1);
|
INITALIZE_BUILTIN_FN("assert", coreAssert, -1);
|
||||||
INITALIZE_BUILTIN_FN("yield", coreYield, -1);
|
INITALIZE_BUILTIN_FN("yield", coreYield, -1);
|
||||||
INITALIZE_BUILTIN_FN("to_string", coreToString, 1);
|
INITALIZE_BUILTIN_FN("to_string", coreToString, 1);
|
||||||
@ -853,9 +869,6 @@ void initializeCore(PKVM* vm) {
|
|||||||
INITALIZE_BUILTIN_FN("input", coreInput, -1);
|
INITALIZE_BUILTIN_FN("input", coreInput, -1);
|
||||||
|
|
||||||
// String functions.
|
// 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_chr", coreStrChr, 1);
|
||||||
INITALIZE_BUILTIN_FN("str_ord", coreStrOrd, 1);
|
INITALIZE_BUILTIN_FN("str_ord", coreStrOrd, 1);
|
||||||
|
|
||||||
@ -1022,13 +1035,31 @@ bool varLesser(Var v1, Var v2) {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// A convinent convenient macro used in varGetAttrib and varSetAttrib.
|
// Here we're switching the FNV-1a hash value of the name (cstring). Which is
|
||||||
#define IS_ATTRIB(name) \
|
// an efficient way than having multiple if (attrib == "name"). From O(n) * k
|
||||||
(attrib->length == strlen(name) && strcmp(name, attrib->data) == 0)
|
// 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.
|
// Set error for accessing non-existed attribute.
|
||||||
#define ERR_NO_ATTRIB() \
|
#define ERR_NO_ATTRIB(on) \
|
||||||
vm->fiber->error = stringFormat(vm, "'$' objects has no attribute " \
|
vm->fiber->error = stringFormat(vm, "'$' object has no attribute " \
|
||||||
"named '$'", \
|
"named '$'", \
|
||||||
varTypeName(on), attrib->data);
|
varTypeName(on), attrib->data);
|
||||||
|
|
||||||
@ -1044,57 +1075,68 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
|||||||
switch (obj->type) {
|
switch (obj->type) {
|
||||||
case OBJ_STRING:
|
case OBJ_STRING:
|
||||||
{
|
{
|
||||||
if (IS_ATTRIB("length")) {
|
String* str = (String*)obj;
|
||||||
size_t length = ((String*)obj)->length;
|
SWITCH_ATTRIB(attrib->data) {
|
||||||
return VAR_NUM((double)length);
|
|
||||||
|
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();
|
UNREACHABLE();
|
||||||
return VAR_NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case OBJ_LIST:
|
case OBJ_LIST:
|
||||||
{
|
{
|
||||||
if (IS_ATTRIB("length")) {
|
List* list = (List*)obj;
|
||||||
size_t length = ((List*)obj)->elements.count;
|
SWITCH_ATTRIB(attrib->data) {
|
||||||
return VAR_NUM((double)length);
|
|
||||||
|
CASE_ATTRIB("length", 0x83d03615) :
|
||||||
|
return VAR_NUM((double)(list->elements.count));
|
||||||
|
|
||||||
|
default:
|
||||||
|
ERR_NO_ATTRIB(on);
|
||||||
|
return VAR_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_NO_ATTRIB();
|
UNREACHABLE();
|
||||||
return VAR_NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case OBJ_MAP:
|
case OBJ_MAP:
|
||||||
{
|
{
|
||||||
TODO; // Not sure should I allow this(below).
|
// Not sure should I allow string values could be accessed with
|
||||||
//Var value = mapGet((Map*)obj, VAR_OBJ(attrib));
|
// this way. ex:
|
||||||
//if (IS_UNDEF(value)) {
|
// map = { "foo" : 42, "can't access" : 32 }
|
||||||
// vm->fiber->error = stringFormat(vm, "Key (\"@\") not exists.",
|
// val = map.foo ## 42
|
||||||
// attrib);
|
TODO;
|
||||||
// return VAR_NULL;
|
|
||||||
//}
|
|
||||||
//return value;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case OBJ_RANGE:
|
case OBJ_RANGE:
|
||||||
{
|
{
|
||||||
Range* range = (Range*)obj;
|
Range* range = (Range*)obj;
|
||||||
|
SWITCH_ATTRIB(attrib->data) {
|
||||||
|
|
||||||
if (IS_ATTRIB("as_list")) {
|
CASE_ATTRIB("as_list", 0x1562c22) :
|
||||||
List* list;
|
return VAR_OBJ(rangeAsList(vm, range));
|
||||||
if (range->from < range->to) {
|
|
||||||
list = newList(vm, (uint32_t)(range->to - range->from));
|
default:
|
||||||
for (double i = range->from; i < range->to; i++) {
|
ERR_NO_ATTRIB(on);
|
||||||
pkVarBufferWrite(&list->elements, vm, VAR_NUM(i));
|
return VAR_NULL;
|
||||||
}
|
|
||||||
} else {
|
|
||||||
list = newList(vm, 0);
|
|
||||||
}
|
|
||||||
return VAR_OBJ(list);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_NO_ATTRIB();
|
UNREACHABLE();
|
||||||
return VAR_NULL;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
case OBJ_SCRIPT: {
|
case OBJ_SCRIPT: {
|
||||||
@ -1114,11 +1156,28 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
|||||||
return scr->globals.data[index];
|
return scr->globals.data[index];
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_NO_ATTRIB();
|
ERR_NO_ATTRIB(on);
|
||||||
return VAR_NULL;
|
return VAR_NULL;
|
||||||
}
|
}
|
||||||
|
|
||||||
case OBJ_FUNC:
|
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_FIBER:
|
||||||
case OBJ_USER:
|
case OBJ_USER:
|
||||||
TODO;
|
TODO;
|
||||||
@ -1133,10 +1192,10 @@ Var varGetAttrib(PKVM* vm, Var on, String* attrib) {
|
|||||||
|
|
||||||
void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
|
void varSetAttrib(PKVM* vm, Var on, String* attrib, Var value) {
|
||||||
|
|
||||||
#define ATTRIB_IMMUTABLE(prop) \
|
#define ATTRIB_IMMUTABLE(name) \
|
||||||
do { \
|
do { \
|
||||||
if (IS_ATTRIB(prop)) { \
|
if ((attrib->length == strlen(name) && strcmp(name, attrib->data) == 0)) { \
|
||||||
vm->fiber->error = stringFormat(vm, "'$' attribute is immutable.", prop); \
|
vm->fiber->error = stringFormat(vm, "'$' attribute is immutable.", name); \
|
||||||
return; \
|
return; \
|
||||||
} \
|
} \
|
||||||
} while (false)
|
} while (false)
|
||||||
@ -1151,21 +1210,30 @@ do { \
|
|||||||
switch (obj->type) {
|
switch (obj->type) {
|
||||||
case OBJ_STRING:
|
case OBJ_STRING:
|
||||||
ATTRIB_IMMUTABLE("length");
|
ATTRIB_IMMUTABLE("length");
|
||||||
ERR_NO_ATTRIB();
|
ATTRIB_IMMUTABLE("lower");
|
||||||
|
ATTRIB_IMMUTABLE("upper");
|
||||||
|
ATTRIB_IMMUTABLE("strip");
|
||||||
|
ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OBJ_LIST:
|
case OBJ_LIST:
|
||||||
ATTRIB_IMMUTABLE("length");
|
ATTRIB_IMMUTABLE("length");
|
||||||
ERR_NO_ATTRIB();
|
ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OBJ_MAP:
|
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;
|
TODO;
|
||||||
ERR_NO_ATTRIB();
|
|
||||||
|
ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OBJ_RANGE:
|
case OBJ_RANGE:
|
||||||
ERR_NO_ATTRIB();
|
ATTRIB_IMMUTABLE("as_list");
|
||||||
|
ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OBJ_SCRIPT: {
|
case OBJ_SCRIPT: {
|
||||||
@ -1187,20 +1255,22 @@ do { \
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
ERR_NO_ATTRIB();
|
ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
case OBJ_FUNC:
|
case OBJ_FUNC:
|
||||||
ERR_NO_ATTRIB();
|
ATTRIB_IMMUTABLE("arity");
|
||||||
|
ATTRIB_IMMUTABLE("name");
|
||||||
|
ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OBJ_FIBER:
|
case OBJ_FIBER:
|
||||||
ERR_NO_ATTRIB();
|
ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
case OBJ_USER:
|
case OBJ_USER:
|
||||||
TODO; //ERR_NO_ATTRIB();
|
TODO; //ERR_NO_ATTRIB(on);
|
||||||
return;
|
return;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
@ -1208,6 +1278,8 @@ do { \
|
|||||||
}
|
}
|
||||||
CHECK_MISSING_OBJ_TYPE(7);
|
CHECK_MISSING_OBJ_TYPE(7);
|
||||||
UNREACHABLE();
|
UNREACHABLE();
|
||||||
|
|
||||||
|
#undef ATTRIB_IMMUTABLE
|
||||||
}
|
}
|
||||||
|
|
||||||
Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
||||||
@ -1221,7 +1293,7 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
|||||||
switch (obj->type) {
|
switch (obj->type) {
|
||||||
case OBJ_STRING:
|
case OBJ_STRING:
|
||||||
{
|
{
|
||||||
int32_t index;
|
int64_t index;
|
||||||
String* str = ((String*)obj);
|
String* str = ((String*)obj);
|
||||||
if (!validateInteger(vm, key, &index, "List index")) {
|
if (!validateInteger(vm, key, &index, "List index")) {
|
||||||
return VAR_NULL;
|
return VAR_NULL;
|
||||||
@ -1235,12 +1307,12 @@ Var varGetSubscript(PKVM* vm, Var on, Var key) {
|
|||||||
|
|
||||||
case OBJ_LIST:
|
case OBJ_LIST:
|
||||||
{
|
{
|
||||||
int32_t index;
|
int64_t index;
|
||||||
pkVarBuffer* elems = &((List*)obj)->elements;
|
pkVarBuffer* elems = &((List*)obj)->elements;
|
||||||
if (!validateInteger(vm, key, &index, "List index")) {
|
if (!validateInteger(vm, key, &index, "List index")) {
|
||||||
return VAR_NULL;
|
return VAR_NULL;
|
||||||
}
|
}
|
||||||
if (!validateIndex(vm, index, (int)elems->count, "List")) {
|
if (!validateIndex(vm, index, elems->count, "List")) {
|
||||||
return VAR_NULL;
|
return VAR_NULL;
|
||||||
}
|
}
|
||||||
return elems->data[index];
|
return elems->data[index];
|
||||||
@ -1293,10 +1365,10 @@ void varsetSubscript(PKVM* vm, Var on, Var key, Var value) {
|
|||||||
|
|
||||||
case OBJ_LIST:
|
case OBJ_LIST:
|
||||||
{
|
{
|
||||||
int32_t index;
|
int64_t index;
|
||||||
pkVarBuffer* elems = &((List*)obj)->elements;
|
pkVarBuffer* elems = &((List*)obj)->elements;
|
||||||
if (!validateInteger(vm, key, &index, "List index")) return;
|
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;
|
elems->data[index] = value;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -185,7 +185,6 @@ void dumpFunctionCode(PKVM* vm, Function* func) {
|
|||||||
case OP_LIST_APPEND: NO_ARGS(); break;
|
case OP_LIST_APPEND: NO_ARGS(); break;
|
||||||
case OP_MAP_INSERT: NO_ARGS(); break;
|
case OP_MAP_INSERT: NO_ARGS(); break;
|
||||||
|
|
||||||
|
|
||||||
case OP_PUSH_LOCAL_0:
|
case OP_PUSH_LOCAL_0:
|
||||||
case OP_PUSH_LOCAL_1:
|
case OP_PUSH_LOCAL_1:
|
||||||
case OP_PUSH_LOCAL_2:
|
case OP_PUSH_LOCAL_2:
|
||||||
|
@ -153,7 +153,7 @@ OPCODE(SET_ATTRIB, 2, -1)
|
|||||||
// Pop var, key, get value and push the result.
|
// Pop var, key, get value and push the result.
|
||||||
OPCODE(GET_SUBSCRIPT, 0, -1)
|
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).
|
// pop the var and the key. (ex: map[key] += value).
|
||||||
OPCODE(GET_SUBSCRIPT_KEEP, 0, 1)
|
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 "pk_var.h"
|
||||||
|
|
||||||
#include <math.h>
|
#include <math.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
#include "pk_utils.h"
|
#include "pk_utils.h"
|
||||||
#include "pk_vm.h"
|
#include "pk_vm.h"
|
||||||
|
|
||||||
@ -424,6 +426,97 @@ Fiber* newFiber(PKVM* vm, Function* fn) {
|
|||||||
return fiber;
|
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) {
|
void listInsert(PKVM* vm, List* self, uint32_t index, Var value) {
|
||||||
|
|
||||||
// Add an empty slot at the end of the buffer.
|
// 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
|
// TODO: Union tagging implementation of all the above macros ignore macros
|
||||||
// starts with an underscore.
|
// starts with an underscore.
|
||||||
|
|
||||||
|
|
||||||
typedef enum {
|
typedef enum {
|
||||||
VAR_UNDEFINED, //< Internal type for exceptions.
|
VAR_UNDEFINED, //< Internal type for exceptions.
|
||||||
VAR_NULL, //< Null pointer type.
|
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.
|
// working list to traverse and update the vm's [bytes_allocated] value.
|
||||||
void blackenObjects(PKVM* vm);
|
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
|
// Insert [value] to the list at [index] and shift down the rest of the
|
||||||
// elements.
|
// elements.
|
||||||
void listInsert(PKVM* vm, List* self, uint32_t index, Var value);
|
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
|
result = 'data' -> fn1 -> fn2{'suff'} -> fn3
|
||||||
assert(result == '[fn3:[fn2:[fn1:data]|suff]]')
|
assert(result == '[fn3:[fn2:[fn1:data]|suff]]')
|
||||||
|
|
||||||
result = ' tEST+InG ' -> str_strip -> str_lower
|
# str_lower(s) function refactored to s.lower attribute.
|
||||||
assert(result == 'test+ing')
|
#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