/*
* C TemplateS : template expander
* Based on the original libctemplate by Stephen C. Losen
*
* Version 0.1
*
* Copyright (c) 2017-2019 Alexander M. Pickering (alex@cogarr.net)
*
* Distributed under GPL V3, see COPYING for more information.
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as publish by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public Liscense for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*
*/
#include
#include
#include
#include
#include
#include
#include
#include
struct TMPL_varitem* TMPL_alloc_varitem(void);
void TMPL_free_varitem(struct TMPL_varitem* vi);
int TMPL_add_var_to_varlist(
struct TMPL_varlist* t,
const char* varname,
const char* var
);
void print_ast_helper(
struct TMPL_templates* t,
struct TMPL_tagnode* cursor,
int level
);
void print_ast(struct TMPL_templates* root);
struct TMPL_token* TMPL_tokenize(const char* tmplstr, size_t strlen);
struct TMPL_tagnode* alloc_tagnode(void);
size_t get_quoted_string(const char* start, size_t len);
int is_whitespace(char c);
struct TMPL_tagnode* parse_text(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_else(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_elseif(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_if(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_loop(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_variable(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_continue(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_break(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_tagnode* parse_end(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_varlist* TMPL_alloc_varlist(void);
int TMPL_free_hashmapitems(any_t a, any_t b);
void TMPL_free_varlist(struct TMPL_varlist* vl);
struct TMPL_loop* TMPL_alloc_loop(void);
void TMPL_free_loop(struct TMPL_loop* tl);
int TMPL_add_varlist_to_loop(
struct TMPL_loop* tl,
struct TMPL_varlist* vl
);
int TMPL_add_loop_to_varlist(
struct TMPL_varlist* vl,
const char* name,
struct TMPL_loop* l
);
struct TMPL_tagnode* parse(
struct TMPL_token* head,
struct TMPL_buf* errbuf
);
struct TMPL_templates* alloc_templates(void);
struct TMPL_templates* compile(const char* tmplstr);
int render_variable(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int render_text(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int print_pair(any_t indent, any_t b);
int render_if(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int render_elseif(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int render_loop(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int render_continue(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int render_break(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int render_any(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
int TMPL_render_helper(
struct TMPL_templates* t,
struct TMPL_varlist* varlist
);
char* TMPL_render(
struct TMPL_templates* t,
struct TMPL_varlist* varlist,
size_t* size_p
);
int TMPL_alloc_template(
const char* tmplstr,
struct TMPL_templates **t
);
void TMPL_free_template(struct TMPL_templates* t);
void TMPL_free_tagnode(struct TMPL_tagnode* tn);
char* TMPL_get_error(struct TMPL_templates* t);
void print_varlist_helper(
struct TMPL_varlist* vl,
int indent
);
struct TMPL_varitem*
TMPL_alloc_varitem(){
struct TMPL_varitem* ret = (struct TMPL_varitem*)malloc(sizeof(struct TMPL_varitem));
return ret;
}
void
TMPL_free_varitem(struct TMPL_varitem* vi){
if(vi->type == vartype_var){
free(vi->item.s);
}else{
TMPL_free_loop(vi->item.l);
}
free(vi);
}
/* Adds a variable to the variable list, returns -1 on error*/
int
TMPL_add_var_to_varlist(struct TMPL_varlist* t, const char* varname, const char* var){
struct TMPL_varitem* vi = TMPL_alloc_varitem();
vi->type = vartype_var;
size_t slen = strlen(var);/*strlen dosn't count \0*/
vi->item.s = (char*)malloc(sizeof(char)*(slen + 1));/*+1 for \0*/
vi->item.s[slen] = '\0';
memcpy(vi->item.s,var,slen);
int succ = hashmap_put(t->map,(char*)varname,(char*)vi);
if(succ != MAP_OK) return 0;
return -1;
}
void
print_ast_helper(struct TMPL_templates* t, struct TMPL_tagnode* cursor, int level){
/*printf("Helper called, level %d\n",level);*/
int i;
/*printf("Cursor is: %p\n", (void*)cursor);*/
if(t->cursor == cursor){
printf("> ");
}else{
printf(" ");
}
for(i = 0; i < level; i++){
printf("|");
}
/*printf("type is %d\n",cursor->type);*/
printf("} %d ->",(int)cursor->type);
switch(cursor->type){
case tag_null:
printf("TAG NULL");
break;
case tag_text:
printf("TAG Text \"");
if(cursor->TMPL_tag.text.len < 10){
size_t i;
for(i = 0; i < cursor->TMPL_tag.text.len; i++)
putchar(*(cursor->TMPL_tag.text.start + i));
printf("\"\n");
}else{
int i;
for(i = 0; i < 7; i++)
putchar(*(cursor->TMPL_tag.text.start + i));
printf("...\"\n");
}
break;
case tag_var:
printf("TAG var(%s)",cursor->TMPL_tag.var.varname);
if(cursor->TMPL_tag.var.defaultval != NULL){
printf(" +default:%d",(int)cursor->TMPL_tag.var.default_len);
}
printf("\n");
break;
case tag_if:
case tag_elseif:
printf("TAG If/elseif,(%s) == (%s)\n",cursor->TMPL_tag.ifelse.varname,cursor->TMPL_tag.ifelse.testval);
level++;
printf(" ");
for(i=0;iTMPL_tag.ifelse.tbranch,level);
if(cursor->TMPL_tag.ifelse.fbranch != NULL){
printf(" ");
for(i=0;iTMPL_tag.ifelse.fbranch,level);
}
level--;
break;
case tag_else:
printf("TAG Else\n");
break;
case tag_end:
printf("TAG End\n");
break;
case tag_loop:
printf("TAG Loop (%s)(%p), body:\n",cursor->TMPL_tag.loop.loopname, (void*)cursor);
print_ast_helper(t,cursor->TMPL_tag.loop.body,level+1);
break;
case tag_break:
printf("TAG Break (%d) -> (%p)\n",cursor->TMPL_tag.breakcont.level, (void*)cursor->TMPL_tag.breakcont.into);
break;
case tag_continue:
printf("TAG Continue (%d)\n",cursor->TMPL_tag.breakcont.level);
break;
}
if(cursor->next != NULL){
print_ast_helper(t,cursor->next,level);
}
}
void
print_ast(struct TMPL_templates* t){
printf("continues: %5d | breaks: %5d | jumping: %5d\n",t->continues, t->breaks, t->jumping);
printf("-----------------------------\n");
print_ast_helper(t,t->roottag,0);
}
struct TMPL_token*
TMPL_tokenize(const char* tmplstr, size_t m_strlen){
if(m_strlen == 0) return NULL;
struct TMPL_token* first;
const char* textcursor = tmplstr;
size_t newlength;
enum TMPL_tagtype ttype;
ttype = starts_with_token(tmplstr+1,m_strlen);
if(*tmplstr == '<' && ttype != tag_null){
first = scan_tag(textcursor,m_strlen,&newlength);
first->type = ttype;
}else{
first = scan_text(textcursor,m_strlen,&newlength);
first->type = tag_text;
}
textcursor += newlength;
first->end = textcursor;
first->length = newlength;
m_strlen -= newlength;
struct TMPL_token* tokencursor = first;
struct TMPL_token* newnode;
while(*textcursor != '\0'){
ttype = starts_with_token(textcursor+1,m_strlen);
if(ttype == tag_null){
newnode = scan_text(textcursor,m_strlen,&newlength);
newnode->type = tag_text;
}else{
newnode = scan_tag(textcursor,m_strlen,&newlength);
newnode->type = ttype;
}
newnode->last = tokencursor;
tokencursor->next = newnode;
textcursor += newlength;
m_strlen -= newlength;
tokencursor = newnode;
}
return first;
}
struct TMPL_tagnode*
alloc_tagnode(){
struct TMPL_tagnode* t = malloc(sizeof(struct TMPL_tagnode));
return t;
}
/*Finds a quoted string, allows for \"*/
size_t
get_quoted_string(const char* start, size_t len){
size_t i;
int setup = 0;
for(i = 0; i < len; i++){
if(*(start+i) == '\\'){
setup = 1;
}else if(*(start+i) == '"'){
if(setup == 1){
/*Do nothing, this is \"*/
}else{
return i;
}
}else{
setup = 0;
}
}
return i;
}
int
is_whitespace(char c){
return (c == ' ' || c == '\t' || c == '\n') ? 1 : 0;
}
const char* tokentype_to_str(struct TMPL_token *head){
switch(head->type){
case tag_null:
return "NULL";
case tag_text:
return "TEXT";
case tag_var:
return "VAR";
case tag_if:
return "IF";
case tag_elseif:
return "ELSEIF";
case tag_else:
return "ELSE";
case tag_end:
return "END";
case tag_loop:
return "LOOP";
case tag_break:
return "BREAK";
case tag_continue:
return "CONTINUE";
default:
return "UNKNOWN";
}
}
struct TMPL_tagnode*
parse_text(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
head->into = t;
t->TMPL_tag.text.start = head->start;
t->TMPL_tag.text.len = head->length;
t->type = head->type;
struct TMPL_tagnode* n = parse(head->next, errbuf);
t->next = n;
return t;
}
struct TMPL_tagnode*
parse_else(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* branch = parse(head->next,errbuf);
return branch;
}
struct TMPL_tagnode*
parse_elseif(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
head->into = t;
const char* start_of_attribs = head->start + TAG_ELSEIF_LENGTH;
int name_offset = kmp(start_of_attribs,head->length, ATTRIBUTE_VARNAME,ATTRIBUTE_VARNAME_LENGTH);
const char* start_of_name = start_of_attribs + name_offset + ATTRIBUTE_VARNAME_LENGTH;
while(is_whitespace(*start_of_name))
start_of_name++;
if(*start_of_name != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in after name near ");
bputsn(errbuf,head->start, ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_name++;
}
while(is_whitespace(*start_of_name))
start_of_name++;
start_of_name++;//consume "
size_t name_length = get_quoted_string(start_of_name,head->length);
char* name = (char*)malloc(sizeof(char)*name_length);
memcpy(name,start_of_name,name_length);
name[name_length] = '\0';
t->TMPL_tag.ifelse.varname = name;
int testval_offset = kmp(start_of_attribs,head->length, ATTRIBUTE_VALUE,ATTRIBUTE_VALUE_LENGTH);
if(testval_offset == -1){
t->TMPL_tag.ifelse.testval = NULL;
}else{
const char* start_of_value = start_of_attribs + testval_offset + ATTRIBUTE_VALUE_LENGTH;
while(is_whitespace(*start_of_value))
start_of_value++;
if(*start_of_value != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in after value near ");
bputsn(errbuf,head->start,ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_value++;//consume =
}
while(is_whitespace(*start_of_value))
start_of_value++;
start_of_value++;//consume "
size_t value_length = get_quoted_string(start_of_value,head->length);
char* value = (char*)malloc(sizeof(char)*value_length);
memcpy(value,start_of_value,value_length);
value[value_length] = '\0';
t->TMPL_tag.ifelse.testval = value;
}
struct TMPL_token* cursor = head->next;
int nest_level = 0;
struct TMPL_token* fstart = NULL;
while(nest_level > 0 || cursor->type != tag_end){
if(fstart == NULL){
if(cursor->type == tag_elseif){
fstart = cursor;
}else if(cursor->type == tag_else){
fstart = cursor;
}
}
if(cursor->type == tag_if || cursor->type == tag_loop){
nest_level++;
}else if(cursor->type == tag_end){
nest_level--;
}
cursor = cursor->next;
}
if(fstart != NULL){
if(fstart->type == tag_elseif){
t->TMPL_tag.ifelse.fbranch = parse_elseif(fstart,errbuf);
}else if(fstart->type == tag_else){
t->TMPL_tag.ifelse.fbranch = parse_else(fstart,errbuf);
}else{
t->TMPL_tag.ifelse.fbranch = parse(fstart,errbuf);
}
}else{
t->TMPL_tag.ifelse.fbranch = NULL;
}
t->TMPL_tag.ifelse.tbranch = parse(head->next,errbuf);
t->next = parse(cursor->next,errbuf);
t->type = tag_elseif;
return t;
}
struct TMPL_tagnode*
parse_if(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
head->into = t;
const char* start_of_attribs = head->start + TAG_IF_LENGTH;
/*Find the name of the varialbe*/
int name_offset = kmp(start_of_attribs,head->length,ATTRIBUTE_VARNAME,ATTRIBUTE_VARNAME_LENGTH);
const char* start_of_name = start_of_attribs + name_offset + ATTRIBUTE_VARNAME_LENGTH;
while(is_whitespace(*start_of_name))
start_of_name++;
if(*start_of_name != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in after name near ");
bputsn(errbuf, head->start, ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_name++;
}
while(is_whitespace(*start_of_name))
start_of_name++;
start_of_name++;//consume "
size_t name_length = get_quoted_string(start_of_name,head->length);
char* name = (char*)malloc(sizeof(char)*name_length);
memcpy(name,start_of_name,name_length);
name[name_length] = '\0';
t->TMPL_tag.ifelse.varname = name;
/*Find the name to check against*/
int testval_offset = kmp(start_of_attribs,head->length, ATTRIBUTE_VALUE,ATTRIBUTE_VALUE_LENGTH);
if(testval_offset == -1){
t->TMPL_tag.ifelse.testval = NULL;
}else{
const char* start_of_value = start_of_attribs + testval_offset + ATTRIBUTE_VALUE_LENGTH;
while(is_whitespace(*start_of_value))
start_of_value++;
if(*start_of_value != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in after value near ");
bputsn(errbuf,head->start,ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_value++;
}
while(is_whitespace(*start_of_value))
start_of_value++;
start_of_value++;//consume "
size_t value_length = get_quoted_string(start_of_value,head->length);
char* value = (char*)malloc(sizeof(char)*value_length);
memcpy(value,start_of_value,value_length);
value[value_length] = '\0';
t->TMPL_tag.ifelse.testval = value;
}
/*Find the true branch*/
struct TMPL_token* cursor = head->next;
int nest_level = 0;
struct TMPL_token* fstart = NULL;
while(nest_level > 0 || cursor->type != tag_end){
if(fstart == NULL){
if(cursor->type == tag_elseif){
fstart = cursor;
}else if(cursor->type == tag_else){
fstart = cursor;
}
}
if(cursor->type == tag_if || cursor->type == tag_loop){
nest_level++;
}else if(cursor->type == tag_end){
nest_level--;
}
cursor = cursor->next;
}
if(fstart != NULL){
if(fstart->type == tag_elseif){
t->TMPL_tag.ifelse.fbranch = parse_elseif(fstart,errbuf);
}else if(fstart->type == tag_else){
t->TMPL_tag.ifelse.fbranch = parse_else(fstart,errbuf);
}else{
t->TMPL_tag.ifelse.fbranch = parse(fstart,errbuf);
}
}else{
t->TMPL_tag.ifelse.fbranch = NULL;
}
t->TMPL_tag.ifelse.tbranch = parse(head->next,errbuf);
/*Walk the tbranch and try to find if we have elseifs*/
t->next = parse(cursor->next,errbuf);
t->type = tag_if;
return t;
}
struct TMPL_tagnode*
parse_loop(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
head->into = t;
const char* loop_start = head->start + TAG_LOOP_LENGTH;
int loop_length = head->length - TAG_LOOP_LENGTH;
int name_offset = kmp(loop_start,loop_length, ATTRIBUTE_VARNAME, ATTRIBUTE_VARNAME_LENGTH);
if(name_offset == -1 && DEBUGGING){
bputs(errbuf,"Parsing error: Could not find name attribute for loop near\n");
bputsn(errbuf,head->start, ERRBUF_HINTLEN);
return NULL;
}
if(name_offset >= 0){
const char* start_of_name = loop_start + name_offset + ATTRIBUTE_VARNAME_LENGTH;
while(is_whitespace(*start_of_name))
start_of_name++;
if(*start_of_name != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in after name near ");
bputsn(errbuf, head->start, ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_name++;//consume '='
}
while(is_whitespace(*start_of_name))
start_of_name++;
start_of_name++;//consume "
size_t name_size = get_quoted_string(start_of_name,head->length);
char* loopname = (char*)malloc(sizeof(char)*(name_size + 1));
memcpy(loopname,start_of_name,name_size);
loopname[name_size] = '\0';
t->TMPL_tag.loop.loopname = loopname;
}
t->type = tag_loop;
int nest_level = 0;
struct TMPL_token* cursor = head->next;
while(cursor != NULL && (nest_level > 0 || cursor->type != tag_end)){
if(cursor == NULL){
bputs(errbuf,"Parse error : tried parsing loop and hit end of stream near\n");
bputsn(errbuf,head->start,ERRBUF_HINTLEN);
return NULL;
}
if(cursor->type == tag_if ||
cursor->type == tag_loop){
nest_level++;
}else if(cursor->type == tag_end){
nest_level--;
}
cursor = cursor->next;
}
if(cursor == NULL){
bputs(errbuf,"Parse error: Tried parsing loop and hit end of stream near\n");
bputsn(errbuf,head->start,ERRBUF_HINTLEN);
return NULL;
}
t->next = parse(cursor->next,errbuf);
t->TMPL_tag.loop.body = parse(head->next,errbuf);
return t;
}
const char end_str[] = "";
struct TMPL_tagnode* parse_end(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
if(t == NULL){
bputs(errbuf,"Parse error: Failed to create a tag node");
return NULL;
}
head->into = t;
t->TMPL_tag.text.start = end_str;
t->TMPL_tag.text.len = 0;
t->type = tag_text;
t->next = NULL;
return t;
}
/*Parses the tokens*/
struct TMPL_tagnode*
parse_variable(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
head->into =t;
/*
^*/
const char* start_of_attribs = head->start + TAG_VAR_LENGTH;
/*Find the length of attributes*/
int attribs_length = head->length - TAG_VAR_LENGTH;
/*Find where the "name" attribute is*/
int in_name = kmp(start_of_attribs,attribs_length,ATTRIBUTE_VARNAME,ATTRIBUTE_VARNAME_LENGTH);
/*Find where the "default" attribute is*/
int in_default = kmp(start_of_attribs,attribs_length,ATTRIBUTE_DEFAULT,ATTRIBUTE_DEFAULT_LENGTH);
if(in_name == -1){
bputs(errbuf,"Parsing error: Could not find \"name\" field in near ");
bputsn(errbuf,head->start,ERR_MSG_LEN);
bputs(errbuf,"\n");
}
if(in_name >= 0){
const char* start_of_name = start_of_attribs + in_name + ATTRIBUTE_VARNAME_LENGTH;
while(is_whitespace(*start_of_name))
start_of_name++;
if(*start_of_name != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in after name near ");
bputsn(errbuf,head->start, ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_name++;//consume =
}
while(is_whitespace(*start_of_name))
start_of_name++;
start_of_name++; //consume "
size_t name_size = get_quoted_string(start_of_name,head->length);
char* name = (char*)malloc(sizeof(char)*(name_size + 1));
memcpy(name,start_of_name,name_size);
name[name_size] = '\0';
t->TMPL_tag.var.varname = name;
t->TMPL_tag.var.name_len = name_size;
}
if(in_default >= 0){
const char* start_of_default = start_of_attribs + in_default + ATTRIBUTE_DEFAULT_LENGTH;
while(is_whitespace(*start_of_default))
start_of_default++;
if(*start_of_default != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in after default near ");
bputsn(errbuf,head->start, ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_default++;//consume =
}
while(is_whitespace(*start_of_default))
start_of_default++;
start_of_default++; //consume "
size_t default_size = get_quoted_string(start_of_default,head->length);
char* def = (char*) malloc(sizeof(char)*(default_size + 1));
def[default_size] = '\0';
memcpy(def,start_of_default,default_size);
t->TMPL_tag.var.defaultval = def;
t->TMPL_tag.var.default_len = default_size;
}else{
t->TMPL_tag.var.defaultval = NULL;
t->TMPL_tag.var.default_len = 0;
}
t->type = tag_var;
t->next = parse(head->next,errbuf);
return t;
}
struct TMPL_tagnode*
parse_continue(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
head->into = t;
const char* start_of_attribs = head->start + TAG_CONTINUE_LENGTH;
unsigned int level = 1; //how many loops to continue
/*We might have multiple loops to continue through*/
int level_offset = kmp(start_of_attribs,head->length,ATTRIBUTE_LEVEL, ATTRIBUTE_LEVEL_LENGTH);
if(level_offset != -1){//level found
const char* start_of_level = start_of_attribs + level_offset + ATTRIBUTE_LEVEL_LENGTH;
while(is_whitespace(*start_of_level))
start_of_level++;
if(*start_of_level != '='){
if(*start_of_level == '>'){
level = 1;
}else{
bputs(errbuf,"Parsing error: Expected \"=\" in near ");
bputsn(errbuf,head->start,ERR_MSG_LEN);
bputs(errbuf,"\n");
}
}else{
start_of_level++;
while(is_whitespace(*start_of_level)){
start_of_level++;
}
sscanf(start_of_level,"%u",&level);
}
}
int y = level;
struct TMPL_token* cursor = head;
while(y > 0){
//Scan backwards until we find the loop we're in
while(cursor != NULL && cursor->type != tag_loop){
cursor = cursor->last;
}
y--;
if(y > 0 && cursor == NULL){
size_t msglen = snprintf(NULL,0,"Parsing error: Continue was %d levels, but was only nested %d levels deep\n",level, level - y);
char errmsg[msglen];
sprintf(errmsg, "Parsing error: Continue was %d levels, but was only nested %d levels deep\n",level, level - y);
bputs(errbuf,errmsg);
}
}
t->TMPL_tag.breakcont.level = level;
t->next = parse(head->next,errbuf);
t->type = tag_continue;
return t;
}
struct TMPL_tagnode*
parse_break(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* t = alloc_tagnode();
head->into = t;
const char* start_of_attribs = head->start + TAG_BREAK_LENGTH;
unsigned int level = 1; //how many loops to continue
/*We might have multiple loops to continue through*/
int level_offset = kmp(start_of_attribs,head->length,ATTRIBUTE_LEVEL, ATTRIBUTE_LEVEL_LENGTH);
if(level_offset != -1){ //level found
const char* start_of_level = start_of_attribs + level_offset + ATTRIBUTE_LEVEL_LENGTH;
while(is_whitespace(*start_of_level))
start_of_level++;
if(*start_of_level != '='){
bputs(errbuf,"Parsing error: Expected \"=\" in near ");
bputsn(errbuf,head->start,ERR_MSG_LEN);
bputs(errbuf,"\n");
}else{
start_of_level++;
while(is_whitespace(*start_of_level)){
start_of_level++;
}
sscanf(start_of_level,"%u",&level);
}
}
t->TMPL_tag.breakcont.level = level;
int y = level;
struct TMPL_token* cursor = head;
while(y > 0){
//Scan backwards until we find the loop we're in
while(cursor != NULL && cursor->type != tag_loop){
char token_text[cursor->length+1];
memcpy(token_text, cursor->start, cursor->length);
token_text[cursor->length] = '\0';
cursor = cursor->last;
}
y--;
if(y > 0 && cursor == NULL){
exit(-1);
size_t msglen = snprintf(NULL,0,"Parsing error: Break was %d levels, but was only nested %d levels deep\n",level,level - y);
char errmsg[msglen];
sprintf(errmsg,"Parsing error: Break was %d levels, but was only nested %d levels deep\n",level,level - y);
bputs(errbuf,errmsg);
}else if(y > 0){
cursor = cursor->last; //move back one
}//else break
}
t->next = NULL;
t->TMPL_tag.breakcont.into = cursor->into;
t->type = tag_break;
return t;
}
struct TMPL_varlist*
TMPL_alloc_varlist(){
struct TMPL_varlist* ret = (struct TMPL_varlist*)malloc(sizeof(struct TMPL_varlist));
ret->map = hashmap_new();
ret->parent = NULL;
return ret;
}
int
TMPL_free_hashmapitems(any_t a, any_t b){
(void)(a);/*Unused parameter*/
struct TMPL_varitem* vi = (struct TMPL_varitem*)b;
TMPL_free_varitem(vi);
return MAP_OK;
}
void
TMPL_free_varlist(struct TMPL_varlist* vl){
hashmap_iterate(vl->map,TMPL_free_hashmapitems,NULL);
hashmap_free(vl->map);
free(vl);
}
struct TMPL_loop*
TMPL_alloc_loop(){
struct TMPL_loop* ret = (struct TMPL_loop*)malloc(sizeof(struct TMPL_loop));
if(ret == NULL) return NULL;
ret->loop_len = 0;
ret->varlist = NULL;
ret->next = NULL;
ret->tail = NULL;
return ret;
}
void
TMPL_free_loop(struct TMPL_loop* tl){
if(tl->next != NULL){
TMPL_free_loop(tl->next);
}
if(tl->varlist != NULL)
TMPL_free_varlist(tl->varlist);
free(tl);
}
int TMPL_add_varlist_to_loop(struct TMPL_loop* tl,struct TMPL_varlist* vl){
vl->parent = tl;
if(tl->loop_len == 0){/*Add the first node*/
tl->varlist = vl;
tl->tail = tl;
tl->loop_len++;
}else{
struct TMPL_loop* new = TMPL_alloc_loop();
if(new == NULL) return -1;
new->varlist = vl;
tl->tail->next = new;
tl->tail = new;
}
return 0;
}
/*Returns 0 for "OK" or -1 for "out of memory"*/
int
TMPL_add_loop_to_varlist(struct TMPL_varlist* vl, const char* name, struct TMPL_loop* l){
struct TMPL_varitem* vi = TMPL_alloc_varitem();
vi->type = vartype_loop;
vi->item.l = l;
l->parent = vl;
int err = hashmap_put(vl->map,(char*)name,vi);
if(err == MAP_OK) return 0;
return -1;
}
/*Parses tokens into a syntax tree*/
struct TMPL_tagnode*
parse(struct TMPL_token* head, struct TMPL_buf* errbuf){
struct TMPL_tagnode* root;
if(head == NULL){
return NULL;
}
switch(head->type){
case tag_text:
root = parse_text(head,errbuf);
break;
case tag_var:
root = parse_variable(head,errbuf);
break;
case tag_loop:
root = parse_loop(head,errbuf);
break;
case tag_end:
//A 0-sized string to make break fall out right
root = parse_end(head,errbuf);
break;
case tag_if:
root = parse_if(head,errbuf);
break;
case tag_elseif:
root = NULL;
break;
case tag_else:
root = NULL;
break;
case tag_continue:
root = parse_continue(head,errbuf);
break;
case tag_break:
root = parse_break(head,errbuf);
break;
default:
exit(-1);
break;
}
if(errbuf->total_len > 0){
printf("Error parsing %p\n",(void*)head);
size_t dummy;
printf("Error: %s\n",bstringify(errbuf,&dummy));
return NULL;
}
return root;
}
struct TMPL_templates*
alloc_templates(){
struct TMPL_templates* t = malloc(sizeof(struct TMPL_templates));
return t;
}
struct TMPL_templates*
compile(const char* tmplstr){
size_t slen = strlen(tmplstr);
struct TMPL_templates* ret = alloc_templates();
ret->out = alloc_tmpl_buf();
ret->errout = alloc_tmpl_buf();
ret->breaks = 0;
ret->continues = 0;
ret->jumping = 0;
struct TMPL_token* tokens = TMPL_tokenize(tmplstr,slen);
struct TMPL_tagnode* ast = parse(tokens,ret->errout);
if(ast == NULL){
}else{
ret->roottag = ast;
ret->cursor = ret->roottag;
}
return ret;
}
void
advance_cursor(struct TMPL_templates* t){
if(t->cursor != NULL && t->cursor->type != tag_end)
t->cursor = t->cursor->next;
}
int
render_variable(struct TMPL_templates* t, struct TMPL_varlist* varlist){
struct TMPL_varitem* vi;
char* varname = t->cursor->TMPL_tag.var.varname;
int err = hashmap_get(varlist->map,varname,(void**)&vi);
if(err == MAP_OK){
bputs(t->out,vi->item.s);
}else if(err == MAP_MISSING){
if(t->cursor->TMPL_tag.var.defaultval != NULL){
bputs(t->out,t->cursor->TMPL_tag.var.defaultval);
}else{
printf("Variable not bound, and no default\n");
/*Error, not bound and no default*/
return -1;
}
}else{
printf("Not MAP_OK or MAP_MISSING: %d\n", err);
return -1;
}
advance_cursor(t);
return 0;
}
int
render_text(struct TMPL_templates* t, struct TMPL_varlist* varlist){
(void)(varlist); /*Unused parameter*/
struct TMPL_buf* buf = t->out;
char* text = (char*)t->cursor->TMPL_tag.text.start;
size_t length = t->cursor->TMPL_tag.text.len;
bputsn(buf,text,length);
advance_cursor(t);
return 0;
}
int
print_pair(any_t indent, any_t b){
struct TMPL_varitem* vi = (struct TMPL_varitem*)b;
char *varstring;
switch(vi->type){
case 0:
varstring = "Null";
break;
case 1:
varstring = "Loop";
break;
case 2:
varstring = "Var";
break;
default:
varstring = "Error";
}
printf("Print pair, varitem is %p, type is %d (%s)\n",(void*)vi,(int)vi->type, varstring);
int* ip = (int*)indent;
int ind = *ip;
if(vi->type == vartype_var){
int i;
for(i = 0; i < ind; i++)
printf("\t");
printf("%s\n",vi->item.s);
}else{
struct TMPL_loop* cursor;
printf("{\n");
ind++;
/*If the loop doesn't have any varlists added, the cursor's varlist will be null*/
for(cursor = vi->item.l; cursor != NULL && cursor->varlist != NULL; cursor = cursor->next){
printf("First iteration though, cursor was %p\n",(void*)cursor);
print_varlist_helper(cursor->varlist,ind);
}
printf("}\n");
}
return MAP_OK;
}
void
print_varlist_helper(struct TMPL_varlist* vl, int indent){
hashmap_iterate(vl->map, print_pair,&indent);
}
void
print_varlist(struct TMPL_varlist* vl){
print_varlist_helper(vl,0);
}
int
render_if(struct TMPL_templates* t, struct TMPL_varlist* varlist){
char* varname = t->cursor->TMPL_tag.ifelse.varname;
char* testval = t->cursor->TMPL_tag.ifelse.testval;
struct TMPL_varitem* vi;
int err;
err = hashmap_get(varlist->map,varname,(void**)&vi);
struct TMPL_templates* nt = (struct TMPL_templates*)malloc(sizeof(struct TMPL_templates));
nt->out = t->out;
nt->errout = t->errout;
nt->breaks = t->breaks;
nt->continues = t->continues;
nt->jumping = 0;
if(err == MAP_OK){
//These two if statements can't be combined because of
//the condition for the elseif
if(testval == NULL || strcmp(vi->item.s, testval) == 0){
nt->roottag = t->cursor->TMPL_tag.ifelse.tbranch;
nt->cursor = nt->roottag;
err = TMPL_render_helper(nt,varlist);
if(nt->jumping)
t->cursor = nt->cursor;
t->breaks += nt->breaks;
t->continues = nt->continues;
t->jumping = nt->jumping;
}else{
}
}else if(t->cursor->TMPL_tag.ifelse.fbranch != NULL){
nt->roottag = t->cursor->TMPL_tag.ifelse.fbranch;
nt->cursor = nt->roottag;
err = TMPL_render_helper(nt,varlist);
if(nt->jumping)
t->cursor = nt->cursor;
t->breaks += nt->breaks;
t->continues = nt->continues;
t->jumping = nt->jumping;
}else{
}
advance_cursor(t);
return err;
}
/*Exactly the same thing as if*/
int
render_elseif(struct TMPL_templates* t, struct TMPL_varlist* varlist){
return render_if(t,varlist);
}
int
resolve_name(struct TMPL_varlist* varlist, char* name,struct TMPL_varitem** item){
return hashmap_get(varlist->map,name,(void*)item);
/*
struct TMPL_varlist* cursor = varlist;
int err;
do{
err = hashmap_get(cursor->map,name,(void*)item);
cursor = cursor->parent;
}while(cursor != NULL && err == MAP_MISSING);
if(err == MAP_MISSING){
return MAP_MISSING;
}
return MAP_OK;
*/
}
int
render_break(struct TMPL_templates* t, struct TMPL_varlist* varlist){
(void)(varlist); /*Unused parameter*/
t->breaks += t->cursor->TMPL_tag.breakcont.level;
t->jumping = 1;
/*t->cursor = t->cursor->TMPL_tag.breakcont.into->next;*/
t->cursor = t->cursor->TMPL_tag.breakcont.into;
return 0;
}
int
render_continue(struct TMPL_templates* t, struct TMPL_varlist* varlist){
(void)(varlist); /*Unused parameter*/
t->continues = t->continues + t->cursor->TMPL_tag.breakcont.level;
t->jumping = 1;
return 0;
}
int
render_loop(struct TMPL_templates* t, struct TMPL_varlist* varlist){
char* loopname = t->cursor->TMPL_tag.loop.loopname;
struct TMPL_tagnode* loopnode = t->cursor;
struct TMPL_varitem* loop;
int err;
err = resolve_name(varlist,loopname,&loop);
printf("When resolving name, err is %d\n",err);
/*What should happen when we get to a loop that does not have a name?
We follow the precident set for variables, and throw an error.*/
if(err != MAP_OK){
bputs(t->errout,"Render error: Variable \"");
bputs(t->errout,loopname);
bputs(t->errout,"\" was not bound to a value\n");
return -1;
}
struct TMPL_templates* nt = (struct TMPL_templates*)malloc(sizeof(struct TMPL_templates));
nt->out = t->out;
nt->errout = t->errout;
nt->roottag = t->cursor->TMPL_tag.loop.body;
nt->cursor = nt->roottag;
nt->breaks = t->breaks;
nt->continues = t->continues;
nt->jumping = 0;
struct TMPL_loop* cursor;
/*If the loop has no items, it's varlist will be null*/
for(cursor = loop->item.l; cursor != NULL && cursor->varlist != NULL; cursor = cursor->next){
nt->cursor = nt->roottag;
if(err != 0){
}
if(t->jumping){
if(t->breaks > 0){
t->breaks--;
if(t->breaks == 0){
t->jumping = 0;
}
break;
}
if(t->continues > 0){
t->continues--;
if(t->continues == 0){
t->jumping = 0;
}
}
}else{
err = TMPL_render_helper(nt,cursor->varlist);
t->breaks += nt->breaks;
t->continues += nt->continues;
t->jumping += nt->jumping;
if(nt->jumping){
t->cursor = nt->cursor;
}
nt->breaks = 0;
nt->continues = 0;
nt->jumping = 0;
}
}
if(t->jumping > 0){
t->breaks--;
if(t->breaks == 0){
t->jumping = 0;
}
}
if(t->cursor == loopnode)
advance_cursor(t);
free(nt);
return err;
}
int
render_any(struct TMPL_templates* t, struct TMPL_varlist* varlist){
/*Interpret the template*/
if(t->jumping) return 0;
int err;
switch(t->cursor->type){
case tag_text:
err = render_text(t,varlist);
if(err < 0){
}
break;
case tag_var:
err = render_variable(t,varlist);
if(err < 0){
}
break;
case tag_loop:
err = render_loop(t,varlist);
if(err < 0){
}
break;
case tag_if:
err = render_if(t,varlist);
if(err < 0){
}
break;
case tag_elseif:
err = render_elseif(t,varlist);
if(err < 0){
}
break;
case tag_break:
err = render_break(t,varlist);
break;
default:
exit(-1);
break;
}
return err;
}
int
TMPL_render_helper(struct TMPL_templates* t, struct TMPL_varlist* varlist){
int err;
while(t->cursor != NULL && t->jumping == 0 && err == 0){
err = render_any(t,varlist);
}
return err;
}
char*
TMPL_render(struct TMPL_templates* t, struct TMPL_varlist* varlist, size_t* size_p){
int err;
if(t->out != NULL){
free_tmpl_buf(t->out);
}
t->out = alloc_tmpl_buf();
t->cursor = t->roottag;
err = TMPL_render_helper(t,varlist);
if(err){
return NULL;
}
char* ret = bstringify(t->out,size_p);
return ret;
}
const char*
TMPL_err(struct TMPL_templates* t, size_t* size){
return bstringify(t->errout,size);
}
int
TMPL_alloc_template(const char* tmplstr, struct TMPL_templates **t){
struct TMPL_templates* n = compile(tmplstr);
if(n != NULL){
*t = n;
return 0;
}else{
TMPL_free_template(n);
return -1;
}
}
void
TMPL_free_template(struct TMPL_templates* t){
free_tmpl_buf(t->out);
free_tmpl_buf(t->errout);
TMPL_free_tagnode(t->roottag);
free(t);
}
void
TMPL_free_tagnode(struct TMPL_tagnode* tn){
switch(tn->type){
case tag_text:
break;
case tag_var:
free(tn->TMPL_tag.var.varname);
if(tn->TMPL_tag.var.defaultval != NULL){
free(tn->TMPL_tag.var.defaultval);
}
break;
case tag_if:
case tag_elseif:
case tag_else:
free(tn->TMPL_tag.ifelse.varname);
if(tn->TMPL_tag.ifelse.testval != NULL)
free(tn->TMPL_tag.ifelse.testval);
TMPL_free_tagnode(tn->TMPL_tag.ifelse.tbranch);
if(tn->TMPL_tag.ifelse.fbranch){
TMPL_free_tagnode(tn->TMPL_tag.ifelse.fbranch);
}
break;
case tag_loop:
free(tn->TMPL_tag.loop.loopname);
TMPL_free_tagnode(tn->TMPL_tag.loop.body);
break;
case tag_null:
case tag_break:
case tag_continue:
case tag_end:
break;
}
if(tn->next != NULL)
TMPL_free_tagnode(tn->next);
free(tn);
}
char*
TMPL_get_error(struct TMPL_templates* t){
size_t dummy;
return bstringify(t->errout,&dummy);
}