libs/web: rewrite template engine, merge lmo library
[project/luci.git] / libs / web / src / template_parser.c
1 /*
2  * LuCI Template - Parser implementation
3  *
4  *   Copyright (C) 2009-2012 Jo-Philipp Wich <xm@subsignal.org>
5  *
6  *  Licensed under the Apache License, Version 2.0 (the "License");
7  *  you may not use this file except in compliance with the License.
8  *  You may obtain a copy of the License at
9  *
10  *      http://www.apache.org/licenses/LICENSE-2.0
11  *
12  *  Unless required by applicable law or agreed to in writing, software
13  *  distributed under the License is distributed on an "AS IS" BASIS,
14  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15  *  See the License for the specific language governing permissions and
16  *  limitations under the License.
17  */
18
19 #include "template_parser.h"
20 #include "template_utils.h"
21 #include "template_lmo.h"
22
23
24 /* leading and trailing code for different types */
25 const char *gen_code[9][2] = {
26         { NULL,                                 NULL                    },
27         { "write(\"",                   "\")"                   },
28         { NULL,                                 NULL                    },
29         { "write(tostring(",    " or \"\"))"    },
30         { "include(\"",                 "\")"                   },
31         { "write(\"",                   "\")"                   },
32         { "write(\"",                   "\")"                   },
33         { NULL,                                 " "                             },
34         { NULL,                                 NULL                    },
35 };
36
37 /* Simple strstr() like function that takes len arguments for both haystack and needle. */
38 static char *strfind(char *haystack, int hslen, const char *needle, int ndlen)
39 {
40         int match = 0;
41         int i, j;
42
43         for( i = 0; i < hslen; i++ )
44         {
45                 if( haystack[i] == needle[0] )
46                 {
47                         match = ((ndlen == 1) || ((i + ndlen) <= hslen));
48
49                         for( j = 1; (j < ndlen) && ((i + j) < hslen); j++ )
50                         {
51                                 if( haystack[i+j] != needle[j] )
52                                 {
53                                         match = 0;
54                                         break;
55                                 }
56                         }
57
58                         if( match )
59                                 return &haystack[i];
60                 }
61         }
62
63         return NULL;
64 }
65
66 struct template_parser * template_open(const char *file)
67 {
68         struct stat s;
69         static struct template_parser *parser;
70
71         if (!(parser = malloc(sizeof(*parser))))
72                 goto err;
73
74         memset(parser, 0, sizeof(*parser));
75         parser->fd = -1;
76         parser->file = file;
77
78         if (stat(file, &s))
79                 goto err;
80
81         if ((parser->fd = open(file, O_RDONLY)) < 0)
82                 goto err;
83
84         parser->size = s.st_size;
85         parser->mmap = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
86                                                 parser->fd, 0);
87
88         if (parser->mmap != MAP_FAILED)
89         {
90                 parser->off = parser->mmap;
91                 parser->cur_chunk.type = T_TYPE_INIT;
92                 parser->cur_chunk.s    = parser->mmap;
93                 parser->cur_chunk.e    = parser->mmap;
94
95                 return parser;
96         }
97
98 err:
99         template_close(parser);
100         return NULL;
101 }
102
103 void template_close(struct template_parser *parser)
104 {
105         if (!parser)
106                 return;
107
108         if (parser->gc != NULL)
109                 free(parser->gc);
110
111         if ((parser->mmap != NULL) && (parser->mmap != MAP_FAILED))
112                 munmap(parser->mmap, parser->size);
113
114         if (parser->fd >= 0)
115                 close(parser->fd);
116
117         free(parser);
118 }
119
120 void template_text(struct template_parser *parser, const char *e)
121 {
122         const char *s = parser->off;
123
124         if (s < (parser->mmap + parser->size))
125         {
126                 if (parser->strip_after)
127                 {
128                         while ((s <= e) && isspace(*s))
129                                 s++;
130                 }
131
132                 parser->cur_chunk.type = T_TYPE_TEXT;
133         }
134         else
135         {
136                 parser->cur_chunk.type = T_TYPE_EOF;
137         }
138
139         parser->cur_chunk.line = parser->line;
140         parser->cur_chunk.s = s;
141         parser->cur_chunk.e = e;
142 }
143
144 void template_code(struct template_parser *parser, const char *e)
145 {
146         const char *s = parser->off;
147
148         parser->strip_before = 0;
149         parser->strip_after = 0;
150
151         if (*s == '-')
152         {
153                 parser->strip_before = 1;
154                 for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
155         }
156
157         if (*(e-1) == '-')
158         {
159                 parser->strip_after = 1;
160                 for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
161         }
162
163         switch (*s)
164         {
165                 /* comment */
166                 case '#':
167                         s++;
168                         parser->cur_chunk.type = T_TYPE_COMMENT;
169                         break;
170
171                 /* include */
172                 case '+':
173                         s++;
174                         parser->cur_chunk.type = T_TYPE_INCLUDE;
175                         break;
176
177                 /* translate */
178                 case ':':
179                         s++;
180                         parser->cur_chunk.type = T_TYPE_I18N;
181                         break;
182
183                 /* translate raw */
184                 case '_':
185                         s++;
186                         parser->cur_chunk.type = T_TYPE_I18N_RAW;
187                         break;
188
189                 /* expr */
190                 case '=':
191                         s++;
192                         parser->cur_chunk.type = T_TYPE_EXPR;
193                         break;
194
195                 /* code */
196                 default:
197                         parser->cur_chunk.type = T_TYPE_CODE;
198                         break;
199         }
200
201         parser->cur_chunk.line = parser->line;
202         parser->cur_chunk.s = s;
203         parser->cur_chunk.e = e;
204 }
205
206 static const char *
207 template_format_chunk(struct template_parser *parser, size_t *sz)
208 {
209         const char *s, *p;
210         const char *head, *tail;
211         struct template_chunk *c = &parser->prv_chunk;
212         struct template_buffer *buf;
213
214         *sz = 0;
215         s = parser->gc = NULL;
216
217         if (parser->strip_before && c->type == T_TYPE_TEXT)
218         {
219                 while ((c->e > c->s) && isspace(*(c->e - 1)))
220                         c->e--;
221         }
222
223         /* empty chunk */
224         if (c->s == c->e)
225         {
226                 if (c->type == T_TYPE_EOF)
227                 {
228                         *sz = 0;
229                         s = NULL;
230                 }
231                 else
232                 {
233                         *sz = 1;
234                         s = " ";
235                 }
236         }
237
238         /* format chunk */
239         else if ((buf = buf_init(c->e - c->s)) != NULL)
240         {
241                 if ((head = gen_code[c->type][0]) != NULL)
242                         buf_append(buf, head, strlen(head));
243
244                 switch (c->type)
245                 {
246                         case T_TYPE_TEXT:
247                                 escape_luastr(buf, c->s, c->e - c->s, 0);
248                                 break;
249
250                         case T_TYPE_EXPR:
251                                 buf_append(buf, c->s, c->e - c->s);
252                                 for (p = c->s; p < c->e; p++)
253                                         parser->line += (*p == '\n');
254                                 break;
255
256                         case T_TYPE_INCLUDE:
257                                 escape_luastr(buf, c->s, c->e - c->s, 0);
258                                 break;
259
260                         case T_TYPE_I18N:
261                                 translate_luastr(buf, c->s, c->e - c->s, 1);
262                                 break;
263
264                         case T_TYPE_I18N_RAW:
265                                 translate_luastr(buf, c->s, c->e - c->s, 0);
266                                 break;
267
268                         case T_TYPE_CODE:
269                                 buf_append(buf, c->s, c->e - c->s);
270                                 for (p = c->s; p < c->e; p++)
271                                         parser->line += (*p == '\n');
272                                 break;
273                 }
274
275                 if ((tail = gen_code[c->type][1]) != NULL)
276                         buf_append(buf, tail, strlen(tail));
277
278                 *sz = buf_length(buf);
279                 s = parser->gc = buf_destroy(buf);
280
281                 if (!*sz)
282                 {
283                         *sz = 1;
284                         s = " ";
285                 }
286         }
287
288         return s;
289 }
290
291 const char *template_reader(lua_State *L, void *ud, size_t *sz)
292 {
293         struct template_parser *parser = ud;
294         int rem = parser->size - (parser->off - parser->mmap);
295         char *tag;
296
297         parser->prv_chunk = parser->cur_chunk;
298
299         /* free previous string */
300         if (parser->gc)
301         {
302                 free(parser->gc);
303                 parser->gc = NULL;
304         }
305
306         /* before tag */
307         if (!parser->in_expr)
308         {
309                 if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL)
310                 {
311                         template_text(parser, tag);
312                         parser->off = tag + 2;
313                         parser->in_expr = 1;
314                 }
315                 else
316                 {
317                         template_text(parser, parser->mmap + parser->size);
318                         parser->off = parser->mmap + parser->size;
319                 }
320         }
321
322         /* inside tag */
323         else
324         {
325                 if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL)
326                 {
327                         template_code(parser, tag);
328                         parser->off = tag + 2;
329                         parser->in_expr = 0;
330                 }
331                 else
332                 {
333                         /* unexpected EOF */
334                         template_code(parser, parser->mmap + parser->size);
335
336                         *sz = 1;
337                         return "\033";
338                 }
339         }
340
341         return template_format_chunk(parser, sz);
342 }
343
344 int template_error(lua_State *L, struct template_parser *parser)
345 {
346         const char *err = luaL_checkstring(L, -1);
347         const char *off = parser->prv_chunk.s;
348         const char *ptr;
349         char msg[1024];
350         int line = 0;
351         int chunkline = 0;
352
353         fprintf(stderr, "E[%s]\n", err);
354
355         if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL)
356         {
357                 chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
358
359                 while (*ptr)
360                 {
361                         if (*ptr++ == ' ')
362                         {
363                                 err = ptr;
364                                 break;
365                         }
366                 }
367         }
368
369         if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL)
370         {
371                 off = parser->mmap + parser->size;
372                 err = "'%>' expected before end of file";
373                 chunkline = 0;
374         }
375
376         for (ptr = parser->mmap; ptr < off; ptr++)
377                 if (*ptr == '\n')
378                         line++;
379
380         snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
381                          parser->file, line + chunkline, err ? err : "(unknown error)");
382
383         lua_pushnil(L);
384         lua_pushinteger(L, line + chunkline);
385         lua_pushstring(L, msg);
386
387         return 3;
388 }