summaryrefslogtreecommitdiffhomepage
path: root/libs/web/src/template_parser.c
diff options
context:
space:
mode:
Diffstat (limited to 'libs/web/src/template_parser.c')
-rw-r--r--libs/web/src/template_parser.c603
1 files changed, 263 insertions, 340 deletions
diff --git a/libs/web/src/template_parser.c b/libs/web/src/template_parser.c
index a0a400bdf7..69f0f1c485 100644
--- a/libs/web/src/template_parser.c
+++ b/libs/web/src/template_parser.c
@@ -1,7 +1,7 @@
/*
* LuCI Template - Parser implementation
*
- * Copyright (C) 2009 Jo-Philipp Wich <xm@subsignal.org>
+ * Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -17,17 +17,21 @@
*/
#include "template_parser.h"
+#include "template_utils.h"
+#include "template_lmo.h"
/* leading and trailing code for different types */
-const char * gen_code[7][2] = {
+const char *gen_code[9][2] = {
+ { NULL, NULL },
{ "write(\"", "\")" },
{ NULL, NULL },
{ "write(tostring(", " or \"\"))" },
{ "include(\"", "\")" },
- { "write(pcdata(translate(\"", "\")))" },
- { "write(translate(\"", "\"))" },
- { NULL, " " }
+ { "write(\"", "\")" },
+ { "write(\"", "\")" },
+ { NULL, " " },
+ { NULL, NULL },
};
/* Simple strstr() like function that takes len arguments for both haystack and needle. */
@@ -59,407 +63,326 @@ static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
return NULL;
}
-/*
- * Inspect current read buffer and find the number of "vague" characters at the end
- * which could indicate an opening token. Returns the number of "vague" chars.
- * The last continuous sequence of whitespace, optionally followed by a "<" is
- * treated as "vague" because whitespace may be discarded if the upcoming opening
- * token indicates pre-whitespace-removal ("<%-"). A single remaining "<" char
- * can't be differentiated from an opening token ("<%"), so it's kept to be processed
- * in the next cycle.
- */
-static int stokscan(struct template_parser *data, int off, int no_whitespace)
+struct template_parser * template_open(const char *file)
{
- int i;
- int skip = 0;
- int tokoff = data->bufsize - 1;
+ struct stat s;
+ static struct template_parser *parser;
- for( i = tokoff; i >= off; i-- )
- {
- if( data->buf[i] == T_TOK_START[0] )
- {
- skip = tokoff - i + 1;
- tokoff = i - 1;
- break;
- }
- }
+ if (!(parser = malloc(sizeof(*parser))))
+ goto err;
+
+ memset(parser, 0, sizeof(*parser));
+ parser->fd = -1;
+ parser->file = file;
+
+ if (stat(file, &s))
+ goto err;
+
+ if ((parser->fd = open(file, O_RDONLY)) < 0)
+ goto err;
- if( !no_whitespace )
+ parser->size = s.st_size;
+ parser->mmap = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
+ parser->fd, 0);
+
+ if (parser->mmap != MAP_FAILED)
{
- for( i = tokoff; i >= off; i-- )
- {
- if( isspace(data->buf[i]) )
- skip++;
- else
- break;
- }
+ parser->off = parser->mmap;
+ parser->cur_chunk.type = T_TYPE_INIT;
+ parser->cur_chunk.s = parser->mmap;
+ parser->cur_chunk.e = parser->mmap;
+
+ return parser;
}
- return skip;
+err:
+ template_close(parser);
+ return NULL;
}
-/*
- * Similar to stokscan() but looking for closing token indicators.
- * Matches "-", optionally followed by a "%" char.
- */
-static int etokscan(struct template_parser *data)
+void template_close(struct template_parser *parser)
{
- int skip = 0;
+ if (!parser)
+ return;
+
+ if (parser->gc != NULL)
+ free(parser->gc);
- if( (data->bufsize > 0) && (data->buf[data->bufsize-1] == T_TOK_END[0]) )
- skip++;
+ if ((parser->mmap != NULL) && (parser->mmap != MAP_FAILED))
+ munmap(parser->mmap, parser->size);
- if( (data->bufsize > skip) && (data->buf[data->bufsize-skip-1] == T_TOK_SKIPWS[0]) )
- skip++;
+ if (parser->fd >= 0)
+ close(parser->fd);
- return skip;
+ free(parser);
}
-/*
- * Generate Lua expressions from the given raw code, write it into the
- * output buffer and set the lua_Reader specific size pointer.
- * Takes parser-state, lua_Reader's size pointer and generator flags
- * as parameter. The given flags indicate whether leading or trailing
- * code should be added. Returns a pointer to the output buffer.
- */
-static const char * generate_expression(struct template_parser *data, size_t *sz, int what)
+void template_text(struct template_parser *parser, const char *e)
{
- char tmp[T_OUTBUFSZ];
- int i;
- int size = 0;
- int start = 0;
- int whitespace = 0;
-
- memset(tmp, 0, T_OUTBUFSZ);
-
- /* Inject leading expression code (if any) */
- if( (what & T_GEN_START) && (gen_code[data->type][0] != NULL) )
- {
- memcpy(tmp, gen_code[data->type][0], strlen(gen_code[data->type][0]));
- size += strlen(gen_code[data->type][0]);
- }
+ const char *s = parser->off;
- /* Parse source buffer */
- for( i = 0; i < data->outsize; i++ )
+ if (s < (parser->mmap + parser->size))
{
- /* Skip leading whitespace for non-raw and non-expr chunks */
- if( !start && isspace(data->out[i]) && (data->type == T_TYPE_I18N ||
- data->type == T_TYPE_I18N_RAW || data->type == T_TYPE_INCLUDE) )
- continue;
- else if( !start )
- start = 1;
-
- /* Found whitespace after i18n key */
- if( data->type == T_TYPE_I18N || data->type == T_TYPE_I18N_RAW )
+ if (parser->strip_after)
{
- /* Is initial whitespace, insert space */
- if( !whitespace && isspace(data->out[i]) )
- {
- tmp[size++] = ' ';
- whitespace = 1;
- }
-
- /* Suppress subsequent whitespace, escape special chars */
- else if( !isspace(data->out[i]) )
- {
- if( data->out[i] == '\\' || data->out[i] == '"' )
- tmp[size++] = '\\';
-
- tmp[size++] = data->out[i];
- whitespace = 0;
- }
+ while ((s <= e) && isspace(*s))
+ s++;
}
- /* Escape quotes, backslashes and newlines for plain and include expressions */
- else if( (data->type == T_TYPE_TEXT || data->type == T_TYPE_INCLUDE) &&
- (data->out[i] == '\\' || data->out[i] == '"' || data->out[i] == '\n' || data->out[i] == '\t') )
- {
- tmp[size++] = '\\';
+ parser->cur_chunk.type = T_TYPE_TEXT;
+ }
+ else
+ {
+ parser->cur_chunk.type = T_TYPE_EOF;
+ }
- switch(data->out[i])
- {
- case '\n':
- tmp[size++] = 'n';
- break;
+ parser->cur_chunk.line = parser->line;
+ parser->cur_chunk.s = s;
+ parser->cur_chunk.e = e;
+}
- case '\t':
- tmp[size++] = 't';
- break;
+void template_code(struct template_parser *parser, const char *e)
+{
+ const char *s = parser->off;
- default:
- tmp[size++] = data->out[i];
- }
- }
+ parser->strip_before = 0;
+ parser->strip_after = 0;
- /* Normal char */
- else
- {
- tmp[size++] = data->out[i];
- }
+ if (*s == '-')
+ {
+ parser->strip_before = 1;
+ for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
}
- /* Inject trailing expression code (if any) */
- if( (what & T_GEN_END) && (gen_code[data->type][1] != NULL) )
+ if (*(e-1) == '-')
{
- /* Strip trailing space for i18n expressions */
- if( data->type == T_TYPE_I18N || data->type == T_TYPE_I18N_RAW )
- if( (size > 0) && (tmp[size-1] == ' ') )
- size--;
-
- memcpy(&tmp[size], gen_code[data->type][1], strlen(gen_code[data->type][1]));
- size += strlen(gen_code[data->type][1]);
+ parser->strip_after = 1;
+ for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
}
- *sz = data->outsize = size;
- memset(data->out, 0, T_OUTBUFSZ);
- memcpy(data->out, tmp, size);
+ switch (*s)
+ {
+ /* comment */
+ case '#':
+ s++;
+ parser->cur_chunk.type = T_TYPE_COMMENT;
+ break;
- //printf("<<<%i|%i|%i|%s>>>\n", what, data->type, *sz, data->out);
+ /* include */
+ case '+':
+ s++;
+ parser->cur_chunk.type = T_TYPE_INCLUDE;
+ break;
- return data->out;
-}
+ /* translate */
+ case ':':
+ s++;
+ parser->cur_chunk.type = T_TYPE_I18N;
+ break;
-/*
- * Move the number of bytes specified in data->bufsize from the
- * given source pointer to the beginning of the read buffer.
- */
-static void bufmove(struct template_parser *data, const char *src)
-{
- if( data->bufsize > 0 )
- memmove(data->buf, src, data->bufsize);
- else if( data->bufsize < 0 )
- data->bufsize = 0;
+ /* translate raw */
+ case '_':
+ s++;
+ parser->cur_chunk.type = T_TYPE_I18N_RAW;
+ break;
+
+ /* expr */
+ case '=':
+ s++;
+ parser->cur_chunk.type = T_TYPE_EXPR;
+ break;
- data->buf[data->bufsize] = 0;
+ /* code */
+ default:
+ parser->cur_chunk.type = T_TYPE_CODE;
+ break;
+ }
+
+ parser->cur_chunk.line = parser->line;
+ parser->cur_chunk.s = s;
+ parser->cur_chunk.e = e;
}
-/*
- * Move the given amount of bytes from the given source pointer
- * to the output buffer and set data->outputsize.
- */
-static void bufout(struct template_parser *data, const char *src, int len)
+static const char *
+template_format_chunk(struct template_parser *parser, size_t *sz)
{
- if( len >= 0 )
- {
- memset(data->out, 0, T_OUTBUFSZ);
- memcpy(data->out, src, len);
- data->outsize = len;
- }
- else
+ const char *s, *p;
+ const char *head, *tail;
+ struct template_chunk *c = &parser->prv_chunk;
+ struct template_buffer *buf;
+
+ *sz = 0;
+ s = parser->gc = NULL;
+
+ if (parser->strip_before && c->type == T_TYPE_TEXT)
{
- data->outsize = 0;
+ while ((c->e > c->s) && isspace(*(c->e - 1)))
+ c->e--;
}
-}
-/*
- * lua_Reader compatible function that parses template code on demand from
- * the given file handle.
- */
-const char *template_reader(lua_State *L, void *ud, size_t *sz)
-{
- struct template_parser *data = ud;
- char *match = NULL;
- int off = 0;
- int ignore = 0;
- int genflags = 0;
- int readlen = 0;
- int vague = 0;
-
- while( !(data->flags & T_FLAG_EOF) || (data->bufsize > 0) )
+ /* empty chunk */
+ if (c->s == c->e)
{
- /* Fill buffer */
- if( !(data->flags & T_FLAG_EOF) && (data->bufsize < T_READBUFSZ) )
+ if (c->type == T_TYPE_EOF)
+ {
+ *sz = 0;
+ s = NULL;
+ }
+ else
{
- if( (readlen = read(data->fd, &data->buf[data->bufsize], T_READBUFSZ - data->bufsize)) > 0 )
- data->bufsize += readlen;
- else if( readlen == 0 )
- data->flags |= T_FLAG_EOF;
- else
- return NULL;
+ *sz = 1;
+ s = " ";
}
+ }
+
+ /* format chunk */
+ else if ((buf = buf_init(c->e - c->s)) != NULL)
+ {
+ if ((head = gen_code[c->type][0]) != NULL)
+ buf_append(buf, head, strlen(head));
- /* Evaluate state */
- switch(data->state)
+ switch (c->type)
{
- /* Plain text chunk (before "<%") */
- case T_STATE_TEXT_INIT:
- case T_STATE_TEXT_NEXT:
- off = 0; ignore = 0; *sz = 0;
- data->type = T_TYPE_TEXT;
-
- /* Skip leading whitespace if requested */
- if( data->flags & T_FLAG_SKIPWS )
- {
- data->flags &= ~T_FLAG_SKIPWS;
- while( (off < data->bufsize) && isspace(data->buf[off]) )
- off++;
- }
+ case T_TYPE_TEXT:
+ escape_luastr(buf, c->s, c->e - c->s, 0);
+ break;
- /* Found "<%" */
- if( (match = strfind(&data->buf[off], data->bufsize - off - 1, T_TOK_START, strlen(T_TOK_START))) != NULL )
- {
- readlen = (int)(match - &data->buf[off]);
- data->bufsize -= (readlen + strlen(T_TOK_START) + off);
- match += strlen(T_TOK_START);
-
- /* Check for leading '-' */
- if( match[0] == T_TOK_SKIPWS[0] )
- {
- data->bufsize--;
- match++;
-
- while( (readlen > 1) && isspace(data->buf[off+readlen-1]) )
- {
- readlen--;
- }
- }
-
- bufout(data, &data->buf[off], readlen);
- bufmove(data, match);
- data->state = T_STATE_CODE_INIT;
- }
+ case T_TYPE_EXPR:
+ buf_append(buf, c->s, c->e - c->s);
+ for (p = c->s; p < c->e; p++)
+ parser->line += (*p == '\n');
+ break;
- /* Maybe plain chunk */
- else
- {
- /* Preserve trailing "<" or white space, maybe a start token */
- vague = stokscan(data, off, 0);
-
- /* We can process some bytes ... */
- if( vague < data->bufsize )
- {
- readlen = data->bufsize - vague - off;
- }
-
- /* No bytes to process, so try to remove at least whitespace ... */
- else
- {
- /* ... but try to preserve trailing "<" ... */
- vague = stokscan(data, off, 1);
-
- if( vague < data->bufsize )
- {
- readlen = data->bufsize - vague - off;
- }
-
- /* ... no chance, push out buffer */
- else
- {
- readlen = vague - off;
- vague = 0;
- }
- }
-
- bufout(data, &data->buf[off], readlen);
-
- data->state = T_STATE_TEXT_NEXT;
- data->bufsize = vague;
- bufmove(data, &data->buf[off+readlen]);
- }
+ case T_TYPE_INCLUDE:
+ escape_luastr(buf, c->s, c->e - c->s, 0);
+ break;
- if( ignore || data->outsize == 0 )
- continue;
- else
- return generate_expression(data, sz, T_GEN_START | T_GEN_END);
+ case T_TYPE_I18N:
+ translate_luastr(buf, c->s, c->e - c->s, 1);
+ break;
+ case T_TYPE_I18N_RAW:
+ translate_luastr(buf, c->s, c->e - c->s, 0);
break;
- /* Ignored chunk (inside "<%# ... %>") */
- case T_STATE_SKIP:
- ignore = 1;
+ case T_TYPE_CODE:
+ buf_append(buf, c->s, c->e - c->s);
+ for (p = c->s; p < c->e; p++)
+ parser->line += (*p == '\n');
+ break;
+ }
- /* Initial code chunk ("<% ...") */
- case T_STATE_CODE_INIT:
- off = 0;
+ if ((tail = gen_code[c->type][1]) != NULL)
+ buf_append(buf, tail, strlen(tail));
- /* Check for leading '-' */
- if( data->buf[off] == T_TOK_SKIPWS[0] )
- off++;
+ *sz = buf_length(buf);
+ s = parser->gc = buf_destroy(buf);
- /* Determine code type */
- switch(data->buf[off])
- {
- case '#':
- ignore = 1;
- off++;
- data->type = T_TYPE_COMMENT;
- break;
-
- case '=':
- off++;
- data->type = T_TYPE_EXPR;
- break;
-
- case '+':
- off++;
- data->type = T_TYPE_INCLUDE;
- break;
-
- case ':':
- off++;
- data->type = T_TYPE_I18N;
- break;
-
- case '_':
- off++;
- data->type = T_TYPE_I18N_RAW;
- break;
-
- default:
- data->type = T_TYPE_CODE;
- break;
- }
+ if (!*sz)
+ {
+ *sz = 1;
+ s = " ";
+ }
+ }
- /* Subsequent code chunk ("..." or "... %>") */
- case T_STATE_CODE_NEXT:
- /* Found "%>" */
- if( (match = strfind(&data->buf[off], data->bufsize - off, T_TOK_END, strlen(T_TOK_END))) != NULL )
- {
- genflags = ( data->state == T_STATE_CODE_INIT )
- ? (T_GEN_START | T_GEN_END) : T_GEN_END;
+ return s;
+}
- readlen = (int)(match - &data->buf[off]);
+const char *template_reader(lua_State *L, void *ud, size_t *sz)
+{
+ struct template_parser *parser = ud;
+ int rem = parser->size - (parser->off - parser->mmap);
+ char *tag;
- /* Check for trailing '-' */
- if( (match > data->buf) && (*(match-1) == T_TOK_SKIPWS[0]) )
- {
- readlen--;
- data->flags |= T_FLAG_SKIPWS;
- }
+ parser->prv_chunk = parser->cur_chunk;
- bufout(data, &data->buf[off], readlen);
+ /* free previous string */
+ if (parser->gc)
+ {
+ free(parser->gc);
+ parser->gc = NULL;
+ }
- data->state = T_STATE_TEXT_INIT;
- data->bufsize -= ((int)(match - &data->buf[off]) + strlen(T_TOK_END) + off);
- bufmove(data, &match[strlen(T_TOK_END)]);
- }
+ /* before tag */
+ if (!parser->in_expr)
+ {
+ if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL)
+ {
+ template_text(parser, tag);
+ parser->off = tag + 2;
+ parser->in_expr = 1;
+ }
+ else
+ {
+ template_text(parser, parser->mmap + parser->size);
+ parser->off = parser->mmap + parser->size;
+ }
+ }
- /* Code chunk */
- else
- {
- genflags = ( data->state == T_STATE_CODE_INIT ) ? T_GEN_START : 0;
+ /* inside tag */
+ else
+ {
+ if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL)
+ {
+ template_code(parser, tag);
+ parser->off = tag + 2;
+ parser->in_expr = 0;
+ }
+ else
+ {
+ /* unexpected EOF */
+ template_code(parser, parser->mmap + parser->size);
- /* Preserve trailing "%" and "-", maybe an end token */
- vague = etokscan(data);
- readlen = data->bufsize - off - vague;
- bufout(data, &data->buf[off], readlen);
+ *sz = 1;
+ return "\033";
+ }
+ }
- data->state = T_STATE_CODE_NEXT;
- data->bufsize = vague;
- bufmove(data, &data->buf[readlen+off]);
- }
+ return template_format_chunk(parser, sz);
+}
- if( ignore || (data->outsize == 0 && !genflags) )
- continue;
- else
- return generate_expression(data, sz, genflags);
+int template_error(lua_State *L, struct template_parser *parser)
+{
+ const char *err = luaL_checkstring(L, -1);
+ const char *off = parser->prv_chunk.s;
+ const char *ptr;
+ char msg[1024];
+ int line = 0;
+ int chunkline = 0;
+ fprintf(stderr, "E[%s]\n", err);
+
+ if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL)
+ {
+ chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
+
+ while (*ptr)
+ {
+ if (*ptr++ == ' ')
+ {
+ err = ptr;
break;
+ }
}
}
- *sz = 0;
- return NULL;
-}
+ if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL)
+ {
+ off = parser->mmap + parser->size;
+ err = "'%>' expected before end of file";
+ chunkline = 0;
+ }
+
+ for (ptr = parser->mmap; ptr < off; ptr++)
+ if (*ptr == '\n')
+ line++;
+ snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
+ parser->file, line + chunkline, err ? err : "(unknown error)");
+ lua_pushnil(L);
+ lua_pushinteger(L, line + chunkline);
+ lua_pushstring(L, msg);
+
+ return 3;
+}