The Perl and Raku Conference 2025: Greenville, South Carolina - June 27-29 Learn more

#include <stdarg.h>
#include <stdio.h>
#include <v8.h>
#include "pl_util.h"
#include "pl_console.h"
#define NEED_newRV_noinc_GLOBAL
#include "ppport.h"
#define CONSOLE_FLUSH 0x01
#define CONSOLE_TARGET_STDOUT 0x10
#define CONSOLE_TARGET_STDERR 0x20
static void save_msg(pTHX_ V8Context* ctx, const char* target, SV* message)
{
STRLEN tlen = strlen(target);
AV* data = 0;
int top = 0;
SV* pvalue = 0;
SV** found = hv_fetch(ctx->msgs, target, tlen, 0);
if (found) {
SV* ref = SvRV(*found);
/* value not a valid arrayref? bail out */
if (SvTYPE(ref) != SVt_PVAV) {
return;
}
data = (AV*) ref;
top = av_top_index(data);
} else {
SV* ref = 0;
data = newAV();
ref = newRV_noinc((SV*) data);
if (hv_store(ctx->msgs, target, tlen, ref, 0)) {
SvREFCNT_inc(ref);
}
top = -1;
}
pvalue = sv_2mortal(message);
if (av_store(data, ++top, pvalue)) {
SvREFCNT_inc(pvalue);
}
else {
croak("Could not store message in target %*.*s\n", (int) tlen, (int) tlen, target);
}
}
static int console_output_line(pTHX_ V8Context* ctx, SV* message, unsigned int flags)
{
STRLEN mlen = 0;
char* mstr = SvPV(message, mlen);
if (ctx->flags & V8_OPT_FLAG_SAVE_MESSAGES) {
const char* target = (flags & CONSOLE_TARGET_STDERR) ? "stderr" : "stdout";
save_msg(aTHX_ ctx, target, message);
}
else {
PerlIO* fp = (flags & CONSOLE_TARGET_STDERR) ? PerlIO_stderr() : PerlIO_stdout();
PerlIO_printf(fp, "%s\n", mstr);
if (flags & CONSOLE_FLUSH) {
PerlIO_flush(fp);
}
}
return mlen;
}
static int console_output(const FunctionCallbackInfo<Value>& args, unsigned int flags, const char* preamble = 0, bool stack = false, int start = 0)
{
dTHX;
Isolate* isolate = args.GetIsolate();
HandleScope handle_scope(isolate);
Local<External> v8_val = Local<External>::Cast(args.Data());
V8Context* ctx = (V8Context*) v8_val->Value();
Local<Context> context = Local<Context>::New(isolate, *ctx->persistent_context);
Context::Scope context_scope(context);
SV* message = newSVpvs("");
bool separate = false;
if (preamble) {
Perl_sv_catpv(aTHX_ message, preamble);
Perl_sv_catpvf(aTHX_ message, ":");
separate = true;
}
Local<Object> global = context->Global();
Local<Name> to_str_nam = String::NewFromUtf8(isolate, "JSON_stringify_with_cycles", NewStringType::kNormal).ToLocalChecked();
Local<Value> to_str_val = global->Get(to_str_nam);
if (to_str_val->IsFunction()) {
Local<Function> to_str_fun = Local<Function>::Cast(to_str_val);
Local<Value> sargs[1];
for (int j = start; j < args.Length(); j++) {
/* add separator if necessary */
if (separate) {
Perl_sv_catpvf(aTHX_ message, " ");
}
separate = true;
/* for non-objects, just get their value as string */
if (!args[j]->IsObject()) {
String::Utf8Value str(isolate, args[j]);
Perl_sv_catpvf(aTHX_ message, "%s", *str);
continue;
}
/* convert each object arg to JSON */
sargs[0] = args[j];
Local<Value> ret;
if (!to_str_fun->Call(context, global, 1, sargs).ToLocal(&ret)) {
continue;
}
Local<String> json = Local<String>::Cast(ret);
String::Utf8Value str(isolate, json);
Perl_sv_catpvf(aTHX_ message, "%s", *str);
}
}
if (stack) {
#if 0
/* TODO: generate a stack trace */
v8_inspector::V8Inspector* inspector = new v8_inspector::V8InspectorImpl::V8InspectorImpl();
inspector->captureStackTrace();
#endif
}
return console_output_line(aTHX_ ctx, message, flags);
}
static void console_assert(const FunctionCallbackInfo<Value>& args)
{
if (args.Length() < 1) {
return;
}
bool silent = args[0]->BooleanValue();
if (silent) {
return;
}
console_output(args, CONSOLE_TARGET_STDOUT | CONSOLE_FLUSH, "AssertionError", true, 1);
}
static void console_log(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDOUT | CONSOLE_FLUSH);
}
static void console_debug(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDOUT | CONSOLE_FLUSH);
}
static void console_trace(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDOUT | CONSOLE_FLUSH, "Trace", true);
}
static void console_info(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDOUT | CONSOLE_FLUSH);
}
static void console_warn(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDERR | CONSOLE_FLUSH);
}
static void console_error(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDERR | CONSOLE_FLUSH, "Error", true);
}
static void console_exception(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDERR | CONSOLE_FLUSH, "Error", true);
}
static void console_dir(const FunctionCallbackInfo<Value>& args)
{
console_output(args, CONSOLE_TARGET_STDERR | CONSOLE_FLUSH);
}
int pl_register_console_functions(V8Context* ctx)
{
typedef void (*Handler)(const FunctionCallbackInfo<Value>& args);
static struct Data {
const char* name;
Handler func;
} data[] = {
{ "console.assert" , console_assert },
{ "console.log" , console_log },
{ "console.debug" , console_debug },
{ "console.trace" , console_trace },
{ "console.info" , console_info },
{ "console.warn" , console_warn },
{ "console.error" , console_error },
{ "console.exception", console_exception },
{ "console.dir" , console_dir },
};
HandleScope handle_scope(ctx->isolate);
Local<Context> context = Local<Context>::New(ctx->isolate, *ctx->persistent_context);
Context::Scope context_scope(context);
Local<Value> v8_ctx = External::New(ctx->isolate, ctx);
int n = sizeof(data) / sizeof(data[0]);
for (int j = 0; j < n; ++j) {
Local<Object> object;
Local<Value> slot;
bool found = find_parent(ctx, data[j].name, context, object, slot, true);
if (!found) {
pl_show_error(ctx, "could not create parent for %s", data[j].name);
continue;
}
Local<FunctionTemplate> ft = FunctionTemplate::New(ctx->isolate, data[j].func, v8_ctx);
Local<Function> v8_func = ft->GetFunction();
object->Set(slot, v8_func);
}
return n;
}
int pl_show_error(V8Context* ctx, const char* fmt, ...)
{
dTHX;
SV* message = newSVpvs("");
va_list ap;
va_start(ap, fmt);
Perl_sv_vcatpvf(aTHX_ message, fmt, &ap);
va_end(ap);
return console_output_line(aTHX_ ctx, message, CONSOLE_TARGET_STDERR | CONSOLE_FLUSH);
}