native interface refactored

This commit is contained in:
Thakee Nathees 2022-05-04 11:58:23 +05:30
parent a9f57d9ec7
commit 3c107d4da2
21 changed files with 622 additions and 739 deletions

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2020-2022 Thakee Nathees
* Copyright (c) 2021-2022 Pocketlang Contributors
* Distributed Under The MIT License
*/
#ifndef COMMON_H
#define COMMON_H
#include <pocketlang.h>
#include <assert.h>
#include <errno.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdio.h>
#include "modules/common.h"
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#elif defined(__clang__)
#pragma clang diagnostic ignored "-Wint-to-pointer-cast"
#pragma clang diagnostic ignored "-Wunused-parameter"
#elif defined(_MSC_VER)
#pragma warning(disable:26812)
#endif
#define CLI_NOTICE \
"PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \
"Copyright (c) 2020-2021 ThakeeNathees\n" \
"Copyright (c) 2021-2022 Pocketlang Contributors\n" \
"Free and open source software under the terms of the MIT license.\n"
// FIXME: the vm user data of cli.
typedef struct {
bool repl_mode;
} VmUserData;
#endif // COMMON_H

View File

@ -4,110 +4,23 @@
* Distributed Under The MIT License
*/
#include "internal.h"
#include "modules/modules.h"
#include "thirdparty/argparse/argparse.h"
#include <pocketlang.h>
#include <stdio.h>
// FIXME: Everything below here is temporary and for testing.
int repl(PKVM* vm, const PkCompileOptions* options);
const char* read_line(uint32_t* length);
#if defined(__GNUC__)
#pragma GCC diagnostic ignored "-Wint-to-pointer-cast"
#pragma GCC diagnostic ignored "-Wunused-parameter"
#elif defined(__clang__)
#pragma clang diagnostic ignored "-Wint-to-pointer-cast"
#pragma clang diagnostic ignored "-Wunused-parameter"
#elif defined(_MSC_VER)
#pragma warning(disable:26812)
#endif
// ---------------------------------------
void onResultDone(PKVM* vm, PkStringPtr result) {
if ((bool)result.user_data) {
free((void*)result.string);
}
}
void stderrWrite(PKVM* vm, const char* text) {
fprintf(stderr, "%s", text);
}
void stdoutWrite(PKVM* vm, const char* text) {
fprintf(stdout, "%s", text);
}
PkStringPtr stdinRead(PKVM* vm) {
PkStringPtr result;
result.string = read_line(NULL);
result.on_done = onResultDone;
result.user_data = (void*)true;
return result;
}
PkStringPtr resolvePath(PKVM* vm, const char* from, const char* path) {
PkStringPtr result;
result.on_done = onResultDone;
size_t from_dir_len;
pathGetDirName(from, &from_dir_len);
// FIXME: Should handle paths with size of more than FILENAME_MAX.
if (from_dir_len == 0 || pathIsAbsolute(path)) {
size_t length = strlen(path);
char* resolved = (char*)malloc(length + 1);
pathNormalize(path, resolved, length + 1);
result.string = resolved;
result.user_data = (void*)true;
} else {
char from_dir[FILENAME_MAX];
strncpy(from_dir, from, from_dir_len);
from_dir[from_dir_len] = '\0';
char fullpath[FILENAME_MAX];
size_t length = pathJoin(from_dir, path, fullpath, sizeof(fullpath));
char* resolved = (char*)malloc(length + 1);
pathNormalize(fullpath, resolved, length + 1);
result.string = resolved;
result.user_data = (void*)true;
}
return result;
}
PkStringPtr loadScript(PKVM* vm, const char* path) {
PkStringPtr result;
result.on_done = onResultDone;
// Open the file.
FILE* file = fopen(path, "r");
if (file == NULL) {
// Setting .string to NULL to indicate the failure of loading the script.
result.string = NULL;
return result;
}
// Get the source length.
fseek(file, 0, SEEK_END);
long file_size = ftell(file);
fseek(file, 0, SEEK_SET);
// Read source to buffer.
char* buff = (char*)malloc((size_t)(file_size) + 1);
// Using read instead of file_size is because "\r\n" is read as '\n' in
// windows.
if (buff != NULL) {
size_t read = fread(buff, sizeof(char), file_size, file);
buff[read] = '\0';
}
fclose(file);
result.string = buff;
result.user_data = (void*)true;
return result;
}
#include "modules/modules.h"
#include "thirdparty/argparse/argparse.h"
// FIXME:
// Included for isatty(). This should be moved to somewhere. and I'm not sure
@ -115,20 +28,23 @@ PkStringPtr loadScript(PKVM* vm, const char* path) {
#ifdef _WIN32
#include <Windows.h>
#include <io.h>
#define isatty _isatty
#define fileno _fileno
#else
#include <unistd.h>
#endif
#define CLI_NOTICE \
"PocketLang " PK_VERSION_STRING " (https://github.com/ThakeeNathees/pocketlang/)\n" \
"Copyright (c) 2020-2021 ThakeeNathees\n" \
"Copyright (c) 2021-2022 Pocketlang Contributors\n" \
"Free and open source software under the terms of the MIT license.\n"
// Create new pocket VM and set it's configuration.
static PKVM* intializePocketVM() {
PkConfiguration config = pkNewConfiguration();
config.stderr_write = stderrWrite;
config.stdout_write = stdoutWrite;
config.stdin_read = stdinRead;
config.load_script_fn = loadScript;
config.resolve_path_fn = resolvePath;
config.resolve_path_fn = pathResolveImport;
// FIXME:
// Refactor and make it portable. Maybe custom is_tty() function?.
@ -195,21 +111,12 @@ int main(int argc, const char** argv) {
// Create and initialize pocket VM.
PKVM* vm = intializePocketVM();
VmUserData user_data;
user_data.repl_mode = false;
pkSetUserData(vm, &user_data);
REGISTER_ALL_MODULES(vm);
PkCompileOptions options = pkNewCompilerOptions();
options.debug = debug;
if (cmd != NULL) { // pocket -c "print('foo')"
PkStringPtr source = { cmd, NULL, NULL, 0, 0 };
PkStringPtr path = { "@(Source)", NULL, NULL, 0, 0 };
PkResult result = pkInterpretSource(vm, source, path, NULL);
exitcode = (int)result;
PkResult result = pkRunString(vm, cmd);
exitcode = (int) result;
} else if (argc == 0) { // Run on REPL mode.
@ -218,22 +125,13 @@ int main(int argc, const char** argv) {
printf("%s", CLI_NOTICE);
}
options.repl_mode = true;
exitcode = repl(vm, &options);
exitcode = pkRunREPL(vm);
} else { // pocket file.pk ...
PkStringPtr resolved = resolvePath(vm, ".", argv[0]);
PkStringPtr source = loadScript(vm, resolved.string);
if (source.string != NULL) {
PkResult result = pkInterpretSource(vm, source, resolved, &options);
exitcode = (int)result;
} else {
fprintf(stderr, "Error: cannot open file at \"%s\"\n", resolved.string);
if (resolved.on_done != NULL) resolved.on_done(vm, resolved);
if (source.on_done != NULL) source.on_done(vm, source);
}
const char* file_path = argv[0];
PkResult result = pkRunFile(vm, file_path);
exitcode = (int) result;
}
// Cleanup the VM and exit.

View File

@ -57,13 +57,9 @@ void registerModuleMath(PKVM* vm);
// can be used for cli's internals we're defining such functions here and they
// will be imported in the cli.
bool pathIsAbsolute(const char* path);
void pathGetDirName(const char* path, size_t* length);
size_t pathNormalize(const char* path, char* buff, size_t buff_size);
size_t pathJoin(const char* from, const char* path, char* buffer,
size_t buff_size);
// The pocketlang's import statement path resolving function. This
// implementation is required by pockelang from it's hosting application
// inorder to use the import statements.
char* pathResolveImport(PKVM * vm, const char* from, const char* path);
#endif // MODULES_H

View File

@ -6,6 +6,11 @@
#include "modules.h"
// FIXME:
// In windows MinGw support most of the posix libraries. So we only need to
// check if it's MSVC (and tcc in windows) or not for posix headers and
// Refactor the bellow macro includes.
#include "thirdparty/cwalk/cwalk.h"
#if defined(_WIN32) && (defined(_MSC_VER) || defined(__TINYC__))
#include "thirdparty/dirent/dirent.h"
@ -22,35 +27,89 @@
#define get_cwd getcwd
#endif
// The maximum path size that pocketlang's default import system supports
// including the null terminator. To be able to support more characters
// override the functions from the host application. Since this is very much
// platform specific we're defining a more general limit.
// See: https://insanecoding.blogspot.com/2007/11/pathmax-simply-isnt.html
#define MAX_PATH_LEN 4096
// The cstring pointer buffer size used in path.join(p1, p2, ...). Tune this
// value as needed.
#define MAX_JOIN_PATHS 8
static inline size_t pathAbs(const char* path, char* buff, size_t buff_size);
static inline bool pathIsFileExists(const char* path);
/*****************************************************************************/
/* PATH SHARED FUNCTIONS */
/*****************************************************************************/
bool pathIsAbsolute(const char* path) {
return cwk_path_is_absolute(path);
}
// Implementation of pocketlang import path resolving function.
char* pathResolveImport(PKVM* vm, const char* from, const char* path) {
void pathGetDirName(const char* path, size_t* length) {
cwk_path_get_dirname(path, length);
}
// Buffers to store intermediate path results.
char buff1[FILENAME_MAX];
char buff2[FILENAME_MAX];
size_t pathNormalize(const char* path, char* buff, size_t buff_size) {
return cwk_path_normalize(path, buff, buff_size);
}
// If the path is absolute, Just normalize and return it.
if (cwk_path_is_absolute(path)) {
// buff1 = normalized path. +1 for null terminator.
size_t size = cwk_path_normalize(path, buff1, sizeof(buff1)) + 1;
size_t pathJoin(const char* path_a, const char* path_b, char* buffer,
size_t buff_size) {
return cwk_path_join(path_a, path_b, buffer, buff_size);
char* ret = pkAllocString(vm, size);
memcpy(ret, buff1, size);
return ret;
}
if (from == NULL) { //< [path] is relative to cwd.
// buff1 = absolute path of [path].
pathAbs(path, buff1, sizeof(buff1));
// buff2 = normalized path. +1 for null terminator.
size_t size = cwk_path_normalize(buff1, buff2, sizeof(buff2)) + 1;
char* ret = pkAllocString(vm, size);
memcpy(ret, buff2, size);
return ret;
}
// Import statements doesn't support absolute paths.
ASSERT(cwk_path_is_absolute(from), "From path should be absolute.");
// From is a path of a script. Try relative to that script.
{
size_t from_dir_length = 0;
cwk_path_get_dirname(from, &from_dir_length);
if (from_dir_length == 0) return NULL;
// buff1 = from directory.
strncpy(buff1, from, sizeof(buff1));
buff1[from_dir_length] = '\0';
// buff2 = absolute joined path.
cwk_path_join(buff1, path, buff2, sizeof(buff2));
// buff1 = normalized absolute path. +1 for null terminator
size_t size = cwk_path_normalize(buff2, buff1, sizeof(buff1)) + 1;
if (pathIsFileExists(buff1)) {
char* ret = pkAllocString(vm, size);
memcpy(ret, buff1, size);
return ret;
}
}
// Cannot resolve the path. Return NULL to indicate failure.
return NULL;
}
/*****************************************************************************/
/* PATH INTERNAL FUNCTIONS */
/*****************************************************************************/
// FIXME: refactor (ref: https://stackoverflow.com/a/230068/10846399)
static inline bool pathIsFileExists(const char* path) {
FILE* file = fopen(path, "r");
if (file != NULL) {
@ -79,7 +138,7 @@ static inline bool pathIsExists(const char* path) {
static inline size_t pathAbs(const char* path, char* buff, size_t buff_size) {
char cwd[FILENAME_MAX];
char cwd[MAX_PATH_LEN];
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
// TODO: handle error.
@ -99,7 +158,7 @@ DEF(_pathSetStyleUnix, "") {
}
DEF(_pathGetCWD, "") {
char cwd[FILENAME_MAX];
char cwd[MAX_PATH_LEN];
if (get_cwd(cwd, sizeof(cwd)) == NULL) {
// TODO: Handle error.
}
@ -110,7 +169,7 @@ DEF(_pathAbspath, "") {
const char* path;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
char abspath[FILENAME_MAX];
char abspath[MAX_PATH_LEN];
uint32_t len = (uint32_t) pathAbs(path, abspath, sizeof(abspath));
pkSetSlotStringLength(vm, 0, abspath, len);
}
@ -120,13 +179,13 @@ DEF(_pathRelpath, "") {
if (!pkValidateSlotString(vm, 1, &from, NULL)) return;
if (!pkValidateSlotString(vm, 2, &path, NULL)) return;
char abs_from[FILENAME_MAX];
char abs_from[MAX_PATH_LEN];
pathAbs(from, abs_from, sizeof(abs_from));
char abs_path[FILENAME_MAX];
char abs_path[MAX_PATH_LEN];
pathAbs(path, abs_path, sizeof(abs_path));
char result[FILENAME_MAX];
char result[MAX_PATH_LEN];
uint32_t len = (uint32_t) cwk_path_get_relative(abs_from, abs_path,
result, sizeof(result));
pkSetSlotStringLength(vm, 0, result, len);
@ -147,7 +206,7 @@ DEF(_pathJoin, "") {
}
paths[argc] = NULL;
char result[FILENAME_MAX];
char result[MAX_PATH_LEN];
uint32_t len = (uint32_t) cwk_path_join_multiple(paths, result,
sizeof(result));
pkSetSlotStringLength(vm, 0, result, len);
@ -157,7 +216,7 @@ DEF(_pathNormalize, "") {
const char* path;
if (!pkValidateSlotString(vm, 1, &path, NULL)) return;
char result[FILENAME_MAX];
char result[MAX_PATH_LEN];
uint32_t len = (uint32_t) cwk_path_normalize(path, result, sizeof(result));
pkSetSlotStringLength(vm, 0, result, len);
}

View File

@ -1,148 +0,0 @@
/*
* Copyright (c) 2020-2022 Thakee Nathees
* Copyright (c) 2021-2022 Pocketlang Contributors
* Distributed Under The MIT License
*/
// The REPL (Read Evaluate Print Loop) implementation.
// https://en.wikipedia.org/wiki/Read-eval-print_loop.
#include "internal.h"
#include <ctype.h> // isspace
#include "utils.h"
// FIXME: use fgetc char by char till reach a new line.
// TODO: Will need refactoring to use a byte buffer.
//
// Read a line from stdin and returns it without the line ending. Accepting
// an optional argument [length] (could be NULL). Note that the returned string
// is heap allocated and should be cleaned by 'free()' function.
const char* read_line(uint32_t* length) {
const int size = 1024;
char* mem = (char*)malloc(size);
if (fgets(mem, size, stdin) == NULL) {
// TODO: handle error.
}
size_t len = strlen(mem);
// FIXME: handle \r\n, this is temp.
mem[len - 1] = '\0';
if (length != NULL) *length = (uint32_t)(len - 1);
return mem;
}
// Read a line from stdin and returns it without the line ending.
static void readLine(ByteBuffer* buff) {
char c;
do {
c = (char)fgetc(stdin);
if (c == '\n') break;
byteBufferWrite(buff, (uint8_t)c);
} while (c != EOF);
byteBufferWrite(buff, '\0');
}
// Returns true if the string is empty, used to check if the input line is
// empty to skip compilation of empty string.
static inline bool is_str_empty(const char* line) {
ASSERT(line != NULL, OOPS);
for (const char* c = line; *c != '\0'; c++) {
if (!isspace(*c)) return false;
}
return true;
}
// The main loop of the REPL. Will return the exit code.
int repl(PKVM* vm, const PkCompileOptions* options) {
// Set repl_mode of the user_data.
VmUserData* user_data = (VmUserData*)pkGetUserData(vm);
user_data->repl_mode = true;
// The main module that'll be used to compile and execute the input source.
PkHandle* module = pkNewModule(vm, "@(REPL)");
// A buffer to store lines read from stdin.
ByteBuffer lines;
byteBufferInit(&lines);
// A buffer to store a line read from stdin.
ByteBuffer line;
byteBufferInit(&line);
// Will be set to true if the compilation failed with unexpected EOF to add
// more lines to the [lines] buffer.
bool need_more_lines = false;
bool done = false;
do {
// Print the input listening line.
if (!need_more_lines) {
printf(">>> ");
} else {
printf("... ");
}
// Read a line from stdin and add the line to the lines buffer.
readLine(&line);
bool is_empty = is_str_empty((const char*)line.data);
// If the line contains EOF, REPL should be stopped.
if (line.count >= 2 && *((char*)(line.data) + line.count -2) == EOF) {
fprintf(stdout, "\n");
break;
}
// If the line is empty, we don't have to compile it.
if (is_empty && !need_more_lines) {
byteBufferClear(&line);
ASSERT(lines.count == 0, OOPS);
continue;
}
if (lines.count != 0) byteBufferWrite(&lines, '\n');
byteBufferAddString(&lines, (const char*)line.data, line.count - 1);
byteBufferClear(&line);
byteBufferWrite(&lines, '\0');
// Compile the buffer to the module.
PkStringPtr source_ptr = { (const char*)lines.data, NULL, NULL };
PkResult result = pkCompileModule(vm, module, source_ptr, options);
if (result == PK_RESULT_UNEXPECTED_EOF) {
ASSERT(lines.count > 0 && lines.data[lines.count - 1] == '\0', OOPS);
lines.count -= 1; // Remove the null byte to append a new string.
need_more_lines = true;
continue;
}
// We're buffering the lines for unexpected EOF, if we reached here that
// means it's either successfully compiled or compilation error. Clean the
// buffer for the next iteration.
need_more_lines = false;
byteBufferClear(&lines);
if (result != PK_RESULT_SUCCESS) continue;
// Compiled source would be the "main" function of the module. Run it.
PkHandle* _main = pkModuleGetMainFunction(vm, module);
ASSERT(_main != NULL, OOPS);
result = pkRunFunction(vm, _main, 0, -1, -1);
pkReleaseHandle(vm, _main);
} while (!done);
byteBufferClear(&lines);
pkReleaseHandle(vm, module);
return 0;
}

View File

@ -1,69 +0,0 @@
/*
* Copyright (c) 2020-2022 Thakee Nathees
* Copyright (c) 2021-2022 Pocketlang Contributors
* Distributed Under The MIT License
*/
#include "utils.h"
// The factor by which a buffer will grow when it's capacity reached.
#define GROW_FACTOR 2
// The initial minimum capacity of a buffer to allocate.
#define MIN_CAPACITY 8
static inline int powerOf2Ceil(int n) {
n--;
n |= n >> 1;
n |= n >> 2;
n |= n >> 4;
n |= n >> 8;
n |= n >> 16;
n++;
return n;
}
/*****************************************************************************/
/* BYTE BUFFER IMPLEMENTATION */
/*****************************************************************************/
void byteBufferInit(ByteBuffer* buffer) {
buffer->data = NULL;
buffer->count = 0;
buffer->capacity = 0;
}
void byteBufferClear(ByteBuffer* buffer) {
free(buffer->data);
buffer->data = NULL;
buffer->count = 0;
buffer->capacity = 0;
}
void byteBufferReserve(ByteBuffer* buffer, size_t size) {
if (buffer->capacity < size) {
int capacity = powerOf2Ceil((int)size);
if (capacity < MIN_CAPACITY) capacity = MIN_CAPACITY;
buffer->data = (uint8_t*)realloc(buffer->data, capacity * sizeof(uint8_t));
buffer->capacity = capacity;
}
}
void byteBufferFill(ByteBuffer* buffer, uint8_t data, int count) {
byteBufferReserve(buffer, buffer->count + count);
for (int i = 0; i < count; i++) {
buffer->data[buffer->count++] = data;
}
}
void byteBufferWrite(ByteBuffer* buffer, uint8_t data) {
byteBufferFill(buffer, data, 1);
}
void byteBufferAddString(ByteBuffer* buffer, const char* str,
uint32_t length) {
byteBufferReserve(buffer, buffer->count + length);
for (uint32_t i = 0; i < length; i++) {
buffer->data[buffer->count++] = *(str++);
}
}

View File

@ -1,38 +0,0 @@
/*
* Copyright (c) 2020-2022 Thakee Nathees
* Copyright (c) 2021-2022 Pocketlang Contributors
* Distributed Under The MIT License
*/
#include "internal.h"
typedef struct {
uint8_t* data;
uint32_t count;
uint32_t capacity;
} ByteBuffer;
/*****************************************************************************/
/* BYTE BUFFER */
/*****************************************************************************/
// Initialize a new buffer int instance.
void byteBufferInit(ByteBuffer* buffer);
// Clears the allocated elements from the VM's realloc function.
void byteBufferClear(ByteBuffer* buffer);
// Ensure the capacity is greater than [size], if not resize.
void byteBufferReserve(ByteBuffer* buffer, size_t size);
// Fill the buffer at the end of it with provided data if the capacity
// isn't enough using VM's realloc function.
void byteBufferFill(ByteBuffer* buffer, uint8_t data, int count);
// Write to the buffer with provided data at the end of the buffer.
void byteBufferWrite(ByteBuffer* buffer, uint8_t data);
// Add all the characters to the buffer, byte buffer can also be used as a
// buffer to write string (like a string stream). Note that this will not
// add a null byte '\0' at the end.
void byteBufferAddString(ByteBuffer* buffer, const char* str, uint32_t length);

View File

@ -31,32 +31,7 @@ int runSource(const char* source) {
config.resolve_path_fn = NULL;
PKVM* vm = pkNewVM(&config);
// FIXME:
// The bellow blocks of code can be simplified with
//
// PkResult result = pkInterpretSource(vm, src, path, NULL);
//
// But the path is printed on the error messages as "@(TRY)"
// If we create a module and compile it, then it won't have a
// path and the error message is simplified. This behavior needs
// to be changed in the error report function.
PkResult result;
PkHandle* module = pkNewModule(vm, "@(TRY)");
do {
PkStringPtr src = { .string = source };
result = pkCompileModule(vm, module, src, NULL);
if (result != PK_RESULT_SUCCESS) break;
PkHandle* _main = pkModuleGetMainFunction(vm, module);
result = pkRunFunction(vm, _main, 0, -1, -1);
pkReleaseHandle(vm, _main);
} while (false);
pkReleaseHandle(vm, module);
PkResult result = pkRunString(vm, source);
pkFreeVM(vm);
return (int)result;

View File

@ -12,15 +12,29 @@ import sys
def detect_leak():
trace_log_id = "[memory trace]"
total_bytes = 0 ## Totally allocated bytes.
mem = dict() ## key = address, value = bytes.
str_alloc_id = "[alloc string]"
strs = dict() ## string allocations.
trace_log_file_path = sys.argv[1]
with open(trace_log_file_path, 'r') as f:
for line in f.readlines():
if line.startswith(trace_log_id):
if line.startswith(str_alloc_id):
line = line[len(trace_log_id) + 1:].strip()
type, data = split_and_strip(line, ':')
if type == "alloc":
addr, bytes = split_and_strip(data, '=')
bytes = int(bytes.replace(' bytes', ''))
strs[addr] = bytes
else: ## "dealloc"
addr = data.strip()
strs.pop(addr)
elif line.startswith(trace_log_id):
line = line[len(trace_log_id) + 1:].strip()
type, data = split_and_strip(line, ':')
@ -57,6 +71,12 @@ def detect_leak():
print(f" {addr} : {mem[addr]} bytes")
success = False
if len(strs) != 0:
print_err("Memory leak detected - string allocation(s) were never freed.")
for addr in strs:
print(f" {addr} : {strs[addr]} bytes")
success = False
if success:
print("No leaks were detected.")
else:

View File

@ -70,11 +70,8 @@ typedef struct PKVM PKVM;
typedef struct PkHandle PkHandle;
typedef enum PkVarType PkVarType;
typedef enum PkErrorType PkErrorType;
typedef enum PkResult PkResult;
typedef struct PkStringPtr PkStringPtr;
typedef struct PkConfiguration PkConfiguration;
typedef struct PkCompileOptions PkCompileOptions;
// C function pointer which is callable from pocketLang by native module
// functions.
@ -92,34 +89,28 @@ typedef void (*pkNativeFn)(PKVM* vm);
// function will return NULL.
typedef void* (*pkReallocFn)(void* memory, size_t new_size, void* user_data);
// Error callback function pointer. For runtime error it'll call first with
// PK_ERROR_RUNTIME followed by multiple callbacks with PK_ERROR_STACKTRACE.
// The error messages should be written to stderr.
typedef void (*pkErrorFn) (PKVM* vm, PkErrorType type,
const char* file, int line,
const char* message);
// Function callback to write [text] to stdout or stderr.
typedef void (*pkWriteFn) (PKVM* vm, const char* text);
// A function callback to read a line from stdin. The returned string shouldn't
// contain a line ending (\n or \r\n).
typedef PkStringPtr (*pkReadFn) (PKVM* vm);
// A function callback symbol for clean/free the pkStringResult.
typedef void (*pkResultDoneFn) (PKVM* vm, PkStringPtr result);
// A function callback to resolve the import script name from the [from] path
// to an absolute (or relative to the cwd). This is required to solve same
// script imported with different relative path. Set the string attribute to
// NULL to indicate if it's failed to resolve.
typedef PkStringPtr (*pkResolvePathFn) (PKVM* vm, const char* from,
const char* path);
// contain a line ending (\n or \r\n). The returned string **must** be
// allocated with pkAllocString() and the VM will claim the ownership of the
// string.
typedef char* (*pkReadFn) (PKVM* vm);
// Load and return the script. Called by the compiler to fetch initial source
// code and source for import statements. Set the string attribute to NULL
// to indicate if it's failed to load the script.
typedef PkStringPtr (*pkLoadScriptFn) (PKVM* vm, const char* path);
// code and source for import statements. Return NULL to indicate failure to
// load. Otherwise the string **must** be allocated with pkAllocString() and
// the VM will claim the ownership of the string.
typedef char* (*pkLoadScriptFn) (PKVM* vm, const char* path);
// A function callback to resolve the import statement path. [from] path can
// be either path to a script or NULL if [path] is relative to cwd. The return
// value should be a normalized absolute path of the [path]. Return NULL to
// indicate failure to resolve. Othrewise the string **must** be allocated with
// pkAllocString() and the VM will claim the ownership of the string.
typedef char* (*pkResolvePathFn) (PKVM* vm, const char* from,
const char* path);
// A function callback to allocate and return a new instance of the registered
// class. Which will be called when the instance is constructed. The returned/
@ -158,31 +149,19 @@ enum PkVarType {
enum PkResult {
PK_RESULT_SUCCESS = 0, // Successfully finished the execution.
// Note that this result is internal and will not be returned to the host
// anymore.
//
// Unexpected EOF while compiling the source. This is another compile time
// error that will ONLY be returned if we're compiling with the REPL mode set
// in the compile options. We need this specific error to indicate the host
// application to add another line to the last input. If REPL is not enabled,
// this will be PK_RESULT_COMPILE_ERROR.
// error that will ONLY be returned if we're compiling in REPL mode. We need
// this specific error to indicate the host application to add another line
// to the last input. If REPL is not enabled this will be compile error.
PK_RESULT_UNEXPECTED_EOF,
PK_RESULT_COMPILE_ERROR, // Compilation failed.
PK_RESULT_RUNTIME_ERROR, // An error occurred at runtime.
};
// A string pointer wrapper to pass c string between host application and
// pocket VM. With a on_done() callback to clean it when the pocket VM is done
// with the string.
struct PkStringPtr {
const char* string; //< The string result.
pkResultDoneFn on_done; //< Called once vm done with the string.
void* user_data; //< User related data.
// These values are provided by the pocket VM to the host application, you're
// not expected to set this when provideing string to the pocket VM.
uint32_t length; //< Length of the string.
uint32_t hash; //< Its 32 bit FNV-1a hash.
};
struct PkConfiguration {
// The callback used to allocate, reallocate, and free. If the function
@ -203,18 +182,6 @@ struct PkConfiguration {
void* user_data;
};
// The options to configure the compilation provided by the command line
// arguments (or other ways the host application provides).
struct PkCompileOptions {
// Compile debug version of the source.
bool debug;
// Set to true if compiling in REPL mode, This will print repr version of
// each evaluated non-null values.
bool repl_mode;
};
/*****************************************************************************/
/* POCKETLANG PUBLIC API */
/*****************************************************************************/
@ -224,11 +191,6 @@ struct PkCompileOptions {
// application.
PK_PUBLIC PkConfiguration pkNewConfiguration();
// Create a new pkCompilerOptions with the default values and return it.
// Override those default configuration to adopt to another hosting
// application.
PK_PUBLIC PkCompileOptions pkNewCompilerOptions();
// Allocate, initialize and returns a new VM.
PK_PUBLIC PKVM* pkNewVM(PkConfiguration* config);
@ -241,6 +203,16 @@ PK_PUBLIC void pkSetUserData(PKVM* vm, void* user_data);
// Returns the associated user data.
PK_PUBLIC void* pkGetUserData(const PKVM* vm);
// Allocate memory with [size] and return it. This function should be called
// when the host application want to send strings to the PKVM that are claimed
// by the VM once the caller returned it.
PK_PUBLIC char* pkAllocString(PKVM* vm, size_t size);
// Complementary function to pkAllocString. This should not be called if the
// string is returned to the VM. Since PKVM will claim the ownership and
// deallocate the string itself.
PK_PUBLIC void pkDeAllocString(PKVM* vm, char* ptr);
// Release the handle and allow its value to be garbage collected. Always call
// this for every handles before freeing the VM.
PK_PUBLIC void pkReleaseHandle(PKVM* vm, PkHandle* handle);
@ -261,10 +233,6 @@ PK_PUBLIC void pkModuleAddFunction(PKVM* vm, PkHandle* module,
const char* name,
pkNativeFn fptr, int arity);
// Returns the main function of the [module]. When a module is compiled all of
// it's statements are wrapped around an implicit main function.
PK_PUBLIC PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module);
// Create a new class on the [module] with the [name] and return it.
// If the [base_class] is NULL by default it'll set to "Object" class.
PK_PUBLIC PkHandle* pkNewClass(PKVM* vm, const char* name,
@ -278,28 +246,20 @@ PK_PUBLIC void pkClassAddMethod(PKVM* vm, PkHandle* cls,
const char* name,
pkNativeFn fptr, int arity);
// Compile the [module] with the provided [source]. Set the compiler options
// with the the [options] argument or set to NULL for default options.
PK_PUBLIC PkResult pkCompileModule(PKVM* vm, PkHandle* module,
PkStringPtr source,
const PkCompileOptions* options);
// Run the source string. The [source] is expected to be valid till this
// function returns.
PK_PUBLIC PkResult pkRunString(PKVM* vm, const char* source);
// Interpret the source and return the result. Once it's done with the source
// and path 'on_done' will be called to clean the string if it's not NULL.
// Set the compiler options with the the [options] argument or it can be set to
// NULL for default options.
PK_PUBLIC PkResult pkInterpretSource(PKVM* vm,
PkStringPtr source,
PkStringPtr path,
const PkCompileOptions* options);
// Run the file at [path] relative to the current working directory.
PK_PUBLIC PkResult pkRunFile(PKVM* vm, const char* path);
// Run a function with [argc] arguments and their values are stored at the VM's
// slots starting at index [argv_slot] till the next [argc] consequent slots.
// If [argc] is 0 [argv_slot] value will be ignored.
// To store the return value set [ret_slot] to a valid slot index, if it's
// negative the return value will be ignored.
PK_PUBLIC PkResult pkRunFunction(PKVM* vm, PkHandle* fn,
int argc, int argv_slot, int ret_slot);
// FIXME:
// Currently exit function will terminate the process which should exit from
// the function and return to the caller.
//
// Run pocketlang REPL mode. If there isn't any stdin read function defined,
// or imput function ruturned NULL, it'll immediatly return a runtime error.
PK_PUBLIC PkResult pkRunREPL(PKVM* vm);
/*****************************************************************************/
/* NATIVE / RUNTIME FUNCTION API */

View File

@ -442,7 +442,7 @@ struct Compiler {
//
Compiler* next_compiler;
const PkCompileOptions* options; //< To configure the compilation.
const CompileOptions* options; //< To configure the compilation.
Module* module; //< Current module that's being compiled.
Loop* loop; //< Current loop the we're parsing.
@ -537,7 +537,7 @@ static void parserInit(Parser* parser, PKVM* vm, Compiler* compiler,
}
static void compilerInit(Compiler* compiler, PKVM* vm, const char* source,
Module* module, const PkCompileOptions* options) {
Module* module, const CompileOptions* options) {
compiler->next_compiler = NULL;
@ -2800,27 +2800,30 @@ static Module* importFile(Compiler* compiler, const char* path) {
PKVM* vm = compiler->parser.vm;
// Resolve the path.
PkStringPtr resolved = { path, NULL, NULL };
// FIXME: REPL mode mdule doesn't have a path, So we're using NULL and the
// resolve path function will use cwd to resolve.
const char* from = NULL;
if (compiler->module->path != NULL) from = compiler->module->path->data;
char* resolved = NULL;
if (vm->config.resolve_path_fn != NULL) {
resolved = vm->config.resolve_path_fn(vm, compiler->module->path->data,
path);
resolved = vm->config.resolve_path_fn(vm, from, path);
}
if (resolved.string == NULL) {
if (resolved == NULL) {
semanticError(compiler, compiler->parser.previous,
"Cannot resolve path '%s' from '%s'", path,
compiler->module->path->data);
"Cannot resolve path '%s' from '%s'", path, from);
return NULL;
}
// Create new string for the resolved path. And free the resolved path.
int index = 0;
String* path_ = moduleAddString(compiler->module, compiler->parser.vm,
resolved.string,
(uint32_t)strlen(resolved.string),
resolved,
(uint32_t)strlen(resolved),
&index);
if (resolved.on_done != NULL) resolved.on_done(vm, resolved);
pkDeAllocString(vm, resolved);
// Check if the script already compiled and cached in the PKVM.
Var entry = mapGet(vm->modules, VAR_OBJ(path_));
@ -2842,9 +2845,11 @@ static Module* importFile(Compiler* compiler, const char* path) {
return NULL;
}
// Load the script at the path.
PkStringPtr source = vm->config.load_script_fn(vm, path_->data);
if (source.string == NULL) {
// Load the script at the path. Note that if the source is not NULL, it's
// our responsibility to deallocate the string with the pkDeallocString()
// function.
char* source = vm->config.load_script_fn(vm, path_->data);
if (source == NULL) {
semanticError(compiler, compiler->parser.previous,
"Error loading script at \"%s\"", path_->data);
return NULL;
@ -2854,18 +2859,8 @@ static Module* importFile(Compiler* compiler, const char* path) {
Module* module = newModule(vm);
module->path = path_;
vmPushTempRef(vm, &module->_super); // module.
{
moduleAddMain(vm, module);
// Add '__file__' variable with it's path as value. If the path starts with
// '@' It's a special file (@(REPL) or @(TRY)) and don't define __file__.
if (module->path->data[0] != SPECIAL_NAME_CHAR) {
moduleAddGlobal(vm, module, "__file__", 8, VAR_OBJ(module->path));
}
// TODO: Add ARGV to the module's globals.
initializeScript(vm, module);
vmRegisterModule(vm, module, path_);
}
vmPopTempRef(vm); // module.
// Push the compiled script on the stack.
@ -2874,13 +2869,13 @@ static Module* importFile(Compiler* compiler, const char* path) {
// Even if we're running on repl mode the imported module cannot run on
// repl mode.
PkCompileOptions options = pkNewCompilerOptions();
CompileOptions options = newCompilerOptions();
if (compiler->options) options = *compiler->options;
options.repl_mode = false;
// Compile the source to the module and clean the source.
PkResult result = compile(vm, module, source.string, &options);
if (source.on_done != NULL) source.on_done(vm, source);
PkResult result = compile(vm, module, source, &options);
pkDeAllocString(vm, source);
if (result != PK_RESULT_SUCCESS) {
semanticError(compiler, compiler->parser.previous,
@ -3429,8 +3424,15 @@ static void compileTopLevelStatement(Compiler* compiler) {
}
CompileOptions newCompilerOptions() {
CompileOptions options;
options.debug = false;
options.repl_mode = false;
return options;
}
PkResult compile(PKVM* vm, Module* module, const char* source,
const PkCompileOptions* options) {
const CompileOptions* options) {
ASSERT(module != NULL, OOPS);

View File

@ -23,13 +23,32 @@ typedef enum {
// compilers, we're restricted syntax-wise and from compile-time optimizations.
typedef struct Compiler Compiler;
// The options to configure the compilation provided by the command line
// arguments (or other ways the host application provides). For now this
// struct is not publicily visible to the host for the sake of simplicity
// (like lua does) it needs to be a somehow addressed (TODO:).
typedef struct {
// Compile debug version of the source. In release mode all the assertions
// and debug informations will be stripped (TODO:) and wll be optimized.
bool debug;
// Set to true if compiling in REPL mode, This will print repr version of
// each evaluated non-null values.
bool repl_mode;
} CompileOptions;
// Create a new CompilerOptions with the default values and return it.
CompileOptions newCompilerOptions();
// This will take source code as a cstring, compiles it to pocketlang bytecodes
// and append them to the module's implicit main function. On a successfull
// compilation it'll return PK_RESULT_SUCCESS, otherwise it'll return
// PK_RESULT_COMPILE_ERROR but if repl_mode set in the [options], and we've
// reached and unexpected EOF it'll return PK_RESULT_UNEXPECTED_EOF.
PkResult compile(PKVM* vm, Module* module, const char* source,
const PkCompileOptions* options);
const CompileOptions* options);
// Mark the heap allocated objects of the compiler at the garbage collection
// called at the marking phase of vmCollectGarbage().

View File

@ -153,6 +153,16 @@ void initializeCore(PKVM* vm) {
initializePrimitiveClasses(vm);
}
void initializeScript(PKVM* vm, Module* module) {
ASSERT(module->path != NULL, OOPS);
ASSERT(module->path->data[0] != SPECIAL_NAME_CHAR, OOPS);
// A script's path will always the absolute normalized path (the path
// resolving function would do take care of it) which is something that
// was added after python 3.9.
moduleAddGlobal(vm, module, "__file__", 8, VAR_OBJ(module->path));
}
/*****************************************************************************/
/* INTERNAL FUNCTIONS */
/*****************************************************************************/
@ -442,7 +452,7 @@ DEF(coreInput,
"an optional argument [msg] and prints it before reading.") {
int argc = ARGC;
if (argc != 1 && argc != 2) {
if (argc > 1) { // input() or input(str).
RET_ERR(newString(vm, "Invalid argument count."));
}
@ -455,9 +465,13 @@ DEF(coreInput,
vm->config.stdout_write(vm, str->data);
}
PkStringPtr result = vm->config.stdin_read(vm);
String* line = newString(vm, result.string);
if (result.on_done) result.on_done(vm, result);
char* str = vm->config.stdin_read(vm);
if (str == NULL) { //< Input failed !?
RET_ERR(newString(vm, "Input function failed."));
}
String* line = newString(vm, str);
pkDeAllocString(vm, str);
RET(VAR_OBJ(line));
}

View File

@ -42,6 +42,11 @@
// Initialize core language, builtin function and core libs.
void initializeCore(PKVM* vm);
// Initialize a script module. At the moment it'll define __file__ global
// as an absolute path of the script. [path] should be the normalized absolute
// path of the script.
void initializeScript(PKVM* vm, Module* module);
// Create a new module with the given [name] and returns as a Module*.
// This is function is a wrapper around `newModule()` function to create
// native modules for pocket core and public native api.

View File

@ -9,6 +9,8 @@
#include "include/pocketlang.h"
#include "pk_core.h"
#include "pk_compiler.h"
#include "pk_utils.h"
#include "pk_value.h"
#include "pk_vm.h"
@ -58,16 +60,41 @@
// configuration if the host doesn't provided any allocators for us.
static void* defaultRealloc(void* memory, size_t new_size, void* _);
static void stderrWrite(PKVM* vm, const char* text);
static void stdoutWrite(PKVM* vm, const char* text);
static char* stdinRead(PKVM* vm);
static char* loadScript(PKVM* vm, const char* path);
char* pkAllocString(PKVM* vm, size_t size) {
ASSERT(vm->config.realloc_fn != NULL, "PKVM's allocator was NULL.");
#if TRACE_MEMORY
void* ptr = vm->config.realloc_fn(NULL, size, vm->config.user_data);
printf("[alloc string] alloc : %p = %+li bytes\n", ptr, (long) size);
return (char*) ptr;
#else
return (char*) vm->config.realloc_fn(NULL, size, vm->config.user_data);
#endif
}
void pkDeAllocString(PKVM* vm, char* str) {
ASSERT(vm->config.realloc_fn != NULL, "PKVM's allocator was NULL.");
#if TRACE_MEMORY
printf("[alloc string] dealloc : %p\n", str);
#endif
vm->config.realloc_fn(str, 0, vm->config.user_data);
}
PkConfiguration pkNewConfiguration() {
PkConfiguration config;
config.realloc_fn = defaultRealloc;
config.stdout_write = NULL;
config.stderr_write = NULL;
config.stdin_read = NULL;
config.stdout_write = stdoutWrite;
config.stderr_write = stderrWrite;
config.stdin_read = stdinRead;
config.load_script_fn = NULL;
config.resolve_path_fn = NULL;
config.load_script_fn = loadScript;
config.use_ansi_color = false;
config.user_data = NULL;
@ -75,13 +102,6 @@ PkConfiguration pkNewConfiguration() {
return config;
}
PkCompileOptions pkNewCompilerOptions() {
PkCompileOptions options;
options.debug = false;
options.repl_mode = false;
return options;
}
PKVM* pkNewVM(PkConfiguration* config) {
PkConfiguration default_config = pkNewConfiguration();
@ -163,20 +183,6 @@ void pkModuleAddFunction(PKVM* vm, PkHandle* module, const char* name,
NULL /*TODO: Public API for function docstring.*/);
}
PkHandle* pkModuleGetMainFunction(PKVM* vm, PkHandle* module) {
CHECK_HANDLE_TYPE(module, OBJ_MODULE);
Module* _module = (Module*)AS_OBJ(module->value);
int main_index = moduleGetGlobalIndex(_module, IMPLICIT_MAIN_NAME,
(uint32_t)strlen(IMPLICIT_MAIN_NAME));
if (main_index == -1) return NULL;
ASSERT_INDEX(main_index, (int)_module->globals.count);
Var main_fn = _module->globals.data[main_index];
ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS);
return vmNewHandle(vm, main_fn);
}
PkHandle* pkNewClass(PKVM* vm, const char* name,
PkHandle* base_class, PkHandle* module,
pkNewInstanceFn new_fn,
@ -250,80 +256,230 @@ void pkReleaseHandle(PKVM* vm, PkHandle* handle) {
DEALLOCATE(vm, handle, PkHandle);
}
PkResult pkCompileModule(PKVM* vm, PkHandle* module_handle, PkStringPtr source,
const PkCompileOptions* options) {
CHECK_ARG_NULL(source.string);
CHECK_HANDLE_TYPE(module_handle, OBJ_MODULE);
PkResult pkRunString(PKVM* vm, const char* source) {
Module* module = (Module*)AS_OBJ(module_handle->value);
PkResult result = PK_RESULT_SUCCESS;
PkResult result = compile(vm, module, source.string, options);
if (source.on_done) source.on_done(vm, source);
return result;
}
// This function is responsible to call on_done function if it's done with the
// provided string pointers.
PkResult pkInterpretSource(PKVM* vm, PkStringPtr source, PkStringPtr path,
const PkCompileOptions* options) {
String* path_ = newString(vm, path.string);
if (path.on_done) path.on_done(vm, path);
vmPushTempRef(vm, &path_->_super); // path_
// FIXME:
// Should I clean the module if it already exists before compiling it?
// Load a new module to the vm's modules cache.
Module* module = vmGetModule(vm, path_);
if (module == NULL) {
module = newModule(vm);
module->path = path_;
// Create a temproary module for the source.
Module* module = newModule(vm);
vmPushTempRef(vm, &module->_super); // module.
vmRegisterModule(vm, module, path_);
vmPopTempRef(vm); // module.
}
vmPopTempRef(vm); // path_
// Compile the source.
PkResult result = compile(vm, module, source.string, options);
if (source.on_done) source.on_done(vm, source);
{
module->path = newString(vm, "@(String)");
result = compile(vm, module, source, NULL);
if (result != PK_RESULT_SUCCESS) return result;
// Set module initialized to true before the execution ends to prevent cyclic
// inclusion cause a crash.
// Module initialized needs to be set to true just before executing their
// main function to avoid cyclic inclusion crash the VM.
module->initialized = true;
Fiber* fiber = newFiber(vm, module->body);
vmPushTempRef(vm, &fiber->_super); // fiber.
vmPrepareFiber(vm, fiber, 0, NULL /* TODO: get argv from user. */);
vmPrepareFiber(vm, fiber, 0, NULL);
vmPopTempRef(vm); // fiber.
result = vmRunFiber(vm, fiber);
}
vmPopTempRef(vm); // module.
return result;
}
PkResult pkRunFile(PKVM* vm, const char* path) {
// FIXME(_imp_): path resolve function should always expect a source file
// path (ends with .pk) to be consistance with the paths.
// Note: The file may have been imported by some other script and cached in
// the VM's scripts cache. But we're not using that instead, we'll recompile
// the file and update the cache.
ASSERT(vm->config.load_script_fn != NULL,
"No script loading functions defined.");
PkResult result = PK_RESULT_SUCCESS;
Module* module = NULL;
// Resolve the path.
char* resolved_ = NULL;
if (vm->config.resolve_path_fn != NULL) {
resolved_ = vm->config.resolve_path_fn(vm, NULL, path);
}
if (resolved_ == NULL) {
// FIXME: Error print should be moved and check for ascii color codes.
if (vm->config.stderr_write != NULL) {
vm->config.stderr_write(vm, "Error finding script at \"");
vm->config.stderr_write(vm, path);
vm->config.stderr_write(vm, "\"\n");
}
return PK_RESULT_COMPILE_ERROR;
}
module = newModule(vm);
vmPushTempRef(vm, &module->_super); // module.
{
// Set module path and and deallocate resolved.
String* script_path = newString(vm, resolved_);
vmPushTempRef(vm, &script_path->_super); // script_path.
pkDeAllocString(vm, resolved_);
module->path = script_path;
vmPopTempRef(vm); // script_path.
initializeScript(vm, module);
const char* _path = module->path->data;
char* source = vm->config.load_script_fn(vm, _path);
if (source == NULL) {
result = PK_RESULT_COMPILE_ERROR;
// FIXME: Error print should be moved and check for ascii color codes.
if (vm->config.stderr_write != NULL) {
vm->config.stderr_write(vm, "Error loading script at \"");
vm->config.stderr_write(vm, _path);
vm->config.stderr_write(vm, "\"\n");
}
} else {
result = compile(vm, module, source, NULL);
pkDeAllocString(vm, source);
}
if (result == PK_RESULT_SUCCESS) {
vmRegisterModule(vm, module, module->path);
}
}
vmPopTempRef(vm); // module.
if (result != PK_RESULT_SUCCESS) return result;
// Module initialized needs to be set to true just before executing their
// main function to avoid cyclic inclusion crash the VM.
module->initialized = true;
Fiber* fiber = newFiber(vm, module->body);
vmPushTempRef(vm, &fiber->_super); // fiber.
vmPrepareFiber(vm, fiber, 0, NULL);
vmPopTempRef(vm); // fiber.
return vmRunFiber(vm, fiber);
}
PK_PUBLIC PkResult pkRunFunction(PKVM* vm, PkHandle* fn,
int argc, int argv_slot, int ret_slot) {
CHECK_HANDLE_TYPE(fn, OBJ_CLOSURE);
Closure* closure = (Closure*)AS_OBJ(fn->value);
// FIXME: this should be moved to somewhere general.
//
// Returns true if the string is empty, used to check if the input line is
// empty to skip compilation of empty string in the bellow repl mode.
static inline bool isStringEmpty(const char* line) {
ASSERT(line != NULL, OOPS);
ASSERT(argc >= 0, "argc cannot be negative.");
Var* argv = NULL;
if (argc != 0) {
for (int i = 0; i < argc; i++) {
VALIDATE_SLOT_INDEX(argv_slot + i);
for (const char* c = line; *c != '\0'; c++) {
if (!utilIsSpace(*c)) return false;
}
argv = &SLOT(argv_slot);
return true;
}
// FIXME:
// this should be moved to somewhere general along with isStringEmpty().
//
// This function will get the main function from the module to run it in the
// repl mode.
Closure* moduleGetMainFunction(PKVM* vm, Module* module) {
int main_index = moduleGetGlobalIndex(module, IMPLICIT_MAIN_NAME,
(uint32_t) strlen(IMPLICIT_MAIN_NAME));
if (main_index == -1) return NULL;
ASSERT_INDEX(main_index, (int) module->globals.count);
Var main_fn = module->globals.data[main_index];
ASSERT(IS_OBJ_TYPE(main_fn, OBJ_CLOSURE), OOPS);
return (Closure*) AS_OBJ(main_fn);
}
PkResult pkRunREPL(PKVM* vm) {
pkWriteFn printfn = vm->config.stdout_write;
pkWriteFn printerrfn = vm->config.stderr_write;
pkReadFn inputfn = vm->config.stdin_read;
PkResult result = PK_RESULT_SUCCESS;
CompileOptions options = newCompilerOptions();
options.repl_mode = true;
if (inputfn == NULL) {
if (printerrfn) printerrfn(vm, "REPL failed to input.");
return PK_RESULT_RUNTIME_ERROR;
}
Var* ret = NULL;
if (ret_slot >= 0) {
VALIDATE_SLOT_INDEX(ret_slot);
ret = &SLOT(ret_slot);
// The main module that'll be used to compile and execute the input source.
PkHandle* module = pkNewModule(vm, "@(REPL)");
ASSERT(IS_OBJ_TYPE(module->value, OBJ_MODULE), OOPS);
Module* _module = (Module*)AS_OBJ(module->value);
// A buffer to store multiple lines read from stdin.
pkByteBuffer lines;
pkByteBufferInit(&lines);
// Will be set to true if the compilation failed with unexpected EOF to add
// more lines to the [lines] buffer.
bool need_more_lines = false;
bool done = false;
do {
const char* listening = (!need_more_lines) ? ">>> " : "... ";
printfn(vm, listening);
// Read a line from stdin and add the line to the lines buffer.
char* line = inputfn(vm);
if (line == NULL) {
if (printerrfn) printerrfn(vm, "REPL failed to input.");
result = PK_RESULT_RUNTIME_ERROR;
break;
}
return vmRunFunction(vm, closure, argc, argv, ret);
// If the line contains EOF, REPL should be stopped.
size_t line_length = strlen(line);
if (line_length >= 1 && *(line + line_length - 1) == EOF) {
printfn(vm, "\n");
result = PK_RESULT_SUCCESS;
pkDeAllocString(vm, line);
break;
}
// If the line is empty, we don't have to compile it.
if (isStringEmpty(line)) {
if (need_more_lines) ASSERT(lines.count != 0, OOPS);
pkDeAllocString(vm, line);
continue;
}
// Add the line to the lines buffer.
if (lines.count != 0) pkByteBufferWrite(&lines, vm, '\n');
pkByteBufferAddString(&lines, vm, line, (uint32_t) line_length);
pkDeAllocString(vm, line);
pkByteBufferWrite(&lines, vm, '\0');
// Compile the buffer to the module.
result = compile(vm, _module, (const char*) lines.data, &options);
if (result == PK_RESULT_UNEXPECTED_EOF) {
ASSERT(lines.count > 0 && lines.data[lines.count - 1] == '\0', OOPS);
lines.count -= 1; // Remove the null byte to append a new string.
need_more_lines = true;
continue;
}
// We're buffering the lines for unexpected EOF, if we reached here that
// means it's either successfully compiled or compilation error. Clean the
// buffer for the next iteration.
need_more_lines = false;
pkByteBufferClear(&lines, vm);
if (result != PK_RESULT_SUCCESS) continue;
// Compiled source would be the "main" function of the module. Run it.
Closure* _main = moduleGetMainFunction(vm, _module);
ASSERT(_main != NULL, OOPS);
result = vmRunFunction(vm, _main, 0, NULL, NULL);
} while (!done);
pkReleaseHandle(vm, module);
return result;
}
/*****************************************************************************/
@ -542,3 +698,53 @@ static void* defaultRealloc(void* memory, size_t new_size, void* _) {
}
return realloc(memory, new_size);
}
void stderrWrite(PKVM* vm, const char* text) {
fprintf(stderr, "%s", text);
}
void stdoutWrite(PKVM* vm, const char* text) {
fprintf(stdout, "%s", text);
}
static char* stdinRead(PKVM* vm) {
pkByteBuffer buff;
pkByteBufferInit(&buff);
char c;
do {
c = (char)fgetc(stdin);
if (c == '\n') break;
pkByteBufferWrite(&buff, vm, (uint8_t)c);
} while (c != EOF);
pkByteBufferWrite(&buff, vm, '\0');
char* str = pkAllocString(vm, buff.count);
memcpy(str, buff.data, buff.count);
return str;
}
static char* loadScript(PKVM* vm, const char* path) {
FILE* file = fopen(path, "r");
if (file == NULL) return NULL;
// Get the source length. In windows the ftell will includes the cariage
// return when using ftell with fseek. But that's not an issue since
// we'll be allocating more memory than needed for fread().
fseek(file, 0, SEEK_END);
size_t file_size = ftell(file);
fseek(file, 0, SEEK_SET);
// Allocate string + 1 for the NULL terminator.
char* buff = pkAllocString(vm, file_size + 1);
ASSERT(buff != NULL, "pkAllocString failed.");
clearerr(file);
size_t read = fread(buff, sizeof(char), file_size, file);
ASSERT(read <= file_size, "fread() failed.");
buff[read] = '\0';
fclose(file);
return buff;
}

View File

@ -19,6 +19,10 @@ int utilPowerOf2Ceil(int n) {
return n;
}
bool utilIsSpace(char c) {
return (c == ' ' || c == '\t' || c == '\n' || c == '\v');
}
// Function implementation, see utils.h for description.
bool utilIsName(char c) {
return ('a' <= c && c <= 'z') || ('A' <= c && c <= 'Z') || (c == '_');

View File

@ -7,12 +7,16 @@
#ifndef PK_UTILS_H
#define PK_UTILS_H
#include "pk_internal.h"
#include <stdbool.h>
#include <stdint.h>
// Returns the smallest power of two that is equal to or greater than [n].
// Source : https://github.com/wren-lang/wren/blob/main/src/vm/wren_utils.h#L119
int utilPowerOf2Ceil(int n);
// Returns true if c in [ ' ', '\t', '\n', '\v' ]
bool utilIsSpace(char c);
// Returns true if `c` is [A-Za-z_].
bool utilIsName(char c);
@ -34,15 +38,10 @@ uint32_t utilHashNumber(double num);
// Generate a has code for [string].
uint32_t utilHashString(const char* string);
#endif // PK_UTILS_H
/****************************************************************************
* UTF8 *
****************************************************************************/
#ifndef UTF8_H
#define UTF8_H
/** @file
* A tiny UTF-8 utility library.
*
@ -66,7 +65,6 @@ uint32_t utilHashString(const char* string);
* is called the leading byte and the rest of the bytes of the character is
* called continuation bytes.
*
* <pre>
* example:
* v-- leading byte v-- continuation byte => 2 bytes
* é = 11000011 10101001
@ -74,16 +72,9 @@ uint32_t utilHashString(const char* string);
* 110 means 2 bytes 10 means continuation
*
* (note that the character é is 8 bit long with ANSI encoding)
* </pre>
*
* USAGE:
* // define implementation only a single *.c source file like this
* #define UTF8_IMPLEMENT
* #include "utf8.h"
*/
#include <stdint.h>
// Returns the number of bytes the the [value] would take to encode. returns 0
// if the value is invalid utf8 representation.
//
@ -121,4 +112,4 @@ int utf8_encodeValue(int value, uint8_t* bytes);
// value.
int utf8_decodeBytes(uint8_t* bytes, int* value);
#endif // UTF8_H
#endif // PK_UTILS_H

View File

@ -15,6 +15,11 @@ integrate pocket VM with your application
----
#### `example0.c` - Contains how to run simple pocket script
```
gcc example0.c -o example0 ../../src/*.c -I../../src/include -lm
```
#### `example1.c` - Contains how to pass values between pocket VM and C
```
gcc example1.c -o example1 ../../src/*.c -I../../src/include -lm

27
tests/native/example0.c Normal file
View File

@ -0,0 +1,27 @@
/*
* Copyright (c) 2020-2021 Thakee Nathees
* Distributed Under The MIT License
*/
#include <pocketlang.h>
int main(int argc, char** argv) {
// Create a new pocket VM.
PKVM* vm = pkNewVM(NULL);
// Run a string.
pkRunString(vm, "print('hello world')");
// TODO: move path module to src/ or write a simple path resolving
// function to support pkRunFile() without requesting someone
// to provide path resolving function.
//
// Run a script from file.
//pkRunFile(vm, "script.pk");
// Free the VM.
pkFreeVM(vm);
return 0;
}

View File

@ -6,7 +6,6 @@
// This is a minimal example on how to pass values between pocket VM and C.
#include <pocketlang.h>
#include <stdio.h>
// The pocket script we're using to test.
static const char* code =
@ -32,26 +31,14 @@ static void cFunction(PKVM* vm) {
pkSetSlotNumber(vm, 0, 3.14);
}
/*****************************************************************************/
/* POCKET VM CALLBACKS */
/*****************************************************************************/
static void stdoutCallback(PKVM* vm, const char* text) {
fprintf(stdout, "%s", text);
}
/*****************************************************************************/
/* MAIN */
/*****************************************************************************/
int main(int argc, char** argv) {
// Pocket VM configuration.
PkConfiguration config = pkNewConfiguration();
config.stdout_write = stdoutCallback;
// Create a new pocket VM.
PKVM* vm = pkNewVM(&config);
PKVM* vm = pkNewVM(NULL);
// Registering a native module.
PkHandle* my_module = pkNewModule(vm, "my_module");
@ -59,15 +46,11 @@ int main(int argc, char** argv) {
pkRegisterModule(vm, my_module);
pkReleaseHandle(vm, my_module);
// The path and the source code.
PkStringPtr source = { .string = code };
PkStringPtr path = { .string = "./some/path/" };
// Run the code.
PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/);
PkResult result = pkRunString(vm, code);
// Free the VM.
pkFreeVM(vm);
return (int)result;
return (int) result;
}

View File

@ -11,7 +11,6 @@
#include <pocketlang.h>
#include <stdlib.h> /* For malloc */
#include <stdio.h> /* For printf */
#include <string.h> /* For strncmp */
#include <math.h> /* For sqrt */
@ -36,7 +35,7 @@ static const char* code =
" print() \n"
" \n"
" v2 = Vec2(5, 6) \n"
" v3 = v1.add(v2) \n"
" v3 = v1 + v2 \n"
" print('v3 = ${v3}') \n"
" print('v3.x = ${v3.x}') \n"
" print('v3.y = ${v3.y}') \n"
@ -64,8 +63,36 @@ void _deleteVec(void* vector) {
free(vector);
}
// Vec2 'add' method.
void _vec2Add(PKVM* vm) {
void _vecGetter(PKVM* vm) {
const char* name = pkGetSlotString(vm, 1, NULL);
Vector* self = (Vector*)pkGetSelf(vm);
if (strcmp("x", name) == 0) {
pkSetSlotNumber(vm, 0, self->x);
return;
} else if (strcmp("y", name) == 0) {
pkSetSlotNumber(vm, 0, self->y);
return;
}
}
void _vecSetter(PKVM* vm) {
const char* name = pkGetSlotString(vm, 1, NULL);
Vector* self = (Vector*)pkGetSelf(vm);
if (strcmp("x", name) == 0) {
double x;
if (!pkValidateSlotNumber(vm, 2, &x)) return;
self->x = x;
return;
} else if (strcmp("y", name) == 0) {
double y;
if (!pkValidateSlotNumber(vm, 2, &y)) return;
self->y = y;
return;
}
}
// Vec2 '+' operator method.
void _vecAdd(PKVM* vm) {
Vector* self = (Vector*)pkGetSelf(vm);
// FIXME:
// Temproarly it's not possible to get vector from the args since the native
@ -78,7 +105,7 @@ void registerVector(PKVM* vm) {
PkHandle* Vec2 = pkNewClass(vm, "Vec2", NULL /*Base Class*/,
vector, _newVec, _deleteVec);
pkClassAddMethod(vm, Vec2, "add", _vec2Add, 1);
pkClassAddMethod(vm, Vec2, "+", _vecAdd, 1);
pkReleaseHandle(vm, Vec2);
pkRegisterModule(vm, vector);
@ -89,23 +116,12 @@ void registerVector(PKVM* vm) {
/* POCKET VM CALLBACKS */
/*****************************************************************************/
void stdoutCallback(PKVM* vm, const char* text) {
fprintf(stdout, "%s", text);
}
int main(int argc, char** argv) {
PkConfiguration config = pkNewConfiguration();
config.stdout_write = stdoutCallback;
PKVM* vm = pkNewVM(&config);
PKVM* vm = pkNewVM(NULL);
registerVector(vm);
PkStringPtr source = { .string = code };
PkStringPtr path = { .string = "./some/path/" };
PkResult result = pkInterpretSource(vm, source, path, NULL/*options*/);
pkRunString(vm, code);
pkFreeVM(vm);
return result;
return 0;
}