working on documentation and more API functions
This commit is contained in:
+56
-13
@@ -20,6 +20,50 @@ bool compiler_code_state_is_neutral(const CompilerCodeState& state)
|
||||
return(!state.inside_quote && !state.inside_line_comment && !state.inside_block_comment);
|
||||
}
|
||||
|
||||
String compiler_cpp_raw_string_delimiter(const String& content)
|
||||
{
|
||||
StringList candidates = {
|
||||
"",
|
||||
"UCE",
|
||||
"UCE_LITERAL",
|
||||
"uce_literal_0",
|
||||
"uce_literal_1"
|
||||
};
|
||||
|
||||
u64 hash = 1469598103934665603ULL;
|
||||
for(unsigned char c : content)
|
||||
{
|
||||
hash ^= c;
|
||||
hash *= 1099511628211ULL;
|
||||
}
|
||||
|
||||
for(u32 i = 0; i < 64; i += 1)
|
||||
{
|
||||
String suffix = to_hex<u64>(hash ^ ((u64)i * 0x9E3779B97F4A7C15ULL), 12);
|
||||
candidates.push_back("UCE" + suffix);
|
||||
}
|
||||
|
||||
for(auto& delimiter : candidates)
|
||||
{
|
||||
String terminator = ")" + delimiter + "\"";
|
||||
if(content.find(terminator) == String::npos)
|
||||
return(delimiter);
|
||||
}
|
||||
|
||||
return("");
|
||||
}
|
||||
|
||||
String compiler_cpp_string_literal(const String& content)
|
||||
{
|
||||
String delimiter = compiler_cpp_raw_string_delimiter(content);
|
||||
if(delimiter != "" || content.find(")\"") == String::npos)
|
||||
return("R\"" + delimiter + "(" + content + ")" + delimiter + "\"");
|
||||
|
||||
// This fallback is only reachable for deliberately adversarial content that
|
||||
// contains every generated raw-string delimiter candidate.
|
||||
return(json_escape(content));
|
||||
}
|
||||
|
||||
void compiler_code_state_consume(CompilerCodeState& state, String& buffer, const String& content, u32& i)
|
||||
{
|
||||
char c = content[i];
|
||||
@@ -172,16 +216,15 @@ void compiler_append_text_literal_output(String& parsed_content, String& literal
|
||||
{
|
||||
if(literal_buffer == "")
|
||||
return;
|
||||
parsed_content.append("print(R\"(" + literal_buffer + ")\");");
|
||||
parsed_content.append("print(" + compiler_cpp_string_literal(literal_buffer) + ");");
|
||||
literal_buffer.clear();
|
||||
}
|
||||
|
||||
String compiler_process_text_literal(Request* context, SharedUnit* su, String content)
|
||||
{
|
||||
String parsed_content;
|
||||
String html_output_start = "print(R\"(";
|
||||
String html_output_end = ")\");";
|
||||
String code_buffer = "";
|
||||
String literal_buffer = "";
|
||||
CompilerCodeState code_state;
|
||||
bool inside_code = false;
|
||||
bool is_field = false;
|
||||
@@ -200,6 +243,7 @@ String compiler_process_text_literal(Request* context, SharedUnit* su, String co
|
||||
inside_code = true;
|
||||
code_buffer = "";
|
||||
code_state = CompilerCodeState();
|
||||
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
||||
if(c2 == '=')
|
||||
{
|
||||
is_field = true;
|
||||
@@ -221,7 +265,7 @@ String compiler_process_text_literal(Request* context, SharedUnit* su, String co
|
||||
continue;
|
||||
}
|
||||
|
||||
parsed_content.append(1, c);
|
||||
literal_buffer.append(1, c);
|
||||
continue;
|
||||
}
|
||||
|
||||
@@ -234,27 +278,23 @@ String compiler_process_text_literal(Request* context, SharedUnit* su, String co
|
||||
if(escape_field)
|
||||
{
|
||||
parsed_content.append(
|
||||
html_output_end +
|
||||
"print(html_escape( " +
|
||||
code_buffer +
|
||||
" )); " +
|
||||
html_output_start
|
||||
" )); "
|
||||
);
|
||||
}
|
||||
else
|
||||
{
|
||||
parsed_content.append(
|
||||
html_output_end +
|
||||
"print( " +
|
||||
code_buffer +
|
||||
" ); " +
|
||||
html_output_start
|
||||
" ); "
|
||||
);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
parsed_content.append(html_output_end + code_buffer + html_output_start);
|
||||
parsed_content.append(code_buffer);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
@@ -269,7 +309,10 @@ String compiler_process_text_literal(Request* context, SharedUnit* su, String co
|
||||
compiler_code_state_consume(code_state, code_buffer, content, i);
|
||||
}
|
||||
|
||||
return(html_output_start + parsed_content + html_output_end);
|
||||
if(literal_buffer != "")
|
||||
compiler_append_text_literal_output(parsed_content, literal_buffer);
|
||||
|
||||
return(parsed_content);
|
||||
}
|
||||
|
||||
String compiler_rewrite_named_render_syntax(String content)
|
||||
@@ -444,4 +487,4 @@ String compiler_preprocess_source(Request* context, SharedUnit* su, String conte
|
||||
{
|
||||
content = compiler_rewrite_named_render_syntax(content);
|
||||
return(compiler_preprocess_shared_unit_char_wise(context, su, content));
|
||||
}
|
||||
}
|
||||
|
||||
+338
-111
@@ -13,6 +13,8 @@ const char* UCE_SETUP_SYMBOL = "__uce_set_current_request";
|
||||
const char* UCE_RENDER_SYMBOL = "__uce_render";
|
||||
const char* UCE_COMPONENT_SYMBOL = "__uce_component";
|
||||
const char* UCE_WEBSOCKET_SYMBOL = "__uce_websocket";
|
||||
const char* UCE_ONCE_SYMBOL = "__uce_once";
|
||||
const char* UCE_INIT_SYMBOL = "__uce_init";
|
||||
const u64 UCE_UNIT_ABI_VERSION = 1;
|
||||
|
||||
struct SharedUnitFilesystemState
|
||||
@@ -48,6 +50,10 @@ struct SharedUnitCompileCheck
|
||||
bool needs_compile = false;
|
||||
};
|
||||
|
||||
void compiler_unload_failed_shared_unit(SharedUnit* su);
|
||||
bool compiler_run_unit_init(Request* context, SharedUnit* su, String* error_out = 0);
|
||||
bool compiler_run_unit_once_if_needed(Request* context, SharedUnit* su, String* error_out = 0);
|
||||
|
||||
bool compiler_config_truthy(String raw, bool default_value)
|
||||
{
|
||||
raw = to_lower(trim(raw));
|
||||
@@ -67,19 +73,6 @@ bool compiler_jit_compile_on_request_enabled(Request* context)
|
||||
return(compiler_config_truthy(context->server->config["JIT_COMPILE_ON_REQUEST"], true));
|
||||
}
|
||||
|
||||
u64 compiler_failure_retry_seconds(Request* context)
|
||||
{
|
||||
if(!context || !context->server)
|
||||
return(10);
|
||||
auto raw = trim(context->server->config["COMPILE_FAILURE_RETRY_SECONDS"]);
|
||||
if(raw == "")
|
||||
return(10);
|
||||
auto value = int_val(raw);
|
||||
if(value < 0)
|
||||
return(0);
|
||||
return((u64)value);
|
||||
}
|
||||
|
||||
bool compiler_is_u64_string(String value)
|
||||
{
|
||||
value = trim(value);
|
||||
@@ -167,16 +160,16 @@ bool compiler_failure_retry_deferred(Request* context, SharedUnit* su, const Sha
|
||||
{
|
||||
if(!context || !su)
|
||||
return(false);
|
||||
auto retry_seconds = compiler_failure_retry_seconds(context);
|
||||
if(retry_seconds == 0)
|
||||
return(false);
|
||||
if(!state.compile_output_exists || state.compile_output_time == 0)
|
||||
return(false);
|
||||
if(state.source_time == 0)
|
||||
return(false);
|
||||
if(state.source_time > state.compile_output_time)
|
||||
return(false);
|
||||
return(time() < (u64)state.compile_output_time + retry_seconds);
|
||||
auto required_failure_inputs_time = std::max({
|
||||
state.source_time,
|
||||
state.setup_template_time,
|
||||
state.compiler_abi_time
|
||||
});
|
||||
return(state.compile_output_time >= required_failure_inputs_time);
|
||||
}
|
||||
|
||||
String compiler_failure_output_for_state(SharedUnit* su, const SharedUnitFilesystemState& state)
|
||||
@@ -653,6 +646,8 @@ void load_shared_unit(Request* context, SharedUnit* su)
|
||||
su->on_render = 0;
|
||||
su->on_component = 0;
|
||||
su->on_websocket = 0;
|
||||
su->on_once = 0;
|
||||
su->on_init = 0;
|
||||
su->on_setup = 0;
|
||||
su->so_handle = 0;
|
||||
su->compiler_messages = "";
|
||||
@@ -695,17 +690,31 @@ void load_shared_unit(Request* context, SharedUnit* su)
|
||||
dlerror();
|
||||
su->on_websocket = (request_ref_handler)dlsym(su->so_handle, UCE_WEBSOCKET_SYMBOL);
|
||||
dlerror();
|
||||
su->on_once = (request_ref_handler)dlsym(su->so_handle, UCE_ONCE_SYMBOL);
|
||||
dlerror();
|
||||
su->on_init = (request_ref_handler)dlsym(su->so_handle, UCE_INIT_SYMBOL);
|
||||
dlerror();
|
||||
su->api_declarations = split(file_get_contents(su->api_file_name), "\n");
|
||||
String init_error = "";
|
||||
if(!compiler_run_unit_init(context, su, &init_error))
|
||||
{
|
||||
if(init_error != "")
|
||||
su->compiler_messages = init_error;
|
||||
compiler_unload_failed_shared_unit(su);
|
||||
}
|
||||
//else
|
||||
// printf("(i) loaded unit %s\n", su->file_name.c_str());
|
||||
}
|
||||
else
|
||||
{
|
||||
const char* dl_error = dlerror();
|
||||
su->compiler_messages = "could not open " + su->so_name;
|
||||
if(dl_error && String(dl_error) != "")
|
||||
su->compiler_messages += ": " + String(dl_error);
|
||||
su->compile_status = "load_error";
|
||||
su->compile_error_status = su->compiler_messages;
|
||||
su->last_error = time();
|
||||
printf("Error loading unit %s, could not open %s\n", su->file_name.c_str(), su->so_name.c_str());
|
||||
printf("Error loading unit %s, %s\n", su->file_name.c_str(), su->compiler_messages.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1176,6 +1185,183 @@ struct UnitInvocationScope
|
||||
}
|
||||
};
|
||||
|
||||
enum class UnitCallMacroKind
|
||||
{
|
||||
none,
|
||||
render,
|
||||
component,
|
||||
once,
|
||||
init
|
||||
};
|
||||
|
||||
struct UnitCallMacroTarget
|
||||
{
|
||||
UnitCallMacroKind kind = UnitCallMacroKind::none;
|
||||
String handler_name;
|
||||
};
|
||||
|
||||
struct RequestPropsScope
|
||||
{
|
||||
Request* context = 0;
|
||||
DTree previous_props;
|
||||
|
||||
RequestPropsScope(Request* context, const DTree& props)
|
||||
{
|
||||
this->context = context;
|
||||
if(this->context)
|
||||
{
|
||||
previous_props = this->context->props;
|
||||
this->context->props = props;
|
||||
}
|
||||
}
|
||||
|
||||
~RequestPropsScope()
|
||||
{
|
||||
if(context)
|
||||
context->props = previous_props;
|
||||
}
|
||||
};
|
||||
|
||||
void compiler_unload_failed_shared_unit(SharedUnit* su)
|
||||
{
|
||||
if(!su)
|
||||
return;
|
||||
if(su->so_handle)
|
||||
dlclose(su->so_handle);
|
||||
su->so_handle = 0;
|
||||
su->api_functions.clear();
|
||||
su->on_setup = 0;
|
||||
su->on_render = 0;
|
||||
su->on_component = 0;
|
||||
su->on_websocket = 0;
|
||||
su->on_once = 0;
|
||||
su->on_init = 0;
|
||||
}
|
||||
|
||||
bool compiler_run_unit_init(Request* context, SharedUnit* su, String* error_out)
|
||||
{
|
||||
if(!su || !su->on_init)
|
||||
return(true);
|
||||
if(!context)
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = "internal error: INIT() requires a Request context";
|
||||
return(false);
|
||||
}
|
||||
if(!su->on_setup)
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name;
|
||||
return(false);
|
||||
}
|
||||
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
try
|
||||
{
|
||||
su->on_init(*context);
|
||||
return(true);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
su->runtime_error_status = "uncaught exception during INIT";
|
||||
su->compile_status = "load_error";
|
||||
su->compile_error_status = su->runtime_error_status;
|
||||
su->last_error = time();
|
||||
if(error_out)
|
||||
*error_out = su->runtime_error_status;
|
||||
return(false);
|
||||
}
|
||||
}
|
||||
|
||||
bool compiler_run_unit_once_if_needed(Request* context, SharedUnit* su, String* error_out)
|
||||
{
|
||||
if(!su || !su->on_once)
|
||||
return(true);
|
||||
if(!context)
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = "internal error: ONCE() requires a Request context";
|
||||
return(false);
|
||||
}
|
||||
if(!su->on_setup)
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name;
|
||||
return(false);
|
||||
}
|
||||
|
||||
if(context->once_units.find(su->file_name) != context->once_units.end())
|
||||
return(true);
|
||||
|
||||
context->once_units.insert(su->file_name);
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
try
|
||||
{
|
||||
su->on_once(*context);
|
||||
return(true);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
context->once_units.erase(su->file_name);
|
||||
su->runtime_error_status = "uncaught exception during ONCE";
|
||||
su->last_error = time();
|
||||
if(error_out)
|
||||
*error_out = su->runtime_error_status;
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
String unit_call_macro_trim(String function_name)
|
||||
{
|
||||
function_name = trim(function_name);
|
||||
if(function_name.length() >= 2 && function_name.substr(function_name.length() - 2) == "()")
|
||||
function_name = trim(function_name.substr(0, function_name.length() - 2));
|
||||
return(function_name);
|
||||
}
|
||||
|
||||
UnitCallMacroTarget unit_call_macro_target(String function_name)
|
||||
{
|
||||
UnitCallMacroTarget target;
|
||||
function_name = unit_call_macro_trim(function_name);
|
||||
|
||||
if(function_name == "RENDER")
|
||||
{
|
||||
target.kind = UnitCallMacroKind::render;
|
||||
return(target);
|
||||
}
|
||||
if(function_name.rfind("RENDER:", 0) == 0)
|
||||
{
|
||||
target.kind = UnitCallMacroKind::render;
|
||||
target.handler_name = trim(function_name.substr(7));
|
||||
return(target);
|
||||
}
|
||||
if(function_name == "COMPONENT")
|
||||
{
|
||||
target.kind = UnitCallMacroKind::component;
|
||||
return(target);
|
||||
}
|
||||
if(function_name.rfind("COMPONENT:", 0) == 0)
|
||||
{
|
||||
target.kind = UnitCallMacroKind::component;
|
||||
target.handler_name = trim(function_name.substr(10));
|
||||
return(target);
|
||||
}
|
||||
if(function_name == "ONCE")
|
||||
{
|
||||
target.kind = UnitCallMacroKind::once;
|
||||
return(target);
|
||||
}
|
||||
if(function_name == "INIT")
|
||||
{
|
||||
target.kind = UnitCallMacroKind::init;
|
||||
return(target);
|
||||
}
|
||||
|
||||
return(target);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
String component_normalize_path(String name)
|
||||
@@ -1262,6 +1448,70 @@ String component_handler_symbol(String render_name)
|
||||
return(String(UCE_COMPONENT_SYMBOL) + "_" + safe_name(render_name));
|
||||
}
|
||||
|
||||
String compiler_missing_request_handler_message(UnitCallMacroKind kind, String handler_name)
|
||||
{
|
||||
handler_name = trim(handler_name);
|
||||
if(kind == UnitCallMacroKind::render)
|
||||
{
|
||||
if(handler_name == "" || handler_name == "render")
|
||||
return("no RENDER() entry point");
|
||||
return("no RENDER:" + handler_name + "() entry point");
|
||||
}
|
||||
if(kind == UnitCallMacroKind::component)
|
||||
{
|
||||
if(handler_name == "")
|
||||
return("no COMPONENT() entry point");
|
||||
return("no COMPONENT:" + handler_name + "() entry point");
|
||||
}
|
||||
if(kind == UnitCallMacroKind::once)
|
||||
return("no ONCE() entry point");
|
||||
if(kind == UnitCallMacroKind::init)
|
||||
return("no INIT() entry point");
|
||||
return("request handler not found");
|
||||
}
|
||||
|
||||
bool compiler_prepare_request_handler(Request* context, SharedUnit* su, String* error_out = 0, bool run_once = false)
|
||||
{
|
||||
if(!su->on_setup)
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + su->file_name;
|
||||
return(false);
|
||||
}
|
||||
if(run_once && !compiler_run_unit_once_if_needed(context, su, error_out))
|
||||
return(false);
|
||||
return(true);
|
||||
}
|
||||
|
||||
void compiler_execute_request_handler(
|
||||
Request* context,
|
||||
SharedUnit* su,
|
||||
request_ref_handler handler,
|
||||
bool count_request,
|
||||
String runtime_error_status
|
||||
)
|
||||
{
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
f64 render_start = time_precise();
|
||||
compiler_begin_render_result(su, count_request);
|
||||
try
|
||||
{
|
||||
handler(*context);
|
||||
compiler_record_render_result(su, time_precise() - render_start, true);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
compiler_record_render_result(
|
||||
su,
|
||||
time_precise() - render_start,
|
||||
false,
|
||||
runtime_error_status
|
||||
);
|
||||
throw;
|
||||
}
|
||||
}
|
||||
|
||||
request_ref_handler get_page_render_handler(SharedUnit* su, String render_name)
|
||||
{
|
||||
String symbol = page_render_handler_symbol(render_name);
|
||||
@@ -1300,46 +1550,24 @@ bool compiler_invoke_render(Request* context, String file_name, String render_na
|
||||
if(!su)
|
||||
return(false);
|
||||
|
||||
if(!su->on_setup)
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + file_name;
|
||||
if(!compiler_prepare_request_handler(context, su, error_out, true))
|
||||
return(false);
|
||||
}
|
||||
|
||||
auto handler = get_page_render_handler(su, render_name);
|
||||
if(!handler)
|
||||
{
|
||||
if(error_out)
|
||||
{
|
||||
if(trim(render_name) == "" || trim(render_name) == "render")
|
||||
*error_out = "no RENDER() entry point";
|
||||
else
|
||||
*error_out = "no RENDER:" + render_name + "() entry point";
|
||||
}
|
||||
*error_out = compiler_missing_request_handler_message(UnitCallMacroKind::render, render_name);
|
||||
return(false);
|
||||
}
|
||||
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
f64 render_start = time_precise();
|
||||
bool count_request = compiler_is_request_entry_unit(context, su);
|
||||
compiler_begin_render_result(su, count_request);
|
||||
try
|
||||
{
|
||||
handler(*context);
|
||||
compiler_record_render_result(su, time_precise() - render_start, true);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
compiler_record_render_result(
|
||||
su,
|
||||
time_precise() - render_start,
|
||||
false,
|
||||
"uncaught exception during render"
|
||||
);
|
||||
throw;
|
||||
}
|
||||
compiler_execute_request_handler(
|
||||
context,
|
||||
su,
|
||||
handler,
|
||||
compiler_is_request_entry_unit(context, su),
|
||||
"uncaught exception during render"
|
||||
);
|
||||
return(true);
|
||||
}
|
||||
|
||||
@@ -1349,45 +1577,24 @@ bool compiler_invoke_component(Request* context, String file_name, String render
|
||||
if(!su)
|
||||
return(false);
|
||||
|
||||
if(!su->on_setup)
|
||||
{
|
||||
if(error_out)
|
||||
*error_out = "internal error: " + String(UCE_SETUP_SYMBOL) + "() not defined in " + file_name;
|
||||
if(!compiler_prepare_request_handler(context, su, error_out, true))
|
||||
return(false);
|
||||
}
|
||||
|
||||
auto handler = get_component_handler(su, render_name);
|
||||
if(!handler)
|
||||
{
|
||||
if(error_out)
|
||||
{
|
||||
if(trim(render_name) == "")
|
||||
*error_out = "no COMPONENT() entry point";
|
||||
else
|
||||
*error_out = "no COMPONENT:" + render_name + "() entry point";
|
||||
}
|
||||
*error_out = compiler_missing_request_handler_message(UnitCallMacroKind::component, render_name);
|
||||
return(false);
|
||||
}
|
||||
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
f64 render_start = time_precise();
|
||||
compiler_begin_render_result(su, false);
|
||||
try
|
||||
{
|
||||
handler(*context);
|
||||
compiler_record_render_result(su, time_precise() - render_start, true);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
compiler_record_render_result(
|
||||
su,
|
||||
time_precise() - render_start,
|
||||
false,
|
||||
"uncaught exception during component render"
|
||||
);
|
||||
throw;
|
||||
}
|
||||
compiler_execute_request_handler(
|
||||
context,
|
||||
su,
|
||||
handler,
|
||||
false,
|
||||
"uncaught exception during component render"
|
||||
);
|
||||
return(true);
|
||||
}
|
||||
|
||||
@@ -1421,26 +1628,13 @@ void compiler_invoke_websocket(Request* context, String file_name)
|
||||
return;
|
||||
}
|
||||
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
f64 render_start = time_precise();
|
||||
bool count_request = compiler_is_request_entry_unit(context, su);
|
||||
compiler_begin_render_result(su, count_request);
|
||||
try
|
||||
{
|
||||
su->on_websocket(*context);
|
||||
compiler_record_render_result(su, time_precise() - render_start, true);
|
||||
}
|
||||
catch(...)
|
||||
{
|
||||
compiler_record_render_result(
|
||||
su,
|
||||
time_precise() - render_start,
|
||||
false,
|
||||
"uncaught exception during websocket handler"
|
||||
);
|
||||
throw;
|
||||
}
|
||||
compiler_execute_request_handler(
|
||||
context,
|
||||
su,
|
||||
su->on_websocket,
|
||||
compiler_is_request_entry_unit(context, su),
|
||||
"uncaught exception during websocket handler"
|
||||
);
|
||||
}
|
||||
|
||||
void unit_render(String file_name)
|
||||
@@ -1499,14 +1693,11 @@ void component_render(String name, DTree props, Request& context)
|
||||
return;
|
||||
}
|
||||
|
||||
DTree previous_props = context.props;
|
||||
context.props = props;
|
||||
RequestPropsScope props_scope(&context, props);
|
||||
|
||||
String error_message = "";
|
||||
if(!compiler_invoke_component(&context, resolved_name, render_name, &error_message) && error_message != "")
|
||||
print(component_error_banner(error_message));
|
||||
|
||||
context.props = previous_props;
|
||||
}
|
||||
|
||||
String component(String name)
|
||||
@@ -1558,16 +1749,52 @@ DTree* unit_call(String file_name, String function_name, DTree* call_param)
|
||||
}
|
||||
else
|
||||
{
|
||||
auto f = (dtree_call_handler)dlsym(su->so_handle, function_name.c_str());
|
||||
if(!f)
|
||||
auto macro_target = unit_call_macro_target(function_name);
|
||||
if(macro_target.kind != UnitCallMacroKind::none)
|
||||
{
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
RequestPropsScope props_scope(context, (call_param ? *call_param : DTree()));
|
||||
String error_message = "";
|
||||
|
||||
if(macro_target.kind == UnitCallMacroKind::render)
|
||||
{
|
||||
if(!compiler_invoke_render(context, su->file_name, macro_target.handler_name, &error_message) && error_message != "")
|
||||
print("Error: unit_call() ", error_message);
|
||||
}
|
||||
else if(macro_target.kind == UnitCallMacroKind::component)
|
||||
{
|
||||
if(!compiler_invoke_component(context, su->file_name, macro_target.handler_name, &error_message) && error_message != "")
|
||||
print("Error: unit_call() ", error_message);
|
||||
}
|
||||
else
|
||||
{
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
request_ref_handler handler = 0;
|
||||
|
||||
if(macro_target.kind == UnitCallMacroKind::once)
|
||||
handler = su->on_once;
|
||||
else if(macro_target.kind == UnitCallMacroKind::init)
|
||||
handler = su->on_init;
|
||||
|
||||
if(!handler)
|
||||
print("Error: unit_call() ", compiler_missing_request_handler_message(macro_target.kind, macro_target.handler_name));
|
||||
else
|
||||
handler(*context);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
result = f(call_param);
|
||||
auto f = (dtree_call_handler)dlsym(su->so_handle, function_name.c_str());
|
||||
if(!f)
|
||||
{
|
||||
print("Error: unit_call() function '", function_name, "' not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
UnitInvocationScope invoke_scope(context, su);
|
||||
su->on_setup(context);
|
||||
result = f(call_param);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
|
||||
#define RENDER(X) extern "C" void __uce_render(X)
|
||||
#define COMPONENT(X) extern "C" void __uce_component(X)
|
||||
#define ONCE(X) extern "C" void __uce_once(X)
|
||||
#define INIT(X) extern "C" void __uce_init(X)
|
||||
#define WS(X) extern "C" void __uce_websocket(X)
|
||||
#define EXPORT extern "C"
|
||||
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
#include "functionlib.h"
|
||||
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
#include <stdexcept>
|
||||
|
||||
String var_dump(StringMap map, String prefix, String postfix)
|
||||
{
|
||||
String result = "";
|
||||
@@ -151,6 +155,347 @@ String replace(String s, String search, String replace_with)
|
||||
return(result);
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
String regex_flags_label(String flags)
|
||||
{
|
||||
return(flags == "" ? "default" : flags);
|
||||
}
|
||||
|
||||
void regex_throw(String function_name, String message)
|
||||
{
|
||||
throw std::runtime_error(function_name + "(): " + message);
|
||||
}
|
||||
|
||||
String regex_pcre2_error(int error_code)
|
||||
{
|
||||
PCRE2_UCHAR buffer[256];
|
||||
pcre2_get_error_message(error_code, buffer, sizeof(buffer));
|
||||
return(String(reinterpret_cast<char*>(buffer)));
|
||||
}
|
||||
|
||||
uint32_t regex_compile_options(String flags, String function_name)
|
||||
{
|
||||
uint32_t options = PCRE2_UTF | PCRE2_UCP;
|
||||
for(char flag : flags)
|
||||
{
|
||||
switch(flag)
|
||||
{
|
||||
case('i'):
|
||||
options |= PCRE2_CASELESS;
|
||||
break;
|
||||
case('m'):
|
||||
options |= PCRE2_MULTILINE;
|
||||
break;
|
||||
case('s'):
|
||||
options |= PCRE2_DOTALL;
|
||||
break;
|
||||
case('x'):
|
||||
options |= PCRE2_EXTENDED;
|
||||
break;
|
||||
case('u'):
|
||||
options |= PCRE2_UTF | PCRE2_UCP;
|
||||
break;
|
||||
case('a'):
|
||||
options &= ~PCRE2_UTF;
|
||||
options &= ~PCRE2_UCP;
|
||||
break;
|
||||
default:
|
||||
regex_throw(function_name, "unknown regex flag '" + String(1, flag) + "'");
|
||||
}
|
||||
}
|
||||
return(options);
|
||||
}
|
||||
|
||||
struct RegexCode {
|
||||
pcre2_code* code = 0;
|
||||
|
||||
RegexCode(String pattern, String flags, String function_name)
|
||||
{
|
||||
int error_code = 0;
|
||||
PCRE2_SIZE error_offset = 0;
|
||||
code = pcre2_compile(
|
||||
reinterpret_cast<PCRE2_SPTR>(pattern.c_str()),
|
||||
pattern.length(),
|
||||
regex_compile_options(flags, function_name),
|
||||
&error_code,
|
||||
&error_offset,
|
||||
0
|
||||
);
|
||||
if(!code)
|
||||
regex_throw(function_name, "could not compile pattern at offset " + std::to_string((u64)error_offset) + ": " + regex_pcre2_error(error_code));
|
||||
|
||||
pcre2_jit_compile(code, PCRE2_JIT_COMPLETE);
|
||||
}
|
||||
|
||||
~RegexCode()
|
||||
{
|
||||
if(code)
|
||||
pcre2_code_free(code);
|
||||
}
|
||||
};
|
||||
|
||||
struct RegexMatchData {
|
||||
pcre2_match_data* data = 0;
|
||||
|
||||
RegexMatchData(pcre2_code* code)
|
||||
{
|
||||
data = pcre2_match_data_create_from_pattern(code, 0);
|
||||
if(!data)
|
||||
regex_throw("regex", "could not allocate match data");
|
||||
}
|
||||
|
||||
~RegexMatchData()
|
||||
{
|
||||
if(data)
|
||||
pcre2_match_data_free(data);
|
||||
}
|
||||
};
|
||||
|
||||
String regex_subject_slice(String subject, PCRE2_SIZE start, PCRE2_SIZE end)
|
||||
{
|
||||
if(start == PCRE2_UNSET || end == PCRE2_UNSET || end < start || start > subject.length())
|
||||
return("");
|
||||
if(end > subject.length())
|
||||
end = subject.length();
|
||||
return(subject.substr(start, end - start));
|
||||
}
|
||||
|
||||
size_t regex_next_utf8_offset(String subject, size_t offset)
|
||||
{
|
||||
if(offset >= subject.length())
|
||||
return(subject.length() + 1);
|
||||
|
||||
unsigned char c = (unsigned char)subject[offset];
|
||||
size_t step = 1;
|
||||
if((c & 0x80) == 0)
|
||||
step = 1;
|
||||
else if((c & 0xE0) == 0xC0)
|
||||
step = 2;
|
||||
else if((c & 0xF0) == 0xE0)
|
||||
step = 3;
|
||||
else if((c & 0xF8) == 0xF0)
|
||||
step = 4;
|
||||
|
||||
if(offset + step > subject.length())
|
||||
step = 1;
|
||||
return(offset + step);
|
||||
}
|
||||
|
||||
void regex_add_named_captures(DTree& result, pcre2_code* code, String subject, PCRE2_SIZE* ovector, int rc)
|
||||
{
|
||||
uint32_t name_count = 0;
|
||||
uint32_t entry_size = 0;
|
||||
PCRE2_SPTR name_table = 0;
|
||||
|
||||
pcre2_pattern_info(code, PCRE2_INFO_NAMECOUNT, &name_count);
|
||||
if(name_count == 0)
|
||||
return;
|
||||
|
||||
pcre2_pattern_info(code, PCRE2_INFO_NAMEENTRYSIZE, &entry_size);
|
||||
pcre2_pattern_info(code, PCRE2_INFO_NAMETABLE, &name_table);
|
||||
|
||||
for(uint32_t i = 0; i < name_count; i += 1)
|
||||
{
|
||||
PCRE2_SPTR entry = name_table + (i * entry_size);
|
||||
uint32_t group_index = (entry[0] << 8) | entry[1];
|
||||
String name(reinterpret_cast<const char*>(entry + 2));
|
||||
if(group_index >= (uint32_t)rc)
|
||||
continue;
|
||||
|
||||
PCRE2_SIZE start = ovector[group_index * 2];
|
||||
PCRE2_SIZE end = ovector[group_index * 2 + 1];
|
||||
if(start == PCRE2_UNSET || end == PCRE2_UNSET)
|
||||
continue;
|
||||
|
||||
result["named"][name] = regex_subject_slice(subject, start, end);
|
||||
result["named_offsets"][name]["index"] = (f64)group_index;
|
||||
result["named_offsets"][name]["start"] = (f64)start;
|
||||
result["named_offsets"][name]["end"] = (f64)end;
|
||||
}
|
||||
}
|
||||
|
||||
DTree regex_build_match_tree(String pattern, String flags, String subject, pcre2_code* code, pcre2_match_data* match_data, int rc)
|
||||
{
|
||||
DTree result;
|
||||
result["matched"].set_bool(rc >= 0);
|
||||
result["pattern"] = pattern;
|
||||
result["flags"] = regex_flags_label(flags);
|
||||
|
||||
if(rc < 0)
|
||||
return(result);
|
||||
|
||||
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data);
|
||||
result["start"] = (f64)ovector[0];
|
||||
result["end"] = (f64)ovector[1];
|
||||
result["match"] = regex_subject_slice(subject, ovector[0], ovector[1]);
|
||||
|
||||
for(int i = 0; i < rc; i += 1)
|
||||
{
|
||||
DTree capture;
|
||||
PCRE2_SIZE start = ovector[i * 2];
|
||||
PCRE2_SIZE end = ovector[i * 2 + 1];
|
||||
capture["index"] = (f64)i;
|
||||
capture["matched"].set_bool(start != PCRE2_UNSET && end != PCRE2_UNSET);
|
||||
if(start != PCRE2_UNSET && end != PCRE2_UNSET)
|
||||
{
|
||||
capture["start"] = (f64)start;
|
||||
capture["end"] = (f64)end;
|
||||
capture["text"] = regex_subject_slice(subject, start, end);
|
||||
}
|
||||
result["captures"].push(capture);
|
||||
}
|
||||
|
||||
regex_add_named_captures(result, code, subject, ovector, rc);
|
||||
return(result);
|
||||
}
|
||||
|
||||
int regex_match_at(RegexCode& regex, RegexMatchData& match_data, String subject, size_t offset, uint32_t options, String function_name)
|
||||
{
|
||||
int rc = pcre2_match(
|
||||
regex.code,
|
||||
reinterpret_cast<PCRE2_SPTR>(subject.c_str()),
|
||||
subject.length(),
|
||||
offset,
|
||||
options,
|
||||
match_data.data,
|
||||
0
|
||||
);
|
||||
|
||||
if(rc == PCRE2_ERROR_NOMATCH)
|
||||
return(rc);
|
||||
if(rc < 0)
|
||||
regex_throw(function_name, "match failed: " + regex_pcre2_error(rc));
|
||||
return(rc);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
bool regex_match(String pattern, String subject, String flags)
|
||||
{
|
||||
RegexCode regex(pattern, flags, "regex_match");
|
||||
RegexMatchData match_data(regex.code);
|
||||
int rc = regex_match_at(regex, match_data, subject, 0, PCRE2_ANCHORED | PCRE2_ENDANCHORED, "regex_match");
|
||||
return(rc >= 0);
|
||||
}
|
||||
|
||||
DTree regex_search(String pattern, String subject, String flags)
|
||||
{
|
||||
RegexCode regex(pattern, flags, "regex_search");
|
||||
RegexMatchData match_data(regex.code);
|
||||
int rc = regex_match_at(regex, match_data, subject, 0, 0, "regex_search");
|
||||
return(regex_build_match_tree(pattern, flags, subject, regex.code, match_data.data, rc));
|
||||
}
|
||||
|
||||
DTree regex_search_all(String pattern, String subject, String flags)
|
||||
{
|
||||
RegexCode regex(pattern, flags, "regex_search_all");
|
||||
RegexMatchData match_data(regex.code);
|
||||
DTree result;
|
||||
result["matched"].set_bool(false);
|
||||
result["pattern"] = pattern;
|
||||
result["flags"] = regex_flags_label(flags);
|
||||
|
||||
size_t offset = 0;
|
||||
while(offset <= subject.length())
|
||||
{
|
||||
int rc = regex_match_at(regex, match_data, subject, offset, 0, "regex_search_all");
|
||||
if(rc == PCRE2_ERROR_NOMATCH)
|
||||
break;
|
||||
|
||||
DTree match = regex_build_match_tree(pattern, flags, subject, regex.code, match_data.data, rc);
|
||||
result["matches"].push(match);
|
||||
result["matched"].set_bool(true);
|
||||
|
||||
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data.data);
|
||||
size_t start = ovector[0];
|
||||
size_t end = ovector[1];
|
||||
if(end > offset)
|
||||
offset = end;
|
||||
else
|
||||
offset = regex_next_utf8_offset(subject, offset);
|
||||
}
|
||||
|
||||
result["count"] = (f64)result["matches"].deref()._map.size();
|
||||
return(result);
|
||||
}
|
||||
|
||||
String regex_replace(String pattern, String replacement, String subject, String flags)
|
||||
{
|
||||
RegexCode regex(pattern, flags, "regex_replace");
|
||||
PCRE2_SIZE output_length = 0;
|
||||
uint32_t options = PCRE2_SUBSTITUTE_GLOBAL | PCRE2_SUBSTITUTE_OVERFLOW_LENGTH;
|
||||
|
||||
int rc = pcre2_substitute(
|
||||
regex.code,
|
||||
reinterpret_cast<PCRE2_SPTR>(subject.c_str()),
|
||||
subject.length(),
|
||||
0,
|
||||
options,
|
||||
0,
|
||||
0,
|
||||
reinterpret_cast<PCRE2_SPTR>(replacement.c_str()),
|
||||
replacement.length(),
|
||||
0,
|
||||
&output_length
|
||||
);
|
||||
|
||||
if(rc != PCRE2_ERROR_NOMEMORY && rc < 0)
|
||||
regex_throw("regex_replace", "substitution failed: " + regex_pcre2_error(rc));
|
||||
|
||||
String output;
|
||||
output.resize(output_length);
|
||||
rc = pcre2_substitute(
|
||||
regex.code,
|
||||
reinterpret_cast<PCRE2_SPTR>(subject.c_str()),
|
||||
subject.length(),
|
||||
0,
|
||||
PCRE2_SUBSTITUTE_GLOBAL,
|
||||
0,
|
||||
0,
|
||||
reinterpret_cast<PCRE2_SPTR>(replacement.c_str()),
|
||||
replacement.length(),
|
||||
reinterpret_cast<PCRE2_UCHAR*>(&output[0]),
|
||||
&output_length
|
||||
);
|
||||
|
||||
if(rc < 0)
|
||||
regex_throw("regex_replace", "substitution failed: " + regex_pcre2_error(rc));
|
||||
|
||||
output.resize(output_length);
|
||||
return(output);
|
||||
}
|
||||
|
||||
StringList regex_split(String pattern, String subject, String flags)
|
||||
{
|
||||
RegexCode regex(pattern, flags, "regex_split");
|
||||
RegexMatchData match_data(regex.code);
|
||||
StringList result;
|
||||
|
||||
size_t offset = 0;
|
||||
size_t last_end = 0;
|
||||
while(offset <= subject.length())
|
||||
{
|
||||
int rc = regex_match_at(regex, match_data, subject, offset, 0, "regex_split");
|
||||
if(rc == PCRE2_ERROR_NOMATCH)
|
||||
break;
|
||||
|
||||
PCRE2_SIZE* ovector = pcre2_get_ovector_pointer(match_data.data);
|
||||
size_t start = ovector[0];
|
||||
size_t end = ovector[1];
|
||||
result.push_back(subject.substr(last_end, start - last_end));
|
||||
last_end = end;
|
||||
|
||||
if(end > offset)
|
||||
offset = end;
|
||||
else
|
||||
offset = regex_next_utf8_offset(subject, offset);
|
||||
}
|
||||
|
||||
result.push_back(subject.substr(last_end));
|
||||
return(result);
|
||||
}
|
||||
|
||||
String trim(String raw)
|
||||
{
|
||||
s64 len = raw.length();
|
||||
|
||||
@@ -14,6 +14,11 @@ bool str_starts_with(String haystack, String needle);
|
||||
bool str_ends_with(String haystack, String needle);
|
||||
bool contains(String haystack, String needle);
|
||||
String replace(String s, String search, String replace_with);
|
||||
bool regex_match(String pattern, String subject, String flags = "");
|
||||
DTree regex_search(String pattern, String subject, String flags = "");
|
||||
DTree regex_search_all(String pattern, String subject, String flags = "");
|
||||
String regex_replace(String pattern, String replacement, String subject, String flags = "");
|
||||
StringList regex_split(String pattern, String subject, String flags = "");
|
||||
|
||||
String trim(String raw);
|
||||
StringList split_space(String str);
|
||||
|
||||
+5
-1
@@ -7,6 +7,7 @@
|
||||
#include <functional>
|
||||
#include <sstream>
|
||||
#include <atomic>
|
||||
#include <set>
|
||||
|
||||
typedef unsigned char u8;
|
||||
typedef signed char s8;
|
||||
@@ -84,6 +85,8 @@ struct SharedUnit {
|
||||
request_ref_handler on_render = 0;
|
||||
request_ref_handler on_component = 0;
|
||||
request_ref_handler on_websocket = 0;
|
||||
request_ref_handler on_once = 0;
|
||||
request_ref_handler on_init = 0;
|
||||
|
||||
String compiler_messages;
|
||||
String compile_status = "unknown";
|
||||
@@ -156,8 +159,9 @@ struct Request {
|
||||
StringMap cookies;
|
||||
StringMap session;
|
||||
String session_loaded_hash = "";
|
||||
std::set<String> once_units;
|
||||
|
||||
//DTree var;
|
||||
DTree call;
|
||||
DTree cfg;
|
||||
DTree props;
|
||||
DTree connection;
|
||||
|
||||
@@ -702,6 +702,13 @@ String ws_connection_id()
|
||||
return(context->resources.websocket_connection_id);
|
||||
}
|
||||
|
||||
String ws_message()
|
||||
{
|
||||
if(!context)
|
||||
return("");
|
||||
return(context->in);
|
||||
}
|
||||
|
||||
String ws_scope()
|
||||
{
|
||||
return(current_ws_scope());
|
||||
|
||||
Reference in New Issue
Block a user