mirror of
https://github.com/zekexiao/pocketlang.git
synced 2025-02-06 04:37:47 +08:00
Merge pull request #221 from ThakeeNathees/native-interface
native interface refactored
This commit is contained in:
commit
9dc2a99c3f
@ -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
|
158
cli/main.c
158
cli/main.c
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
}
|
||||
|
148
cli/repl.c
148
cli/repl.c
@ -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;
|
||||
}
|
||||
|
69
cli/utils.c
69
cli/utils.c
@ -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++);
|
||||
}
|
||||
}
|
38
cli/utils.h
38
cli/utils.h
@ -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);
|
@ -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;
|
||||
|
@ -12,31 +12,45 @@ 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, ':')
|
||||
|
||||
addr, bytes = split_and_strip(data, '=')
|
||||
bytes = bytes.replace(' bytes', '')
|
||||
|
||||
|
||||
if type == "malloc":
|
||||
bytes = int(bytes)
|
||||
assert(bytes >= 0); total_bytes += bytes
|
||||
mem[addr] = bytes
|
||||
|
||||
|
||||
elif type == "free":
|
||||
bytes = int(bytes)
|
||||
assert(bytes <= 0); total_bytes += bytes
|
||||
mem.pop(addr)
|
||||
|
||||
|
||||
elif type == "realloc":
|
||||
oldp, newp = split_and_strip(addr, '->')
|
||||
olds, news = split_and_strip(bytes, '->')
|
||||
@ -50,13 +64,19 @@ def detect_leak():
|
||||
if total_bytes != 0:
|
||||
print_err(f"Memory leak detected - {total_bytes} bytes were never freed.")
|
||||
success = False
|
||||
|
||||
|
||||
if len(mem) != 0:
|
||||
print_err("Memory leak detected - some addresses were never freed.")
|
||||
for addr in mem:
|
||||
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:
|
||||
@ -70,6 +90,6 @@ def split_and_strip(string, delim):
|
||||
def print_err(msg):
|
||||
print("Error:", msg, file=sys.stderr)
|
||||
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
detect_leak()
|
||||
|
@ -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 */
|
||||
|
@ -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),
|
||||
&index);
|
||||
if (resolved.on_done != NULL) resolved.on_done(vm, resolved);
|
||||
resolved,
|
||||
(uint32_t)strlen(resolved),
|
||||
&index);
|
||||
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.
|
||||
|
||||
vmRegisterModule(vm, module, path_);
|
||||
}
|
||||
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);
|
||||
|
||||
|
@ -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().
|
||||
|
@ -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));
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
356
src/pk_public.c
356
src/pk_public.c
@ -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;
|
||||
|
||||
// Create a temproary module for the source.
|
||||
Module* module = newModule(vm);
|
||||
vmPushTempRef(vm, &module->_super); // module.
|
||||
{
|
||||
module->path = newString(vm, "@(String)");
|
||||
result = compile(vm, module, source, NULL);
|
||||
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.
|
||||
result = vmRunFiber(vm, fiber);
|
||||
}
|
||||
vmPopTempRef(vm); // module.
|
||||
|
||||
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) {
|
||||
PkResult pkRunFile(PKVM* vm, const char* path) {
|
||||
|
||||
String* path_ = newString(vm, path.string);
|
||||
if (path.on_done) path.on_done(vm, path);
|
||||
vmPushTempRef(vm, &path_->_super); // path_
|
||||
// FIXME(_imp_): path resolve function should always expect a source file
|
||||
// path (ends with .pk) to be consistance with the paths.
|
||||
|
||||
// FIXME:
|
||||
// Should I clean the module if it already exists before compiling it?
|
||||
// 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.
|
||||
|
||||
// Load a new module to the vm's modules cache.
|
||||
Module* module = vmGetModule(vm, path_);
|
||||
if (module == NULL) {
|
||||
module = newModule(vm);
|
||||
module->path = path_;
|
||||
vmPushTempRef(vm, &module->_super); // module.
|
||||
vmRegisterModule(vm, module, path_);
|
||||
vmPopTempRef(vm); // module.
|
||||
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);
|
||||
}
|
||||
vmPopTempRef(vm); // path_
|
||||
|
||||
// Compile the source.
|
||||
PkResult result = compile(vm, module, source.string, options);
|
||||
if (source.on_done) source.on_done(vm, source);
|
||||
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;
|
||||
|
||||
// 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.
|
||||
|
||||
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;
|
||||
for (const char* c = line; *c != '\0'; c++) {
|
||||
if (!utilIsSpace(*c)) return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
if (argc != 0) {
|
||||
for (int i = 0; i < argc; i++) {
|
||||
VALIDATE_SLOT_INDEX(argv_slot + i);
|
||||
// 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;
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
argv = &SLOT(argv_slot);
|
||||
}
|
||||
|
||||
Var* ret = NULL;
|
||||
if (ret_slot >= 0) {
|
||||
VALIDATE_SLOT_INDEX(ret_slot);
|
||||
ret = &SLOT(ret_slot);
|
||||
}
|
||||
// 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;
|
||||
}
|
||||
|
||||
return vmRunFunction(vm, closure, argc, argv, ret);
|
||||
// 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;
|
||||
}
|
||||
|
@ -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 == '_');
|
||||
|
@ -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
|
||||
|
@ -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
27
tests/native/example0.c
Normal 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;
|
||||
}
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user