refactor: gemar grand rework
This commit is contained in:
@@ -1,100 +1,707 @@
|
|||||||
#include <sys/socket.h>
|
/*
|
||||||
#include <netinet/in.h>
|
* hemar.c
|
||||||
#include <arpa/inet.h>
|
* Template parser implementation for Hemar
|
||||||
#include <time.h>
|
*/
|
||||||
#include <unistd.h>
|
#include "hemar.h"
|
||||||
#include "postgres.h"
|
#include "postgres.h"
|
||||||
#include "catalog/pg_type_d.h"
|
|
||||||
#include "fmgr.h"
|
|
||||||
#include "nodes/pg_list.h"
|
|
||||||
#include "parser/parse_func.h"
|
|
||||||
#include "utils/builtins.h"
|
|
||||||
#include "utils/datum.h"
|
|
||||||
#include "utils/json.h"
|
|
||||||
#include "utils/memutils.h"
|
|
||||||
#include "utils/regproc.h"
|
|
||||||
#include <string.h>
|
#include <string.h>
|
||||||
|
#include <ctype.h>
|
||||||
|
|
||||||
#define PRECOMPILED_LOG_LEVEL LOG_LEVEL_EXCEPTION
|
#include "utils/elog.h"
|
||||||
|
#include "utils/memutils.h"
|
||||||
|
#include "mb/pg_wchar.h"
|
||||||
|
|
||||||
#include "hectic.h"
|
/* Helper function to skip whitespace */
|
||||||
|
static const char *
|
||||||
#ifdef PG_MODULE_MAGIC
|
skip_whitespace(const char *s)
|
||||||
PG_MODULE_MAGIC;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
static MemoryContext HemarContext = NULL;
|
|
||||||
|
|
||||||
#define LOG_FILE "/tmp/hemar.log"
|
|
||||||
|
|
||||||
#define INIT \
|
|
||||||
MemoryContext oldctx; \
|
|
||||||
oldctx = MemoryContextSwitchTo(HemarContext); \
|
|
||||||
/* logger_init(); \
|
|
||||||
logger_level(LOG_LEVEL_TRACE); \
|
|
||||||
logger_set_file(LOG_FILE); \
|
|
||||||
logger_set_output_mode(LOG_OUTPUT_BOTH);*/ \
|
|
||||||
Arena arena = arena_init(MEM_MiB);
|
|
||||||
|
|
||||||
|
|
||||||
#define FREE \
|
|
||||||
/*DISPOSABLE_ARENA_FREE*/; \
|
|
||||||
/*arena_free(&arena);*/ \
|
|
||||||
/*logger_free();*/ \
|
|
||||||
arena_reset(&arena); \
|
|
||||||
MemoryContextSwitchTo(oldctx); \
|
|
||||||
MemoryContextReset(HemarContext);
|
|
||||||
|
|
||||||
void noop_free(void* ptr) {
|
|
||||||
(void)ptr; // suppress unused warning
|
|
||||||
}
|
|
||||||
|
|
||||||
void _PG_init(void)
|
|
||||||
{
|
{
|
||||||
HemarContext = AllocSetContextCreate(TopMemoryContext,
|
while (*s && isspace((unsigned char)*s))
|
||||||
"HemarContext",
|
s++;
|
||||||
ALLOCSET_DEFAULT_SIZES);
|
return s;
|
||||||
|
|
||||||
MemoryAllocator allocators = {
|
|
||||||
.malloc = palloc,
|
|
||||||
.free = noop_free
|
|
||||||
};
|
|
||||||
|
|
||||||
set_memory_allocator(allocators);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
PG_FUNCTION_INFO_V1(pg_template_parse);
|
/* Initialize template value based on type */
|
||||||
|
static TemplateValue
|
||||||
|
init_template_value(TemplateNodeType type)
|
||||||
|
{
|
||||||
|
TemplateValue value;
|
||||||
|
|
||||||
Datum pg_template_parse(PG_FUNCTION_ARGS) {
|
switch (type)
|
||||||
INIT;
|
{
|
||||||
|
case TEMPLATE_NODE_TEXT:
|
||||||
text *template_text = PG_GETARG_TEXT_PP(0);
|
value.text.content = NULL;
|
||||||
const char *template_str = text_to_cstring(template_text);
|
break;
|
||||||
raise_notice("template_str: %s", template_str);
|
case TEMPLATE_NODE_INTERPOLATE:
|
||||||
|
value.interpolate.key = NULL;
|
||||||
TemplateResult template_result;
|
break;
|
||||||
TemplateConfig config = template_default_config(&arena);
|
case TEMPLATE_NODE_SECTION:
|
||||||
|
value.section.iterator = NULL;
|
||||||
raise_info("start parsing....");
|
value.section.collection = NULL;
|
||||||
template_result = template_parse(&arena, &template_str, &config);
|
value.section.body = NULL;
|
||||||
raise_info("parsing finished....");
|
break;
|
||||||
|
case TEMPLATE_NODE_EXECUTE:
|
||||||
if (IS_RESULT_ERROR(template_result)) {
|
value.execute.code = NULL;
|
||||||
FREE;
|
break;
|
||||||
ereport(ERROR, (errcode(ERRCODE_INVALID_PARAMETER_VALUE),
|
case TEMPLATE_NODE_INCLUDE:
|
||||||
errmsg("Failed to parse template: %s",
|
value.include.key = NULL;
|
||||||
RESULT_ERROR_MESSAGE(template_result))));
|
break;
|
||||||
|
default:
|
||||||
|
elog(ERROR, "Unknown template node type: %d", type);
|
||||||
|
/* This won't be reached due to elog(ERROR) */
|
||||||
|
memset(&value, 0, sizeof(value));
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
const char *json_str = TEMPLATE_NODE_TO_JSON_STR(&arena, &(RESULT_SOME_VALUE(template_result)));
|
return value;
|
||||||
Json *json = json_parse(&arena, &json_str); \
|
}
|
||||||
|
|
||||||
char *result_str = JSON_TO_STR(&arena, json);
|
/* Initialize a new template node */
|
||||||
|
static TemplateNode *
|
||||||
raise_notice("%s", result_str);
|
init_template_node(MemoryContext context, TemplateNodeType type)
|
||||||
text *result = cstring_to_text(result_str);
|
{
|
||||||
|
TemplateNode *node;
|
||||||
FREE;
|
|
||||||
|
node = (TemplateNode *) MemoryContextAlloc(context, sizeof(TemplateNode));
|
||||||
PG_RETURN_TEXT_P(result);
|
node->next = NULL;
|
||||||
|
node->type = type;
|
||||||
|
node->value = (TemplateValue *) MemoryContextAlloc(context, sizeof(TemplateValue));
|
||||||
|
*node->value = init_template_value(type);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Error code to string conversion */
|
||||||
|
const char *
|
||||||
|
template_error_to_string(TemplateErrorCode code)
|
||||||
|
{
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case TEMPLATE_ERROR_NONE:
|
||||||
|
return "No error";
|
||||||
|
case TEMPLATE_ERROR_UNKNOWN_TAG:
|
||||||
|
return "Unknown tag";
|
||||||
|
case TEMPLATE_ERROR_NESTED_INTERPOLATION:
|
||||||
|
return "Nested interpolation";
|
||||||
|
case TEMPLATE_ERROR_NESTED_SECTION_ITERATOR:
|
||||||
|
return "Nested section iterator";
|
||||||
|
case TEMPLATE_ERROR_UNEXPECTED_SECTION_END:
|
||||||
|
return "Unexpected section end";
|
||||||
|
case TEMPLATE_ERROR_NESTED_INCLUDE:
|
||||||
|
return "Nested include";
|
||||||
|
case TEMPLATE_ERROR_NESTED_EXECUTE:
|
||||||
|
return "Nested execute";
|
||||||
|
case TEMPLATE_ERROR_INVALID_CONFIG:
|
||||||
|
return "Invalid config";
|
||||||
|
case TEMPLATE_ERROR_OUT_OF_MEMORY:
|
||||||
|
return "Out of memory";
|
||||||
|
case TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END:
|
||||||
|
return "Unexpected include end";
|
||||||
|
case TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END:
|
||||||
|
return "Unexpected execute end";
|
||||||
|
default:
|
||||||
|
return "Unknown error";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Default template configuration */
|
||||||
|
TemplateConfig
|
||||||
|
template_default_config(MemoryContext context)
|
||||||
|
{
|
||||||
|
TemplateConfig config;
|
||||||
|
|
||||||
|
config.Syntax.Braces.open = "{%";
|
||||||
|
config.Syntax.Braces.close = "%}";
|
||||||
|
config.Syntax.Section.control = "for ";
|
||||||
|
config.Syntax.Section.source = "in ";
|
||||||
|
config.Syntax.Section.begin = "do ";
|
||||||
|
config.Syntax.Interpolate.invoke = "";
|
||||||
|
config.Syntax.Include.invoke = "include ";
|
||||||
|
config.Syntax.Execute.invoke = "exec ";
|
||||||
|
config.Syntax.nesting = "->";
|
||||||
|
|
||||||
|
return config;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Validate template configuration */
|
||||||
|
bool
|
||||||
|
template_validate_config(const TemplateConfig *config, TemplateErrorCode *error_code)
|
||||||
|
{
|
||||||
|
if (!config)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check open brace */
|
||||||
|
if (!config->Syntax.Braces.open || strlen(config->Syntax.Braces.open) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check close brace */
|
||||||
|
if (!config->Syntax.Braces.close || strlen(config->Syntax.Braces.close) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check section control */
|
||||||
|
if (!config->Syntax.Section.control || strlen(config->Syntax.Section.control) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check section source */
|
||||||
|
if (!config->Syntax.Section.source || strlen(config->Syntax.Section.source) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check section begin */
|
||||||
|
if (!config->Syntax.Section.begin || strlen(config->Syntax.Section.begin) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check interpolate invoke */
|
||||||
|
if (!config->Syntax.Interpolate.invoke || strlen(config->Syntax.Interpolate.invoke) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check include invoke */
|
||||||
|
if (!config->Syntax.Include.invoke || strlen(config->Syntax.Include.invoke) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check execute invoke */
|
||||||
|
if (!config->Syntax.Execute.invoke || strlen(config->Syntax.Execute.invoke) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Check nesting */
|
||||||
|
if (!config->Syntax.nesting || strlen(config->Syntax.nesting) > TEMPLATE_MAX_PREFIX_LEN)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_INVALID_CONFIG;
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_NONE;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse interpolation tag */
|
||||||
|
static TemplateNode *
|
||||||
|
template_parse_interpolation(MemoryContext context, const char **s_ptr,
|
||||||
|
const TemplateConfig *config, TemplateErrorCode *error_code)
|
||||||
|
{
|
||||||
|
const char **s = s_ptr;
|
||||||
|
const char *key_start;
|
||||||
|
size_t key_len;
|
||||||
|
TemplateNode *node;
|
||||||
|
|
||||||
|
node = init_template_node(context, TEMPLATE_NODE_INTERPOLATE);
|
||||||
|
|
||||||
|
/* Skip to the content */
|
||||||
|
*s += strlen(config->Syntax.Braces.open);
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
*s += strlen(config->Syntax.Interpolate.invoke);
|
||||||
|
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
key_start = *s;
|
||||||
|
|
||||||
|
while (**s != '\0')
|
||||||
|
{
|
||||||
|
if (isspace((unsigned char)**s) ||
|
||||||
|
strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_NESTED_INTERPOLATION;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
key_len = *s - key_start;
|
||||||
|
node->value->interpolate.key = MemoryContextStrdup(context, pnstrdup(key_start, key_len));
|
||||||
|
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
|
||||||
|
/* Check for closing brace */
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*s_ptr = *s + strlen(config->Syntax.Braces.close);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse section tag */
|
||||||
|
static TemplateNode *
|
||||||
|
template_parse_section(MemoryContext context, const char **s_ptr,
|
||||||
|
const TemplateConfig *config, TemplateErrorCode *error_code)
|
||||||
|
{
|
||||||
|
const char **s = s_ptr;
|
||||||
|
const char *iterator_start, *collection_start;
|
||||||
|
size_t iterator_len, collection_len;
|
||||||
|
TemplateNode *node, *body_node;
|
||||||
|
|
||||||
|
node = init_template_node(context, TEMPLATE_NODE_SECTION);
|
||||||
|
|
||||||
|
/* Skip to the content */
|
||||||
|
*s += strlen(config->Syntax.Braces.open);
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
*s += strlen(config->Syntax.Section.control);
|
||||||
|
|
||||||
|
/* Find the iterator name */
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
iterator_start = *s;
|
||||||
|
|
||||||
|
while (**s != '\0')
|
||||||
|
{
|
||||||
|
if (isspace((unsigned char)**s) ||
|
||||||
|
strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
iterator_len = *s - iterator_start;
|
||||||
|
node->value->section.iterator = MemoryContextStrdup(context, pnstrdup(iterator_start, iterator_len));
|
||||||
|
|
||||||
|
/* Find the collection name */
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) != 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*s += strlen(config->Syntax.Section.source);
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
collection_start = *s;
|
||||||
|
|
||||||
|
while (**s != '\0')
|
||||||
|
{
|
||||||
|
if (isspace((unsigned char)**s) ||
|
||||||
|
strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_NESTED_SECTION_ITERATOR;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
collection_len = *s - collection_start;
|
||||||
|
node->value->section.collection = MemoryContextStrdup(context, pnstrdup(collection_start, collection_len));
|
||||||
|
|
||||||
|
/* Check for 'do' keyword */
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
if (strncmp(*s, config->Syntax.Section.begin, strlen(config->Syntax.Section.begin)) != 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*s += strlen(config->Syntax.Section.begin);
|
||||||
|
|
||||||
|
/* Parse the body */
|
||||||
|
body_node = template_parse(context, s, config, true, error_code);
|
||||||
|
if (!body_node)
|
||||||
|
{
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
node->value->section.body = body_node;
|
||||||
|
|
||||||
|
/* Skip to the end of the section */
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
|
||||||
|
/* Check for closing brace */
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_SECTION_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*s_ptr = *s + strlen(config->Syntax.Braces.close);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse include tag */
|
||||||
|
static TemplateNode *
|
||||||
|
template_parse_include(MemoryContext context, const char **s_ptr,
|
||||||
|
const TemplateConfig *config, TemplateErrorCode *error_code)
|
||||||
|
{
|
||||||
|
const char **s = s_ptr;
|
||||||
|
const char *include_start;
|
||||||
|
size_t include_len;
|
||||||
|
TemplateNode *node;
|
||||||
|
|
||||||
|
node = init_template_node(context, TEMPLATE_NODE_INCLUDE);
|
||||||
|
|
||||||
|
/* Skip to the content */
|
||||||
|
*s += strlen(config->Syntax.Braces.open);
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
*s += strlen(config->Syntax.Include.invoke);
|
||||||
|
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
include_start = *s;
|
||||||
|
|
||||||
|
while (**s != '\0')
|
||||||
|
{
|
||||||
|
if (isspace((unsigned char)**s) ||
|
||||||
|
strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_NESTED_INCLUDE;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
include_len = *s - include_start;
|
||||||
|
node->value->include.key = MemoryContextStrdup(context, pnstrdup(include_start, include_len));
|
||||||
|
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
|
||||||
|
/* Check for closing brace */
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*s_ptr = *s + strlen(config->Syntax.Braces.close);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse execute tag */
|
||||||
|
static TemplateNode *
|
||||||
|
template_parse_execute(MemoryContext context, const char **s_ptr,
|
||||||
|
const TemplateConfig *config, TemplateErrorCode *error_code)
|
||||||
|
{
|
||||||
|
const char **s = s_ptr;
|
||||||
|
const char *code_start;
|
||||||
|
size_t code_len;
|
||||||
|
TemplateNode *node;
|
||||||
|
|
||||||
|
node = init_template_node(context, TEMPLATE_NODE_EXECUTE);
|
||||||
|
|
||||||
|
/* Skip to the content */
|
||||||
|
*s += strlen(config->Syntax.Braces.open);
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
*s += strlen(config->Syntax.Execute.invoke);
|
||||||
|
|
||||||
|
*s = skip_whitespace(*s);
|
||||||
|
code_start = *s;
|
||||||
|
|
||||||
|
while (**s != '\0')
|
||||||
|
{
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_NESTED_EXECUTE;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
|
||||||
|
code_len = *s - code_start;
|
||||||
|
node->value->execute.code = MemoryContextStrdup(context, pnstrdup(code_start, code_len));
|
||||||
|
|
||||||
|
/* Check for closing brace */
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) != 0)
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END;
|
||||||
|
template_free_node(node);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
*s_ptr = *s + strlen(config->Syntax.Braces.close);
|
||||||
|
|
||||||
|
return node;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Main template parser function */
|
||||||
|
TemplateNode *
|
||||||
|
template_parse(MemoryContext context, const char **s, const TemplateConfig *config,
|
||||||
|
bool inner_parse, TemplateErrorCode *error_code)
|
||||||
|
{
|
||||||
|
const char *start;
|
||||||
|
TemplateNode *root, *current, *tag_node;
|
||||||
|
bool current_node_filled = false;
|
||||||
|
|
||||||
|
if (!template_validate_config(config, error_code))
|
||||||
|
{
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
start = *s;
|
||||||
|
root = init_template_node(context, TEMPLATE_NODE_TEXT);
|
||||||
|
current = root;
|
||||||
|
|
||||||
|
while (*s && **s != '\0')
|
||||||
|
{
|
||||||
|
/* Check for closing brace in inner parse */
|
||||||
|
if (inner_parse && strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
|
||||||
|
{
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
|
||||||
|
{
|
||||||
|
/* Handle text before tag */
|
||||||
|
if (start != *s)
|
||||||
|
{
|
||||||
|
if (current_node_filled)
|
||||||
|
{
|
||||||
|
TemplateNode *new_node = init_template_node(context, TEMPLATE_NODE_TEXT);
|
||||||
|
current->next = new_node;
|
||||||
|
current = new_node;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current->type = TEMPLATE_NODE_TEXT;
|
||||||
|
*current->value = init_template_value(TEMPLATE_NODE_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t text_len = *s - start;
|
||||||
|
current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len));
|
||||||
|
current_node_filled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Parse the tag */
|
||||||
|
tag_node = NULL;
|
||||||
|
const char *tag_prefix = *s + strlen(config->Syntax.Braces.open);
|
||||||
|
tag_prefix = skip_whitespace(tag_prefix);
|
||||||
|
|
||||||
|
/* Determine tag type by prefix */
|
||||||
|
if (strncmp(tag_prefix, config->Syntax.Section.control, strlen(config->Syntax.Section.control)) == 0)
|
||||||
|
{
|
||||||
|
tag_node = template_parse_section(context, s, config, error_code);
|
||||||
|
}
|
||||||
|
else if (strncmp(tag_prefix, config->Syntax.Include.invoke, strlen(config->Syntax.Include.invoke)) == 0)
|
||||||
|
{
|
||||||
|
tag_node = template_parse_include(context, s, config, error_code);
|
||||||
|
}
|
||||||
|
else if (strncmp(tag_prefix, config->Syntax.Execute.invoke, strlen(config->Syntax.Execute.invoke)) == 0)
|
||||||
|
{
|
||||||
|
tag_node = template_parse_execute(context, s, config, error_code);
|
||||||
|
}
|
||||||
|
else if (strncmp(tag_prefix, config->Syntax.Interpolate.invoke, strlen(config->Syntax.Interpolate.invoke)) == 0)
|
||||||
|
{
|
||||||
|
tag_node = template_parse_interpolation(context, s, config, error_code);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_UNKNOWN_TAG;
|
||||||
|
template_free_node(root);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!tag_node)
|
||||||
|
{
|
||||||
|
template_free_node(root);
|
||||||
|
return NULL;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (current_node_filled)
|
||||||
|
{
|
||||||
|
current->next = tag_node;
|
||||||
|
current = tag_node;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
*current = *tag_node;
|
||||||
|
pfree(tag_node);
|
||||||
|
}
|
||||||
|
|
||||||
|
current_node_filled = true;
|
||||||
|
start = *s;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
(*s)++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Handle remaining text */
|
||||||
|
if (start != *s)
|
||||||
|
{
|
||||||
|
if (current_node_filled)
|
||||||
|
{
|
||||||
|
TemplateNode *new_node = init_template_node(context, TEMPLATE_NODE_TEXT);
|
||||||
|
current->next = new_node;
|
||||||
|
current = new_node;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
current->type = TEMPLATE_NODE_TEXT;
|
||||||
|
*current->value = init_template_value(TEMPLATE_NODE_TEXT);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t text_len = *s - start;
|
||||||
|
current->value->text.content = MemoryContextStrdup(context, pnstrdup(start, text_len));
|
||||||
|
current_node_filled = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* If no nodes were created, ensure we have at least an empty text node */
|
||||||
|
if (!current_node_filled)
|
||||||
|
{
|
||||||
|
current->type = TEMPLATE_NODE_TEXT;
|
||||||
|
*current->value = init_template_value(TEMPLATE_NODE_TEXT);
|
||||||
|
current->value->text.content = MemoryContextStrdup(context, "");
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error_code)
|
||||||
|
*error_code = TEMPLATE_ERROR_NONE;
|
||||||
|
|
||||||
|
return root;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Free a template node and all its children */
|
||||||
|
void
|
||||||
|
template_free_node(TemplateNode *node)
|
||||||
|
{
|
||||||
|
TemplateNode *current, *next;
|
||||||
|
|
||||||
|
if (!node)
|
||||||
|
return;
|
||||||
|
|
||||||
|
current = node;
|
||||||
|
|
||||||
|
while (current)
|
||||||
|
{
|
||||||
|
next = current->next;
|
||||||
|
|
||||||
|
if (current->value)
|
||||||
|
{
|
||||||
|
switch (current->type)
|
||||||
|
{
|
||||||
|
case TEMPLATE_NODE_TEXT:
|
||||||
|
if (current->value->text.content)
|
||||||
|
pfree(current->value->text.content);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TEMPLATE_NODE_INTERPOLATE:
|
||||||
|
if (current->value->interpolate.key)
|
||||||
|
pfree(current->value->interpolate.key);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TEMPLATE_NODE_SECTION:
|
||||||
|
if (current->value->section.iterator)
|
||||||
|
pfree(current->value->section.iterator);
|
||||||
|
if (current->value->section.collection)
|
||||||
|
pfree(current->value->section.collection);
|
||||||
|
if (current->value->section.body)
|
||||||
|
template_free_node(current->value->section.body);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TEMPLATE_NODE_EXECUTE:
|
||||||
|
if (current->value->execute.code)
|
||||||
|
pfree(current->value->execute.code);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case TEMPLATE_NODE_INCLUDE:
|
||||||
|
if (current->value->include.key)
|
||||||
|
pfree(current->value->include.key);
|
||||||
|
break;
|
||||||
|
|
||||||
|
default:
|
||||||
|
/* Should not happen */
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
pfree(current->value);
|
||||||
|
}
|
||||||
|
|
||||||
|
pfree(current);
|
||||||
|
current = next;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
111
package/c/hemar/hemar.h
Executable file
111
package/c/hemar/hemar.h
Executable file
@@ -0,0 +1,111 @@
|
|||||||
|
/*
|
||||||
|
* hemar.h
|
||||||
|
* Template parser for Hemar
|
||||||
|
*/
|
||||||
|
#ifndef HEMAR_TEMPLATE_H
|
||||||
|
#define HEMAR_TEMPLATE_H
|
||||||
|
|
||||||
|
#include "postgres.h"
|
||||||
|
#include "utils/memutils.h"
|
||||||
|
|
||||||
|
/* Maximum length for template syntax elements */
|
||||||
|
#define TEMPLATE_MAX_PREFIX_LEN 32
|
||||||
|
|
||||||
|
/* Template error codes */
|
||||||
|
typedef enum {
|
||||||
|
TEMPLATE_ERROR_NONE = 0,
|
||||||
|
TEMPLATE_ERROR_UNKNOWN_TAG,
|
||||||
|
TEMPLATE_ERROR_NESTED_INTERPOLATION,
|
||||||
|
TEMPLATE_ERROR_NESTED_SECTION_ITERATOR,
|
||||||
|
TEMPLATE_ERROR_UNEXPECTED_SECTION_END,
|
||||||
|
TEMPLATE_ERROR_NESTED_INCLUDE,
|
||||||
|
TEMPLATE_ERROR_NESTED_EXECUTE,
|
||||||
|
TEMPLATE_ERROR_INVALID_CONFIG,
|
||||||
|
TEMPLATE_ERROR_OUT_OF_MEMORY,
|
||||||
|
TEMPLATE_ERROR_UNEXPECTED_INCLUDE_END,
|
||||||
|
TEMPLATE_ERROR_UNEXPECTED_EXECUTE_END
|
||||||
|
} TemplateErrorCode;
|
||||||
|
|
||||||
|
/* Template node types */
|
||||||
|
typedef enum {
|
||||||
|
TEMPLATE_NODE_TEXT,
|
||||||
|
TEMPLATE_NODE_INTERPOLATE,
|
||||||
|
TEMPLATE_NODE_SECTION,
|
||||||
|
TEMPLATE_NODE_EXECUTE,
|
||||||
|
TEMPLATE_NODE_INCLUDE
|
||||||
|
} TemplateNodeType;
|
||||||
|
|
||||||
|
/* Template configuration structure */
|
||||||
|
typedef struct {
|
||||||
|
struct {
|
||||||
|
struct {
|
||||||
|
const char *open; /* Default: "{%" */
|
||||||
|
const char *close; /* Default: "%}" */
|
||||||
|
} Braces;
|
||||||
|
struct {
|
||||||
|
const char *control; /* default: "for " */
|
||||||
|
const char *source; /* default: "in " */
|
||||||
|
const char *begin; /* default: "do " */
|
||||||
|
} Section;
|
||||||
|
struct {
|
||||||
|
const char *invoke; /* default: "" */
|
||||||
|
} Interpolate;
|
||||||
|
struct {
|
||||||
|
const char *invoke; /* default: "include " */
|
||||||
|
} Include;
|
||||||
|
struct {
|
||||||
|
const char *invoke; /* default: "exec " */
|
||||||
|
} Execute;
|
||||||
|
const char *nesting; /* default: "->" */
|
||||||
|
} Syntax;
|
||||||
|
} TemplateConfig;
|
||||||
|
|
||||||
|
/* Forward declaration */
|
||||||
|
typedef struct TemplateNode TemplateNode;
|
||||||
|
|
||||||
|
/* Template value structures */
|
||||||
|
typedef struct {
|
||||||
|
char *iterator;
|
||||||
|
char *collection;
|
||||||
|
TemplateNode *body;
|
||||||
|
} TemplateSectionValue;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *key;
|
||||||
|
} TemplateInterpolateValue;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *code;
|
||||||
|
} TemplateExecuteValue;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *key;
|
||||||
|
} TemplateIncludeValue;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
char *content;
|
||||||
|
} TemplateTextValue;
|
||||||
|
|
||||||
|
typedef union {
|
||||||
|
TemplateSectionValue section;
|
||||||
|
TemplateInterpolateValue interpolate;
|
||||||
|
TemplateExecuteValue execute;
|
||||||
|
TemplateIncludeValue include;
|
||||||
|
TemplateTextValue text;
|
||||||
|
} TemplateValue;
|
||||||
|
|
||||||
|
/* Template node structure */
|
||||||
|
struct TemplateNode {
|
||||||
|
TemplateNodeType type;
|
||||||
|
TemplateValue *value;
|
||||||
|
TemplateNode *next;
|
||||||
|
};
|
||||||
|
|
||||||
|
/* Function declarations */
|
||||||
|
TemplateConfig template_default_config(MemoryContext context);
|
||||||
|
bool template_validate_config(const TemplateConfig *config, TemplateErrorCode *error_code);
|
||||||
|
TemplateNode *template_parse(MemoryContext context, const char **s, const TemplateConfig *config, bool inner_parse, TemplateErrorCode *error_code);
|
||||||
|
void template_free_node(TemplateNode *node);
|
||||||
|
const char *template_error_to_string(TemplateErrorCode code);
|
||||||
|
|
||||||
|
#endif /* HEMAR_TEMPLATE_H */
|
||||||
Reference in New Issue
Block a user