Rework LuCI build system
[project/luci.git] / modules / luci-base / 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 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->data = mmap(NULL, parser->size, PROT_READ, MAP_PRIVATE,
86 parser->fd, 0);
87
88 if (parser->data != MAP_FAILED)
89 {
90 parser->off = parser->data;
91 parser->cur_chunk.type = T_TYPE_INIT;
92 parser->cur_chunk.s = parser->data;
93 parser->cur_chunk.e = parser->data;
94
95 return parser;
96 }
97
98 err:
99 template_close(parser);
100 return NULL;
101 }
102
103 struct template_parser * template_string(const char *str, uint32_t len)
104 {
105 struct template_parser *parser;
106
107 if (!str) {
108 errno = EINVAL;
109 goto err;
110 }
111
112 if (!(parser = malloc(sizeof(*parser))))
113 goto err;
114
115 memset(parser, 0, sizeof(*parser));
116 parser->fd = -1;
117
118 parser->size = len;
119 parser->data = (char*)str;
120
121 parser->off = parser->data;
122 parser->cur_chunk.type = T_TYPE_INIT;
123 parser->cur_chunk.s = parser->data;
124 parser->cur_chunk.e = parser->data;
125
126 return parser;
127
128 err:
129 template_close(parser);
130 return NULL;
131 }
132
133 void template_close(struct template_parser *parser)
134 {
135 if (!parser)
136 return;
137
138 if (parser->gc != NULL)
139 free(parser->gc);
140
141 /* if file is not set, we were parsing a string */
142 if (parser->file) {
143 if ((parser->data != NULL) && (parser->data != MAP_FAILED))
144 munmap(parser->data, parser->size);
145
146 if (parser->fd >= 0)
147 close(parser->fd);
148 }
149
150 free(parser);
151 }
152
153 void template_text(struct template_parser *parser, const char *e)
154 {
155 const char *s = parser->off;
156
157 if (s < (parser->data + parser->size))
158 {
159 if (parser->strip_after)
160 {
161 while ((s <= e) && isspace(*s))
162 s++;
163 }
164
165 parser->cur_chunk.type = T_TYPE_TEXT;
166 }
167 else
168 {
169 parser->cur_chunk.type = T_TYPE_EOF;
170 }
171
172 parser->cur_chunk.line = parser->line;
173 parser->cur_chunk.s = s;
174 parser->cur_chunk.e = e;
175 }
176
177 void template_code(struct template_parser *parser, const char *e)
178 {
179 const char *s = parser->off;
180
181 parser->strip_before = 0;
182 parser->strip_after = 0;
183
184 if (*s == '-')
185 {
186 parser->strip_before = 1;
187 for (s++; (s <= e) && (*s == ' ' || *s == '\t'); s++);
188 }
189
190 if (*(e-1) == '-')
191 {
192 parser->strip_after = 1;
193 for (e--; (e >= s) && (*e == ' ' || *e == '\t'); e--);
194 }
195
196 switch (*s)
197 {
198 /* comment */
199 case '#':
200 s++;
201 parser->cur_chunk.type = T_TYPE_COMMENT;
202 break;
203
204 /* include */
205 case '+':
206 s++;
207 parser->cur_chunk.type = T_TYPE_INCLUDE;
208 break;
209
210 /* translate */
211 case ':':
212 s++;
213 parser->cur_chunk.type = T_TYPE_I18N;
214 break;
215
216 /* translate raw */
217 case '_':
218 s++;
219 parser->cur_chunk.type = T_TYPE_I18N_RAW;
220 break;
221
222 /* expr */
223 case '=':
224 s++;
225 parser->cur_chunk.type = T_TYPE_EXPR;
226 break;
227
228 /* code */
229 default:
230 parser->cur_chunk.type = T_TYPE_CODE;
231 break;
232 }
233
234 parser->cur_chunk.line = parser->line;
235 parser->cur_chunk.s = s;
236 parser->cur_chunk.e = e;
237 }
238
239 static const char *
240 template_format_chunk(struct template_parser *parser, size_t *sz)
241 {
242 const char *s, *p;
243 const char *head, *tail;
244 struct template_chunk *c = &parser->prv_chunk;
245 struct template_buffer *buf;
246
247 *sz = 0;
248 s = parser->gc = NULL;
249
250 if (parser->strip_before && c->type == T_TYPE_TEXT)
251 {
252 while ((c->e > c->s) && isspace(*(c->e - 1)))
253 c->e--;
254 }
255
256 /* empty chunk */
257 if (c->s == c->e)
258 {
259 if (c->type == T_TYPE_EOF)
260 {
261 *sz = 0;
262 s = NULL;
263 }
264 else
265 {
266 *sz = 1;
267 s = " ";
268 }
269 }
270
271 /* format chunk */
272 else if ((buf = buf_init(c->e - c->s)) != NULL)
273 {
274 if ((head = gen_code[c->type][0]) != NULL)
275 buf_append(buf, head, strlen(head));
276
277 switch (c->type)
278 {
279 case T_TYPE_TEXT:
280 luastr_escape(buf, c->s, c->e - c->s, 0);
281 break;
282
283 case T_TYPE_EXPR:
284 buf_append(buf, c->s, c->e - c->s);
285 for (p = c->s; p < c->e; p++)
286 parser->line += (*p == '\n');
287 break;
288
289 case T_TYPE_INCLUDE:
290 luastr_escape(buf, c->s, c->e - c->s, 0);
291 break;
292
293 case T_TYPE_I18N:
294 luastr_translate(buf, c->s, c->e - c->s, 1);
295 break;
296
297 case T_TYPE_I18N_RAW:
298 luastr_translate(buf, c->s, c->e - c->s, 0);
299 break;
300
301 case T_TYPE_CODE:
302 buf_append(buf, c->s, c->e - c->s);
303 for (p = c->s; p < c->e; p++)
304 parser->line += (*p == '\n');
305 break;
306 }
307
308 if ((tail = gen_code[c->type][1]) != NULL)
309 buf_append(buf, tail, strlen(tail));
310
311 *sz = buf_length(buf);
312 s = parser->gc = buf_destroy(buf);
313
314 if (!*sz)
315 {
316 *sz = 1;
317 s = " ";
318 }
319 }
320
321 return s;
322 }
323
324 const char *template_reader(lua_State *L, void *ud, size_t *sz)
325 {
326 struct template_parser *parser = ud;
327 int rem = parser->size - (parser->off - parser->data);
328 char *tag;
329
330 parser->prv_chunk = parser->cur_chunk;
331
332 /* free previous string */
333 if (parser->gc)
334 {
335 free(parser->gc);
336 parser->gc = NULL;
337 }
338
339 /* before tag */
340 if (!parser->in_expr)
341 {
342 if ((tag = strfind(parser->off, rem, "<%", 2)) != NULL)
343 {
344 template_text(parser, tag);
345 parser->off = tag + 2;
346 parser->in_expr = 1;
347 }
348 else
349 {
350 template_text(parser, parser->data + parser->size);
351 parser->off = parser->data + parser->size;
352 }
353 }
354
355 /* inside tag */
356 else
357 {
358 if ((tag = strfind(parser->off, rem, "%>", 2)) != NULL)
359 {
360 template_code(parser, tag);
361 parser->off = tag + 2;
362 parser->in_expr = 0;
363 }
364 else
365 {
366 /* unexpected EOF */
367 template_code(parser, parser->data + parser->size);
368
369 *sz = 1;
370 return "\033";
371 }
372 }
373
374 return template_format_chunk(parser, sz);
375 }
376
377 int template_error(lua_State *L, struct template_parser *parser)
378 {
379 const char *err = luaL_checkstring(L, -1);
380 const char *off = parser->prv_chunk.s;
381 const char *ptr;
382 char msg[1024];
383 int line = 0;
384 int chunkline = 0;
385
386 if ((ptr = strfind((char *)err, strlen(err), "]:", 2)) != NULL)
387 {
388 chunkline = atoi(ptr + 2) - parser->prv_chunk.line;
389
390 while (*ptr)
391 {
392 if (*ptr++ == ' ')
393 {
394 err = ptr;
395 break;
396 }
397 }
398 }
399
400 if (strfind((char *)err, strlen(err), "'char(27)'", 10) != NULL)
401 {
402 off = parser->data + parser->size;
403 err = "'%>' expected before end of file";
404 chunkline = 0;
405 }
406
407 for (ptr = parser->data; ptr < off; ptr++)
408 if (*ptr == '\n')
409 line++;
410
411 snprintf(msg, sizeof(msg), "Syntax error in %s:%d: %s",
412 parser->file ? parser->file : "[string]", line + chunkline, err ? err : "(unknown error)");
413
414 lua_pushnil(L);
415 lua_pushinteger(L, line + chunkline);
416 lua_pushstring(L, msg);
417
418 return 3;
419 }