fix: hemar: parser

This commit is contained in:
2025-05-16 00:30:11 +00:00
parent 725f6eea30
commit f0d7ca60fe
2 changed files with 41 additions and 38 deletions

View File

@@ -374,14 +374,15 @@ template_parse_section(MemoryContext context, const char **s_ptr,
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
*s += strlen(config->Syntax.Section.control); *s += strlen(config->Syntax.Section.control);
/* Find the iterator name */ /* Skip whitespace after control keyword */
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
/* Find the iterator name */
iterator_start = *s; iterator_start = *s;
while (**s != '\0') while (**s != '\0')
{ {
if (isspace((unsigned char)**s) || if (isspace((unsigned char)**s))
strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) == 0)
break; break;
if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0) if (strncmp(*s, config->Syntax.Braces.open, strlen(config->Syntax.Braces.open)) == 0)
@@ -392,14 +393,6 @@ template_parse_section(MemoryContext context, const char **s_ptr,
return NULL; return NULL;
} }
if (strncmp(*s, config->Syntax.Braces.close, strlen(config->Syntax.Braces.close)) == 0)
{
if (error_code)
*error_code = TEMPLATE_ERROR_NO_SOURSE_IN_SECTION;
template_free_node(node);
return NULL;
}
(*s)++; (*s)++;
} }
@@ -407,7 +400,7 @@ template_parse_section(MemoryContext context, const char **s_ptr,
node->value->section.iterator = MemoryContextStrdup(context, pnstrdup(iterator_start, iterator_len)); node->value->section.iterator = MemoryContextStrdup(context, pnstrdup(iterator_start, iterator_len));
elog(DEBUG1, "TPS: Parsed section iterator: %s", node->value->section.iterator); elog(DEBUG1, "TPS: Parsed section iterator: %s", node->value->section.iterator);
/* Find the collection name */ /* Find the source keyword "in" */
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
if (strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) != 0) if (strncmp(*s, config->Syntax.Section.source, strlen(config->Syntax.Section.source)) != 0)
@@ -419,7 +412,11 @@ template_parse_section(MemoryContext context, const char **s_ptr,
} }
*s += strlen(config->Syntax.Section.source); *s += strlen(config->Syntax.Section.source);
/* Skip whitespace after source keyword */
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
/* Find the collection name */
collection_start = *s; collection_start = *s;
while (**s != '\0') while (**s != '\0')
@@ -577,7 +574,9 @@ template_parse_include(MemoryContext context, const char **s_ptr,
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
*s += strlen(config->Syntax.Include.invoke); *s += strlen(config->Syntax.Include.invoke);
/* Skip whitespace after include keyword */
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
include_start = *s; include_start = *s;
while (**s != '\0') while (**s != '\0')
@@ -638,7 +637,9 @@ template_parse_execute(MemoryContext context, const char **s_ptr,
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
*s += strlen(config->Syntax.Execute.invoke); *s += strlen(config->Syntax.Execute.invoke);
/* Skip whitespace after execute keyword */
*s = skip_whitespace(*s); *s = skip_whitespace(*s);
code_start = *s; code_start = *s;
/* Track quote state to handle SQL content properly */ /* Track quote state to handle SQL content properly */
@@ -775,13 +776,14 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
typedef struct { typedef struct {
const char *prefix; const char *prefix;
int tag_type; int tag_type;
bool requires_space_after; /* Whether this prefix requires whitespace after it */
} PrefixMatch; } PrefixMatch;
PrefixMatch matches[] = { PrefixMatch matches[] = {
{config->Syntax.Section.control, 1}, {config->Syntax.Section.control, 1, true}, /* "for" needs space after */
{config->Syntax.Include.invoke, 2}, {config->Syntax.Include.invoke, 2, true}, /* "include" needs space after */
{config->Syntax.Execute.invoke, 3}, {config->Syntax.Execute.invoke, 3, true}, /* "exec" needs space after */
{config->Syntax.Interpolate.invoke, 4} {config->Syntax.Interpolate.invoke, 4, false} /* Empty prefix doesn't need space */
}; };
int matched_type = 0; int matched_type = 0;
@@ -790,8 +792,17 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
/* Find longest match (in case when one prefix is part of another) */ /* Find longest match (in case when one prefix is part of another) */
for (int i = 0; i < 4; i++) { for (int i = 0; i < 4; i++) {
if (strncmp(tag_prefix, matches[i].prefix, strlen(matches[i].prefix)) == 0) { if (strncmp(tag_prefix, matches[i].prefix, strlen(matches[i].prefix)) == 0) {
/* >= because one of the prefixes may be empty */ /* Check if the match requires a space after it */
if (strlen(matches[i].prefix) >= max_length) { bool valid_match = true;
if (matches[i].requires_space_after) {
/* If we need space after the prefix, verify it exists */
const char *after_prefix = tag_prefix + strlen(matches[i].prefix);
if (*after_prefix && !isspace((unsigned char)*after_prefix)) {
valid_match = false;
}
}
if (valid_match && strlen(matches[i].prefix) >= max_length) {
max_length = strlen(matches[i].prefix); max_length = strlen(matches[i].prefix);
matched_type = matches[i].tag_type; matched_type = matches[i].tag_type;
} }
@@ -809,7 +820,7 @@ template_parse(MemoryContext context, const char **s, const TemplateConfig *conf
tag_node = template_parse_include(context, s, config, error_code); tag_node = template_parse_include(context, s, config, error_code);
} else if (matched_type == 3) { } else if (matched_type == 3) {
/* Execute tag */ /* Execute tag */
elog(LOG, "TPE: Parsing include tag at position: %.50s", *s); elog(LOG, "TPE: Parsing execute tag at position: %.50s", *s);
tag_node = template_parse_execute(context, s, config, error_code); tag_node = template_parse_execute(context, s, config, error_code);
} else if (matched_type == 4) { } else if (matched_type == 4) {
/* Interpolation tag */ /* Interpolation tag */

View File

@@ -4,6 +4,8 @@
-- Load extension if not already loaded -- Load extension if not already loaded
-- CREATE EXTENSION IF NOT EXISTS hemar; -- CREATE EXTENSION IF NOT EXISTS hemar;
-- SAFETY(yukkop): !!! If you fix identation, you will ruin the tests.
-- Create test function to validate template parsing -- Create test function to validate template parsing
CREATE OR REPLACE FUNCTION test_template_parse(template_text text, expected_structure text) RETURNS boolean AS $$ CREATE OR REPLACE FUNCTION test_template_parse(template_text text, expected_structure text) RETURNS boolean AS $$
DECLARE DECLARE
@@ -165,18 +167,8 @@ TEXT: "</div>"$expected8$
-- Test 9: Execute tag with complex SQL -- Test 9: Execute tag with complex SQL
total_tests := total_tests + 1; total_tests := total_tests + 1;
result := test_template_parse( result := test_template_parse(
$template9${{ exec '{{ exec SELECT 123 AS number; }}',
IF condition THEN 'EXECUTE: "SELECT 123 AS number;"'
RETURN 'value1';
ELSE
RETURN 'value2';
END IF;
}}$template9$,
$expected9$EXECUTE: "IF condition THEN
RETURN 'value1';
ELSE
RETURN 'value2';
END IF;"$expected9$
); );
IF result THEN IF result THEN
passed_tests := passed_tests + 1; passed_tests := passed_tests + 1;
@@ -282,7 +274,6 @@ INTERPOLATE: "var3"$expected11$
{{ end }} {{ end }}
<div>code insertion:</div> <div>code insertion:</div>
// FIXME: IT NEED A SPECE PIZDEZZZZ
{{ exec {{ exec
context + '{"name3": "zalupa"}'; context + '{"name3": "zalupa"}';
@@ -295,7 +286,8 @@ INTERPOLATE: "var3"$expected11$
}} }}
<div id="footer">...</div>$template15$, <div id="footer">...</div>$template15$,
$expected15$TEXT: "<div>text before<div> $expected15$Template parsed successfully. Structure:
TEXT: "<div>text before<div>
" "
INCLUDE: "inner_template" INCLUDE: "inner_template"
@@ -329,7 +321,7 @@ EXECUTE: "context + '{"name3": "zalupa"}';
RETURN 'some other text';" RETURN 'some other text';"
TEXT: " TEXT: "
<div id=\"footer\">...</div>"$expected15$ <div id="footer">...</div>"$expected15$
); );
IF result THEN IF result THEN
passed_tests := passed_tests + 1; passed_tests := passed_tests + 1;