From cc3e59b84f84e993d461c4261725ab0d6598874e Mon Sep 17 00:00:00 2001 From: Marco Roth Date: Fri, 6 Mar 2026 18:00:50 +0100 Subject: [PATCH] C: Add `hb_string_trim`/`hb_string_is_blank` and simplify string operations --- src/analyze/conditional_elements.c | 58 ++++------------------------- src/analyze/conditional_open_tags.c | 14 +------ src/extract.c | 9 ++--- src/include/util.h | 2 - src/include/util/hb_string.h | 4 ++ src/parser.c | 33 +++++++--------- src/util.c | 8 ---- src/util/hb_string.c | 37 ++++++++++++++++++ 8 files changed, 66 insertions(+), 99 deletions(-) diff --git a/src/analyze/conditional_elements.c b/src/analyze/conditional_elements.c index 9f42be018..acc4849bd 100644 --- a/src/analyze/conditional_elements.c +++ b/src/analyze/conditional_elements.c @@ -3,7 +3,6 @@ #include "../include/element_source.h" #include "../include/errors.h" #include "../include/token_struct.h" -#include "../include/util.h" #include "../include/util/hb_allocator.h" #include "../include/util/hb_array.h" #include "../include/util/hb_string.h" @@ -37,38 +36,19 @@ static hb_string_T extract_condition_from_erb_content(AST_NODE_T* erb_node, bool if (!content_token || hb_string_is_empty(content_token->value)) { return HB_STRING_NULL; } - const char* data = content_token->value.data; - size_t remaining = content_token->value.length; - - while (remaining > 0 && is_whitespace(*data)) { - data++; - remaining--; - } + hb_string_T value = hb_string_trim_start(content_token->value); if (*is_if) { - if (remaining >= 3 && strncmp(data, "if", 2) == 0 && is_whitespace(data[2])) { - data += 3; - remaining -= 3; - } + if (hb_string_starts_with(value, hb_string("if "))) { value = hb_string_slice(value, 3); } } else { - if (remaining >= 7 && strncmp(data, "unless", 6) == 0 && is_whitespace(data[6])) { - data += 7; - remaining -= 7; - } + if (hb_string_starts_with(value, hb_string("unless "))) { value = hb_string_slice(value, 7); } } - while (remaining > 0 && is_whitespace(*data)) { - data++; - remaining--; - } - if (remaining == 0) { return HB_STRING_NULL; } + value = hb_string_trim(value); - while (remaining > 0 && is_whitespace(data[remaining - 1])) { - remaining--; - } - if (remaining == 0) { return HB_STRING_NULL; } + if (hb_string_is_empty(value)) { return HB_STRING_NULL; } - return (hb_string_T) { .data = (char*) data, .length = (uint32_t) remaining }; + return value; } static bool is_simple_erb_conditional(AST_NODE_T* node) { @@ -107,18 +87,7 @@ static bool contains_single_open_tag(hb_array_T* statements, AST_HTML_OPEN_TAG_N if (child->type == AST_HTML_TEXT_NODE) { AST_HTML_TEXT_NODE_T* text = (AST_HTML_TEXT_NODE_T*) child; - bool whitespace_only = true; - - if (!hb_string_is_empty(text->content)) { - for (size_t ci = 0; ci < text->content.length; ci++) { - if (!is_whitespace(text->content.data[ci])) { - whitespace_only = false; - break; - } - } - } - - if (whitespace_only) { continue; } + if (hb_string_is_blank(text->content)) { continue; } return false; } @@ -156,18 +125,7 @@ static bool contains_single_close_tag(hb_array_T* statements, AST_HTML_CLOSE_TAG if (child->type == AST_HTML_TEXT_NODE) { AST_HTML_TEXT_NODE_T* text = (AST_HTML_TEXT_NODE_T*) child; - bool whitespace_only = true; - - if (!hb_string_is_empty(text->content)) { - for (size_t ci = 0; ci < text->content.length; ci++) { - if (!is_whitespace(text->content.data[ci])) { - whitespace_only = false; - break; - } - } - } - - if (whitespace_only) { continue; } + if (hb_string_is_blank(text->content)) { continue; } return false; } diff --git a/src/analyze/conditional_open_tags.c b/src/analyze/conditional_open_tags.c index 293217b42..5f124f48b 100644 --- a/src/analyze/conditional_open_tags.c +++ b/src/analyze/conditional_open_tags.c @@ -3,7 +3,6 @@ #include "../include/element_source.h" #include "../include/errors.h" #include "../include/token_struct.h" -#include "../include/util.h" #include "../include/util/hb_allocator.h" #include "../include/util/hb_array.h" #include "../include/util/hb_string.h" @@ -87,18 +86,7 @@ static single_open_tag_result_T get_single_open_tag_from_statements(hb_array_T* if (node->type == AST_HTML_TEXT_NODE) { AST_HTML_TEXT_NODE_T* text = (AST_HTML_TEXT_NODE_T*) node; - bool whitespace_only = true; - - if (!hb_string_is_empty(text->content)) { - for (size_t ci = 0; ci < text->content.length; ci++) { - if (!is_whitespace(text->content.data[ci])) { - whitespace_only = false; - break; - } - } - } - - if (whitespace_only) { continue; } + if (hb_string_is_blank(text->content)) { continue; } if (result.tag) { hb_string_T tag_name = get_open_tag_name(result.tag); diff --git a/src/extract.c b/src/extract.c index cd4848e4d..56e05aaa3 100644 --- a/src/extract.c +++ b/src/extract.c @@ -99,13 +99,10 @@ void herb_extract_ruby_to_buffer_with_options( bool is_inline_comment = false; if (!extract_options.comments && !is_comment_tag && !hb_string_is_empty(token->value)) { - const char* content = token->value.data; - - while (*content == ' ' || *content == '\t') { - content++; - } + hb_string_T trimmed = hb_string_trim_start(token->value); - if (*content == '#' && token->location.start.line == token->location.end.line) { + if (!hb_string_is_empty(trimmed) && trimmed.data[0] == '#' + && token->location.start.line == token->location.end.line) { is_comment_tag = true; is_inline_comment = true; } diff --git a/src/include/util.h b/src/include/util.h index 47376cb11..2fa9fd8a0 100644 --- a/src/include/util.h +++ b/src/include/util.h @@ -7,8 +7,6 @@ int is_newline(int character); int is_whitespace(int character); -const char* skip_whitespace(const char* pointer); - hb_string_T escape_newlines(hb_string_T input); hb_string_T quoted_string(hb_string_T input); char* herb_strdup(const char* s); diff --git a/src/include/util/hb_string.h b/src/include/util/hb_string.h index a74f073e4..6acc0aef1 100644 --- a/src/include/util/hb_string.h +++ b/src/include/util/hb_string.h @@ -26,6 +26,10 @@ bool hb_string_is_empty(hb_string_T string); hb_string_T hb_string_truncate(hb_string_T string, uint32_t max_length); hb_string_T hb_string_range(hb_string_T string, uint32_t from, uint32_t to); +hb_string_T hb_string_trim_start(hb_string_T string); +hb_string_T hb_string_trim_end(hb_string_T string); +hb_string_T hb_string_trim(hb_string_T string); +bool hb_string_is_blank(hb_string_T string); hb_string_T hb_string_copy(hb_string_T string, hb_allocator_T* allocator); char* hb_string_to_c_string_using_malloc(hb_string_T string); diff --git a/src/parser.c b/src/parser.c index c99ef403d..cc07f9bd1 100644 --- a/src/parser.c +++ b/src/parser.c @@ -336,9 +336,8 @@ static AST_HTML_ATTRIBUTE_NAME_NODE_T* parser_parse_html_attribute_name(parser_T TOKEN_EOF )) { if (token_is(parser, TOKEN_ERB_START)) { - const char* tag = parser->current_token->value.data; - size_t tag_length = parser->current_token->value.length; - bool is_output_tag = (tag_length >= 3 && tag[2] == '='); + hb_string_T tag = parser->current_token->value; + bool is_output_tag = (tag.length >= 3 && tag.data[2] == '='); if (!is_output_tag) { bool is_control_flow = parser_lookahead_erb_is_control_flow(parser); @@ -921,14 +920,14 @@ static bool parser_lookahead_erb_is_attribute(lexer_T* lexer) { } while (true); } -static bool starts_with_keyword(const char* pointer, size_t remaining, const char* keyword) { - size_t length = strlen(keyword); - if (remaining < length) { return false; } - if (strncmp(pointer, keyword, length) != 0) { return false; } +static bool starts_with_keyword(hb_string_T string, const char* keyword) { + hb_string_T prefix = hb_string(keyword); + if (string.length < prefix.length) { return false; } + if (strncmp(string.data, prefix.data, prefix.length) != 0) { return false; } - if (remaining == length) { return true; } + if (string.length == prefix.length) { return true; } - return is_whitespace(pointer[length]); + return is_whitespace(string.data[prefix.length]); } // TODO: ideally we could avoid basing this off of strings, and use the step in analyze.c @@ -942,18 +941,12 @@ static bool parser_lookahead_erb_is_control_flow(parser_T* parser) { return false; } - const char* data = content->value.data; - size_t remaining = content->value.length; + hb_string_T trimmed = hb_string_trim_start(content->value); - while (remaining > 0 && is_whitespace(*data)) { - data++; - remaining--; - } - - bool is_control_flow = starts_with_keyword(data, remaining, "end") || starts_with_keyword(data, remaining, "else") - || starts_with_keyword(data, remaining, "elsif") || starts_with_keyword(data, remaining, "in") - || starts_with_keyword(data, remaining, "when") || starts_with_keyword(data, remaining, "rescue") - || starts_with_keyword(data, remaining, "ensure"); + bool is_control_flow = starts_with_keyword(trimmed, "end") || starts_with_keyword(trimmed, "else") + || starts_with_keyword(trimmed, "elsif") || starts_with_keyword(trimmed, "in") + || starts_with_keyword(trimmed, "when") || starts_with_keyword(trimmed, "rescue") + || starts_with_keyword(trimmed, "ensure"); token_free(content, parser->allocator); diff --git a/src/util.c b/src/util.c index 9b79b7e9b..2d705aa49 100644 --- a/src/util.c +++ b/src/util.c @@ -14,14 +14,6 @@ int is_whitespace(int character) { return character == ' ' || character == '\t' || character == '\n' || character == '\r'; } -const char* skip_whitespace(const char* pointer) { - while (is_whitespace(*pointer)) { - pointer++; - } - - return pointer; -} - hb_string_T escape_newlines(hb_string_T input) { hb_buffer_T buffer; diff --git a/src/util/hb_string.c b/src/util/hb_string.c index 9112230ac..1dd5d8058 100644 --- a/src/util/hb_string.c +++ b/src/util/hb_string.c @@ -1,5 +1,6 @@ #include "../include/util/hb_string.h" #include "../include/macros.h" +#include "../include/util.h" #include #include @@ -68,6 +69,42 @@ hb_string_T hb_string_range(hb_string_T string, uint32_t from, uint32_t to) { return hb_string_truncate(hb_string_slice(string, from), to - from); } +hb_string_T hb_string_trim_start(hb_string_T string) { + if (hb_string_is_empty(string)) { return string; } + + uint32_t offset = 0; + while (offset < string.length && is_whitespace(string.data[offset])) { + offset++; + } + + return hb_string_slice(string, offset); +} + +hb_string_T hb_string_trim_end(hb_string_T string) { + if (hb_string_is_empty(string)) { return string; } + + uint32_t length = string.length; + while (length > 0 && is_whitespace(string.data[length - 1])) { + length--; + } + + return hb_string_truncate(string, length); +} + +hb_string_T hb_string_trim(hb_string_T string) { + return hb_string_trim_end(hb_string_trim_start(string)); +} + +bool hb_string_is_blank(hb_string_T string) { + if (hb_string_is_empty(string)) { return true; } + + for (uint32_t i = 0; i < string.length; i++) { + if (!is_whitespace(string.data[i])) { return false; } + } + + return true; +} + hb_string_T hb_string_copy(hb_string_T string, hb_allocator_T* allocator) { if (hb_string_is_null(string)) { return HB_STRING_NULL; } if (hb_string_is_empty(string)) { return HB_STRING_EMPTY; }