|
#include <assert.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <JavaScriptCore/JavaScript.h>
#include "linenoise.h"
#include "zip.h"
#define CONSOLE_LOG_BUF_SIZE 1000
char console_log_buf[CONSOLE_LOG_BUF_SIZE];
JSStringRef to_string(JSContextRef ctx, JSValueRef val);
JSValueRef evaluate_script(JSContextRef ctx, char *script, char *source);
JSValueRef evaluate_source(JSContextRef ctx, char *type, char *source_value, bool expression, char *set_ns);
char *munge(char *s);
void bootstrap(JSContextRef ctx, char *deps_file_path, char *goog_base_path);
JSObjectRef get_function(JSContextRef ctx, char *namespace, char *name);
char* get_contents(char *path);
void debug_print_value(char *prefix, JSContextRef ctx, JSValueRef exception) {
#ifdef DEBUG
if (exception != NULL) {
JSStringRef ex_str = to_string(ctx, exception);
char ex_buf[1000];
ex_buf[0] = '\0';
JSStringGetUTF8CString(ex_str, ex_buf, 1000-1);
printf("%s: %s\n", prefix, ex_buf);
JSStringRelease(ex_str);
}
#endif
}
JSValueRef function_console_log(JSContextRef ctx, JSObjectRef function, JSObjectRef this_object,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
for (int i = 0; i < argc; i++) {
if (i > 0) {
fprintf(stdout, " ");
}
JSStringRef str = to_string(ctx, args[i]);
JSStringGetUTF8CString(str, console_log_buf, CONSOLE_LOG_BUF_SIZE);
fprintf(stdout, "%s", console_log_buf);
}
fprintf(stdout, "\n");
return JSValueMakeUndefined(ctx);
}
JSValueRef function_console_error(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
for (int i = 0; i < argc; i++) {
if (i > 0) {
fprintf(stderr, " ");
}
JSStringRef str = to_string(ctx, args[i]);
JSStringGetUTF8CString(str, console_log_buf, CONSOLE_LOG_BUF_SIZE);
fprintf(stderr, "%s", console_log_buf);
}
fprintf(stderr, "\n");
return JSValueMakeUndefined(ctx);
}
JSValueRef function_read_file(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: implement fully
fprintf(stderr, "WARN: %s: stub\n", __func__);
if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeString) {
char path[100];
JSStringRef path_str = JSValueToStringCopy(ctx, args[0], NULL);
JSStringGetUTF8CString(path_str, path, 100);
JSStringRelease(path_str);
debug_print_value
("read_file", ctx, args[0]);
char full_path[150];
// TODO: should not load from here?
snprintf(full_path, 150, "%s/%s", "out", path);
char *contents = get_contents(full_path);
JSStringRef contents_str = JSStringCreateWithUTF8CString(contents);
free(contents);
JSValueRef res[2];
res[0] = JSValueMakeString(ctx, contents_str);
res[1] = JSValueMakeNumber(ctx, 0);
return JSObjectMakeArray(ctx, 2, res, NULL);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_load(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: implement fully
fprintf(stderr, "WARN: %s: stub\n", __func__);
if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeString) {
char path[100];
JSStringRef path_str = JSValueToStringCopy(ctx, args[0], NULL);
JSStringGetUTF8CString(path_str, path, 100);
JSStringRelease(path_str);
debug_print_value
("load", ctx, args[0]);
char full_path[150];
// TODO: should not load from here?
snprintf(full_path, 150, "%s/%s", "out", path);
JSValueRef contents_val = NULL;
char *contents = get_contents(full_path);
if (contents != NULL) {
JSStringRef contents_str = JSStringCreateWithUTF8CString(contents);
free(contents);
contents_val = JSValueMakeString(ctx, contents_str);
}
JSValueRef res[2];
res[0] = contents_val;
res[1] = JSValueMakeNumber(ctx, 0);
return JSObjectMakeArray(ctx, 2, res, NULL);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_load_deps_cljs_files(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: not implemented
fprintf(stderr, "WARN: %s: stub\n", __func__);
return JSObjectMakeArray(ctx, 0, NULL, NULL);
}
JSValueRef function_eval(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
JSValueRef val = NULL;
if (argc == 2
&& JSValueGetType(ctx, args[0]) == kJSTypeString
&& JSValueGetType(ctx, args[1]) == kJSTypeString) {
debug_print_value("eval", ctx, args[1]);
JSStringRef sourceRef = JSValueToStringCopy(ctx, args[0], NULL);
JSStringRef pathRef = JSValueToStringCopy(ctx, args[1], NULL);
JSEvaluateScript(ctx, sourceRef, NULL, pathRef, 0, &val);
JSStringRelease(pathRef);
JSStringRelease(sourceRef);
}
return val != NULL ? val : JSValueMakeNull(ctx);
}
JSValueRef function_get_term_size(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
// TODO: not implemented
fprintf(stderr, "WARN: %s: stub\n", __func__);
return JSValueMakeNull(ctx);
}
JSValueRef function_print_fn(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 1 && JSValueIsString(ctx, args[0])) {
JSStringRef val_str = JSValueToStringCopy(ctx, args[0], NULL);
char buf[1000];
JSStringGetUTF8CString(val_str, buf, 1000);
fprintf(stdout, "%s", buf);
fflush(stdout);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_print_err_fn(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 1 && JSValueIsString(ctx, args[0])) {
JSStringRef val_str = JSValueToStringCopy(ctx, args[0], NULL);
char buf[1000];
JSStringGetUTF8CString(val_str, buf, 1000);
fprintf(stderr, "%s", buf);
fflush(stderr);
}
return JSValueMakeNull(ctx);
}
JSValueRef function_import_script(JSContextRef ctx, JSObjectRef function, JSObjectRef thisObject,
size_t argc, const JSValueRef args[], JSValueRef* exception) {
if (argc == 1 && JSValueGetType(ctx, args[0]) == kJSTypeString) {
JSStringRef path_str_ref = JSValueToStringCopy(ctx, args[0], NULL);
char path[100];
path[0] = '\0';
JSStringGetUTF8CString(path_str_ref, path, 100);
JSStringRelease(path_str_ref);
char full_path[150];
snprintf(full_path, 150, "%s/%s", "out", path);
char *buf = get_contents(full_path);
if (buf == NULL) {
goto err;
}
evaluate_script(ctx, buf, full_path);
free(buf);
}
return JSValueMakeUndefined(ctx);
err:
// TODO: Fill exception with error from errno
return JSValueMakeUndefined(ctx);
}
void register_global_function(JSContextRef ctx, char *name, JSObjectCallAsFunctionCallback handler) {
JSObjectRef global_obj = JSContextGetGlobalObject(ctx);
JSStringRef fn_name = JSStringCreateWithUTF8CString(name);
JSObjectRef fn_obj = JSObjectMakeFunctionWithCallback(ctx, fn_name, handler);
JSObjectSetProperty(ctx, global_obj, fn_name, fn_obj, kJSPropertyAttributeNone, NULL);
}
int main(int argc, char **argv) {
JSGlobalContextRef ctx = JSGlobalContextCreate(NULL);
JSStringRef nameRef = JSStringCreateWithUTF8CString("jsc-test");
JSGlobalContextSetName(ctx, nameRef);
JSObjectRef global_obj = JSContextGetGlobalObject(ctx);
evaluate_script(ctx, "var global = this;", "<init>");
register_global_function(ctx, "IMPORT_SCRIPT", function_import_script);
bootstrap(ctx, "out/main.js", "out/goog/base.js");
register_global_function(ctx, "CONSOLE_LOG", function_console_log);
register_global_function(ctx, "CONSOLE_ERROR", function_console_error);
evaluate_script(ctx, "var console = {};"\
"console.log = CONSOLE_LOG;"\
"console.error = CONSOLE_ERROR;", "<init>");
// require app namespaces
evaluate_script(ctx, "goog.require('planck.repl');", "<init>");
// without this things won't work
evaluate_script(ctx, "var window = global;", "<init>");
register_global_function(ctx, "PLANCK_READ_FILE", function_read_file);
register_global_function(ctx, "PLANCK_LOAD", function_load);
register_global_function(ctx, "PLANCK_LOAD_DEPS_CLJS_FILES", function_load_deps_cljs_files);
register_global_function(ctx, "PLANCK_EVAL", function_eval);
register_global_function(ctx, "PLANCK_GET_TERM_SIZE", function_get_term_size);
register_global_function(ctx, "PLANCK_PRINT_FN", function_print_fn);
register_global_function(ctx, "PLANCK_PRINT_ERR_FN", function_print_err_fn);
evaluate_script(ctx, "cljs.core.set_print_fn_BANG_.call(null,PLANCK_PRINT_FN);", "<init>");
evaluate_script(ctx, "cljs.core.set_print_err_fn_BANG_.call(null,PLANCK_PRINT_ERR_FN);", "<init>");
bool repl = true;
{
JSValueRef arguments[4];
bool verbose = true, static_fns = true;
arguments[0] = JSValueMakeBoolean(ctx, repl);
arguments[1] = JSValueMakeBoolean(ctx, verbose);
JSStringRef cache_path_str = JSStringCreateWithUTF8CString(".planck_cache");
arguments[2] = JSValueMakeString(ctx, cache_path_str);
arguments[3] = JSValueMakeBoolean(ctx, static_fns);
JSValueRef ex = NULL;
JSObjectCallAsFunction(ctx, get_function(ctx, "planck.repl", "init"), JSContextGetGlobalObject(ctx), 4, arguments, &ex);
debug_print_value
("planck.repl/init", ctx, ex);
}
if (repl) {
evaluate_source(ctx, "text", "(require '[planck.repl :refer-macros [apropos dir find-doc doc source pst]])", true, "cljs.user");
}
evaluate_script(ctx, "goog.provide('cljs.user');", "<init>");
evaluate_script(ctx, "goog.require('cljs.core');", "<init>");
evaluate_script(ctx, "cljs.core._STAR_assert_STAR_ = true;", "<init>");
//evaluate_source(ctx, "text", "(empty? \"\")", true, "cljs.user");
printf("---\nrequire macros\n---\n");
evaluate_source(ctx, "text", "(require-macros 'planck.repl 'planck.core 'planck.shell 'planck.from.io.aviso.ansi 'clojure.template 'cljs.spec 'cljs.spec.impl.gen 'cljs.test)", true, "cljs.user");
if (repl) {
bool javascript = false;
char *prompt = javascript ? " > " : " => ";
char *line;
while ((line = linenoise(prompt)) != NULL) {
JSValueRef res = NULL;
if (javascript) {
res = evaluate_script(ctx, line, "<stdin>");
} else {
res = evaluate_source(ctx, "text", line, true, "cljs.user");
}
free(line);
char res_buf[1000];
res_buf[0] = '\0';
JSStringRef res_str = to_string(ctx, res);
JSStringGetUTF8CString(res_str, res_buf, 1000);
printf("%s\n", res_buf);
}
}
}
JSStringRef to_string(JSContextRef ctx, JSValueRef val) {
if (JSValueIsUndefined(ctx, val)) {
return JSStringCreateWithUTF8CString("undefined");
} else if (JSValueIsNull(ctx, val)) {
return JSStringCreateWithUTF8CString("null");
} else {
JSStringRef to_string_name = JSStringCreateWithUTF8CString("toString");
JSObjectRef obj = JSValueToObject(ctx, val, NULL);
JSValueRef to_string = JSObjectGetProperty(ctx, obj, to_string_name, NULL);
JSObjectRef to_string_obj = JSValueToObject(ctx, to_string, NULL);
JSValueRef obj_val = JSObjectCallAsFunction(ctx, to_string_obj, obj, 0, NULL, NULL);
return JSValueToStringCopy(ctx, obj_val, NULL);
}
}
JSValueRef evaluate_script(JSContextRef ctx, char *script, char *source) {
JSStringRef script_ref = JSStringCreateWithUTF8CString(script);
JSStringRef source_ref = NULL;
if (source != NULL) {
source_ref = JSStringCreateWithUTF8CString(source);
}
JSValueRef ex = NULL;
JSValueRef val = JSEvaluateScript(ctx, script_ref, NULL, source_ref, 0, &ex);
JSStringRelease(script_ref);
if (source != NULL) {
JSStringRelease(source_ref);
}
debug_print_value
("evaluate_script", ctx, ex);
return val;
}
JSValueRef get_value_on_object(JSContextRef ctx, JSObjectRef obj, char *name) {
JSStringRef name_str = JSStringCreateWithUTF8CString(name);
JSValueRef val = JSObjectGetProperty(ctx, obj, name_str, NULL);
JSStringRelease(name_str);
return val;
}
JSValueRef get_value(JSContextRef ctx, char *namespace, char *name) {
JSValueRef ns_val = NULL;
// printf("get_value: '%s'\n", namespace);
int len = strlen(namespace) + 1;
char *ns_tmp = malloc(len * sizeof(char));
char **ns_tmp_start = &ns_tmp;
strncpy(ns_tmp, namespace, len);
char *ns_part = strtok(ns_tmp, ".");
ns_tmp = NULL;
while (ns_part != NULL) {
char *munged_ns_part = munge(ns_part);
if (ns_val) {
ns_val = get_value_on_object(ctx, JSValueToObject(ctx, ns_val, NULL), munged_ns_part);
} else {
ns_val = get_value_on_object(ctx, JSContextGetGlobalObject(ctx), munged_ns_part);
}
free(munged_ns_part); // TODO: Use a fixed buffer for this? (Which would restrict namespace part length...)
ns_part = strtok(NULL, ".");
}
//free(ns_tmp);
return get_value_on_object(ctx, JSValueToObject(ctx, ns_val, NULL), name);
}
JSObjectRef get_function(JSContextRef ctx, char *namespace, char *name) {
JSValueRef val = get_value(ctx, namespace, name);
assert(!JSValueIsUndefined(ctx, val));
return JSValueToObject(ctx, val, NULL);
}
JSValueRef evaluate_source(JSContextRef ctx, char *type, char *source, bool expression, char *set_ns) {
JSValueRef args[7];
int num_args = 7;
{
JSValueRef source_args[2];
JSStringRef type_str = JSStringCreateWithUTF8CString(type);
source_args[0] = JSValueMakeString(ctx, type_str);
JSStringRef source_str = JSStringCreateWithUTF8CString(source);
source_args[1] = JSValueMakeString(ctx, source_str);
args[0] = JSObjectMakeArray(ctx, 2, source_args, NULL);
}
args[1] = JSValueMakeBoolean(ctx, expression);
args[2] = JSValueMakeBoolean(ctx, false);
args[3] = JSValueMakeBoolean(ctx, false);
JSStringRef set_ns_str = JSStringCreateWithUTF8CString(set_ns);
args[4] = JSValueMakeString(ctx, set_ns_str);
JSStringRef theme_str = JSStringCreateWithUTF8CString("dumb");
args[5] = JSValueMakeString(ctx, theme_str);
args[6] = JSValueMakeNumber(ctx, 0);
JSObjectRef execute_fn = get_function(ctx, "planck.repl", "execute");
JSObjectRef global_obj = JSContextGetGlobalObject(ctx);
JSValueRef ex = NULL;
JSValueRef val = JSObjectCallAsFunction(ctx, execute_fn, global_obj, num_args, args, &ex);
debug_print_value
("planck.repl/execute", ctx, ex);
return ex != NULL ? ex : val;
}
char *munge(char *s) {
int len = strlen(s);
int new_len = 0;
for (int i = 0; i < len; i++) {
switch (s[i]) {
case '!':
new_len += 6; // _BANG_
break;
case '?':
new_len += 7; // _QMARK_
break;
default:
new_len += 1;
}
}
char *ms = malloc((new_len+1) * sizeof(char));
int j = 0;
for (int i = 0; i < len; i++) {
switch (s[i]) {
case '-':
ms[j++] = '_';
break;
case '!':
ms[j++] = '_';
ms[j++] = 'B';
ms[j++] = 'A';
ms[j++] = 'N';
ms[j++] = 'G';
ms[j++] = '_';
break;
case '?':
ms[j++] = '_';
ms[j++] = 'Q';
ms[j++] = 'M';
ms[j++] = 'A';
ms[j++] = 'R';
ms[j++] = 'K';
ms[j++] = '_';
break;
default:
ms[j++] = s[i];
}
}
ms[new_len] = '\0';
return ms;
}
void bootstrap(JSContextRef ctx, char *deps_file_path, char *goog_base_path) {
char source[] = "<bootstrap>";
// Setup CLOSURE_IMPORT_SCRIPT
evaluate_script(ctx, "CLOSURE_IMPORT_SCRIPT = function(src) { IMPORT_SCRIPT('goog/' + src); return true; }", source);
// Load goog base
char *base_script_str = get_contents(goog_base_path);
if (base_script_str == NULL) {
fprintf(stderr, "The goog base JavaScript text could not be loaded");
exit(1);
}
evaluate_script(ctx, base_script_str, "<bootstrap:base>");
free(base_script_str);
// Load the deps file
char *deps_script_str = get_contents(deps_file_path);
if (deps_script_str == NULL) {
fprintf(stderr, "The goog base JavaScript text could not be loaded");
exit(1);
}
evaluate_script(ctx, deps_script_str, "<bootstrap:deps>");
free(deps_script_str);
evaluate_script(ctx, "goog.isProvided_ = function(x) { return false; };", source);
evaluate_script(ctx, "goog.require = function (name) { return CLOSURE_IMPORT_SCRIPT(goog.dependencies_.nameToPath[name]); };", source);
evaluate_script(ctx, "goog.require('cljs.core');", source);
// redef goog.require to track loaded libs
evaluate_script(ctx, "cljs.core._STAR_loaded_libs_STAR_ = cljs.core.into.call(null, cljs.core.PersistentHashSet.EMPTY, [\"cljs.core\"]);\n"
"goog.require = function (name, reload) {\n"
" if(!cljs.core.contains_QMARK_(cljs.core._STAR_loaded_libs_STAR_, name) || reload) {\n"
" var AMBLY_TMP = cljs.core.PersistentHashSet.EMPTY;\n"
" if (cljs.core._STAR_loaded_libs_STAR_) {\n"
" AMBLY_TMP = cljs.core._STAR_loaded_libs_STAR_;\n"
" }\n"
" cljs.core._STAR_loaded_libs_STAR_ = cljs.core.into.call(null, AMBLY_TMP, [name]);\n"
" CLOSURE_IMPORT_SCRIPT(goog.dependencies_.nameToPath[name]);\n"
" }\n"
"};", source);
}
char *get_contents(char *path) {
/*#ifdef DEBUG
printf("get_contents(\"%s\")\n", path);
#endif*/
char *err_prefix;
FILE *f = fopen(path, "r");
if (f == NULL) {
err_prefix = "fopen";
goto err;
}
struct stat f_stat;
if (fstat(fileno(f), &f_stat) < 0) {
err_prefix = "fstat";
goto err;
}
char *buf = malloc(f_stat.st_size + 1);
memset(buf, 0, f_stat.st_size);
fread(buf, f_stat.st_size, 1, f);
buf[f_stat.st_size] = '\0';
if (ferror(f)) {
err_prefix = "fread";
free(buf);
goto err;
}
if (fclose(f) < 0) {
err_prefix = "fclose";
goto err;
}
return buf;
err:
printf("get_contents(\"%s\"): %s: %s\n", path, err_prefix, strerror(errno));
return NULL;
}
|