Sun, 07 Dec 2025 20:16:02 +0100
update uwproj
/* * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER. * * Copyright 2025 Olaf Wintermann. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE * POSSIBILITY OF SUCH DAMAGE. */ #include "strreplace.h" #include <string.h> #include <stdlib.h> #include <ctype.h> #include <cx/buffer.h> StringTemplate* string_template_compile(const CxAllocator *a, cxstring tpl) { StringTemplateSegment *end = NULL; // segment list end int var = FALSE; int error = FALSE; CxBuffer buf; // tmp buffer if(cxBufferInit(&buf, NULL, 128, NULL, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS)) { return NULL; } StringTemplate *t = cxMalloc(a, sizeof(StringTemplate)); if(!t) { cxBufferDestroy(&buf); return NULL; } t->a = a ? a : cxDefaultAllocator; t->segments = NULL; StringTemplateSegment *seg = NULL; for(size_t i=0;i<tpl.length;i++) { char c = tpl.ptr[i]; int add_char = FALSE; // add current char to the buffer int finish_seg = FALSE; // copy buffer to segment string and start new segment if(!seg) { // start new segment seg = cxMalloc(a, sizeof(StringTemplateSegment)); if(seg) { seg->type = var ? STRING_SEGMENT_VAR_PLACEHOLDER : STRING_SEGMENT_STR; seg->str = (cxmutstr){NULL, 0}; seg->num = 0; seg->next = NULL; // add segment to segment list if(end) { end->next = seg; } else { t->segments = seg; } end = seg; } else { error = TRUE; break; } } if(var) { // current segment is a var if(c == '}') { var = FALSE; finish_seg = TRUE; } else if(c == '{') { // noop } else if(!isalnum(c)) { var = FALSE; finish_seg = TRUE; i--; } else { add_char = TRUE; } } else { if(c == '$') { if(i+1<tpl.length && tpl.ptr[i+1] == '$') { // $$ -> $ i++; add_char = TRUE; } else { var = TRUE; if(buf.pos == 0) { // reuse current segment seg->type = STRING_SEGMENT_VAR_PLACEHOLDER; } else { // create new segment finish_seg = TRUE; } } } else { add_char = TRUE; } } if(add_char) { if(cxBufferPut(&buf, c) != c) { error = TRUE; break; } } else if(finish_seg) { // copy buffer content cxmutstr seg_str = cx_strdup_a(a, cx_strn(buf.space, buf.pos)); if(!seg_str.ptr) { error = TRUE; break; } seg->str = seg_str; if(seg->type == STRING_SEGMENT_VAR_PLACEHOLDER) { // is the var segment an integer reference? if(!cx_strtoi(seg_str, &seg->num, 10)) { seg->type = STRING_SEGMENT_NUM_PLACEHOLDER; } } buf.pos = 0; seg = NULL; } } // finish last segment if(seg) { cxmutstr seg_str = cx_strdup_a(a, cx_strn(buf.space, buf.pos)); if(!seg_str.ptr) { error = TRUE; } else { seg->str = seg_str; if(seg->type == STRING_SEGMENT_VAR_PLACEHOLDER) { if(!cx_strtoi(seg_str, &seg->num, 10)) { seg->type = STRING_SEGMENT_NUM_PLACEHOLDER; } } } } cxBufferDestroy(&buf); if(error) { string_template_free(t); return NULL; } return t; } void string_template_free(StringTemplate *tpl) { StringTemplateSegment *seg = tpl->segments; while(seg) { StringTemplateSegment *next = seg->next; cxFree(tpl->a, seg->str.ptr); cxFree(tpl->a, seg); seg = next; } cxFree(tpl->a, tpl); } ssize_t string_template_write_to(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata, void *stream, cx_write_func writef) { if(!tpl) { return -1; } // write each segment to the stream StringTemplateSegment *seg = tpl->segments; ssize_t w = 0; while(seg) { if(seg->type == STRING_SEGMENT_STR) { // just write the segment string if(seg->str.length > 0) { size_t r = writef(seg->str.ptr, 1, seg->str.length, stream); if(r != seg->str.length) { return -1; } w += r; } } else if(varfunc) { // convert var segment to value WSBool free_str = FALSE; cxmutstr str = varfunc(a, seg, userdata, &free_str); if(str.length > 0) { size_t r = writef(str.ptr, 1, str.length, stream); if(r != str.length) { return -1; } w += r; } if(free_str) { cxFree(a, str.ptr); } } seg = seg->next; } return w; } cxmutstr string_template_build_string(StringTemplate *tpl, const CxAllocator *a, strtpl_var_func varfunc, void *userdata) { CxBuffer buf; cxBufferInit(&buf, NULL, 1024, a, CX_BUFFER_AUTO_EXTEND|CX_BUFFER_FREE_CONTENTS); ssize_t w = string_template_write_to(tpl, a, varfunc, userdata, &buf, (cx_write_func)cxBufferWrite); if(w < 0 || cxBufferTerminate(&buf)) { cxBufferDestroy(&buf); return (cxmutstr){ NULL, 0 }; } return (cxmutstr){ buf.space, buf.size }; }