fix: hemar: parser
This commit is contained in:
@@ -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 */
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user