addCharacterClass(const FileInfo *file, const widechar *name, int length,
		TranslationTableHeader *table, int validate) {
	/* Define a character class, Whether predefined or user-defined */
	if (validate) {
		for (int i = 0; i < length; i++) {
			if (!((name[i] >= 'a' && name[i] <= 'z') ||
						(name[i] >= 'A' && name[i] <= 'Z'))) {
				// don't abort because in some cases (before/after rules)
				// this will work fine, but it will not work in multipass
				// expressions
				compileWarning(file,
						"Invalid attribute name: must be a digit between "
						"0 and 7 or a word containing only letters");
			}
		}
		// check that name is not reserved
		int k = 0;
		while (reservedAttributeNames[k]) {
			if (strlen(reservedAttributeNames[k]) == length) {
				int i;
				for (i = 0; i < length; i++)
					if (reservedAttributeNames[k][i] != name[i]) break;
				if (i == length) {
					compileError(file, "Attribute name is reserved: %s",
							reservedAttributeNames[k]);
					return NULL;
				}
			}
			k++;
		}
	}
	CharacterClass **classes = &table->characterClasses;
	TranslationTableCharacterAttributes attribute = getNextAttribute(table);
	CharacterClass *class;
	if (attribute) {
		if (!(class = malloc(sizeof(*class) + CHARSIZE * (length - 1))))
			_lou_outOfMemory();
		else {
			memset(class, 0, sizeof(*class));
			memcpy(class->name, name, CHARSIZE * (class->length = length));
			class->attribute = attribute;
			class->next = *classes;
			*classes = class;
			return class;
		}
	}
	compileError(file, "character class table overflow.");
	return NULL;
}


parseChars(const FileInfo *file, CharsString *result, CharsString *token) {
	int in = 0;
	int out = 0;
	int lastOutSize = 0;
	int lastIn;
	unsigned int ch = 0;
	int numBytes = 0;
	unsigned int utf32 = 0;
	int k;
	while (in < token->length) {
		ch = token->chars[in++] & 0xff;
		if (ch < 128) {
			if (ch == '\\') { /* escape sequence */
				switch (ch = token->chars[in]) {
				case '\\':
					break;
				case 'e':
					ch = 0x1b;
					break;
				case 'f':
					ch = 12;
					break;
				case 'n':
					ch = 10;
					break;
				case 'r':
					ch = 13;
					break;
				case 's':
					ch = ' ';
					break;
				case 't':
					ch = 9;
					break;
				case 'v':
					ch = 11;
					break;
				case 'w':
					ch = LOU_ENDSEGMENT;
					break;
				case 34:
					ch = QUOTESUB;
					break;
				case 'X':
					compileWarning(file, "\\Xhhhh (with a capital 'X') is deprecated.");
				case 'x':
					if (token->length - in > 4) {
						ch = hexValue(file, &token->chars[in + 1], 4);
						in += 4;
					}
					break;
				case 'Y':
					compileWarning(file, "\\Yhhhhh (with a capital 'Y') is deprecated.");
				case 'y':
					if (CHARSIZE == 2) {
					not32:
						compileError(file,
								"liblouis has not been compiled for 32-bit Unicode");
						break;
					}
					if (token->length - in > 5) {
						ch = hexValue(file, &token->chars[in + 1], 5);
						in += 5;
					}
					break;
				case 'Z':
					compileWarning(
							file, "\\Zhhhhhhhh (with a capital 'Z') is deprecated.");
				case 'z':
					if (CHARSIZE == 2) goto not32;
					if (token->length - in > 8) {
						ch = hexValue(file, &token->chars[in + 1], 8);
						in += 8;
					}
					break;
				default:
					compileError(file, "invalid escape sequence '\\%c'", ch);
					break;
				}
				in++;
			}
			if (out >= MAXSTRING - 1) {
				compileError(file, "Token too long");
				result->length = MAXSTRING - 1;
				return 1;
			}
			result->chars[out++] = (widechar)ch;
			continue;
		}
		lastOutSize = out;
		lastIn = in;
		for (numBytes = MAXBYTES - 1; numBytes > 0; numBytes--)
			if (ch >= first0Bit[numBytes]) break;
		utf32 = ch & (0XFF - first0Bit[numBytes]);
		for (k = 0; k < numBytes; k++) {
			if (in >= MAXSTRING - 1 || in >= token->length) break;
			if (out >= MAXSTRING - 1) {
				compileError(file, "Token too long");
				result->length = lastOutSize;
				return 1;
			}
			if (token->chars[in] < 128 || (token->chars[in] & 0x0040)) {
				compileWarning(file, "invalid UTF-8. Assuming Latin-1.");
				result->chars[out++] = token->chars[lastIn];
				in = lastIn + 1;
				continue;
			}
			utf32 = (utf32 << 6) + (token->chars[in++] & 0x3f);
		}
		if (out >= MAXSTRING - 1) {
			compileError(file, "Token too long");
			result->length = lastOutSize;
			return 1;
		}
		if (CHARSIZE == 2 && utf32 > 0xffff) utf32 = 0xffff;
		result->chars[out++] = (widechar)utf32;
	}
	result->length = out;
	return 1;
}

Prevent an invalid memory writes in compileRule

Thanks to Han Zheng for reporting it

Fixes #1214


_lou_allocMem(AllocBuf buffer, int index, int srcmax, int destmax) {
	if (srcmax < 1024) srcmax = 1024;
	if (destmax < 1024) destmax = 1024;
	switch (buffer) {
	case alloc_typebuf:
		if (destmax > sizeTypebuf) {
			if (typebuf != NULL) free(typebuf);
			// TODO: should this be srcmax?
			typebuf = malloc((destmax + 4) * sizeof(formtype));
			if (!typebuf) _lou_outOfMemory();
			sizeTypebuf = destmax;
		}
		return typebuf;

	case alloc_wordBuffer:

		if (wordBuffer != NULL) free(wordBuffer);
		wordBuffer = calloc(srcmax + 4, sizeof(unsigned int));
		if (wordBuffer == NULL) _lou_outOfMemory();
		return wordBuffer;

	case alloc_emphasisBuffer:

		if (emphasisBuffer != NULL) free(emphasisBuffer);
		emphasisBuffer = calloc(srcmax + 4, sizeof(EmphasisInfo));
		if (emphasisBuffer == NULL) _lou_outOfMemory();
		return emphasisBuffer;

	case alloc_destSpacing:
		if (destmax > sizeDestSpacing) {
			if (destSpacing != NULL) free(destSpacing);
			destSpacing = malloc(destmax + 4);
			if (!destSpacing) _lou_outOfMemory();
			sizeDestSpacing = destmax;
		}
		return destSpacing;
	case alloc_passbuf:
		if (index < 0 || index >= MAXPASSBUF) {
			_lou_logMessage(LOU_LOG_FATAL, "Index out of bounds: %d\n", index);
			exit(3);
		}
		if (destmax > sizePassbuf[index]) {
			if (passbuf[index] != NULL) free(passbuf[index]);
			passbuf[index] = malloc((destmax + 4) * CHARSIZE);
			if (!passbuf[index]) _lou_outOfMemory();
			sizePassbuf[index] = destmax;
		}
		return passbuf[index];
	case alloc_posMapping1: {
		int mapSize;
		if (srcmax >= destmax)
			mapSize = srcmax;
		else
			mapSize = destmax;
		if (mapSize > sizePosMapping1) {
			if (posMapping1 != NULL) free(posMapping1);
			posMapping1 = malloc((mapSize + 4) * sizeof(int));
			if (!posMapping1) _lou_outOfMemory();
			sizePosMapping1 = mapSize;
		}
	}
		return posMapping1;
	case alloc_posMapping2: {
		int mapSize;
		if (srcmax >= destmax)
			mapSize = srcmax;
		else
			mapSize = destmax;
		if (mapSize > sizePosMapping2) {
			if (posMapping2 != NULL) free(posMapping2);
			posMapping2 = malloc((mapSize + 4) * sizeof(int));
			if (!posMapping2) _lou_outOfMemory();
			sizePosMapping2 = mapSize;
		}
	}
		return posMapping2;
	case alloc_posMapping3: {
		int mapSize;
		if (srcmax >= destmax)
			mapSize = srcmax;
		else
			mapSize = destmax;
		if (mapSize > sizePosMapping3) {
			if (posMapping3 != NULL) free(posMapping3);
			posMapping3 = malloc((mapSize + 4) * sizeof(int));
			if (!posMapping3) _lou_outOfMemory();
			sizePosMapping3 = mapSize;
		}
	}
		return posMapping3;
	default:
		return NULL;
	}
}


lou_readCharFromFile(const char *fileName, int *mode) {
	/* Read a character from a file, whether big-endian, little-endian or
	 * ASCII8 */
	int ch;
	static FileInfo file;
	if (fileName == NULL) return 0;
	if (*mode == 1) {
		*mode = 0;
		file.fileName = fileName;
		file.encoding = noEncoding;
		file.status = 0;
		file.lineNumber = 0;
		if (!(file.in = fopen(file.fileName, "r"))) {
			_lou_logMessage(LOU_LOG_ERROR, "Cannot open file '%s'", file.fileName);
			*mode = 1;
			return EOF;
		}
	}
	if (file.in == NULL) {
		*mode = 1;
		return EOF;
	}
	ch = getAChar(&file);
	if (ch == EOF) {
		fclose(file.in);
		file.in = NULL;
		*mode = 1;
	}
	return ch;
}


atTokenDelimiter(const FileInfo *file) {
	return file->line[file->linepos] <= 32;
}


getAChar(FileInfo *file) {
	/* Read a big endian, little endian or ASCII 8 file and convert it to
	 * 16- or 32-bit unsigned integers */
	int ch1 = 0, ch2 = 0;
	widechar character;
	if (file->encoding == ascii8)
		if (file->status == 2) {
			file->status++;
			return file->checkencoding[1];
		}
	while ((ch1 = fgetc(file->in)) != EOF) {
		if (file->status < 2) file->checkencoding[file->status] = ch1;
		file->status++;
		if (file->status == 2) {
			if (file->checkencoding[0] == 0xfe && file->checkencoding[1] == 0xff)
				file->encoding = bigEndian;
			else if (file->checkencoding[0] == 0xff && file->checkencoding[1] == 0xfe)
				file->encoding = littleEndian;
			else if (file->checkencoding[0] < 128 && file->checkencoding[1] < 128) {
				file->encoding = ascii8;
				return file->checkencoding[0];
			} else {
				compileError(file,
						"encoding is neither big-endian, little-endian nor ASCII 8.");
				ch1 = EOF;
				break;
				;
			}
			continue;
		}
		switch (file->encoding) {
		case noEncoding:
			break;
		case ascii8:
			return ch1;
			break;
		case bigEndian:
			ch2 = fgetc(file->in);
			if (ch2 == EOF) break;
			character = (widechar)(ch1 << 8) | ch2;
			return (int)character;
			break;
		case littleEndian:
			ch2 = fgetc(file->in);
			if (ch2 == EOF) break;
			character = (widechar)(ch2 << 8) | ch1;
			return (int)character;
			break;
		}
		if (ch1 == EOF || ch2 == EOF) break;
	}
	return EOF;
}


lou_free(void) {
	TranslationTableChainEntry *currentEntry;
	TranslationTableChainEntry *previousEntry;
	lou_logEnd();
	if (translationTableChain != NULL) {
		currentEntry = translationTableChain;
		while (currentEntry) {
			freeTranslationTable(currentEntry->table);
			previousEntry = currentEntry;
			currentEntry = currentEntry->next;
			free(previousEntry);
		}
		translationTableChain = NULL;
	}
	if (typebuf != NULL) free(typebuf);
	typebuf = NULL;
	if (wordBuffer != NULL) free(wordBuffer);
	wordBuffer = NULL;
	if (emphasisBuffer != NULL) free(emphasisBuffer);
	emphasisBuffer = NULL;
	sizeTypebuf = 0;
	if (destSpacing != NULL) free(destSpacing);
	destSpacing = NULL;
	sizeDestSpacing = 0;
	{
		int k;
		for (k = 0; k < MAXPASSBUF; k++) {
			if (passbuf[k] != NULL) free(passbuf[k]);
			passbuf[k] = NULL;
			sizePassbuf[k] = 0;
		}
	}
	if (posMapping1 != NULL) free(posMapping1);
	posMapping1 = NULL;
	sizePosMapping1 = 0;
	if (posMapping2 != NULL) free(posMapping2);
	posMapping2 = NULL;
	sizePosMapping2 = 0;
	if (posMapping3 != NULL) free(posMapping3);
	posMapping3 = NULL;
	sizePosMapping3 = 0;
	opcodeLengths[0] = 0;
}


deallocateCharacterClasses(TranslationTableHeader *table) {
	CharacterClass **classes = &table->characterClasses;
	while (*classes) {
		CharacterClass *class = *classes;
		*classes = (*classes)->next;
		if (class) free(class);
	}
}


putCharDotsMapping(
		const FileInfo *file, widechar c, widechar d, DisplayTableHeader **table) {
	if (!getDotsForChar(c, *table)) {
		CharDotsMapping *cdPtr;
		TranslationTableOffset offset;
		if (!allocateSpaceInDisplayTable(file, &offset, sizeof(*cdPtr), table)) return 0;
		cdPtr = (CharDotsMapping *)&(*table)->ruleArea[offset];
		cdPtr->next = 0;
		cdPtr->lookFor = c;
		cdPtr->found = d;
		const unsigned long int charHash = _lou_charHash(c);
		const TranslationTableOffset bucket = (*table)->charToDots[charHash];
		if (!bucket)
			(*table)->charToDots[charHash] = offset;
		else {
			CharDotsMapping *oldcdPtr = (CharDotsMapping *)&(*table)->ruleArea[bucket];
			while (oldcdPtr->next)
				oldcdPtr = (CharDotsMapping *)&(*table)->ruleArea[oldcdPtr->next];
			oldcdPtr->next = offset;
		}
	}
	if (!getCharForDots(d, *table)) {
		CharDotsMapping *cdPtr;
		TranslationTableOffset offset;
		if (!allocateSpaceInDisplayTable(file, &offset, sizeof(*cdPtr), table)) return 0;
		cdPtr = (CharDotsMapping *)&(*table)->ruleArea[offset];
		cdPtr->next = 0;
		cdPtr->lookFor = d;
		cdPtr->found = c;
		const unsigned long int charHash = _lou_charHash(d);
		const TranslationTableOffset bucket = (*table)->dotsToChar[charHash];
		if (!bucket)
			(*table)->dotsToChar[charHash] = offset;
		else {
			CharDotsMapping *oldcdPtr = (CharDotsMapping *)&(*table)->ruleArea[bucket];
			while (oldcdPtr->next)
				oldcdPtr = (CharDotsMapping *)&(*table)->ruleArea[oldcdPtr->next];
			oldcdPtr->next = offset;
		}
	}
	return 1;
}


_lou_findOpcodeNumber(const char *toFind) {
	/* Used by tools such as lou_debug */
	static TranslationTableOpcode lastOpcode = 0;
	TranslationTableOpcode opcode = lastOpcode;
	int length = (int)strlen(toFind);
	do {
		if (length == opcodeLengths[opcode] &&
				strcasecmp(toFind, opcodeNames[opcode]) == 0) {
			lastOpcode = opcode;
			return opcode;
		}
		opcode++;
		if (opcode >= CTO_None) opcode = 0;
	} while (opcode != lastOpcode);
	return CTO_None;
}


deallocateRuleNames(TranslationTableHeader *table) {
	RuleName **ruleName = &table->ruleNames;
	while (*ruleName) {
		RuleName *rn = *ruleName;
		*ruleName = rn->next;
		free(rn);
	}
}


addForwardRuleWithSingleChar(const FileInfo *file, TranslationTableOffset ruleOffset,
		TranslationTableRule *rule, TranslationTableHeader **table) {
	/* direction = 0, rule->charslen = 1 */
	TranslationTableCharacter *character;
	// get the character from the table, or if the character is not defined yet, define it
	// (without adding attributes)
	if (rule->opcode >= CTO_Pass2 && rule->opcode <= CTO_Pass4) {
		character = putDots(file, rule->charsdots[0], table);
		// putDots may have moved table, so make sure rule is still valid
		rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
	} else if (rule->opcode == CTO_CompDots || rule->opcode == CTO_Comp6) {
		character = putChar(file, rule->charsdots[0], table, NULL);
		// putChar may have moved table, so make sure rule is still valid
		rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
		character->compRule = ruleOffset;
		return;
	} else {
		character = putChar(file, rule->charsdots[0], table, NULL);
		// putChar may have moved table, so make sure rule is still valid
		rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
		// if the new rule is a character definition rule, set the main definition rule of
		// this character to it (possibly overwriting previous definition rules)
		// adding the attributes to the character has already been done elsewhere
		if (rule->opcode >= CTO_Space && rule->opcode < CTO_UpLow) {
			if (character->definitionRule) {
				TranslationTableRule *prevRule =
						(TranslationTableRule *)&(*table)
								->ruleArea[character->definitionRule];
				_lou_logMessage(LOU_LOG_DEBUG,
						"%s:%d: Character already defined (%s). The new definition will "
						"take precedence.",
						file->fileName, file->lineNumber,
						printSource(file, prevRule->sourceFile, prevRule->sourceLine));
			} else if (character->basechar) {
				_lou_logMessage(LOU_LOG_DEBUG,
						"%s:%d: A base rule already exists for this character (%s). The "
						"%s rule will take precedence.",
						file->fileName, file->lineNumber,
						printSource(file, character->sourceFile, character->sourceLine),
						_lou_findOpcodeName(rule->opcode));
				character->basechar = 0;
				character->mode = 0;
			}
			character->definitionRule = ruleOffset;
		}
	}
	// add the new rule to the list of rules associated with this character
	// if the new rule is a character definition rule, it is inserted at the end of the
	// list
	// otherwise it is inserted before the first character definition rule
	TranslationTableOffset *otherRule = &character->otherRules;
	while (*otherRule) {
		TranslationTableRule *r = (TranslationTableRule *)&(*table)->ruleArea[*otherRule];
		if (r->charslen == 0) break;
		if (r->opcode >= CTO_Space && r->opcode < CTO_UpLow)
			if (!(rule->opcode >= CTO_Space && rule->opcode < CTO_UpLow)) break;
		otherRule = &r->charsnext;
	}
	rule->charsnext = *otherRule;
	*otherRule = ruleOffset;
}


finalizeTable(TranslationTableHeader *table) {
	if (table->finalized) return 1;
	// normalize basechar and mode of all characters
	for (int i = 0; i < HASHNUM; i++) {
		TranslationTableOffset characterOffset = table->characters[i];
		while (characterOffset) {
			TranslationTableCharacter *character =
					(TranslationTableCharacter *)&table->ruleArea[characterOffset];
			if (character->basechar) {
				TranslationTableOffset basecharOffset = 0;
				TranslationTableCharacter *basechar = character;
				TranslationTableCharacterAttributes mode = 0;
				int detect_loop = 0;
				while (basechar->basechar) {
					if (basechar->basechar == characterOffset ||
							detect_loop++ > MAX_MODES) {
						_lou_logMessage(LOU_LOG_ERROR,
								"%s: error: Character can not be (indirectly) based on "
								"itself.",
								printSource(NULL, character->sourceFile,
										character->sourceLine));
						errorCount++;
						return 0;
					}
					// inherit basechar mode
					mode |= basechar->mode;
					// compute basechar recursively
					basecharOffset = basechar->basechar;
					basechar =
							(TranslationTableCharacter *)&table->ruleArea[basecharOffset];
					if (character->mode & (basechar->attributes | basechar->mode)) {
						char *attributeName = NULL;
						const CharacterClass *class = table->characterClasses;
						while (class) {
							if (class->attribute == character->mode) {
								attributeName = strdup(
										_lou_showString(class->name, class->length, 0));
								break;
							}
							class = class->next;
						}
						_lou_logMessage(LOU_LOG_ERROR,
								"%s: error: Base character %s can not have the %s "
								"attribute.",
								printSource(NULL, character->sourceFile,
										character->sourceLine),
								_lou_showString(&basechar->value, 1, 0),
								attributeName != NULL ? attributeName : "?");
						errorCount++;
						free(attributeName);
						return 0;
					}
				}
				character->mode = mode;
				character->basechar = basecharOffset;
				// add mode to attributes
				character->attributes |= character->mode;
				if (character->attributes & (CTC_UpperCase | CTC_LowerCase))
					character->attributes |= CTC_Letter;
				// also set the new attributes on the associated dots of the base
				// character
				TranslationTableRule *defRule =
						(TranslationTableRule *)&table
								->ruleArea[basechar->definitionRule];
				if (defRule->dotslen == 1) {
					TranslationTableCharacter *dots =
							getDots(defRule->charsdots[defRule->charslen], table);
					if (dots) {
						dots->attributes |= character->mode;
						if (dots->attributes & (CTC_UpperCase | CTC_LowerCase))
							dots->attributes |= CTC_Letter;
					}
				}
				// store all characters that are based on a base character in list
				if (basechar->linked) character->linked = basechar->linked;
				basechar->linked = characterOffset;
			}
			characterOffset = character->next;
		}
	}
	// add noletsign rules from single-letter word and largesign rules
	for (int i = 0; i < HASHNUM; i++) {
		TranslationTableOffset characterOffset = table->characters[i];
		while (characterOffset) {
			TranslationTableCharacter *character =
					(TranslationTableCharacter *)&table->ruleArea[characterOffset];
			if (character->attributes & CTC_Letter) {
				TranslationTableOffset *otherRule = &character->otherRules;
				while (*otherRule) {
					TranslationTableRule *rule =
							(TranslationTableRule *)&table->ruleArea[*otherRule];
					if (rule->opcode == CTO_WholeWord || rule->opcode == CTO_LargeSign)
						if (table->noLetsignCount < LETSIGNSIZE)
							table->noLetsign[table->noLetsignCount++] =
									rule->charsdots[0];
					otherRule = &rule->charsnext;
				}
			}
			characterOffset = character->next;
		}
	}
	table->finalized = 1;
	return 1;
}


allocateSpaceInTranslationTable(const FileInfo *file, TranslationTableOffset *offset,
		int size, TranslationTableHeader **table) {
	/* allocate memory for table and expand previously allocated memory if necessary */
	int spaceNeeded = ((size + OFFSETSIZE - 1) / OFFSETSIZE) * OFFSETSIZE;
	TranslationTableOffset newTableSize = (*table)->bytesUsed + spaceNeeded;
	TranslationTableOffset tableSize = (*table)->tableSize;
	if (newTableSize > tableSize) {
		TranslationTableHeader *newTable;
		newTableSize += (newTableSize / OFFSETSIZE);
		newTable = realloc(*table, newTableSize);
		if (!newTable) {
			compileError(file, "Not enough memory for translation table.");
			_lou_outOfMemory();
		}
		memset(((unsigned char *)newTable) + tableSize, 0, newTableSize - tableSize);
		/* update references to the old table */
		{
			TranslationTableChainEntry *entry;
			for (entry = translationTableChain; entry != NULL; entry = entry->next)
				if (entry->table == *table)
					entry->table = (TranslationTableHeader *)newTable;
		}
		newTable->tableSize = newTableSize;
		*table = newTable;
	}
	if (offset != NULL) {
		*offset = ((*table)->bytesUsed - sizeof(**table)) / OFFSETSIZE;
		(*table)->bytesUsed += spaceNeeded;
	}
	return 1;
}


compileString(const char *inString, TranslationTableHeader **table,
		DisplayTableHeader **displayTable) {
	/* This function can be used to make changes to tables on the fly. */
	int k;
	FileInfo file;
	if (inString == NULL) return 0;
	memset(&file, 0, sizeof(file));
	file.fileName = inString;
	file.encoding = noEncoding;
	file.lineNumber = 1;
	file.status = 0;
	file.linepos = 0;
	for (k = 0; inString[k]; k++) file.line[k] = inString[k];
	file.line[k] = 0;
	file.linelen = k;
	if (table && *table && (*table)->finalized) {
		compileError(&file, "Table is finalized");
		return 0;
	}
	return compileRule(&file, table, displayTable, NULL);
}


getCharForDots(widechar d, const DisplayTableHeader *table) {
	CharDotsMapping *cdPtr;
	const TranslationTableOffset bucket = table->dotsToChar[_lou_charHash(d)];
	TranslationTableOffset offset = bucket;
	while (offset) {
		cdPtr = (CharDotsMapping *)&table->ruleArea[offset];
		if (cdPtr->lookFor == d) return cdPtr;
		offset = cdPtr->next;
	}
	return NULL;
}


compilePassOpcode(const FileInfo *file, TranslationTableOpcode opcode, int noback,
		int nofor, TranslationTableHeader **table) {
	static CharsString passRuleChars;
	static CharsString passRuleDots;
	/* Compile the operands of a pass opcode */
	widechar passSubOp;
	const CharacterClass *class;
	TranslationTableRule *rule = NULL;
	int k;
	int kk = 0;
	int endTest = 0;
	widechar *passInstructions = passRuleDots.chars;
	int passIC = 0; /* Instruction counter */
	passRuleChars.length = 0;
	CharsString passHoldString;
	widechar passHoldNumber;
	CharsString passLine;
	int passLinepos = 0;
	TranslationTableCharacterAttributes passAttributes;
	passHoldString.length = 0;
	for (k = file->linepos; k < file->linelen; k++)
		passHoldString.chars[passHoldString.length++] = file->line[k];
#define SEPCHAR 0x0001
	for (k = 0; k < passHoldString.length && passHoldString.chars[k] > 32; k++)
		;
	if (k < passHoldString.length)
		passHoldString.chars[k] = SEPCHAR;
	else {
		compileError(file, "Invalid multipass operands");
		return 0;
	}
	parseChars(file, &passLine, &passHoldString);
	/* Compile test part */
	for (k = 0; k < passLine.length && passLine.chars[k] != SEPCHAR; k++)
		;
	endTest = k;
	passLine.chars[endTest] = pass_endTest;
	passLinepos = 0;
	while (passLinepos <= endTest) {
		switch ((passSubOp = passLine.chars[passLinepos])) {
		case pass_lookback:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_lookback))
				return 0;
			passLinepos++;
			passGetNumber(&passLine, &passLinepos, &passHoldNumber);
			if (passHoldNumber == 0) passHoldNumber = 1;
			if (!appendInstructionChar(file, passInstructions, &passIC, passHoldNumber))
				return 0;
			break;
		case pass_not:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_not))
				return 0;
			passLinepos++;
			break;
		case pass_first:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_first))
				return 0;
			passLinepos++;
			break;
		case pass_last:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_last))
				return 0;
			passLinepos++;
			break;
		case pass_search:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_search))
				return 0;
			passLinepos++;
			break;
		case pass_string:
			if (!verifyStringOrDots(file, opcode, 1, 0, nofor)) {
				return 0;
			}
			passLinepos++;
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_string))
				return 0;
			passGetString(&passLine, &passLinepos, &passHoldString, file);
			if (passHoldString.length == 0) {
				compileError(file, "empty string in test part");
				return 0;
			}
			goto testDoCharsDots;
		case pass_dots:
			if (!verifyStringOrDots(file, opcode, 0, 0, nofor)) {
				return 0;
			}
			passLinepos++;
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_dots))
				return 0;
			passGetDots(&passLine, &passLinepos, &passHoldString, file);
			if (passHoldString.length == 0) {
				compileError(file, "expected dot pattern after @ operand in test part");
				return 0;
			}
		testDoCharsDots:
			if (passIC >= MAXSTRING) {
				compileError(
						file, "@ operand in test part of multipass operand too long");
				return 0;
			}
			if (!appendInstructionChar(
						file, passInstructions, &passIC, passHoldString.length))
				return 0;
			for (kk = 0; kk < passHoldString.length; kk++) {
				if (passIC >= MAXSTRING) {
					compileError(
							file, "@ operand in test part of multipass operand too long");
					return 0;
				}
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldString.chars[kk]))
					return 0;
			}
			break;
		case pass_startReplace:
			if (!appendInstructionChar(
						file, passInstructions, &passIC, pass_startReplace))
				return 0;
			passLinepos++;
			break;
		case pass_endReplace:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_endReplace))
				return 0;
			passLinepos++;
			break;
		case pass_variable:
			passLinepos++;
			if (!passGetVariableNumber(file, &passLine, &passLinepos, &passHoldNumber))
				return 0;
			switch (passLine.chars[passLinepos]) {
			case pass_eq:
				if (!appendInstructionChar(file, passInstructions, &passIC, pass_eq))
					return 0;
				goto doComp;
			case pass_lt:
				if (passLine.chars[passLinepos + 1] == pass_eq) {
					passLinepos++;
					if (!appendInstructionChar(
								file, passInstructions, &passIC, pass_lteq))
						return 0;
				} else if (!appendInstructionChar(
								   file, passInstructions, &passIC, pass_lt))
					return 0;
				goto doComp;
			case pass_gt:
				if (passLine.chars[passLinepos + 1] == pass_eq) {
					passLinepos++;
					if (!appendInstructionChar(
								file, passInstructions, &passIC, pass_gteq))
						return 0;
				} else if (!appendInstructionChar(
								   file, passInstructions, &passIC, pass_gt))
					return 0;
			doComp:
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldNumber))
					return 0;
				passLinepos++;
				passGetNumber(&passLine, &passLinepos, &passHoldNumber);
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldNumber))
					return 0;
				break;
			default:
				compileError(file, "incorrect comparison operator");
				return 0;
			}
			break;
		case pass_attributes:
			passLinepos++;
			if (!passGetAttributes(&passLine, &passLinepos, &passAttributes, file))
				return 0;
		insertAttributes:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_attributes))
				return 0;
			if (!appendInstructionChar(
						file, passInstructions, &passIC, (passAttributes >> 48) & 0xffff))
				return 0;
			if (!appendInstructionChar(
						file, passInstructions, &passIC, (passAttributes >> 32) & 0xffff))
				return 0;
			if (!appendInstructionChar(
						file, passInstructions, &passIC, (passAttributes >> 16) & 0xffff))
				return 0;
			if (!appendInstructionChar(
						file, passInstructions, &passIC, passAttributes & 0xffff))
				return 0;
		getRange:
			if (passLine.chars[passLinepos] == pass_until) {
				passLinepos++;
				if (!appendInstructionChar(file, passInstructions, &passIC, 1)) return 0;
				if (!appendInstructionChar(file, passInstructions, &passIC, 0xffff))
					return 0;
				break;
			}
			passGetNumber(&passLine, &passLinepos, &passHoldNumber);
			if (passHoldNumber == 0) {
				if (!appendInstructionChar(file, passInstructions, &passIC, 1)) return 0;
				if (!appendInstructionChar(file, passInstructions, &passIC, 1)) return 0;
				break;
			}
			if (!appendInstructionChar(file, passInstructions, &passIC, passHoldNumber))
				return 0;
			if (passLine.chars[passLinepos] != pass_hyphen) {
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldNumber))
					return 0;
				break;
			}
			passLinepos++;
			passGetNumber(&passLine, &passLinepos, &passHoldNumber);
			if (passHoldNumber == 0) {
				compileError(file, "invalid range");
				return 0;
			}
			if (!appendInstructionChar(file, passInstructions, &passIC, passHoldNumber))
				return 0;
			break;
		case pass_groupstart:
		case pass_groupend: {
			passLinepos++;
			passGetName(&passLine, &passLinepos, &passHoldString);
			TranslationTableOffset ruleOffset = findRuleName(&passHoldString, *table);
			if (ruleOffset)
				rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			if (rule && rule->opcode == CTO_Grouping) {
				if (!appendInstructionChar(file, passInstructions, &passIC, passSubOp))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset >> 16))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset & 0xffff))
					return 0;
				break;
			} else {
				compileError(file, "%s is not a grouping name",
						_lou_showString(
								&passHoldString.chars[0], passHoldString.length, 0));
				return 0;
			}
			break;
		}
		case pass_swap: {
			passLinepos++;
			passGetName(&passLine, &passLinepos, &passHoldString);
			if ((class = findCharacterClass(&passHoldString, *table))) {
				passAttributes = class->attribute;
				goto insertAttributes;
			}
			TranslationTableOffset ruleOffset = findRuleName(&passHoldString, *table);
			if (ruleOffset)
				rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			if (rule &&
					(rule->opcode == CTO_SwapCc || rule->opcode == CTO_SwapCd ||
							rule->opcode == CTO_SwapDd)) {
				if (!appendInstructionChar(file, passInstructions, &passIC, pass_swap))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset >> 16))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset & 0xffff))
					return 0;
				goto getRange;
			}
			compileError(file, "%s is neither a class name nor a swap name.",
					_lou_showString(&passHoldString.chars[0], passHoldString.length, 0));
			return 0;
		}
		case pass_endTest:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_endTest))
				return 0;
			passLinepos++;
			break;
		default:
			compileError(file, "incorrect operator '%c ' in test part",
					passLine.chars[passLinepos]);
			return 0;
		}

	} /* Compile action part */

	/* Compile action part */
	while (passLinepos < passLine.length && passLine.chars[passLinepos] <= 32)
		passLinepos++;
	while (passLinepos < passLine.length && passLine.chars[passLinepos] > 32) {
		if (passIC >= MAXSTRING) {
			compileError(file, "Action part in multipass operand too long");
			return 0;
		}
		switch ((passSubOp = passLine.chars[passLinepos])) {
		case pass_string:
			if (!verifyStringOrDots(file, opcode, 1, 1, nofor)) {
				return 0;
			}
			passLinepos++;
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_string))
				return 0;
			passGetString(&passLine, &passLinepos, &passHoldString, file);
			goto actionDoCharsDots;
		case pass_dots:
			if (!verifyStringOrDots(file, opcode, 0, 1, nofor)) {
				return 0;
			}
			passLinepos++;
			passGetDots(&passLine, &passLinepos, &passHoldString, file);
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_dots))
				return 0;
			if (passHoldString.length == 0) {
				compileError(file, "expected dot pattern after @ operand in action part");
				return 0;
			}
		actionDoCharsDots:
			if (passIC >= MAXSTRING) {
				compileError(
						file, "@ operand in action part of multipass operand too long");
				return 0;
			}
			if (!appendInstructionChar(
						file, passInstructions, &passIC, passHoldString.length))
				return 0;
			for (kk = 0; kk < passHoldString.length; kk++) {
				if (passIC >= MAXSTRING) {
					compileError(file,
							"@ operand in action part of multipass operand too long");
					return 0;
				}
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldString.chars[kk]))
					return 0;
			}
			break;
		case pass_variable:
			passLinepos++;
			if (!passGetVariableNumber(file, &passLine, &passLinepos, &passHoldNumber))
				return 0;
			switch (passLine.chars[passLinepos]) {
			case pass_eq:
				if (!appendInstructionChar(file, passInstructions, &passIC, pass_eq))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldNumber))
					return 0;
				passLinepos++;
				passGetNumber(&passLine, &passLinepos, &passHoldNumber);
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldNumber))
					return 0;
				break;
			case pass_plus:
			case pass_hyphen:
				if (!appendInstructionChar(file, passInstructions, &passIC,
							passLine.chars[passLinepos++]))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, passHoldNumber))
					return 0;
				break;
			default:
				compileError(file, "incorrect variable operator in action part");
				return 0;
			}
			break;
		case pass_copy:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_copy))
				return 0;
			passLinepos++;
			break;
		case pass_omit:
			if (!appendInstructionChar(file, passInstructions, &passIC, pass_omit))
				return 0;
			passLinepos++;
			break;
		case pass_groupreplace:
		case pass_groupstart:
		case pass_groupend: {
			passLinepos++;
			passGetName(&passLine, &passLinepos, &passHoldString);
			TranslationTableOffset ruleOffset = findRuleName(&passHoldString, *table);
			if (ruleOffset)
				rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			if (rule && rule->opcode == CTO_Grouping) {
				if (!appendInstructionChar(file, passInstructions, &passIC, passSubOp))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset >> 16))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset & 0xffff))
					return 0;
				break;
			}
			compileError(file, "%s is not a grouping name",
					_lou_showString(&passHoldString.chars[0], passHoldString.length, 0));
			return 0;
		}
		case pass_swap: {
			passLinepos++;
			passGetName(&passLine, &passLinepos, &passHoldString);
			TranslationTableOffset ruleOffset = findRuleName(&passHoldString, *table);
			if (ruleOffset)
				rule = (TranslationTableRule *)&(*table)->ruleArea[ruleOffset];
			if (rule &&
					(rule->opcode == CTO_SwapCc || rule->opcode == CTO_SwapCd ||
							rule->opcode == CTO_SwapDd)) {
				if (!appendInstructionChar(file, passInstructions, &passIC, pass_swap))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset >> 16))
					return 0;
				if (!appendInstructionChar(
							file, passInstructions, &passIC, ruleOffset & 0xffff))
					return 0;
				break;
			}
			compileError(file, "%s is not a swap name.",
					_lou_showString(&passHoldString.chars[0], passHoldString.length, 0));
			return 0;
			break;
		}
		default:
			compileError(file, "incorrect operator in action part");
			return 0;
		}
	}

	/* Analyze and add rule */
	passRuleDots.length = passIC;

	{
		widechar *characters;
		int length;
		int found = passFindCharacters(
				file, passInstructions, passRuleDots.length, &characters, &length);

		if (!found) return 0;

		if (characters) {
			for (k = 0; k < length; k += 1) passRuleChars.chars[k] = characters[k];
			passRuleChars.length = k;
		}
	}

	if (!addRule(file, opcode, &passRuleChars, &passRuleDots, 0, 0, NULL, NULL, noback,
				nofor, table))
		return 0;
	return 1;
}


resolveSubtable(const char *table, const char *base, const char *searchPath) {
	char *tableFile;
	static struct stat info;

	if (table == NULL || table[0] == '\0') return NULL;
	tableFile = (char *)malloc(MAXSTRING * sizeof(char) * 2);

	//
	// First try to resolve against base
	//
	if (base) {
		int k;
		strcpy(tableFile, base);
		k = (int)strlen(tableFile);
		while (k >= 0 && tableFile[k] != '/' && tableFile[k] != '\\') k--;
		tableFile[++k] = '\0';
		strcat(tableFile, table);
		if (stat(tableFile, &info) == 0 && !(info.st_mode & S_IFDIR)) {
			_lou_logMessage(LOU_LOG_DEBUG, "found table %s", tableFile);
			return tableFile;
		}
	}

	//
	// It could be an absolute path, or a path relative to the current working
	// directory
	//
	strcpy(tableFile, table);
	if (stat(tableFile, &info) == 0 && !(info.st_mode & S_IFDIR)) {
		_lou_logMessage(LOU_LOG_DEBUG, "found table %s", tableFile);
		return tableFile;
	}

	//
	// Then search `LOUIS_TABLEPATH`, `dataPath` and `programPath`
	//
	if (searchPath[0] != '\0') {
		char *dir;
		int last;
		char *cp;
		char *searchPath_copy = strdup(searchPath);
		for (dir = searchPath_copy;; dir = cp + 1) {
			for (cp = dir; *cp != '\0' && *cp != ','; cp++)
				;
			last = (*cp == '\0');
			*cp = '\0';
			if (dir == cp) dir = ".";
			sprintf(tableFile, "%s%c%s", dir, DIR_SEP, table);
			if (stat(tableFile, &info) == 0 && !(info.st_mode & S_IFDIR)) {
				_lou_logMessage(LOU_LOG_DEBUG, "found table %s", tableFile);
				free(searchPath_copy);
				return tableFile;
			}
			if (last) break;
			sprintf(tableFile, "%s%c%s%c%s%c%s", dir, DIR_SEP, "liblouis", DIR_SEP,
					"tables", DIR_SEP, table);
			if (stat(tableFile, &info) == 0 && !(info.st_mode & S_IFDIR)) {
				_lou_logMessage(LOU_LOG_DEBUG, "found table %s", tableFile);
				free(searchPath_copy);
				return tableFile;
			}
			if (last) break;
		}
		free(searchPath_copy);
	}
	free(tableFile);
	return NULL;
}


getRuleDotsText(FileInfo *file, CharsString *ruleDots) {
	CharsString token;
	if (!getToken(file, &token, "characters")) return 0;
	return parseChars(file, ruleDots, &token);
}


passGetVariableNumber(
		const FileInfo *file, CharsString *passLine, int *passLinepos, widechar *number) {
	if (!passGetNumber(passLine, passLinepos, number)) {
		compileError(file, "missing variable number");
		return 0;
	}
	if ((*number >= 0) && (*number < NUMVAR)) return 1;
	compileError(file, "variable number out of range");
	return 0;
}


allocateCharacterClasses(TranslationTableHeader *table) {
	/* Allocate memory for predefined character classes */
	int k = 0;
	table->characterClasses = NULL;
	table->nextCharacterClassAttribute = 1;	 // CTC_Space
	table->nextNumberedCharacterClassAttribute = CTC_UserDefined1;
	while (characterClassNames[k]) {
		widechar wname[MAXSTRING];
		int length = (int)strlen(characterClassNames[k]);
		int kk;
		for (kk = 0; kk < length; kk++) wname[kk] = (widechar)characterClassNames[k][kk];
		if (!addCharacterClass(NULL, wname, length, table, 0)) {
			deallocateCharacterClasses(table);
			return 0;
		}
		k++;
	}
	return 1;
}


passGetNumber(CharsString *passLine, int *passLinepos, widechar *number) {
	/* Convert a string of wide character digits to an integer */
	*number = 0;
	while ((*passLinepos < passLine->length) && (passLine->chars[*passLinepos] >= '0') &&
			(passLine->chars[*passLinepos] <= '9'))
		*number = 10 * (*number) + (passLine->chars[(*passLinepos)++] - '0');
	return 1;
}


getCharacters(FileInfo *file, CharsString *characters) {
	/* Get ruleChars string */
	CharsString token;
	if (!getToken(file, &token, "characters")) return 0;
	return parseChars(file, characters, &token);
}


getOpcode(const FileInfo *file, const CharsString *token) {
	static TranslationTableOpcode lastOpcode = 0;
	TranslationTableOpcode opcode = lastOpcode;

	do {
		if (token->length == opcodeLengths[opcode])
			if (eqasc2uni((unsigned char *)opcodeNames[opcode], &token->chars[0],
						token->length)) {
				lastOpcode = opcode;
				return opcode;
			}
		opcode++;
		if (opcode >= CTO_None) opcode = 0;
	} while (opcode != lastOpcode);
	return CTO_None;
}


_lou_getDotsForChar(widechar c, const DisplayTableHeader *table) {
	CharDotsMapping *cdPtr = getDotsForChar(c, table);
	if (cdPtr) return cdPtr->found;
	return LOU_DOTS;
}


compileMacro(FileInfo *file, const Macro **macro) {

	// parse name
	CharsString token;
	if (!getToken(file, &token, "macro name")) return 0;
	switch (getOpcode(file, &token)) {
	case CTO_UpLow:	 // deprecated so "uplow" may be used as macro name
	case CTO_None:
		break;
	default:
		compileError(file, "Invalid macro name: already taken by an opcode");
		return 0;
	}
	for (int i = 0; i < token.length; i++) {
		if (!((token.chars[i] >= 'a' && token.chars[i] <= 'z') ||
					(token.chars[i] >= 'A' && token.chars[i] <= 'Z') ||
					(token.chars[i] >= '0' && token.chars[i] <= '9'))) {
			compileError(file,
					"Invalid macro name: must be a word containing only letters and "
					"digits");
			return 0;
		}
	}
	static char name[MAXSTRING + 1];
	int name_length;
	for (name_length = 0; name_length < token.length;
			name_length++)	// we know token can not be longer than MAXSTRING
		name[name_length] = (char)token.chars[name_length];
	name[name_length] = '\0';

	// parse body
	static widechar definition[MAXSTRING];
	static int substitutions[2 * MAX_MACRO_VAR];
	int definition_length = 0;
	int substitution_count = 0;
	int argument_count = 0;
	int dollar = 0;

	// ignore rest of line after name and read lines until "eom" is encountered
	while (_lou_getALine(file)) {
		if (file->linelen >= 3 && file->line[0] == 'e' && file->line[1] == 'o' &&
				file->line[2] == 'm') {
			*macro = create_macro(name, definition, definition_length, substitutions,
					substitution_count, argument_count);
			return 1;
		}
		while (!atEndOfLine(file)) {
			widechar c = file->line[file->linepos++];
			if (dollar) {
				dollar = 0;
				if (c >= '0' && c <= '9') {
					if (substitution_count >= MAX_MACRO_VAR) {
						compileError(file,
								"Macro can not have more than %d variable substitutions",
								MAXSTRING);
						return 0;
					}
					substitutions[2 * substitution_count] = definition_length;
					int arg = c - '0';
					substitutions[2 * substitution_count + 1] = arg;
					if (arg > argument_count) argument_count = arg;
					substitution_count++;
					continue;
				}
			} else if (c == '$') {
				dollar = 1;
				continue;
			}
			if (definition_length >= MAXSTRING) {
				compileError(file, "Macro exceeds %d characters", MAXSTRING);
				return 0;
			} else
				definition[definition_length++] = c;
		}
		dollar = 0;
		if (definition_length >= MAXSTRING) {
			compileError(file, "Macro exceeds %d characters", MAXSTRING);
			return 0;
		}
		definition[definition_length++] = '\n';
	}
	compileError(file, "macro must be terminated with 'eom'");
	return 0;
}


getTable(const char *translationTableList, const char *displayTableList,
		TranslationTableHeader **translationTable, DisplayTableHeader **displayTable) {
	/* Keep track of which tables have already been compiled */
	int translationTableListLen, displayTableListLen = 0;
	if (translationTableList == NULL || *translationTableList == 0)
		translationTable = NULL;
	if (displayTableList == NULL || *displayTableList == 0) displayTable = NULL;
	/* See if translation table has already been compiled */
	if (translationTable) {
		translationTableListLen = (int)strlen(translationTableList);
		*translationTable = NULL;
		TranslationTableChainEntry *currentEntry = translationTableChain;
		TranslationTableChainEntry *prevEntry = NULL;
		while (currentEntry != NULL) {
			if (translationTableListLen == currentEntry->tableListLength &&
					(memcmp(&currentEntry->tableList[0], translationTableList,
							translationTableListLen)) == 0) {
				/* Move the table to the top of the table chain. */
				if (prevEntry != NULL) {
					prevEntry->next = currentEntry->next;
					currentEntry->next = translationTableChain;
					translationTableChain = currentEntry;
				}
				*translationTable = currentEntry->table;
				break;
			}
			prevEntry = currentEntry;
			currentEntry = currentEntry->next;
		}
	}
	/* See if display table has already been compiled */
	if (displayTable) {
		displayTableListLen = (int)strlen(displayTableList);
		*displayTable = NULL;
		DisplayTableChainEntry *currentEntry = displayTableChain;
		DisplayTableChainEntry *prevEntry = NULL;
		while (currentEntry != NULL) {
			if (displayTableListLen == currentEntry->tableListLength &&
					(memcmp(&currentEntry->tableList[0], displayTableList,
							displayTableListLen)) == 0) {
				/* Move the table to the top of the table chain. */
				if (prevEntry != NULL) {
					prevEntry->next = currentEntry->next;
					currentEntry->next = displayTableChain;
					displayTableChain = currentEntry;
				}
				*displayTable = currentEntry->table;
				break;
			}
			prevEntry = currentEntry;
			currentEntry = currentEntry->next;
		}
	}
	if ((translationTable && *translationTable == NULL) ||
			(displayTable && *displayTable == NULL)) {
		TranslationTableHeader *newTranslationTable = NULL;
		DisplayTableHeader *newDisplayTable = NULL;
		if (compileTable(translationTableList, displayTableList,
					(translationTable && *translationTable == NULL) ? &newTranslationTable
																	: NULL,
					(displayTable && *displayTable == NULL) ? &newDisplayTable : NULL)) {
			/* Add a new entry to the top of the table chain. */
			if (newTranslationTable != NULL) {
				int entrySize =
						sizeof(TranslationTableChainEntry) + translationTableListLen;
				TranslationTableChainEntry *newEntry = malloc(entrySize);
				if (!newEntry) _lou_outOfMemory();
				newEntry->next = translationTableChain;
				newEntry->table = newTranslationTable;
				newEntry->tableListLength = translationTableListLen;
				memcpy(&newEntry->tableList[0], translationTableList,
						translationTableListLen);
				translationTableChain = newEntry;
				*translationTable = newTranslationTable;
			}
			if (newDisplayTable != NULL) {
				int entrySize = sizeof(DisplayTableChainEntry) + displayTableListLen;
				DisplayTableChainEntry *newEntry = malloc(entrySize);
				if (!newEntry) _lou_outOfMemory();
				newEntry->next = displayTableChain;
				newEntry->table = newDisplayTable;
				newEntry->tableListLength = displayTableListLen;
				memcpy(&newEntry->tableList[0], displayTableList, displayTableListLen);
				displayTableChain = newEntry;
				*displayTable = newDisplayTable;
			}
		} else {
			_lou_logMessage(
					LOU_LOG_ERROR, "%s could not be compiled", translationTableList);
			return;
		}
	}
}


compileWarning(const FileInfo *file, const char *format, ...) {
#ifndef __SYMBIAN32__
	char buffer[MAXSTRING];
	va_list arguments;
	va_start(arguments, format);
	vsnprintf(buffer, sizeof(buffer), format, arguments);
	va_end(arguments);
	if (file)
		_lou_logMessage(LOU_LOG_WARN, "%s:%d: warning: %s", file->fileName,
				file->lineNumber, buffer);
	else
		_lou_logMessage(LOU_LOG_WARN, "warning: %s", buffer);
	warningCount++;
#endif
}


allocateTranslationTable(const FileInfo *file, TranslationTableHeader **table) {
	/* Allocate memory for the table and a guess on the number of rules */
	const TranslationTableOffset startSize = 2 * sizeof(**table);
	if (*table) return 1;
	TranslationTableOffset bytesUsed =
			sizeof(**table) + OFFSETSIZE; /* So no offset is ever zero */
	if (!(*table = malloc(startSize))) {
		compileError(file, "Not enough memory");
		if (*table != NULL) free(*table);
		*table = NULL;
		_lou_outOfMemory();
	}
	memset(*table, 0, startSize);
	(*table)->tableSize = startSize;
	(*table)->bytesUsed = bytesUsed;
	return 1;
}


includeFile(const FileInfo *file, CharsString *includedFile,
		TranslationTableHeader **table, DisplayTableHeader **displayTable) {
	int k;
	char includeThis[MAXSTRING];
	char **tableFiles;
	int rv;
	for (k = 0; k < includedFile->length; k++)
		includeThis[k] = (char)includedFile->chars[k];
	if (k >= MAXSTRING) {
		compileError(file, "Include statement too long: 'include %s'", includeThis);
		return 0;
	}
	includeThis[k] = 0;
	tableFiles = _lou_resolveTable(includeThis, file->fileName);
	if (tableFiles == NULL) {
		errorCount++;
		return 0;
	}
	if (tableFiles[1] != NULL) {
		free_tablefiles(tableFiles);
		compileError(file, "Table list not supported in include statement: 'include %s'",
				includeThis);
		return 0;
	}
	rv = compileFile(*tableFiles, table, displayTable);
	free_tablefiles(tableFiles);
	if (!rv)
		_lou_logMessage(LOU_LOG_ERROR, "%s:%d: Error in included file", file->fileName,
				file->lineNumber);
	return rv;
}


free_macro(const Macro *macro) {
	if (macro) {
		free((char *)macro->name);
		free((char *)macro->definition);
		free((int *)macro->substitutions);
		free((Macro *)macro);
	}
}


_lou_compileTranslationRule(const char *tableList, const char *inString) {
	TranslationTableHeader *table;
	getTable(tableList, NULL, &table, NULL);
	return compileString(inString, &table, NULL);
}


compileCharDef(FileInfo *file, TranslationTableOpcode opcode,
		TranslationTableCharacterAttributes attributes, int noback, int nofor,
		TranslationTableHeader **table, DisplayTableHeader **displayTable) {
	CharsString ruleChars;
	CharsString ruleDots;
	if (!getRuleCharsText(file, &ruleChars)) return 0;
	if (!getRuleDotsPattern(file, &ruleDots)) return 0;
	if (ruleChars.length != 1) {
		compileError(file, "Exactly one character is required.");
		return 0;
	}
	if (ruleDots.length < 1) {
		compileError(file, "At least one cell is required.");
		return 0;
	}
	if (table) {
		TranslationTableCharacter *character;
		TranslationTableCharacter *cell = NULL;
		int k;
		if (attributes & (CTC_UpperCase | CTC_LowerCase)) attributes |= CTC_Letter;
		character = putChar(file, ruleChars.chars[0], table, NULL);
		character->attributes |= attributes;
		for (k = ruleDots.length - 1; k >= 0; k -= 1) {
			cell = getDots(ruleDots.chars[k], *table);
			if (!cell) cell = putDots(file, ruleDots.chars[k], table);
		}
		if (ruleDots.length == 1) cell->attributes |= attributes;
	}
	if (displayTable && ruleDots.length == 1)
		putCharDotsMapping(file, ruleChars.chars[0], ruleDots.chars[0], displayTable);
	if (table)
		if (!addRule(file, opcode, &ruleChars, &ruleDots, 0, 0, NULL, NULL, noback, nofor,
					table))
			return 0;
	return 1;
}


compileTable(const char *tableList, const char *displayTableList,
		TranslationTableHeader **translationTable, DisplayTableHeader **displayTable) {
	char **tableFiles;
	char **subTable;
	if (translationTable && !tableList) return 0;
	if (displayTable && !displayTableList) return 0;
	if (!translationTable && !displayTable) return 0;
	if (translationTable) *translationTable = NULL;
	if (displayTable) *displayTable = NULL;
	errorCount = warningCount = fileCount = 0;
	if (!opcodeLengths[0]) {
		TranslationTableOpcode opcode;
		for (opcode = 0; opcode < CTO_None; opcode++)
			opcodeLengths[opcode] = (short)strlen(opcodeNames[opcode]);
	}
	if (translationTable) allocateTranslationTable(NULL, translationTable);
	if (displayTable) allocateDisplayTable(NULL, displayTable);

	if (translationTable) {
		(*translationTable)->emphClassNames[0] = NULL;
		(*translationTable)->characterClasses = NULL;
		(*translationTable)->ruleNames = NULL;
	}

	/* Compile things that are necesary for the proper operation of
	 * liblouis or liblouisxml or liblouisutdml */
	/* TODO: These definitions seem to be necessary for proper functioning of
	   liblouisutdml. Find a way to satisfy those requirements without hard coding
	   some characters in every table notably behind the users back */
	compileString("space \\xffff 123456789abcdef LOU_ENDSEGMENT", translationTable,
			displayTable);

	if (displayTable && translationTable && strcmp(tableList, displayTableList) == 0) {
		/* Compile the display and translation tables in one go */

		/* Compile all subtables in the list */
		if (!(tableFiles = _lou_resolveTable(tableList, NULL))) {
			errorCount++;
			goto cleanup;
		}
		for (subTable = tableFiles; *subTable; subTable++)
			if (!compileFile(*subTable, translationTable, displayTable)) goto cleanup;
	} else {
		/* Compile the display and translation tables separately */

		if (displayTable) {
			if (!(tableFiles = _lou_resolveTable(displayTableList, NULL))) {
				errorCount++;
				goto cleanup;
			}
			for (subTable = tableFiles; *subTable; subTable++)
				if (!compileFile(*subTable, NULL, displayTable)) goto cleanup;
			free_tablefiles(tableFiles);
			tableFiles = NULL;
		}
		if (translationTable) {
			if (!(tableFiles = _lou_resolveTable(tableList, NULL))) {
				errorCount++;
				goto cleanup;
			}
			for (subTable = tableFiles; *subTable; subTable++)
				if (!compileFile(*subTable, translationTable, NULL)) goto cleanup;
		}
	}

/* Clean up after compiling files */
cleanup:
	free_tablefiles(tableFiles);
	if (warningCount) _lou_logMessage(LOU_LOG_WARN, "%d warnings issued", warningCount);
	if (!errorCount) {
		if (translationTable) setDefaults(*translationTable);
		return 1;
	} else {
		_lou_logMessage(LOU_LOG_ERROR, "%d errors found.", errorCount);
		if (translationTable) {
			if (*translationTable) freeTranslationTable(*translationTable);
			*translationTable = NULL;
		}
		if (displayTable) {
			if (*displayTable) free(*displayTable);
			*displayTable = NULL;
		}
		return 0;
	}
}


addBackwardPassRule(TranslationTableOffset ruleOffset, TranslationTableRule *rule,
		TranslationTableHeader *table) {
	TranslationTableOffset *backPassRule;
	switch (rule->opcode) {
	case CTO_Correct:
		backPassRule = &table->backPassRules[0];
		break;
	case CTO_Context:
		backPassRule = &table->backPassRules[1];
		break;
	case CTO_Pass2:
		backPassRule = &table->backPassRules[2];
		break;
	case CTO_Pass3:
		backPassRule = &table->backPassRules[3];
		break;
	case CTO_Pass4:
		backPassRule = &table->backPassRules[4];
		break;
	default:
		return 0;
	}
	while (*backPassRule) {
		TranslationTableRule *r = (TranslationTableRule *)&table->ruleArea[*backPassRule];
		if (rule->charslen > r->charslen) break;
		backPassRule = &r->dotsnext;
	}
	rule->dotsnext = *backPassRule;
	*backPassRule = ruleOffset;
	return 1;
}


setDefaults(TranslationTableHeader *table) {
	for (int i = 0; i < 3; i++)
		if (!table->emphRules[i][lenPhraseOffset])
			table->emphRules[i][lenPhraseOffset] = 4;
	if (table->numPasses == 0) table->numPasses = 1;
	return 1;
}


getNextAttribute(TranslationTableHeader *table) {
	/* Get the next attribute value, or 0 if there is no more space in the table. */
	TranslationTableCharacterAttributes next = table->nextCharacterClassAttribute;
	if (next) {
		if (next == CTC_LitDigit)
			table->nextCharacterClassAttribute = CTC_UserDefined9;
		else
			table->nextCharacterClassAttribute <<= 1;
		return next;
	} else
		return getNextNumberedAttribute(table);
}


lou_compileString(const char *tableList, const char *inString) {
	TranslationTableHeader *table;
	DisplayTableHeader *displayTable;
	getTable(tableList, tableList, &table, &displayTable);
	if (!table) return 0;
	if (!compileString(inString, &table, &displayTable)) return 0;
	return 1;
}


compileError(const FileInfo *file, const char *format, ...) {
#ifndef __SYMBIAN32__
	char buffer[MAXSTRING];
	va_list arguments;
	va_start(arguments, format);
	vsnprintf(buffer, sizeof(buffer), format, arguments);
	va_end(arguments);
	if (file)
		_lou_logMessage(LOU_LOG_ERROR, "%s:%d: error: %s", file->fileName,
				file->lineNumber, buffer);
	else
		_lou_logMessage(LOU_LOG_ERROR, "error: %s", buffer);
	errorCount++;
#endif
}


passGetAttributes(CharsString *passLine, int *passLinepos,
		TranslationTableCharacterAttributes *attributes, const FileInfo *file) {
	int more = 1;
	*attributes = 0;
	while (more) {
		switch (passLine->chars[*passLinepos]) {
		case pass_any:
			*attributes = 0xffffffff;
			break;
		case pass_digit:
			*attributes |= CTC_Digit;
			break;
		case pass_litDigit:
			*attributes |= CTC_LitDigit;
			break;
		case pass_letter:
			*attributes |= CTC_Letter;
			break;
		case pass_math:
			*attributes |= CTC_Math;
			break;
		case pass_punctuation:
			*attributes |= CTC_Punctuation;
			break;
		case pass_sign:
			*attributes |= CTC_Sign;
			break;
		case pass_space:
			*attributes |= CTC_Space;
			break;
		case pass_uppercase:
			*attributes |= CTC_UpperCase;
			break;
		case pass_lowercase:
			*attributes |= CTC_LowerCase;
			break;
		case pass_class1:
			*attributes |= CTC_UserDefined9;
			break;
		case pass_class2:
			*attributes |= CTC_UserDefined10;
			break;
		case pass_class3:
			*attributes |= CTC_UserDefined11;
			break;
		case pass_class4:
			*attributes |= CTC_UserDefined12;
			break;
		default:
			more = 0;
			break;
		}
		if (more) (*passLinepos)++;
	}
	if (!*attributes) {
		compileError(file, "missing attribute");
		(*passLinepos)--;
		return 0;
	}
	return 1;
}


hexValue(const FileInfo *file, const widechar *digits, int length) {
	int k;
	unsigned int binaryValue = 0;
	for (k = 0; k < length; k++) {
		unsigned int hexDigit = 0;
		if (digits[k] >= '0' && digits[k] <= '9')
			hexDigit = digits[k] - '0';
		else if (digits[k] >= 'a' && digits[k] <= 'f')
			hexDigit = digits[k] - 'a' + 10;
		else if (digits[k] >= 'A' && digits[k] <= 'F')
			hexDigit = digits[k] - 'A' + 10;
		else {
			compileError(file, "invalid %d-digit hexadecimal number", length);
			return (widechar)0xffffffff;
		}
		binaryValue |= hexDigit << (4 * (length - 1 - k));
	}
	return (widechar)binaryValue;
}


create_macro(const char *name, const widechar *definition, int definition_length,
		const int *substitutions, int substitution_count, int argument_count) {
	Macro *m = malloc(sizeof(Macro));
	m->name = strdup(name);
	widechar *definition_copy = malloc(definition_length * sizeof(widechar));
	memcpy(definition_copy, definition, definition_length * sizeof(widechar));
	m->definition = definition_copy;
	m->definition_length = definition_length;
	int *substitutions_copy = malloc(2 * substitution_count * sizeof(int));
	memcpy(substitutions_copy, substitutions, 2 * substitution_count * sizeof(int));
	m->substitutions = substitutions_copy;
	m->substitution_count = substitution_count;
	m->argument_count = argument_count;
	return m;
}


_lou_defaultTableResolver(const char *tableList, const char *base) {
	char *searchPath;
	char **tableFiles;
	char *subTable;
	char *tableList_copy;
	char *cp;
	int last;
	int k;

	/* Set up search path */
	searchPath = _lou_getTablePath();

	/* Count number of subtables in table list */
	k = 0;
	for (cp = (char *)tableList; *cp != '\0'; cp++)
		if (*cp == ',') k++;
	tableFiles = (char **)calloc(k + 2, sizeof(char *));
	if (!tableFiles) _lou_outOfMemory();

	/* Resolve subtables */
	k = 0;
	tableList_copy = strdup(tableList);
	for (subTable = tableList_copy;; subTable = cp + 1) {
		for (cp = subTable; *cp != '\0' && *cp != ','; cp++)
			;
		last = (*cp == '\0');
		*cp = '\0';
		if (!(tableFiles[k++] = resolveSubtable(subTable, base, searchPath))) {
			char *path;
			_lou_logMessage(LOU_LOG_ERROR, "Cannot resolve table '%s'", subTable);
			path = getenv("LOUIS_TABLEPATH");
			if (path != NULL && path[0] != '\0')
				_lou_logMessage(LOU_LOG_ERROR, "LOUIS_TABLEPATH=%s", path);
			free(searchPath);
			free(tableList_copy);
			free_tablefiles(tableFiles);
			return NULL;
		}
		if (k == 1) base = subTable;
		if (last) break;
	}
	free(searchPath);
	free(tableList_copy);
	tableFiles[k] = NULL;
	return tableFiles;
}