From: Jo-Philipp Wich <jow@openwrt.org>
Date: Sun, 25 Nov 2012 19:17:55 +0000 (+0000)
Subject: libs/web: rewrite template engine, merge lmo library
X-Git-Url: https://git.librecmc.org/?a=commitdiff_plain;h=0e50aa690af6cd9f37fa97b4a521fe523cce3c39;p=oweals%2Fluci.git

libs/web: rewrite template engine, merge lmo library

	- template parser: merge lmo library
	- template parser: rewrite to operate on memory mapped files
	- template parser: implement proper line number reporting on syntax errors
	- template parser: process translate tags directly and bypass Lua
	- template lmo: introduce load_catalog(), change_catalog() and close_catalog()
	- template lmo: rewrite index processing to operate directly on the memory mapped file
	- template lmo: implement binary search keys, reducing the lookup complexity to O(log n)
	- po2lmo: write sorted indixes when generating *.lmo archives
	- i18n: use the template parser for translations
	- i18n: stub load(), loadc() and clear()
	- i18n: map setlanguage() to load_catalog()
---

diff --git a/libs/lmo/Makefile b/libs/lmo/Makefile
deleted file mode 100644
index a15390cbe..000000000
--- a/libs/lmo/Makefile
+++ /dev/null
@@ -1,46 +0,0 @@
-ifneq (,$(wildcard ../../build/config.mk))
-include ../../build/config.mk
-include ../../build/module.mk
-include ../../build/gccconfig.mk
-else
-include standalone.mk
-endif
-
-LMO_LDFLAGS    =
-LMO_CFLAGS     =
-LMO_SO         = lmo.so
-LMO_PO2LMO     = po2lmo
-LMO_LOOKUP     = lookup
-LMO_COMMON_OBJ = src/lmo_core.o src/lmo_hash.o
-LMO_PO2LMO_OBJ = src/lmo_po2lmo.o
-LMO_LOOKUP_OBJ = src/lmo_lookup.o
-LMO_LUALIB_OBJ = src/lmo_lualib.o
-
-%.o: %.c
-	$(COMPILE) $(LMO_CFLAGS) $(LUA_CFLAGS) $(FPIC) -c -o $@ $< 
-
-compile: build-clean $(LMO_COMMON_OBJ) $(LMO_PO2LMO_OBJ) $(LMO_LOOKUP_OBJ) $(LMO_LUALIB_OBJ)
-	$(LINK) $(SHLIB_FLAGS) $(LMO_LDFLAGS) -o src/$(LMO_SO) \
-		$(LMO_COMMON_OBJ) $(LMO_LUALIB_OBJ)
-	$(LINK) $(LMO_LDFLAGS) -o src/$(LMO_PO2LMO) $(LMO_COMMON_OBJ) $(LMO_PO2LMO_OBJ)
-	$(LINK) $(LMO_LDFLAGS) -o src/$(LMO_LOOKUP) $(LMO_COMMON_OBJ) $(LMO_LOOKUP_OBJ)
-	mkdir -p dist$(LUA_LIBRARYDIR)
-	cp src/$(LMO_SO) dist$(LUA_LIBRARYDIR)/$(LMO_SO)
-
-install: build
-	cp -pR dist$(LUA_LIBRARYDIR)/* $(LUA_LIBRARYDIR)
-
-clean: build-clean
-
-build-clean:
-	rm -f src/*.o src/lookup src/po2lmo src/lmo.so
-
-host-compile: build-clean host-clean $(LMO_COMMON_OBJ) $(LMO_PO2LMO_OBJ)
-	$(LINK) $(LMO_LDFLAGS) -o src/$(LMO_PO2LMO) $(LMO_COMMON_OBJ) $(LMO_PO2LMO_OBJ)
-
-host-install: host-compile
-	cp src/$(LMO_PO2LMO) ../../build/$(LMO_PO2LMO)
-
-host-clean:
-	rm -f ../../build/$(LMO_PO2LMO)
-
diff --git a/libs/lmo/src/lmo.h b/libs/lmo/src/lmo.h
deleted file mode 100644
index ab17e873f..000000000
--- a/libs/lmo/src/lmo.h
+++ /dev/null
@@ -1,72 +0,0 @@
-/*
- * lmo - Lua Machine Objects - General header
- *
- *   Copyright (C) 2009 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.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-#ifndef _LMO_H_
-#define _LMO_H_
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <stdint.h>
-#include <string.h>
-#include <fcntl.h>
-#include <sys/stat.h>
-#include <sys/mman.h>
-#include <arpa/inet.h>
-#include <unistd.h>
-#include <errno.h>
-
-
-#if (defined(__GNUC__) && defined(__i386__))
-#define sfh_get16(d) (*((const uint16_t *) (d)))
-#else
-#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
-					   +(uint32_t)(((const uint8_t *)(d))[0]) )
-#endif
-
-
-struct lmo_entry {
-	uint32_t key_id;
-	uint32_t val_id;
-	uint32_t offset;
-	uint32_t length;
-	struct lmo_entry *next;
-} __attribute__((packed));
-
-typedef struct lmo_entry lmo_entry_t;
-
-
-struct lmo_archive {
-	int         fd;
-	uint32_t    length;
-	lmo_entry_t *index;
-	char        *mmap;
-};
-
-typedef struct lmo_archive lmo_archive_t;
-
-
-uint32_t sfh_hash(const char * data, int len);
-
-char _lmo_error[1024];
-const char * lmo_error(void);
-
-lmo_archive_t * lmo_open(const char *file);
-int lmo_lookup(lmo_archive_t *ar, const char *key, char *dest, int len);
-void lmo_close(lmo_archive_t *ar);
-
-#endif
diff --git a/libs/lmo/src/lmo_core.c b/libs/lmo/src/lmo_core.c
deleted file mode 100644
index 08141383e..000000000
--- a/libs/lmo/src/lmo_core.c
+++ /dev/null
@@ -1,234 +0,0 @@
-/*
- * lmo - Lua Machine Objects - Base functions
- *
- *   Copyright (C) 2009-2010 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.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-#include "lmo.h"
-
-extern char _lmo_error[1024];
-
-static int lmo_read32( int fd, uint32_t *val )
-{
-	if( read(fd, val, 4) < 4 )
-		return -1;
-
-	*val = ntohl(*val);
-
-	return 4;
-}
-
-static char * error(const char *message, int add_errno)
-{
-	memset(_lmo_error, 0, sizeof(_lmo_error));
-
-	if( add_errno )
-		snprintf(_lmo_error, sizeof(_lmo_error),
-			"%s: %s", message, strerror(errno));
-	else
-		snprintf(_lmo_error, sizeof(_lmo_error), "%s", message);
-
-	return NULL;
-}
-
-const char * lmo_error(void)
-{
-	return _lmo_error;
-}
-
-lmo_archive_t * lmo_open(const char *file)
-{
-	int in = -1;
-	uint32_t idx_offset = 0;
-	uint32_t i;
-	struct stat s;
-
-	lmo_archive_t *ar    = NULL;
-	lmo_entry_t   *head  = NULL;
-	lmo_entry_t   *entry = NULL;
-
-	if( stat(file, &s) == -1 )
-	{
-		error("Can not stat file", 1);
-		goto cleanup;
-	}
-
-	if( (in = open(file, O_RDONLY)) == -1 )
-	{
-		error("Can not open file", 1);
-		goto cleanup;
-	}
-
-	if( lseek(in, -sizeof(uint32_t), SEEK_END) == -1 )
-	{
-		error("Can not seek to eof", 1);
-		goto cleanup;
-	}
-
-	if( lmo_read32(in, &idx_offset) != 4 )
-	{
-		error("Unexpected EOF while reading index offset", 0);
-		goto cleanup;
-	}
-
-	if( lseek(in, (off_t)idx_offset, SEEK_SET) == -1 )
-	{
-		error("Can not seek to index offset", 1);
-		goto cleanup;
-	}
-
-	if( (ar = (lmo_archive_t *) malloc(sizeof(lmo_archive_t))) != NULL )
-	{
-		ar->fd     = in;
-		ar->length = idx_offset;
-
-		fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
-
-		for( i = idx_offset;
-		     i < (s.st_size - sizeof(uint32_t));
-		     i += (4 * sizeof(uint32_t))
-		) {
-			if( (entry = (lmo_entry_t *) malloc(sizeof(lmo_entry_t))) != NULL )
-			{
-				if( (lmo_read32(ar->fd, &entry->key_id) == 4) &&
-				    (lmo_read32(ar->fd, &entry->val_id) == 4) &&
-				    (lmo_read32(ar->fd, &entry->offset) == 4) &&
-				    (lmo_read32(ar->fd, &entry->length) == 4)
-				) {
-					entry->next = head;
-					head = entry;
-				}
-				else
-				{
-					error("Unexpected EOF while reading index entry", 0);
-					goto cleanup;
-				}
-			}
-			else
-			{
-				error("Out of memory", 0);
-				goto cleanup;
-			}
-		}
-
-		ar->index = head;
-
-		if( lseek(ar->fd, 0, SEEK_SET) == -1 )
-		{
-			error("Can not seek to start", 1);
-			goto cleanup;
-		}
-
-		if( (ar->mmap = mmap(NULL, ar->length, PROT_READ, MAP_PRIVATE, ar->fd, 0)) == MAP_FAILED )
-		{
-			error("Failed to memory map archive contents", 1);
-			goto cleanup;
-		}
-
-		return ar;
-	}
-	else
-	{
-		error("Out of memory", 0);
-		goto cleanup;
-	}
-
-
-	cleanup:
-
-	if( in > -1 )
-		close(in);
-
-	if( head != NULL )
-	{
-		entry = head;
-
-		while( entry != NULL )
-		{
-			head = entry->next;
-			free(entry);
-			entry = head;
-		}
-
-		head = entry = NULL;
-	}
-
-	if( ar != NULL )
-	{
-		if( (ar->mmap != NULL) && (ar->mmap != MAP_FAILED) )
-			munmap(ar->mmap, ar->length);
-
-		free(ar);
-		ar = NULL;
-	}
-
-	return NULL;
-}
-
-void lmo_close(lmo_archive_t *ar)
-{
-	lmo_entry_t *head  = NULL;
-	lmo_entry_t *entry = NULL;
-
-	if( ar != NULL )
-	{
-		entry = ar->index;
-
-		while( entry != NULL )
-		{
-			head = entry->next;
-			free(entry);
-			entry = head;
-		}
-
-		head = entry = NULL;
-
-		if( (ar->mmap != NULL) && (ar->mmap != MAP_FAILED) )
-			munmap(ar->mmap, ar->length);
-
-		close(ar->fd);
-		free(ar);
-
-		ar = NULL;
-	}
-}
-
-int lmo_lookup(lmo_archive_t *ar, const char *key, char *dest, int len)
-{
-	uint32_t look_key = sfh_hash(key, strlen(key));
-	int copy_len = -1;
-	lmo_entry_t *entry;
-
-	if( !ar )
-		return copy_len;
-
-	entry = ar->index;
-
-	while( entry != NULL )
-	{
-		if( entry->key_id == look_key )
-		{
-			copy_len = ((len - 1) > entry->length) ? entry->length : (len - 1);
-			memcpy(dest, &ar->mmap[entry->offset], copy_len);
-			dest[copy_len] = '\0';
-
-			break;
-		}
-
-		entry = entry->next;
-	}
-
-	return copy_len;
-}
diff --git a/libs/lmo/src/lmo_hash.c b/libs/lmo/src/lmo_hash.c
deleted file mode 100644
index bc8e6fe4e..000000000
--- a/libs/lmo/src/lmo_hash.c
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Hash function from http://www.azillionmonkeys.com/qed/hash.html
- * Copyright (C) 2004-2008 by Paul Hsieh
- */
-
-#include "lmo.h"
-
-uint32_t sfh_hash(const char * data, int len)
-{
-	uint32_t hash = len, tmp;
-	int rem;
-
-	if (len <= 0 || data == NULL) return 0;
-
-	rem = len & 3;
-	len >>= 2;
-
-	/* Main loop */
-	for (;len > 0; len--) {
-		hash  += sfh_get16(data);
-		tmp    = (sfh_get16(data+2) << 11) ^ hash;
-		hash   = (hash << 16) ^ tmp;
-		data  += 2*sizeof(uint16_t);
-		hash  += hash >> 11;
-	}
-
-	/* Handle end cases */
-	switch (rem) {
-		case 3: hash += sfh_get16(data);
-			hash ^= hash << 16;
-			hash ^= data[sizeof(uint16_t)] << 18;
-			hash += hash >> 11;
-			break;
-		case 2: hash += sfh_get16(data);
-			hash ^= hash << 11;
-			hash += hash >> 17;
-			break;
-		case 1: hash += *data;
-			hash ^= hash << 10;
-			hash += hash >> 1;
-	}
-
-	/* Force "avalanching" of final 127 bits */
-	hash ^= hash << 3;
-	hash += hash >> 5;
-	hash ^= hash << 4;
-	hash += hash >> 17;
-	hash ^= hash << 25;
-	hash += hash >> 6;
-
-	return hash;
-}
-
diff --git a/libs/lmo/src/lmo_lookup.c b/libs/lmo/src/lmo_lookup.c
deleted file mode 100644
index 8b48f7fac..000000000
--- a/libs/lmo/src/lmo_lookup.c
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * lmo - Lua Machine Objects - Lookup utility
- *
- *   Copyright (C) 2009 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.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-#include "lmo.h"
-
-extern char _lmo_error[1024];
-
-static void die(const char *msg)
-{
-	printf("Error: %s\n", msg);
-	exit(1);
-}
-
-static void usage(const char *name)
-{
-	printf("Usage: %s input.lmo key\n", name);
-	exit(1);
-}
-
-int main(int argc, char *argv[])
-{
-	char val[4096];
-	lmo_archive_t *ar = NULL;
-
-	if( argc != 3 )
-		usage(argv[0]);
-
-	if( (ar = (lmo_archive_t *) lmo_open(argv[1])) != NULL )
-	{
-		if( lmo_lookup(ar, argv[2], val, sizeof(val)) > -1 )
-		{
-			printf("%s\n", val);
-		}
-
-		lmo_close(ar);
-	}
-	else
-	{
-		die(lmo_error());
-	}
-
-	return 0;
-}
diff --git a/libs/lmo/src/lmo_lualib.c b/libs/lmo/src/lmo_lualib.c
deleted file mode 100644
index 5cc1e7d69..000000000
--- a/libs/lmo/src/lmo_lualib.c
+++ /dev/null
@@ -1,263 +0,0 @@
-/*
- * lmo - Lua Machine Objects - Lua binding
- *
- *   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.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-#include "lmo_lualib.h"
-
-extern char _lmo_error[1024];
-
-
-static int lmo_L_open(lua_State *L) {
-	const char *filename = luaL_checklstring(L, 1, NULL);
-	lmo_archive_t *ar, **udata;
-
-	if( (ar = lmo_open(filename)) != NULL )
-	{
-		if( (udata = lua_newuserdata(L, sizeof(lmo_archive_t *))) != NULL )
-		{
-			*udata = ar;
-			luaL_getmetatable(L, LMO_ARCHIVE_META);
-			lua_setmetatable(L, -2);
-			return 1;
-		}
-
-		lmo_close(ar);
-		lua_pushnil(L);
-		lua_pushstring(L, "out of memory");
-		return 2;
-	}
-
-	lua_pushnil(L);
-	lua_pushstring(L, lmo_error());
-	return 2;
-}
-
-static uint32_t _lmo_hash_string(lua_State *L, int n) {
-	size_t len;
-	const char *str = luaL_checklstring(L, n, &len);
-	char res[4096];
-	char *ptr, prev;
-
-	if (!str || len >= sizeof(res))
-		return 0;
-
-	for (prev = ' ', ptr = res; *str; prev = *str, str++)
-	{
-		if (isspace(*str))
-		{
-			if (!isspace(prev))
-				*ptr++ = ' ';
-		}
-		else
-		{
-			*ptr++ = *str;
-		}
-	}
-
-	if ((ptr > res) && isspace(*(ptr-1)))
-		ptr--;
-
-	return sfh_hash(res, ptr - res);
-}
-
-static int lmo_L_hash(lua_State *L) {
-	uint32_t hash = _lmo_hash_string(L, 1);
-	lua_pushinteger(L, (lua_Integer)hash);
-	return 1;
-}
-
-static lmo_luaentry_t *_lmo_push_entry(lua_State *L) {
-	lmo_luaentry_t *le;
-
-	if( (le = lua_newuserdata(L, sizeof(lmo_luaentry_t))) != NULL )
-	{
-		luaL_getmetatable(L, LMO_ENTRY_META);
-		lua_setmetatable(L, -2);
-
-		return le;
-	}
-
-	return NULL;
-}
-
-static int _lmo_lookup(lua_State *L, lmo_archive_t *ar, uint32_t hash) {
-	lmo_entry_t *e = ar->index;
-	lmo_luaentry_t *le = NULL;
-
-	while( e != NULL )
-	{
-		if( e->key_id == hash )
-		{
-			if( (le = _lmo_push_entry(L)) != NULL )
-			{
-				le->archive = ar;
-				le->entry   = e;
-				return 1;
-			}
-			else
-			{
-				lua_pushnil(L);
-				lua_pushstring(L, "out of memory");
-				return 2;
-			}
-		}
-
-		e = e->next;
-	}
-
-	lua_pushnil(L);
-	return 1;
-}
-
-static int lmo_L_get(lua_State *L) {
-	lmo_archive_t **ar = luaL_checkudata(L, 1, LMO_ARCHIVE_META);
-	uint32_t hash = (uint32_t) luaL_checkinteger(L, 2);
-	return _lmo_lookup(L, *ar, hash);
-}
-
-static int lmo_L_lookup(lua_State *L) {
-	lmo_archive_t **ar = luaL_checkudata(L, 1, LMO_ARCHIVE_META);
-	uint32_t hash = _lmo_hash_string(L, 2);
-	return _lmo_lookup(L, *ar, hash);
-}
-
-static int lmo_L_foreach(lua_State *L) {
-	lmo_archive_t **ar = luaL_checkudata(L, 1, LMO_ARCHIVE_META);
-	lmo_entry_t *e = (*ar)->index;
-
-	if( lua_isfunction(L, 2) )
-	{
-		while( e != NULL )
-		{
-			lua_pushvalue(L, 2);
-			lua_pushinteger(L, e->key_id);
-			lua_pushlstring(L, &(*ar)->mmap[e->offset], e->length);
-			lua_pcall(L, 2, 0, 0);
-			e = e->next;
-		}
-	}
-
-	return 0;
-}
-
-static int lmo_L__gc(lua_State *L) {
-	lmo_archive_t **ar = luaL_checkudata(L, 1, LMO_ARCHIVE_META);
-
-	if( (*ar) != NULL )
-		lmo_close(*ar);
-
-	*ar = NULL;
-
-	return 0;
-}
-
-static int lmo_L__tostring(lua_State *L) {
-	lmo_archive_t **ar = luaL_checkudata(L, 1, LMO_ARCHIVE_META);
-	lua_pushfstring(L, "LMO Archive (%d bytes)", (*ar)->length);
-	return 1;
-}
-
-
-static int _lmo_convert_entry(lua_State *L, int idx) {
-	lmo_luaentry_t *le = luaL_checkudata(L, idx, LMO_ENTRY_META);
-
-	lua_pushlstring(L,
-		&le->archive->mmap[le->entry->offset],
-		le->entry->length
-	);
-
-	return 1;
-}
-
-static int lmo_L_entry__tostring(lua_State *L) {
-	return _lmo_convert_entry(L, 1);
-}
-
-static int lmo_L_entry__concat(lua_State *L) {
-	if( lua_isuserdata(L, 1) )
-		_lmo_convert_entry(L, 1);
-	else
-		lua_pushstring(L, lua_tostring(L, 1));
-
-	if( lua_isuserdata(L, 2) )
-		_lmo_convert_entry(L, 2);
-	else
-		lua_pushstring(L, lua_tostring(L, 2));
-
-	lua_concat(L, 2);
-
-	return 1;
-}
-
-static int lmo_L_entry__len(lua_State *L) {
-	lmo_luaentry_t *le = luaL_checkudata(L, 1, LMO_ENTRY_META);
-	lua_pushinteger(L, le->entry->length);
-	return 1;
-}
-
-static int lmo_L_entry__gc(lua_State *L) {
-	lmo_luaentry_t *le = luaL_checkudata(L, 1, LMO_ENTRY_META);
-	le->archive = NULL;
-	le->entry   = NULL;
-	return 0;
-}
-
-
-/* lmo method table */
-static const luaL_reg M[] = {
-	{"close",		lmo_L__gc},
-	{"get",			lmo_L_get},
-	{"lookup",		lmo_L_lookup},
-	{"foreach",		lmo_L_foreach},
-	{"__tostring",	lmo_L__tostring},
-	{"__gc",		lmo_L__gc},
-	{NULL,			NULL}
-};
-
-/* lmo.entry method table */
-static const luaL_reg E[] = {
-	{"__tostring",	lmo_L_entry__tostring},
-	{"__concat",	lmo_L_entry__concat},
-	{"__len",		lmo_L_entry__len},
-	{"__gc",		lmo_L_entry__gc},
-	{NULL,			NULL}
-};
-
-/* module table */
-static const luaL_reg R[] = {
-	{"open",	lmo_L_open},
-	{"hash",	lmo_L_hash},
-	{NULL,		NULL}
-};
-
-LUALIB_API int luaopen_lmo(lua_State *L) {
-	luaL_newmetatable(L, LMO_ARCHIVE_META);
-	luaL_register(L, NULL, M);
-	lua_pushvalue(L, -1);
-	lua_setfield(L, -2, "__index");
-	lua_setglobal(L, LMO_ARCHIVE_META);
-
-	luaL_newmetatable(L, LMO_ENTRY_META);
-	luaL_register(L, NULL, E);
-	lua_pushvalue(L, -1);
-	lua_setfield(L, -2, "__index");
-	lua_setglobal(L, LMO_ENTRY_META);	
-
-	luaL_register(L, LMO_LUALIB_META, R);
-
-	return 1;
-}
diff --git a/libs/lmo/src/lmo_lualib.h b/libs/lmo/src/lmo_lualib.h
deleted file mode 100644
index 887889d04..000000000
--- a/libs/lmo/src/lmo_lualib.h
+++ /dev/null
@@ -1,43 +0,0 @@
-/*
- * lmo - Lua Machine Objects - Lua library header
- *
- *   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.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-#ifndef _LMO_LUALIB_H_
-#define _LMO_LUALIB_H_
-
-#include <lua.h>
-#include <lualib.h>
-#include <lauxlib.h>
-#include <ctype.h>
-
-#include "lmo.h"
-
-#define LMO_LUALIB_META  "lmo"
-#define LMO_ARCHIVE_META "lmo.archive"
-#define LMO_ENTRY_META   "lmo.entry"
-
-struct lmo_luaentry {
-	lmo_archive_t *archive;  
-	lmo_entry_t   *entry;
-};
-
-typedef struct lmo_luaentry lmo_luaentry_t;
-
-
-LUALIB_API int luaopen_lmo(lua_State *L);
-
-#endif
diff --git a/libs/lmo/src/lmo_po2lmo.c b/libs/lmo/src/lmo_po2lmo.c
deleted file mode 100644
index f6f399423..000000000
--- a/libs/lmo/src/lmo_po2lmo.c
+++ /dev/null
@@ -1,226 +0,0 @@
-/*
- * lmo - Lua Machine Objects - PO to LMO conversion tool
- *
- *   Copyright (C) 2009-2011 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.
- *  You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- *  Unless required by applicable law or agreed to in writing, software
- *  distributed under the License is distributed on an "AS IS" BASIS,
- *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- *  See the License for the specific language governing permissions and
- *  limitations under the License.
- */
-
-#include "lmo.h"
-
-static void die(const char *msg)
-{
-	fprintf(stderr, "Error: %s\n", msg);
-	exit(1);
-}
-
-static void usage(const char *name)
-{
-	fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
-	exit(1);
-}
-
-static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
-{
-	if( fwrite(ptr, size, nmemb, stream) == 0 )
-		die("Failed to write stdout");
-}
-
-static int extract_string(const char *src, char *dest, int len)
-{
-	int pos = 0;
-	int esc = 0;
-	int off = -1;
-
-	for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
-	{
-		if( (off == -1) && (src[pos] == '"') )
-		{
-			off = pos + 1;
-		}
-		else if( off >= 0 )
-		{
-			if( esc == 1 )
-			{
-				switch (src[pos])
-				{
-				case '"':
-				case '\\':
-					off++;
-					break;
-				}
-				dest[pos-off] = src[pos];
-				esc = 0;
-			}
-			else if( src[pos] == '\\' )
-			{
-				dest[pos-off] = src[pos];
-				esc = 1;
-			}
-			else if( src[pos] != '"' )
-			{
-				dest[pos-off] = src[pos];
-			}
-			else
-			{
-				dest[pos-off] = '\0';
-				break;
-			}
-		}
-	}
-
-	return (off > -1) ? strlen(dest) : -1;
-}
-
-int main(int argc, char *argv[])
-{
-	char line[4096];
-	char key[4096];
-	char val[4096];
-	char tmp[4096];
-	int state  = 0;
-	int offset = 0;
-	int length = 0;
-	uint32_t key_id, val_id;
-
-	FILE *in;
-	FILE *out;
-
-	lmo_entry_t *head  = NULL;
-	lmo_entry_t *entry = NULL;
-
-	if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
-		usage(argv[0]);
-
-	memset(line, 0, sizeof(key));
-	memset(key, 0, sizeof(val));
-	memset(val, 0, sizeof(val));
-
-	while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
-	{
-		if( state == 0 && strstr(line, "msgid \"") == line )
-		{
-			switch(extract_string(line, key, sizeof(key)))
-			{
-				case -1:
-					die("Syntax error in msgid");
-				case 0:
-					state = 1;
-					break;
-				default:
-					state = 2;
-			}
-		}
-		else if( state == 1 || state == 2 )
-		{
-			if( strstr(line, "msgstr \"") == line || state == 2 )
-			{
-				switch(extract_string(line, val, sizeof(val)))
-				{
-					case -1:
-						state = 4;
-						break;
-					default:
-						state = 3;
-				}
-			}
-			else
-			{
-				switch(extract_string(line, tmp, sizeof(tmp)))
-				{
-					case -1:
-						state = 2;
-						break;
-					default:
-						strcat(key, tmp);
-				}
-			}
-		}
-		else if( state == 3 )
-		{
-			switch(extract_string(line, tmp, sizeof(tmp)))
-			{
-				case -1:
-					state = 4;
-					break;
-				default:
-					strcat(val, tmp);
-			}
-		}
-
-		if( state == 4 )
-		{
-			if( strlen(key) > 0 && strlen(val) > 0 )
-			{
-				key_id = sfh_hash(key, strlen(key));
-				val_id = sfh_hash(val, strlen(val));
-
-				if( key_id != val_id )
-				{
-					if( (entry = (lmo_entry_t *) malloc(sizeof(lmo_entry_t))) != NULL )
-					{
-						memset(entry, 0, sizeof(entry));
-						length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
-
-						entry->key_id = htonl(key_id);
-						entry->val_id = htonl(val_id);
-						entry->offset = htonl(offset);
-						entry->length = htonl(strlen(val));
-
-						print(val, length, 1, out);
-						offset += length;
-
-						entry->next = head;
-						head = entry;
-					}
-					else
-					{
-						die("Out of memory");
-					}
-				}
-			}
-
-			state = 0;
-			memset(key, 0, sizeof(key));
-			memset(val, 0, sizeof(val));
-		}
-
-		memset(line, 0, sizeof(line));
-	}
-
-	entry = head;
-	while( entry != NULL )
-	{
-		print(&entry->key_id, sizeof(uint32_t), 1, out);
-		print(&entry->val_id, sizeof(uint32_t), 1, out);
-		print(&entry->offset, sizeof(uint32_t), 1, out);
-		print(&entry->length, sizeof(uint32_t), 1, out);
-		entry = entry->next;
-	}
-
-	if( offset > 0 )
-	{
-		offset = htonl(offset);
-		print(&offset, sizeof(uint32_t), 1, out);
-		fsync(fileno(out));
-		fclose(out);
-	}
-	else
-	{
-		fclose(out);
-		unlink(argv[2]);
-	}
-
-	fclose(in);
-	return(0);
-}
diff --git a/libs/lmo/standalone.mk b/libs/lmo/standalone.mk
deleted file mode 100644
index 66a0e5a2e..000000000
--- a/libs/lmo/standalone.mk
+++ /dev/null
@@ -1,56 +0,0 @@
-LUAC = luac
-LUAC_OPTIONS = -s
-LUA_TARGET ?= source
-
-LUA_MODULEDIR = /usr/local/share/lua/5.1
-LUA_LIBRARYDIR = /usr/local/lib/lua/5.1
-
-OS ?= $(shell uname)
-
-LUA_SHLIBS = $(shell pkg-config --silence-errors --libs lua5.1 || pkg-config --silence-errors --libs lua-5.1 || pkg-config --silence-errors --libs lua)
-LUA_LIBS = $(if $(LUA_SHLIBS),$(LUA_SHLIBS),$(firstword $(wildcard /usr/lib/liblua.a /usr/local/lib/liblua.a /opt/local/lib/liblua.a)))
-LUA_CFLAGS = $(shell pkg-config --silence-errors --cflags lua5.1 || pkg-config --silence-errors --cflags lua-5.1 || pkg-config --silence-errors --cflags lua)
-
-CC = gcc
-AR = ar
-RANLIB = ranlib
-CFLAGS = -O2
-FPIC = -fPIC
-EXTRA_CFLAGS = --std=gnu99
-WFLAGS = -Wall -Werror -pedantic
-CPPFLAGS =
-COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(WFLAGS)
-ifeq ($(OS),Darwin)
-  SHLIB_FLAGS = -bundle -undefined dynamic_lookup
-else
-  SHLIB_FLAGS = -shared
-endif
-LINK = $(CC) $(LDFLAGS)
-
-.PHONY: all build compile luacompile luasource clean luaclean
-
-all: build
-
-build: luabuild gccbuild
-
-luabuild: lua$(LUA_TARGET)
-
-gccbuild: compile
-compile:
-
-clean: luaclean
-
-luasource:
-	mkdir -p dist$(LUA_MODULEDIR)
-	cp -pR root/* dist 2>/dev/null || true
-	cp -pR lua/* dist$(LUA_MODULEDIR) 2>/dev/null || true
-	for i in $$(find dist -name .svn); do rm -rf $$i || true; done
-
-luastrip: luasource
-	for i in $$(find dist -type f -name '*.lua'); do perl -e 'undef $$/; open( F, "< $$ARGV[0]" ) || die $$!; $$src = <F>; close F; $$src =~ s/--\[\[.*?\]\](--)?//gs; $$src =~ s/^\s*--.*?\n//gm; open( F, "> $$ARGV[0]" ) || die $$!; print F $$src; close F' $$i; done
-
-luacompile: luasource
-	for i in $$(find dist -name *.lua -not -name debug.lua); do $(LUAC) $(LUAC_OPTIONS) -o $$i $$i; done
-
-luaclean:
-	rm -rf dist
diff --git a/libs/web/Makefile b/libs/web/Makefile
index d9f9700c1..1d28a3a4c 100644
--- a/libs/web/Makefile
+++ b/libs/web/Makefile
@@ -1,19 +1,28 @@
+ifneq (,$(wildcard ../../build/config.mk))
 include ../../build/config.mk
 include ../../build/module.mk
 include ../../build/gccconfig.mk
+else
+include standalone.mk
+endif
 
 TPL_LDFLAGS    =
 TPL_CFLAGS     =
 TPL_SO         = parser.so
+TPL_PO2LMO     = po2lmo
+TPL_PO2LMO_OBJ = src/po2lmo.o
+TPL_LMO_OBJ    = src/template_lmo.o
 TPL_COMMON_OBJ = src/template_parser.o src/template_utils.o
 TPL_LUALIB_OBJ = src/template_lualib.o
 
 %.o: %.c
 	$(COMPILE) $(TPL_CFLAGS) $(LUA_CFLAGS) $(FPIC) -c -o $@ $<
 
-compile: build-clean $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ)
+compile: build-clean $(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ) $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
 	$(LINK) $(SHLIB_FLAGS) $(TPL_LDFLAGS) -o src/$(TPL_SO) \
-		$(TPL_COMMON_OBJ) $(TPL_LUALIB_OBJ)
+		$(TPL_COMMON_OBJ) $(TPL_LMO_OBJ) $(TPL_LUALIB_OBJ)
+	$(LINK) -o src/$(TPL_PO2LMO) \
+		$(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
 	mkdir -p dist$(LUCI_LIBRARYDIR)/template
 	cp src/$(TPL_SO) dist$(LUCI_LIBRARYDIR)/template/$(TPL_SO)
 
@@ -24,3 +33,12 @@ clean: build-clean
 
 build-clean:
 	rm -f src/*.o src/$(TPL_SO)
+
+host-compile: build-clean host-clean $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
+	$(LINK) -o src/$(TPL_PO2LMO) $(TPL_LMO_OBJ) $(TPL_PO2LMO_OBJ)
+
+host-install: host-compile
+	cp src/$(TPL_PO2LMO) ../../build/$(TPL_PO2LMO)
+
+host-clean:
+	rm -f ../../build/$(TPL_PO2LMO)
diff --git a/libs/web/luasrc/i18n.lua b/libs/web/luasrc/i18n.lua
index 816d90310..ff917c6f3 100644
--- a/libs/web/luasrc/i18n.lua
+++ b/libs/web/luasrc/i18n.lua
@@ -27,7 +27,8 @@ limitations under the License.
 --- LuCI translation library.
 module("luci.i18n", package.seeall)
 require("luci.util")
-require("lmo")
+
+local tparser = require "luci.template.parser"
 
 table   = {}
 i18ndir = luci.util.libpath() .. "/i18n/"
@@ -37,7 +38,6 @@ default = "en"
 
 --- Clear the translation table.
 function clear()
-	table = {}
 end
 
 --- Load a translation and copy its data into the translation table.
@@ -46,33 +46,6 @@ end
 -- @param force	Force reload even if already loaded (optional)
 -- @return		Success status
 function load(file, lang, force)
-	lang = lang and lang:gsub("_", "-") or ""
-	if force or not loaded[lang] or not loaded[lang][file] then
-		local f = lmo.open(i18ndir .. file .. "." .. lang .. ".lmo")
-		if f then
-			if not table[lang] then
-				table[lang] = { f }
-				setmetatable(table[lang], {
-					__index = function(tbl, key)
-						for i = 1, #tbl do
-							local s = rawget(tbl, i):lookup(key)
-							if s then return s end
-						end
-					end
-				})
-			else
-				table[lang][#table[lang]+1] = f
-			end
-
-			loaded[lang] = loaded[lang] or {}
-			loaded[lang][file] = true
-			return true
-		else
-			return false
-		end
-	else
-		return true
-	end
 end
 
 --- Load a translation file using the default translation language.
@@ -80,9 +53,6 @@ end
 -- @param file	Language file
 -- @param force	Force reload even if already loaded (optional)
 function loadc(file, force)
-	load(file, default, force)
-	if context.parent then load(file, context.parent, force) end
-	return load(file, context.lang, force)
 end
 
 --- Set the context default translation language.
@@ -90,16 +60,18 @@ end
 function setlanguage(lang)
 	context.lang   = lang:gsub("_", "-")
 	context.parent = (context.lang:match("^([a-z][a-z])_"))
+	if not tparser.load_catalog(context.lang, i18ndir) then
+		if context.parent then
+			tparser.load_catalog(context.parent, i18ndir)
+		end
+	end
 end
 
 --- Return the translated value for a specific translation key.
 -- @param key	Default translation text
 -- @return		Translated string
 function translate(key)
-	return (table[context.lang] and table[context.lang][key])
-		or (table[context.parent] and table[context.parent][key])
-		or (table[default] and table[default][key])
-		or key
+	return tparser.translate(key) or key
 end
 
 --- Return the translated value for a specific translation key and use it as sprintf pattern.
diff --git a/libs/web/luasrc/template.lua b/libs/web/luasrc/template.lua
index 962c2ea88..72127d1df 100644
--- a/libs/web/luasrc/template.lua
+++ b/libs/web/luasrc/template.lua
@@ -79,9 +79,8 @@ function Template.__init__(self, name)
 		-- If we have no valid template throw error, otherwise cache the template
 		if not self.template then
 			error("Failed to load template '" .. name .. "'.\n" ..
-			      "Error while parsing template '" .. sourcefile .. "'.\n" ..
-			      "A syntax error occured near '" ..
-			      (err or "(nil)"):gsub("\t", "\\t"):gsub("\n", "\\n") .. "'.")
+			      "Error while parsing template '" .. sourcefile .. "':\n" ..
+			      (err or "Unknown syntax error"))
 		else
 			self.cache[name] = self.template
 		end
diff --git a/libs/web/src/po2lmo.c b/libs/web/src/po2lmo.c
new file mode 100644
index 000000000..fb607a46f
--- /dev/null
+++ b/libs/web/src/po2lmo.c
@@ -0,0 +1,242 @@
+/*
+ * lmo - Lua Machine Objects - PO to LMO conversion tool
+ *
+ *   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.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include "template_lmo.h"
+
+static void die(const char *msg)
+{
+	fprintf(stderr, "Error: %s\n", msg);
+	exit(1);
+}
+
+static void usage(const char *name)
+{
+	fprintf(stderr, "Usage: %s input.po output.lmo\n", name);
+	exit(1);
+}
+
+static void print(const void *ptr, size_t size, size_t nmemb, FILE *stream)
+{
+	if( fwrite(ptr, size, nmemb, stream) == 0 )
+		die("Failed to write stdout");
+}
+
+static int extract_string(const char *src, char *dest, int len)
+{
+	int pos = 0;
+	int esc = 0;
+	int off = -1;
+
+	for( pos = 0; (pos < strlen(src)) && (pos < len); pos++ )
+	{
+		if( (off == -1) && (src[pos] == '"') )
+		{
+			off = pos + 1;
+		}
+		else if( off >= 0 )
+		{
+			if( esc == 1 )
+			{
+				switch (src[pos])
+				{
+				case '"':
+				case '\\':
+					off++;
+					break;
+				}
+				dest[pos-off] = src[pos];
+				esc = 0;
+			}
+			else if( src[pos] == '\\' )
+			{
+				dest[pos-off] = src[pos];
+				esc = 1;
+			}
+			else if( src[pos] != '"' )
+			{
+				dest[pos-off] = src[pos];
+			}
+			else
+			{
+				dest[pos-off] = '\0';
+				break;
+			}
+		}
+	}
+
+	return (off > -1) ? strlen(dest) : -1;
+}
+
+static int cmp_index(const void *a, const void *b)
+{
+	uint32_t x = ntohl(((const lmo_entry_t *)a)->key_id);
+	uint32_t y = ntohl(((const lmo_entry_t *)b)->key_id);
+
+	if (x < y)
+		return -1;
+	else if (x > y)
+		return 1;
+
+	return 0;
+}
+
+static void print_index(void *array, int n, FILE *out)
+{
+	lmo_entry_t *e;
+
+	qsort(array, n, sizeof(*e), cmp_index);
+
+	for (e = array; n > 0; n--, e++)
+	{
+		print(&e->key_id, sizeof(uint32_t), 1, out);
+		print(&e->val_id, sizeof(uint32_t), 1, out);
+		print(&e->offset, sizeof(uint32_t), 1, out);
+		print(&e->length, sizeof(uint32_t), 1, out);
+	}
+}
+
+int main(int argc, char *argv[])
+{
+	char line[4096];
+	char key[4096];
+	char val[4096];
+	char tmp[4096];
+	int state  = 0;
+	int offset = 0;
+	int length = 0;
+	int n_entries = 0;
+	void *array = NULL;
+	lmo_entry_t *entry = NULL;
+	uint32_t key_id, val_id;
+
+	FILE *in;
+	FILE *out;
+
+	if( (argc != 3) || ((in = fopen(argv[1], "r")) == NULL) || ((out = fopen(argv[2], "w")) == NULL) )
+		usage(argv[0]);
+
+	memset(line, 0, sizeof(key));
+	memset(key, 0, sizeof(val));
+	memset(val, 0, sizeof(val));
+
+	while( (NULL != fgets(line, sizeof(line), in)) || (state >= 2 && feof(in)) )
+	{
+		if( state == 0 && strstr(line, "msgid \"") == line )
+		{
+			switch(extract_string(line, key, sizeof(key)))
+			{
+				case -1:
+					die("Syntax error in msgid");
+				case 0:
+					state = 1;
+					break;
+				default:
+					state = 2;
+			}
+		}
+		else if( state == 1 || state == 2 )
+		{
+			if( strstr(line, "msgstr \"") == line || state == 2 )
+			{
+				switch(extract_string(line, val, sizeof(val)))
+				{
+					case -1:
+						state = 4;
+						break;
+					default:
+						state = 3;
+				}
+			}
+			else
+			{
+				switch(extract_string(line, tmp, sizeof(tmp)))
+				{
+					case -1:
+						state = 2;
+						break;
+					default:
+						strcat(key, tmp);
+				}
+			}
+		}
+		else if( state == 3 )
+		{
+			switch(extract_string(line, tmp, sizeof(tmp)))
+			{
+				case -1:
+					state = 4;
+					break;
+				default:
+					strcat(val, tmp);
+			}
+		}
+
+		if( state == 4 )
+		{
+			if( strlen(key) > 0 && strlen(val) > 0 )
+			{
+				key_id = sfh_hash(key, strlen(key));
+				val_id = sfh_hash(val, strlen(val));
+
+				if( key_id != val_id )
+				{
+					n_entries++;
+					array = realloc(array, n_entries * sizeof(lmo_entry_t));
+					entry = (lmo_entry_t *)array + n_entries - 1;
+
+					if (!array)
+						die("Out of memory");
+
+					entry->key_id = htonl(key_id);
+					entry->val_id = htonl(val_id);
+					entry->offset = htonl(offset);
+					entry->length = htonl(strlen(val));
+
+					length = strlen(val) + ((4 - (strlen(val) % 4)) % 4);
+
+					print(val, length, 1, out);
+					offset += length;
+				}
+			}
+
+			state = 0;
+			memset(key, 0, sizeof(key));
+			memset(val, 0, sizeof(val));
+		}
+
+		memset(line, 0, sizeof(line));
+	}
+
+	print_index(array, n_entries, out);
+
+	if( offset > 0 )
+	{
+		offset = htonl(offset);
+		print(&offset, sizeof(uint32_t), 1, out);
+		fsync(fileno(out));
+		fclose(out);
+	}
+	else
+	{
+		fclose(out);
+		unlink(argv[2]);
+	}
+
+	fclose(in);
+	return(0);
+}
diff --git a/libs/web/src/template_lmo.c b/libs/web/src/template_lmo.c
new file mode 100644
index 000000000..7fcd2cda1
--- /dev/null
+++ b/libs/web/src/template_lmo.c
@@ -0,0 +1,325 @@
+/*
+ * lmo - Lua Machine Objects - Base functions
+ *
+ *   Copyright (C) 2009-2010 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.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#include "template_lmo.h"
+
+/*
+ * Hash function from http://www.azillionmonkeys.com/qed/hash.html
+ * Copyright (C) 2004-2008 by Paul Hsieh
+ */
+
+uint32_t sfh_hash(const char *data, int len)
+{
+	uint32_t hash = len, tmp;
+	int rem;
+
+	if (len <= 0 || data == NULL) return 0;
+
+	rem = len & 3;
+	len >>= 2;
+
+	/* Main loop */
+	for (;len > 0; len--) {
+		hash  += sfh_get16(data);
+		tmp    = (sfh_get16(data+2) << 11) ^ hash;
+		hash   = (hash << 16) ^ tmp;
+		data  += 2*sizeof(uint16_t);
+		hash  += hash >> 11;
+	}
+
+	/* Handle end cases */
+	switch (rem) {
+		case 3: hash += sfh_get16(data);
+			hash ^= hash << 16;
+			hash ^= data[sizeof(uint16_t)] << 18;
+			hash += hash >> 11;
+			break;
+		case 2: hash += sfh_get16(data);
+			hash ^= hash << 11;
+			hash += hash >> 17;
+			break;
+		case 1: hash += *data;
+			hash ^= hash << 10;
+			hash += hash >> 1;
+	}
+
+	/* Force "avalanching" of final 127 bits */
+	hash ^= hash << 3;
+	hash += hash >> 5;
+	hash ^= hash << 4;
+	hash += hash >> 17;
+	hash ^= hash << 25;
+	hash += hash >> 6;
+
+	return hash;
+}
+
+uint32_t lmo_canon_hash(const char *str, int len)
+{
+	char res[4096];
+	char *ptr, prev;
+	int off;
+
+	if (!str || len >= sizeof(res))
+		return 0;
+
+	for (prev = ' ', ptr = res, off = 0; off < len; prev = *str, off++, str++)
+	{
+		if (isspace(*str))
+		{
+			if (!isspace(prev))
+				*ptr++ = ' ';
+		}
+		else
+		{
+			*ptr++ = *str;
+		}
+	}
+
+	if ((ptr > res) && isspace(*(ptr-1)))
+		ptr--;
+
+	return sfh_hash(res, ptr - res);
+}
+
+lmo_archive_t * lmo_open(const char *file)
+{
+	int in = -1;
+	uint32_t idx_offset = 0;
+	struct stat s;
+
+	lmo_archive_t *ar = NULL;
+
+	if (stat(file, &s) == -1)
+		goto err;
+
+	if ((in = open(file, O_RDONLY)) == -1)
+		goto err;
+
+	if ((ar = (lmo_archive_t *)malloc(sizeof(*ar))) != NULL)
+	{
+		memset(ar, 0, sizeof(*ar));
+
+		ar->fd     = in;
+		ar->size = s.st_size;
+
+		fcntl(ar->fd, F_SETFD, fcntl(ar->fd, F_GETFD) | FD_CLOEXEC);
+
+		if ((ar->mmap = mmap(NULL, ar->size, PROT_READ, MAP_SHARED, ar->fd, 0)) == MAP_FAILED)
+			goto err;
+
+		idx_offset = *((const uint32_t *)
+					   (ar->mmap + ar->size - sizeof(uint32_t)));
+
+		if (idx_offset >= ar->size)
+			goto err;
+
+		ar->index  = (lmo_entry_t *)(ar->mmap + idx_offset);
+		ar->length = (ar->size - idx_offset - sizeof(uint32_t)) / sizeof(lmo_entry_t);
+		ar->end    = ar->mmap + ar->size;
+
+		return ar;
+	}
+
+err:
+	if (in > -1)
+		close(in);
+
+	if (ar != NULL)
+	{
+		if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
+			munmap(ar->mmap, ar->size);
+
+		free(ar);
+	}
+
+	return NULL;
+}
+
+void lmo_close(lmo_archive_t *ar)
+{
+	if (ar != NULL)
+	{
+		if ((ar->mmap != NULL) && (ar->mmap != MAP_FAILED))
+			munmap(ar->mmap, ar->size);
+
+		close(ar->fd);
+		free(ar);
+
+		ar = NULL;
+	}
+}
+
+
+lmo_catalog_t *_lmo_catalogs = NULL;
+lmo_catalog_t *_lmo_active_catalog = NULL;
+
+int lmo_load_catalog(const char *lang, const char *dir)
+{
+	DIR *dh = NULL;
+	char pattern[16];
+	char path[PATH_MAX];
+	struct dirent *de = NULL;
+
+	lmo_archive_t *ar = NULL;
+	lmo_catalog_t *cat = NULL;
+
+	if (!lmo_change_catalog(lang))
+		return 0;
+
+	if (!dir || !(dh = opendir(dir)))
+		goto err;
+
+	if (!(cat = malloc(sizeof(*cat))))
+		goto err;
+
+	memset(cat, 0, sizeof(*cat));
+
+	snprintf(cat->lang, sizeof(cat->lang), "%s", lang);
+	snprintf(pattern, sizeof(pattern), "*.%s.lmo", lang);
+
+	while ((de = readdir(dh)) != NULL)
+	{
+		if (!fnmatch(pattern, de->d_name, 0))
+		{
+			snprintf(path, sizeof(path), "%s/%s", dir, de->d_name);
+			ar = lmo_open(path);
+
+			if (ar)
+			{
+				ar->next = cat->archives;
+				cat->archives = ar;
+			}
+		}
+	}
+
+	closedir(dh);
+
+	cat->next = _lmo_catalogs;
+	_lmo_catalogs = cat;
+
+	if (!_lmo_active_catalog)
+		_lmo_active_catalog = cat;
+
+	return 0;
+
+err:
+	if (dh) closedir(dh);
+	if (cat) free(cat);
+
+	return -1;
+}
+
+int lmo_change_catalog(const char *lang)
+{
+	lmo_catalog_t *cat;
+
+	for (cat = _lmo_catalogs; cat; cat = cat->next)
+	{
+		if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
+		{
+			_lmo_active_catalog = cat;
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+static lmo_entry_t * lmo_find_entry(lmo_archive_t *ar, uint32_t hash)
+{
+	unsigned int m, l, r;
+
+	l = 0;
+	r = ar->length - 1;
+
+	while (1)
+	{
+		m = l + ((r - l) / 2);
+
+		if (r < l)
+			break;
+
+		if (ar->index[m].key_id == hash)
+			return &ar->index[m];
+
+		if (ar->index[m].key_id > hash)
+		{
+			if (!m)
+				break;
+
+			r = m - 1;
+		}
+		else
+		{
+			l = m + 1;
+		}
+	}
+
+	return NULL;
+}
+
+int lmo_translate(const char *key, int keylen, char **out, int *outlen)
+{
+	uint32_t hash;
+	lmo_entry_t *e;
+	lmo_archive_t *ar;
+
+	if (!key || !_lmo_active_catalog)
+		return -2;
+
+	hash = htonl(lmo_canon_hash(key, keylen));
+
+	for (ar = _lmo_active_catalog->archives; ar; ar = ar->next)
+	{
+		if ((e = lmo_find_entry(ar, hash)) != NULL)
+		{
+			*out = ar->mmap + e->offset;
+			*outlen = e->length;
+			return 0;
+		}
+	}
+
+	return -1;
+}
+
+void lmo_close_catalog(const char *lang)
+{
+	lmo_archive_t *ar, *next;
+	lmo_catalog_t *cat, *prev;
+
+	for (prev = NULL, cat = _lmo_catalogs; cat; prev = cat, cat = cat->next)
+	{
+		if (!strncmp(cat->lang, lang, sizeof(cat->lang)))
+		{
+			if (prev)
+				prev->next = cat->next;
+			else
+				_lmo_catalogs = cat->next;
+
+			for (ar = cat->archives; ar; ar = next)
+			{
+				next = ar->next;
+				lmo_close(ar);
+			}
+
+			free(cat);
+			break;
+		}
+	}
+}
diff --git a/libs/web/src/template_lmo.h b/libs/web/src/template_lmo.h
new file mode 100644
index 000000000..a40d7587a
--- /dev/null
+++ b/libs/web/src/template_lmo.h
@@ -0,0 +1,91 @@
+/*
+ * lmo - Lua Machine Objects - General header
+ *
+ *   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.
+ *  You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+#ifndef _TEMPLATE_LMO_H_
+#define _TEMPLATE_LMO_H_
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <stdint.h>
+#include <string.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
+#include <arpa/inet.h>
+#include <unistd.h>
+#include <errno.h>
+#include <fnmatch.h>
+#include <dirent.h>
+#include <ctype.h>
+
+#if (defined(__GNUC__) && defined(__i386__))
+#define sfh_get16(d) (*((const uint16_t *) (d)))
+#else
+#define sfh_get16(d) ((((uint32_t)(((const uint8_t *)(d))[1])) << 8)\
+					   +(uint32_t)(((const uint8_t *)(d))[0]) )
+#endif
+
+
+struct lmo_entry {
+	uint32_t key_id;
+	uint32_t val_id;
+	uint32_t offset;
+	uint32_t length;
+} __attribute__((packed));
+
+typedef struct lmo_entry lmo_entry_t;
+
+
+struct lmo_archive {
+	int         fd;
+	int	        length;
+	uint32_t    size;
+	lmo_entry_t *index;
+	char        *mmap;
+	char		*end;
+	struct lmo_archive *next;
+};
+
+typedef struct lmo_archive lmo_archive_t;
+
+
+struct lmo_catalog {
+	char lang[6];
+	struct lmo_archive *archives;
+	struct lmo_catalog *next;
+};
+
+typedef struct lmo_catalog lmo_catalog_t;
+
+
+uint32_t sfh_hash(const char *data, int len);
+uint32_t lmo_canon_hash(const char *data, int len);
+
+lmo_archive_t * lmo_open(const char *file);
+void lmo_close(lmo_archive_t *ar);
+
+
+extern lmo_catalog_t *_lmo_catalogs;
+extern lmo_catalog_t *_lmo_active_catalog;
+
+int lmo_load_catalog(const char *lang, const char *dir);
+int lmo_change_catalog(const char *lang);
+int lmo_translate(const char *key, int keylen, char **out, int *outlen);
+void lmo_close_catalog(const char *lang);
+
+#endif
diff --git a/libs/web/src/template_lualib.c b/libs/web/src/template_lualib.c
index d3a5f89bb..f40ef2d6a 100644
--- a/libs/web/src/template_lualib.c
+++ b/libs/web/src/template_lualib.c
@@ -21,37 +21,27 @@
 int template_L_parse(lua_State *L)
 {
 	const char *file = luaL_checkstring(L, 1);
-	struct template_parser parser;
-	int lua_status;
+	struct template_parser *parser = template_open(file);
+	int lua_status, rv;
 
-	if( (parser.fd = open(file, O_RDONLY)) > 0 )
+	if (!parser)
 	{
-		parser.flags   = 0;
-		parser.bufsize = 0;
-		parser.state   = T_STATE_TEXT_NEXT;
-
-		lua_status = lua_load(L, template_reader, &parser, file);
+		lua_pushnil(L);
+		lua_pushinteger(L, errno);
+		lua_pushstring(L, strerror(errno));
+		return 3;
+	}
 
-		(void) close(parser.fd);
+	lua_status = lua_load(L, template_reader, parser, file);
 
+	if (lua_status == 0)
+		rv = 1;
+	else
+		rv = template_error(L, parser);
 
-		if( lua_status == 0 )
-		{
-			return 1;
-		}
-		else
-		{
-			lua_pushnil(L);
-			lua_pushinteger(L, lua_status);
-			lua_pushlstring(L, parser.out, parser.outsize);
-			return 3;
-		}
-	}
+	template_close(parser);
 
-	lua_pushnil(L);
-	lua_pushinteger(L, 255);
-	lua_pushstring(L, "No such file or directory");
-	return 3;
+	return rv;
 }
 
 int template_L_sanitize_utf8(lua_State *L)
@@ -88,12 +78,64 @@ int template_L_sanitize_pcdata(lua_State *L)
 	return 0;
 }
 
+static int template_L_load_catalog(lua_State *L) {
+	const char *lang = luaL_optstring(L, 1, "en");
+	const char *dir  = luaL_optstring(L, 2, NULL);
+	lua_pushboolean(L, !lmo_load_catalog(lang, dir));
+	return 1;
+}
+
+static int template_L_close_catalog(lua_State *L) {
+	const char *lang = luaL_optstring(L, 1, "en");
+	lmo_close_catalog(lang);
+	return 0;
+}
+
+static int template_L_change_catalog(lua_State *L) {
+	const char *lang = luaL_optstring(L, 1, "en");
+	lua_pushboolean(L, !lmo_change_catalog(lang));
+	return 1;
+}
+
+static int template_L_translate(lua_State *L) {
+	size_t len;
+	char *tr;
+	int trlen;
+	const char *key = luaL_checklstring(L, 1, &len);
+
+	switch (lmo_translate(key, len, &tr, &trlen))
+	{
+		case 0:
+			lua_pushlstring(L, tr, trlen);
+			return 1;
+
+		case -1:
+			return 0;
+	}
+
+	lua_pushnil(L);
+	lua_pushstring(L, "no catalog loaded");
+	return 2;
+}
+
+static int template_L_hash(lua_State *L) {
+	size_t len;
+	const char *key = luaL_checklstring(L, 1, &len);
+	lua_pushinteger(L, sfh_hash(key, len));
+	return 1;
+}
+
 
 /* module table */
 static const luaL_reg R[] = {
 	{ "parse",				template_L_parse },
 	{ "sanitize_utf8",		template_L_sanitize_utf8 },
 	{ "sanitize_pcdata",	template_L_sanitize_pcdata },
+	{ "load_catalog",		template_L_load_catalog },
+	{ "close_catalog",		template_L_close_catalog },
+	{ "change_catalog",		template_L_change_catalog },
+	{ "translate",			template_L_translate },
+	{ "hash",				template_L_hash },
 	{ NULL,					NULL }
 };
 
diff --git a/libs/web/src/template_lualib.h b/libs/web/src/template_lualib.h
index d628b9dce..1b659be12 100644
--- a/libs/web/src/template_lualib.h
+++ b/libs/web/src/template_lualib.h
@@ -21,6 +21,7 @@
 
 #include "template_parser.h"
 #include "template_utils.h"
+#include "template_lmo.h"
 
 #define TEMPLATE_LUALIB_META  "template.parser"
 
diff --git a/libs/web/src/template_parser.c b/libs/web/src/template_parser.c
index a0a400bdf..69f0f1c48 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;
+}
diff --git a/libs/web/src/template_parser.h b/libs/web/src/template_parser.h
index 24933f0c9..d1c606272 100644
--- a/libs/web/src/template_parser.h
+++ b/libs/web/src/template_parser.h
@@ -21,61 +21,59 @@
 
 #include <stdlib.h>
 #include <stdio.h>
+#include <stdint.h>
 #include <unistd.h>
 #include <fcntl.h>
+#include <sys/stat.h>
+#include <sys/mman.h>
 #include <string.h>
 #include <ctype.h>
+#include <errno.h>
 
 #include <lua.h>
 #include <lualib.h>
 #include <lauxlib.h>
 
 
-#define T_READBUFSZ			1024
-#define T_OUTBUFSZ			T_READBUFSZ * 3
-
-/* parser states */
-#define T_STATE_TEXT_INIT	0
-#define T_STATE_TEXT_NEXT	1
-#define T_STATE_CODE_INIT	2
-#define T_STATE_CODE_NEXT	3
-#define T_STATE_SKIP 		4
-
-/* parser flags */
-#define T_FLAG_EOF			0x01
-#define T_FLAG_SKIPWS		0x02
-
-/* tokens used in matching and expression generation */
-#define T_TOK_START			"<%"
-#define T_TOK_END			"%>"
-#define T_TOK_SKIPWS		"-"
+/* code types */
+#define T_TYPE_INIT			0
+#define T_TYPE_TEXT			1
+#define T_TYPE_COMMENT		2
+#define T_TYPE_EXPR			3
+#define T_TYPE_INCLUDE 		4
+#define T_TYPE_I18N			5
+#define T_TYPE_I18N_RAW		6
+#define T_TYPE_CODE			7
+#define T_TYPE_EOF			8
 
-/* generator flags */
-#define T_GEN_START			0x01
-#define T_GEN_END			0x02
 
-/* code types */
-#define T_TYPE_TEXT			0
-#define T_TYPE_COMMENT		1
-#define T_TYPE_EXPR			2
-#define T_TYPE_INCLUDE 		3
-#define T_TYPE_I18N			4
-#define T_TYPE_I18N_RAW		5
-#define T_TYPE_CODE			6
+struct template_chunk {
+	const char *s;
+	const char *e;
+	int type;
+	int line;
+};
 
 /* parser state */
 struct template_parser {
 	int fd;
-	int bufsize;
-	int outsize;
-	int state;
-	int flags;
-	int type;
-	char buf[T_READBUFSZ];
-	char out[T_OUTBUFSZ];
+	uint32_t size;
+	char *mmap;
+	char *off;
+	char *gc;
+	int line;
+	int in_expr;
+	int strip_before;
+	int strip_after;
+	struct template_chunk prv_chunk;
+	struct template_chunk cur_chunk;
+	const char *file;
 };
 
+struct template_parser * template_open(const char *file);
+void template_close(struct template_parser *parser);
 
 const char *template_reader(lua_State *L, void *ud, size_t *sz);
+int template_error(lua_State *L, struct template_parser *parser);
 
 #endif
diff --git a/libs/web/src/template_utils.c b/libs/web/src/template_utils.c
index 36f08aa22..6ed20d355 100644
--- a/libs/web/src/template_utils.c
+++ b/libs/web/src/template_utils.c
@@ -17,19 +17,23 @@
  */
 
 #include "template_utils.h"
+#include "template_lmo.h"
 
 /* initialize a buffer object */
-static struct template_buffer * buf_init(void)
+struct template_buffer * buf_init(int size)
 {
 	struct template_buffer *buf;
 
+	if (size <= 0)
+		size = 1024;
+
 	buf = (struct template_buffer *)malloc(sizeof(struct template_buffer));
 
 	if (buf != NULL)
 	{
 		buf->fill = 0;
-		buf->size = 1024;
-		buf->data = (unsigned char *)malloc(buf->size);
+		buf->size = size;
+		buf->data = malloc(buf->size);
 
 		if (buf->data != NULL)
 		{
@@ -46,17 +50,21 @@ static struct template_buffer * buf_init(void)
 }
 
 /* grow buffer */
-static int buf_grow(struct template_buffer *buf)
+int buf_grow(struct template_buffer *buf, int size)
 {
 	unsigned int off = (buf->dptr - buf->data);
-	unsigned char *data =
-		(unsigned char *)realloc(buf->data, buf->size + 1024);
+	char *data;
+
+	if (size <= 0)
+		size = 1024;
+
+	data = realloc(buf->data, buf->size + size);
 
 	if (data != NULL)
 	{
 		buf->data  = data;
 		buf->dptr  = data + off;
-		buf->size += 1024;
+		buf->size += size;
 
 		return buf->size;
 	}
@@ -65,9 +73,9 @@ static int buf_grow(struct template_buffer *buf)
 }
 
 /* put one char into buffer object */
-static int buf_putchar(struct template_buffer *buf, unsigned char c)
+int buf_putchar(struct template_buffer *buf, char c)
 {
-	if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf) )
+	if( ((buf->fill + 1) >= buf->size) && !buf_grow(buf, 0) )
 		return 0;
 
 	*(buf->dptr++) = c;
@@ -78,11 +86,11 @@ static int buf_putchar(struct template_buffer *buf, unsigned char c)
 }
 
 /* append data to buffer */
-static int buf_append(struct template_buffer *buf, unsigned char *s, int len)
+int buf_append(struct template_buffer *buf, const char *s, int len)
 {
-	while ((buf->fill + len + 1) >= buf->size)
+	if ((buf->fill + len + 1) >= buf->size)
 	{
-		if (!buf_grow(buf))
+		if (!buf_grow(buf, len + 1))
 			return 0;
 	}
 
@@ -95,13 +103,19 @@ static int buf_append(struct template_buffer *buf, unsigned char *s, int len)
 	return len;
 }
 
+/* read buffer length */
+int buf_length(struct template_buffer *buf)
+{
+	return buf->fill;
+}
+
 /* destroy buffer object and return pointer to data */
-static char * buf_destroy(struct template_buffer *buf)
+char * buf_destroy(struct template_buffer *buf)
 {
-	unsigned char *data = buf->data;
+	char *data = buf->data;
 
 	free(buf);
-	return (char *)data;
+	return data;
 }
 
 
@@ -229,7 +243,7 @@ static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf)
 					!mb_is_surrogate(ptr, n) && !mb_is_illegal(ptr, n))
 				{
 					/* copy sequence */
-					if (!buf_append(buf, ptr, n))
+					if (!buf_append(buf, (char *)ptr, n))
 						return 0;
 				}
 
@@ -266,7 +280,7 @@ static int _validate_utf8(unsigned char **s, int l, struct template_buffer *buf)
 /* sanitize given string and replace all invalid UTF-8 sequences with "?" */
 char * sanitize_utf8(const char *s, unsigned int l)
 {
-	struct template_buffer *buf = buf_init();
+	struct template_buffer *buf = buf_init(l);
 	unsigned char *ptr = (unsigned char *)s;
 	unsigned int v, o;
 
@@ -278,7 +292,7 @@ char * sanitize_utf8(const char *s, unsigned int l)
 		/* ascii char */
 		if ((*ptr >= 0x01) && (*ptr <= 0x7F))
 		{
-			if (!buf_putchar(buf, *ptr++))
+			if (!buf_putchar(buf, (char)*ptr++))
 				break;
 		}
 
@@ -300,7 +314,7 @@ char * sanitize_utf8(const char *s, unsigned int l)
  * Escape XML control chars */
 char * sanitize_pcdata(const char *s, unsigned int l)
 {
-	struct template_buffer *buf = buf_init();
+	struct template_buffer *buf = buf_init(l);
 	unsigned char *ptr = (unsigned char *)s;
 	unsigned int o, v;
 	char esq[8];
@@ -329,7 +343,7 @@ char * sanitize_pcdata(const char *s, unsigned int l)
 		{
 			esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr);
 
-			if (!buf_append(buf, (unsigned char *)esq, esl))
+			if (!buf_append(buf, esq, esl))
 				break;
 
 			ptr++;
@@ -338,7 +352,7 @@ char * sanitize_pcdata(const char *s, unsigned int l)
 		/* ascii char */
 		else if (*ptr <= 0x7F)
 		{
-			buf_putchar(buf, *ptr++);
+			buf_putchar(buf, (char)*ptr++);
 		}
 
 		/* multi byte sequence */
@@ -353,3 +367,68 @@ char * sanitize_pcdata(const char *s, unsigned int l)
 
 	return buf_destroy(buf);
 }
+
+void escape_luastr(struct template_buffer *out, const char *s, unsigned int l,
+				   int escape_xml)
+{
+	int esl;
+	char esq[8];
+	char *ptr;
+
+	for (ptr = (char *)s; ptr < (s + l); ptr++)
+	{
+		switch (*ptr)
+		{
+		case '\\':
+			buf_append(out, "\\\\", 2);
+			break;
+
+		case '"':
+			if (escape_xml)
+				buf_append(out, "&#34;", 5);
+			else
+				buf_append(out, "\\\"", 2);
+			break;
+
+		case '\n':
+			buf_append(out, "\\n", 2);
+			break;
+
+		case '\'':
+		case '&':
+		case '<':
+		case '>':
+			if (escape_xml)
+			{
+				esl = snprintf(esq, sizeof(esq), "&#%i;", *ptr);
+				buf_append(out, esq, esl);
+				break;
+			}
+
+		default:
+			buf_putchar(out, *ptr);
+		}
+	}
+}
+
+void translate_luastr(struct template_buffer *out, const char *s, unsigned int l,
+					  int escape_xml)
+{
+	char *tr;
+	int trlen;
+
+	switch (lmo_translate(s, l, &tr, &trlen))
+	{
+		case 0:
+			escape_luastr(out, tr, trlen, escape_xml);
+			break;
+
+		case -1:
+			escape_luastr(out, s, l, escape_xml);
+			break;
+
+		default:
+			/* no catalog loaded */
+			break;
+	}
+}
diff --git a/libs/web/src/template_utils.h b/libs/web/src/template_utils.h
index 1f7d438c6..371b6a37c 100644
--- a/libs/web/src/template_utils.h
+++ b/libs/web/src/template_utils.h
@@ -1,7 +1,7 @@
 /*
  * LuCI Template - Utility header
  *
- *   Copyright (C) 2010 Jo-Philipp Wich <xm@subsignal.org>
+ *   Copyright (C) 2010-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.
@@ -22,17 +22,28 @@
 #include <stdlib.h>
 #include <stdio.h>
 #include <string.h>
+#include <dlfcn.h>
 
 
 /* buffer object */
 struct template_buffer {
-	unsigned char *data;
-	unsigned char *dptr;
+	char *data;
+	char *dptr;
 	unsigned int size;
 	unsigned int fill;
 };
 
+struct template_buffer * buf_init(int size);
+int buf_grow(struct template_buffer *buf, int size);
+int buf_putchar(struct template_buffer *buf, char c);
+int buf_append(struct template_buffer *buf, const char *s, int len);
+int buf_length(struct template_buffer *buf);
+char * buf_destroy(struct template_buffer *buf);
+
 char * sanitize_utf8(const char *s, unsigned int l);
 char * sanitize_pcdata(const char *s, unsigned int l);
 
+void escape_luastr(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);
+void translate_luastr(struct template_buffer *out, const char *s, unsigned int l, int escape_xml);
+
 #endif
diff --git a/libs/web/standalone.mk b/libs/web/standalone.mk
new file mode 100644
index 000000000..66a0e5a2e
--- /dev/null
+++ b/libs/web/standalone.mk
@@ -0,0 +1,56 @@
+LUAC = luac
+LUAC_OPTIONS = -s
+LUA_TARGET ?= source
+
+LUA_MODULEDIR = /usr/local/share/lua/5.1
+LUA_LIBRARYDIR = /usr/local/lib/lua/5.1
+
+OS ?= $(shell uname)
+
+LUA_SHLIBS = $(shell pkg-config --silence-errors --libs lua5.1 || pkg-config --silence-errors --libs lua-5.1 || pkg-config --silence-errors --libs lua)
+LUA_LIBS = $(if $(LUA_SHLIBS),$(LUA_SHLIBS),$(firstword $(wildcard /usr/lib/liblua.a /usr/local/lib/liblua.a /opt/local/lib/liblua.a)))
+LUA_CFLAGS = $(shell pkg-config --silence-errors --cflags lua5.1 || pkg-config --silence-errors --cflags lua-5.1 || pkg-config --silence-errors --cflags lua)
+
+CC = gcc
+AR = ar
+RANLIB = ranlib
+CFLAGS = -O2
+FPIC = -fPIC
+EXTRA_CFLAGS = --std=gnu99
+WFLAGS = -Wall -Werror -pedantic
+CPPFLAGS =
+COMPILE = $(CC) $(CPPFLAGS) $(CFLAGS) $(EXTRA_CFLAGS) $(WFLAGS)
+ifeq ($(OS),Darwin)
+  SHLIB_FLAGS = -bundle -undefined dynamic_lookup
+else
+  SHLIB_FLAGS = -shared
+endif
+LINK = $(CC) $(LDFLAGS)
+
+.PHONY: all build compile luacompile luasource clean luaclean
+
+all: build
+
+build: luabuild gccbuild
+
+luabuild: lua$(LUA_TARGET)
+
+gccbuild: compile
+compile:
+
+clean: luaclean
+
+luasource:
+	mkdir -p dist$(LUA_MODULEDIR)
+	cp -pR root/* dist 2>/dev/null || true
+	cp -pR lua/* dist$(LUA_MODULEDIR) 2>/dev/null || true
+	for i in $$(find dist -name .svn); do rm -rf $$i || true; done
+
+luastrip: luasource
+	for i in $$(find dist -type f -name '*.lua'); do perl -e 'undef $$/; open( F, "< $$ARGV[0]" ) || die $$!; $$src = <F>; close F; $$src =~ s/--\[\[.*?\]\](--)?//gs; $$src =~ s/^\s*--.*?\n//gm; open( F, "> $$ARGV[0]" ) || die $$!; print F $$src; close F' $$i; done
+
+luacompile: luasource
+	for i in $$(find dist -name *.lua -not -name debug.lua); do $(LUAC) $(LUAC_OPTIONS) -o $$i $$i; done
+
+luaclean:
+	rm -rf dist