aboutsummaryrefslogblamecommitdiffstats
path: root/src/mds-kbdc/compile-layout.c
blob: 5a841ee05a515e178662a4da04be9f598d666354 (plain) (tree)
1
2
3

                                 
                                                                       














                                                                        
                                                                                                         
                                                                                     
 
                       
                              
                   
                      
                      


                   
                   



   

                                                       
                       

   

                               
                                         

   

                                                               

                                                                          



                                                                                            





                                                                                    
 


                              
                                            



                        
                                               
 




                                                      
                                     



                                    
                                          
 







                                   




                                                        






                                                    
                                                    
 


                                      
                                              
 




                                       







                                              
                                                           
 









                                                                                        

                                                                                                      










                                                                                   

                                                                                                            
 

 



                                                                                                    











                                                                                                               


                                                                                            
 

























                                                                                                 













                                                                                


                                                                                                
 




































                                                                                                                   



















                                                                                  


                                                                                                             
 






























































                                                                                                                
         











































                                                                                                                  
                

 

   





                                                                   
                                                               


                                                                    


                                                                                             
 

























                                                                                                 
         























                                                                                                                          
         




































                                                                                                      

                


 
   









                                                                                


                                                                                                      
 
























                                                                                                                              
         


                     













                                                                                        


                                                                                         
 



































                                                                                                                










                                                                                     


                                                                         
 




                                                                               


 






                                                                   
                                                               


                                                                    


                                                                                      
 























































































                                                                                                               







                   






                                                                   

                                                                                             
 








































































































                                                                                                                               
                 
            
           
               










                                                                   

                                                                                               
 























                                                                                                             
  

                    

                 

 








                                                                   

                                                                                      
 






                                                                                                                


 







                                                                   

                                                                                    
 

































































                                                                                             
              































                                                                                
              


               

 








                                                                            

                                                                                        
 













































                                                                                            

 

   








                                                                                   


                                                                                                 
 





































                                                                         
         















                                                                        



   





                                                                

                                                                                               
 



                                                                                               

 







                                                                                    


                                                                                      
 
                                                                                     

 













                                                                                                    



                                                                  
 

















                                                                                                          

 







                                                                      


                                                              
 










                                                                                                      

 








                                                                                          



                                                                            
 
                                                                                                      

 
 





                                                                              

                                          
 




                                      

 
 

                                                                                                    
 


                                                       

 
 
 


                       





                                              

                                                       
 














                                                                    



   




                                              

                                                                     
 





























                                                                                                    








                                              

                                                                   
 





























                                                                                                    








                                              

                                                                   
 





























                                                                                               








                                              

                                                            
 

































                                                                                                        
         





                   








                                              

                                                                        
 




































                                                                                                            








                                              

                                                                        
 











































































                                                                                                            



   




                                                                               

                                                 
 






































                                                                                                    




        




                                                                                  

                                                                     
 















                                                                                                            









                                                                                  

                                                                   
 








                                                                                                     








                                                                                  

                                                                       
 




                                                                                                        








                                                                                  

                                                                           
 




                                                                                                          








                                                                                  

                                                          
 





































                                                                                                       









                                                                              

                                                               
 








































                                                                                      



   




                                              

                                                         
 

























































                                                                                                        
        








                                              

                                                   
 





































                                                                                                     
        








                                              

                                               
 


















































































                                                                                                                








                                              

                                             
 



























                                                                                           








                                              

                                               
 


























                                                                                                 


 









                                                                            
   
                                                                                     



                                                                           

                                                
 
















                                                                                                     



   




                                              

                                                 
 



                                                                








                                              

                                                     
 



                                                                








                                              

                                                   
 






                                                 



   






                                                           

                                            
 


















                                                                                              



   




                                              

                                               
 






































































































                                                                                                    








                                              

                                                             
 





























































                                                                                          



   




                                              

                                               
 











































                                                                          
      









                                                                             











                                                                                              

                                                   
 














                                              



 

                 

                
                    
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015, 2016, 2017  Mattias Andrée (m@maandree.se)
 * 
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */
#include "compile-layout.h"
/* XXX fix so that for-loops do not generate the same errors/warnings in all iterations [loopy_error]. */
/* XXX add pragma support: mark as shadowable, stop warnings, print variable value */

#include "call-stack.h"
#include "builtin-functions.h"
#include "string.h"
#include "variables.h"
#include "callables.h"

#include <stdlib.h>
#include <errno.h>
#include <string.h>



/**
 * This process's value for `mds_kbdc_tree_t.processed`
 */
#define PROCESS_LEVEL 6

/**
 * Tree type constant shortener
 */
#define C(TYPE) MDS_KBDC_TREE_TYPE_##TYPE

/**
 * Add an error with “included from here”-notes and, unless
 * it is a note, “called from here”-notes to the error list
 * 
 * @param  NODE:const mds_kbdc_tree_t*    The node the triggered the error
 * @param  SEVERITY:identifier            * in `MDS_KBDC_PARSE_ERROR_*` to indicate severity
 * @param  ...:const char*, ...           Error description format string and arguments
 * @scope  error:mds_kbdc_parse_error_t*  Variable where the new error will be stored
 */
#define NEW_ERROR(NODE, SEVERITY, ...)\
	do {\
		NEW_ERROR_WITH_INCLUDES(NODE, includes_ptr, SEVERITY, __VA_ARGS__);\
		if (strcmp(#SEVERITY, "NOTE"))\
			DUMP_CALL_STACK;\
	} while (0)

/**
 * Beginning of failure clause
 */
#define FAIL_BEGIN fail: saved_errno = errno

/**
 * End of failure clause
 */
#define FAIL_END return errno = saved_errno, -1



/**
 * Variable whether the latest created error is stored
 */
static mds_kbdc_parse_error_t *error;

/**
 * The parameter of `compile_layout`
 */
static mds_kbdc_parsed_t *restrict result;

/**
 * 3:   `return` is being processed
 * 2:    `break` is being processed
 * 1: `continue` is being processed
 * 0:    Neither is being processed
 */
static int break_level = 0;

/**
 * Whether a second variant has already been encountered
 */
static int multiple_variants = 0;

/**
 * The previous value-statement, which has no effect
 * if we can find another value statemnt that is
 * sure to be evaluated
 * 
 * (We will not look too hard.)
 */
static mds_kbdc_tree_t *last_value_statement = NULL;

/**
 * Address of the current return value
 */
static char32_t **current_return_value = NULL;

/**
 * Whether ‘\set/3’ has been called
 */
static int have_side_effect = 0;



/**
 * Compile a subtree
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int compile_subtree(mds_kbdc_tree_t *restrict tree);

/**
 * Check that a function used in a part of a literal is defined
 * 
 * @param  tree     The statement where the literal is located
 * @param  raw      The beginning of the function call in the literal
 * @param  lineoff  The offset on the line where the function call in the literal beings
 * @param  end      Output parameter for the end of the function call
 * @param  rc       Success status output parameter: zero on success,
 *                  -1 on error, 1 if an undefined function is used
 */
static void check_function_call(const mds_kbdc_tree_t *restrict tree, const char *restrict raw,
                                size_t lineoff, const char *restrict *restrict end, int *restrict rc);

/**
 * Parse an argument in a function call
 * 
 * @param   tree     The statement where the function call appear
 * @param   raw      The beginning of the argument for the function call
 * @param   lineoff  The offset for `raw` on line in which it appears
 * @param   end      Output parameter for the end of the argument
 * @param   value    Output parameter for the value to which the argument evaulates
 * @return           Zero on success, -1 on error
 */
static int parse_function_argument(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff,
                                   const char *restrict *restrict end, char32_t **restrict value);



/*** Macro-, function- and variable-support, string-parsing and value- and mapping-compilation. ***/
/*                           (Basically everything except tree-walking.)                          */


/**
 * Assign a value to a variable, and define or shadow it in the process
 * 
 * @param   variable                  The variable index
 * @param   string                    The variable's new value, must be `NULL` iff `value != NULL`
 * @param   value                     The variable's new value, must be `NULL` iff `string != NULL`
 * @param   statement                 The statement where the variable is assigned, may be `NULL`
 * @param   lineoff                   The offset of the line for where the string selecting the variable begins
 * @param   possibile_shadow_attempt  Whether `statement` is of a type that does not shadow variables,
 *                                    but could easily be mistaked for one that does
 * @return                            Zero on success, -1 on error
 */
static int
let(size_t variable, const char32_t *restrict string, const mds_kbdc_tree_t *restrict value,
    mds_kbdc_tree_t *restrict statement, size_t lineoff, int possibile_shadow_attempt)
{
	mds_kbdc_tree_t *tree = NULL;
	int saved_errno;

	/* Warn if this is a possible shadow attempt. */
	if (possibile_shadow_attempt && variables_let_will_override(variable) && statement &&
	    !variables_has_been_used_in_for(variable) && statement->processed != PROCESS_LEVEL) {
		statement->processed = PROCESS_LEVEL;
		NEW_ERROR(statement, WARNING, "does not shadow existing definition");
		error->start = lineoff;
		error->end = lineoff + (size_t)snprintf(NULL, 0, "\\%zu", variable);
	}

	/* Duplicate value. */
	if (value) {
		fail_if (!(tree = mds_kbdc_tree_dup(value)));
	} else {
		fail_if (!(tree = mds_kbdc_tree_create(C(COMPILED_STRING))));
		fail_if (!(tree->compiled_string.string = string_dup(string)));
	}

	/* Assign variable. */
	fail_if (variables_let(variable, tree));
	return 0;
	FAIL_BEGIN;
	mds_kbdc_tree_free(tree);
	FAIL_END;
}


/**
 * Check that a call to set/3 or get/2 is valid
 * 
 * @param   tree          The statement from where the function is called
 * @param   is_set        Whether a call to set/3 is being checked
 * @param   variable_arg  The first argument
 * @param   index_arg     The second argument
 * @param   start         The offset on the line where the function call begins
 * @param   end           The offset on the line where the function call ends
 * @return                Zero on success, -1 on error, 1 if the call is invalid
 */
static int
check_set_3_get_2_call(mds_kbdc_tree_t *restrict tree, int is_set, const char32_t *variable_arg,
                       const char32_t *index_arg, size_t start, size_t end)
{
#define F (is_set ? "set/3" : "get/2")
#define FUN_ERROR(...)\
	do {\
		NEW_ERROR(__VA_ARGS__);\
		error->start = start;\
		error->end = end;\
		return 1;\
	} while(0);

	mds_kbdc_tree_t *variable;
	mds_kbdc_tree_t *element;
	size_t index, arg_count;

	if (variable_arg[0] <= 0 || variable_arg[1] != -1)
		FUN_ERROR(tree, ERROR, "first argument in call to function ‘%s’ must be a variable index", F);

	if (index_arg[0] < 0 || index_arg[1] != -1)
		FUN_ERROR(tree, ERROR, "second argument in call to function ‘%s’ must be an element index", F);

	variable = variables_get((size_t)*variable_arg);
	if (!variable)
		FUN_ERROR(tree, ERROR, "‘\\%zu’ is not declared", (size_t)*variable_arg);
	if (variable->type != C(ARRAY))
		FUN_ERROR(tree, ERROR, "‘\\%zu’ is not an array", (size_t)*variable_arg);

	arg_count = index = (size_t)*index_arg;
	element = variable->array.elements;
	while (element && index--)
		element = element->next;

	if (!element)
		FUN_ERROR(tree, ERROR, "‘\\%zu’ does not hold %zu %s",
		          (size_t)*variable_arg, arg_count + 1, arg_count ? "elements" : "element");

	return 0;
fail:
	return -1;
#undef FUN_ERROR
#undef F
}


/**
 * Call a function
 * 
 * @param   tree          The statement from where the function is called
 * @param   name          The name of the function, suffixless
 * @param   arguments     The arguments to pass to the function, `NULL`-terminated
 * @param   start         The offset on the line where the function call begins
 * @param   end           The offset on the line where the function call ends
 * @param   return_value  Output parameter for the return value, `NULL` if the
 *                        function did not return, was not defined or otherwise
 *                        invoked an error, which is true will be reported to the
 *                        user from this function and the statement will be marked
 *                        as containing an error
 * @return                Zero on success, -1 on error
 */
static int
call_function(mds_kbdc_tree_t *restrict tree, const char *restrict name, const char32_t **restrict arguments,
              size_t start, size_t end, char32_t **restrict return_value)
{
#define FUN_ERROR(...)\
	do {\
		NEW_ERROR(__VA_ARGS__);\
		error->start = start;\
		error->end = end;\
		tree->processed = PROCESS_LEVEL;\
		free(*return_value), *return_value = NULL;\
		goto done;\
	} while(0);

	size_t i, arg_count = 0, empty_count = 0;
	char32_t **old_return_value;
	mds_kbdc_tree_function_t *function = NULL;
	mds_kbdc_include_stack_t *function_include_stack = NULL;
	mds_kbdc_include_stack_t *our_include_stack = NULL;
	int r, is_set, builtin, saved_errno;

	/* Push call-stack. */
	mds_kbdc_call_stack_push(tree, start, end);

	/* Count the number of arguments we have. */
	while (arguments[arg_count])
		arg_count++;

	/* Push return-stack. */
	*return_value = NULL;
	old_return_value = current_return_value;
	current_return_value = return_value;

	/* Get function definition. */
	builtin = builtin_function_defined(name, arg_count);
	if (!builtin)
		callables_get(name, arg_count, (mds_kbdc_tree_t**)&function, &function_include_stack);
	if (!builtin && !function)
		FUN_ERROR(tree, ERROR, "function ‘%s/%zu’ has not been defined yet", name, arg_count);


	/* Call non-builtin function. */
	if (!builtin) {
		/* Push call stack and set parameters. */
		variables_stack_push();
		for (i = 0; i < arg_count; i++)
			fail_if (let(i + 1, arguments[i], NULL, NULL, 0, 0));

		/* Switch include-stack to the function's. */
		fail_if (our_include_stack = mds_kbdc_include_stack_save(), our_include_stack == NULL);
		fail_if (mds_kbdc_include_stack_restore(function_include_stack));

		/* Call the function. */
		fail_if (compile_subtree(function->inner));

		/* Switch back the include-stack to ours. */
		fail_if (mds_kbdc_include_stack_restore(our_include_stack));
		mds_kbdc_include_stack_free(our_include_stack), our_include_stack = NULL;

		/* Pop call stack. */
		variables_stack_pop();

		/* Check that the function returned a value. */
		if (!*return_value)
			FUN_ERROR(tree, ERROR, "function ‘%s/%zu’ did not return a value", name, arg_count);

		goto done;
	}


	/* Call builtin function. */

	/* Check argument sanity. */
	is_set = arg_count == 3 && !strcmp(name, "set");
	if (is_set || (arg_count == 2 && !strcmp(name, "get"))) {
		fail_if (r = check_set_3_get_2_call(tree, is_set, arguments[0], arguments[1], start, end), r < 0);
		if (r) {
			tree->processed = PROCESS_LEVEL;
			free(*return_value), *return_value = NULL;
			goto done;
		}
	} else {
		for (i = 0; i < arg_count; i++)
			empty_count += !string_length(arguments[i]);
		if (empty_count && empty_count != arg_count)
			FUN_ERROR(tree, ERROR,
			          "built-in function ‘%s/%zu’ requires that either none of"
			          " the arguments are empty strings or that all of them are",
			          name, arg_count);
	}

	/* Call the function. */
	*return_value = builtin_function_invoke(name, arg_count, arguments);
	fail_if (!*return_value);
	have_side_effect |= is_set;
	/* XXX ideally, we want to make sure it is in a scope that actually brings side-effects. */


done:
	/* Pop call-stack. */
	mds_kbdc_call_stack_pop();

	/* Pop return-stack. */
	current_return_value = old_return_value;
	return 0;

	FAIL_BEGIN;
	mds_kbdc_call_stack_pop();
	mds_kbdc_include_stack_free(our_include_stack);
	free(*return_value), *return_value = NULL;
	current_return_value = old_return_value;
	FAIL_END;
#undef FUN_ERROR
}


/**
 * Parse a function call escape
 * 
 * @param   tree     The statement where the escape is located
 * @param   raw      The escape to parse
 * @param   lineoff  The offset on the line where the escape beings
 * @param   escape   Will be set to zero if the escape ended,
 *                   will be set to anything but zero otherwise
 * @param   end      Output parameter for the end of the escape
 * @return           The text the escape represents, `NULL` on error
 */
static char32_t *
parse_function_call(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff,
		    int *restrict escape, const char *restrict *restrict end)
{
#define R(LOWER, UPPER) ((LOWER) <= c && c <= (UPPER))
#define GROW_ARGS\
	if (arguments_ptr == arguments_size)\
		fail_if (xxrealloc(old_arguments, arguments, arguments_size += 4, char32_t*))

	const char *restrict bracket = raw + 1;
	char32_t *rc = NULL;
	const char32_t **restrict arguments_;
	char32_t **restrict arguments = NULL;
	char32_t **restrict old_arguments = NULL;
	size_t arguments_ptr = 0, arguments_size = 0;
	char *restrict name;
	char c;
	int r, saved_errno = 0;

	/* Find the opening bracket associated with the function call and validate the escape. */
	for (; c = *bracket, 1; bracket++) {
		if (c == '_' || R('0', '9') || R('a', 'z') || R('A', 'Z'));
		else if (c == '(') {
			break;
		} else {
			*end = bracket;
			if (tree->processed != PROCESS_LEVEL)
				NEW_ERROR(tree, ERROR, "invalid escape");
			goto error;
		}
	}

	/* Copy the name of the function. */
	name = alloca((size_t)(bracket - raw) * sizeof(char));
	memcpy(name, raw + 1, (size_t)(bracket - raw - 1) * sizeof(char));
	name[bracket - raw - 1] = 0;

	/* Get arguments. */
	for (*end = ++bracket;;) {
		while (**end == ' ')
			(*end)++;
		GROW_ARGS;
		arguments[arguments_ptr] = NULL;
		if (**end == ')') {
			*escape = 0;
			(*end)++;
			arguments_ptr++;
			break;
		} else if (!**end) {
			if (tree->processed != PROCESS_LEVEL)
				NEW_ERROR(tree, ERROR, "incomplete function call");
			goto error;
		}
		r = parse_function_argument(tree, *end, lineoff + (size_t)(*end - raw), end, arguments + arguments_ptr++);
		fail_if (r < 0);
	}

	/* Call the function. */
	if (tree->processed == PROCESS_LEVEL)
		goto stop;
	arguments_ = alloca(arguments_ptr * sizeof(const char32_t*));
	memcpy(arguments_, arguments, arguments_ptr * sizeof(const char32_t*));
	fail_if (call_function(tree, name, arguments_, lineoff, lineoff + (size_t)(*end - raw), &rc));
	if (!rc)
		goto stop;

	goto done;

error:
	error->start = lineoff;
	error->end = lineoff + (size_t)(*end - raw);
stop:
	*escape = 0;
	tree->processed = PROCESS_LEVEL;
	fail_if (xmalloc(rc, 1, char32_t));
	*rc = -1;
	goto done;

fail:
	saved_errno = errno;
	free(rc);
	if (old_arguments)
		arguments = old_arguments;
	while (arguments_ptr--)
		free(arguments[arguments_ptr]);
	free(arguments);
	rc = NULL;
done:
	while (arguments_ptr--)
		free(arguments[arguments_ptr]);
	free(arguments);
	errno = saved_errno;
	return rc;
#undef GROW_ARGS
#undef R
}


/**
 * Check that all functions used in a part of a literal are defined
 * 
 * @param   tree     The statement where the literal is located
 * @param   raw      The beginning of the part of the literal to check
 * @param   lineoff  The offset on the line where the part of the literal beings
 * @param   end      Output parameter for the part of the literal
 * @param   rc       Success status output parameter: zero on success,
 *                   -1 on error, 1 if an undefined function is used
 * @return           The number of arguments the part of the literal contains
 */
static size_t
check_function_calls_in_literal_(const mds_kbdc_tree_t *restrict tree, const char *restrict raw,
                                 size_t lineoff, const char *restrict *restrict end, int *restrict rc)
{
#define R(LOWER, UPPER) ((LOWER) <= c && c <= (UPPER))
	const char *restrict raw_ = raw;
	size_t count = 0;
	int space = 1, quote = 0, escape = 0;
	char c;

	while ((c = *raw++)) {
		if (space && !strchr(" )", c))
			space = 0, count++;

		if (escape) {
			escape = 0;
			if ((c == '_' || R('a', 'z') || R('A', 'Z')) && c != 'u')
				/* \u*() is disallowed because \u* is used for hexadecimal representation. */
				if (check_function_call(tree, raw - 2, lineoff + (size_t)(raw - 2 - raw_), &raw, rc), *rc < 0)
					break;
		} else if (c == '\\') {
			escape = 1;
		} else if (c == '"') {
			quote ^= 1;
		} else if (!quote) {
			space = c == ' ';
			if (c == ')')
				break;
		}
	}

	*end = raw;
	return count;
#undef R
}


/**
 * Check that a function used in a part of a literal is defined
 * 
 * @param  tree     The statement where the literal is located
 * @param  raw      The beginning of the function call in the literal
 * @param  lineoff  The offset on the line where the function call in the literal beings
 * @param  end      Output parameter for the end of the function call
 * @param  rc       Success status output parameter: zero on success,
 *                  -1 on error, 1 if an undefined function is used
 */
static void
check_function_call(const mds_kbdc_tree_t *restrict tree, const char *restrict raw,
                    size_t lineoff, const char *restrict *restrict end, int *restrict rc)
{
	mds_kbdc_tree_t *function = NULL;
	mds_kbdc_include_stack_t *_function_include_stack;
	char *restrict bracket = strchr(raw, '(');
	char *restrict name;
	size_t arg_count;

	/* Check that it is a function call by check that it has an opening bracket. */
	if (!bracket) {
		*end = raw + strlen(raw);
		return;
	}

	/* Copy the name of the function. */
	name = alloca((size_t)(bracket - raw) * sizeof(char));
	memcpy(name, raw + 1, (size_t)(bracket - raw - 1) * sizeof(char));
	name[bracket++ - raw - 1] = 0;

	/* Get the number of arguments used, and check function calls there too. */
	arg_count = check_function_calls_in_literal_(tree, bracket, lineoff + (size_t)(bracket - raw), end, rc);
	if (*rc < 0)
		return;

	/* Check that the function is defined. */
	if (builtin_function_defined(name, arg_count))
		return;
	callables_get(name, arg_count, &function, &_function_include_stack);
	if (function)
		return;
	*rc |= 1;
	NEW_ERROR(tree, ERROR, "function ‘%s/%zu’ has not been defined yet", name, arg_count);
	error->start = lineoff;
	error->end = lineoff + (size_t)(*end - raw);
	return;

fail:
	*rc |= -1;
}


/**
 * Check that all functions used in a literal are defined
 * 
 * @param   tree     The statement where the literal is located
 * @param   raw      The literal to check
 * @param   lineoff  The offset on the line where the literal beings
 * @return           Zero on success, -1 on error, 1 if an undefined function is used
 */
static int
check_function_calls_in_literal(const mds_kbdc_tree_t *restrict tree,
                                const char *restrict raw, size_t lineoff)
{
	int rc = 0;
	(void) check_function_calls_in_literal_(tree, raw, lineoff, &raw, &rc);
	fail_if (rc < 0);
fail:
	return rc;
}


/**
 * Parse an escape, variable dereference or function call
 * 
 * @param   tree     The statement where the escape is located
 * @param   raw      The escape to parse
 * @param   lineoff  The offset on the line where the escape beings
 * @param   escape   Will be set to zero if the escape ended,
 *                   will be set to anything but zero otherwise
 * @param   end      Output parameter for the end of the escape
 * @return           The text the escape represents, `NULL` on error
 */
static char32_t *
parse_escape(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff,
             int *restrict escape, const char *restrict *restrict end)
{
#define R(LOWER, UPPER)        ((LOWER) <= c && c <= (UPPER))
#define CR(COND, LOWER, UPPER) (*escape == (COND) && R(LOWER, UPPER))
#define VARIABLE               (int)(raw - raw_) - (c == '.'), raw_
#define RETURN_ERROR(...)\
	do {\
		NEW_ERROR(__VA_ARGS__);\
		error->start = lineoff;\
		error->end = lineoff + (size_t)(raw - raw_);\
		tree->processed = PROCESS_LEVEL;\
		*escape = 0;\
		if (rc)\
			goto done;\
		fail_if (xmalloc(rc, 1, char32_t));\
		*rc = -1;\
		goto done;\
	} while (0)

	const char* restrict raw_ = raw++;
	char c = *raw++;
	uintmax_t numbuf = 0;
	char32_t* rc = NULL;
	mds_kbdc_tree_t* value;
	int have = 0, saved_errno;


	/* Get escape type. */
	if (c == '0')
		/* Octal representation. */
		*escape = 8, have = 1;
	else if (c == 'u')
		/* Hexadecimal representation. */
		*escape = 16;
	else if (R('1', '9'))
		/* Variable dereference. */
		*escape = 10, have = 1, numbuf = (uintmax_t)(c - '0');
	else if (c == '_' || R('a', 'z') || R('A', 'Z'))
		/* Function call. */
		*escape = 100;
	else
		RETURN_ERROR(tree, ERROR, "invalid escape");


	/* Read escape. */
	if (*escape == 100) {
		/* Function call. */
		fail_if (rc = parse_function_call(tree, raw_, lineoff, escape, end), rc == NULL);
		return rc;
	}
	/* Octal or hexadecimal representation, or variable dereference. */
	for (; (c = *raw); have = 1, raw++) {
		if      (CR( 8, '0', '7')) numbuf =  8 * numbuf + (c & 15);
		else if (CR(16, '0', '9')) numbuf = 16 * numbuf + (c & 15);
		else if (CR(16, 'a', 'f')) numbuf = 16 * numbuf + (c & 15) + 9;
		else if (CR(16, 'A', 'F')) numbuf = 16 * numbuf + (c & 15) + 9;
		else if (CR(10, '0', '9')) numbuf = 10 * numbuf + (c & 15);
		else                       break;
	}
	if (c == '.')
		raw++;
	if (have == 0)
		RETURN_ERROR(tree, ERROR, "invalid escape");


	/* Evaluate escape. */
	if (*escape == 10) {
		/* Variable dereference. */
		if (!(value = variables_get((size_t)numbuf)))
			RETURN_ERROR(tree, ERROR, "variable ‘%.*s’ is not defined", VARIABLE);
		if (value->type == C(ARRAY))
			RETURN_ERROR(tree, ERROR, "variable ‘%.*s’ is an array", VARIABLE);
		if (value->type != C(COMPILED_STRING))
			NEW_ERROR(tree, INTERNAL_ERROR, "variable ‘%.*s’ is of impossible type", VARIABLE);
		fail_if (!(rc = string_dup(value->compiled_string.string)));
	} else {
		/* Octal or hexadecimal representation. */
		fail_if (xmalloc(rc, 2, char32_t));
		rc[0] = (char32_t)numbuf, rc[1] = -1;
	}


done:
	*escape = 0;
	*end = raw;
	return rc;
fail:
	saved_errno = errno;
	free(rc);
	return errno = saved_errno, NULL;
#undef RETURN_ERROR
#undef VARIABLE
#undef CR
#undef R
}


/**
 * Parse a quoted string
 * 
 * @param   tree     The statement where the string is located
 * @param   raw      The string to parse
 * @param   lineoff  The offset on the line where the string beings
 * @return           The string as pure text, `NULL` on error
 */
static char32_t *
parse_quoted_string(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff)
{
#define GROW_BUF\
	if (buf_ptr == buf_size)\
		fail_if (xxrealloc(old_buf, buf, buf_size += 16, char))
#define COPY\
	n = string_length(subrc);\
	fail_if (xxrealloc(old_rc, rc, rc_ptr + n, char32_t));\
	memcpy(rc + rc_ptr, subrc, n * sizeof(char32_t)), rc_ptr += n;\
	free(subrc), subrc = NULL
#define STORE\
	do {\
		if (buf_ptr) {\
			GROW_BUF;\
			buf[buf_ptr] = '\0', buf_ptr = 0;\
			fail_if (!(subrc = string_decode(buf)));\
			COPY;\
		}\
	} while (0)
#define CHAR_ERROR(...)\
	do {\
		NEW_ERROR(__VA_ARGS__);\
		error->end = lineoff + (size_t)(raw - raw_);\
		error->start = error->end - 1;\
	} while (0)

	const char *restrict raw_ = raw;
	char32_t *restrict subrc = NULL;
	char32_t *restrict rc = NULL;
	char32_t *restrict old_rc = NULL;
	char *restrict buf = NULL;
	char *restrict old_buf = NULL;
	size_t rc_ptr = 0, n;
	size_t buf_ptr = 0, buf_size = 0;
	size_t escoff = 0;
	int quote = 0, escape = 0;
	char c;
	int saved_errno;

	/* Parse the string. */
	while ((c = *raw++)) {
		if (escape && quote && strchr("()[]{}<>\"\\,", c)) {
			/* Buffer UTF-8 text for convertion to UTF-32. */
			GROW_BUF;
			buf[buf_ptr++] = c;
			escape = 0;
		} else if (escape) {
			/* Parse escape. */
			raw -= 2, escoff = lineoff + (size_t)(raw - raw_);
			subrc = parse_escape(tree, raw, escoff, &escape, &raw);
			fail_if (!subrc);
			COPY;
		} else if (c == '"')  {
			/* Close or open quote, of it got closed, convert the buffered UTF-8 text to UTF-32. */
			if ((quote ^= 1))
				continue;
			if (quote == 1 && raw != raw_ + 1)
				CHAR_ERROR(tree, WARNING, "strings should either be unquoted or unclosed in one large quoted");
			STORE;
		} else if (c == '\\') {
			/* Convert the buffered UTF-8 text to UTF-32, and start an escape. */
			STORE;
			escape = 1;
		} else if (!quote && tree->processed != PROCESS_LEVEL) {
			/* Only escapes may be used without quotes, if the string contains quotes. */
			if (*raw_ == '"')
				CHAR_ERROR(tree, ERROR, "only escapes may be outside quotes in quoted strings");
			else
				CHAR_ERROR(tree, ERROR, "mixing numericals and escapes is not allowed");
			tree->processed = PROCESS_LEVEL;
		} else {
			/* Buffer UTF-8 text for convertion to UTF-32. */
			GROW_BUF;
			buf[buf_ptr++] = c;
		}
	}

	/* Check that no escape is incomplete. */
	if (escape && tree->processed != PROCESS_LEVEL) {
		NEW_ERROR(tree, ERROR, "incomplete escape");
		error->start = escoff;
		error->end = lineoff + strlen(raw_);
		tree->processed = PROCESS_LEVEL;
	}

	/* Check that the quote is complete. */
	if (quote && tree->processed != PROCESS_LEVEL) {
		NEW_ERROR(tree, ERROR, "quote is not closed");
		error->start = lineoff;
		error->end = lineoff + strlen(raw_);
		tree->processed = PROCESS_LEVEL;
	}

	/* Shrink or grow to string to its minimal size, and -1-terminate it. */
	fail_if (xxrealloc(old_rc, rc, rc_ptr + 1, char32_t));
	rc[rc_ptr] = -1;

	free(buf);
	return rc;
fail:
	saved_errno = errno;
	free(subrc);
	free(old_rc);
	free(old_buf);
	free(rc);
	free(buf);
	return errno = saved_errno, NULL;
#undef CHAR_ERROR
#undef STORE
#undef COPY
#undef GROW_BUF
}


/**
 * Parse am unquoted string
 * 
 * @param   tree     The statement where the string is located
 * @param   raw      The string to parse
 * @param   lineoff  The offset on the line where the string beings
 * @return           The string as pure text, `NULL` on error
 */
static char32_t *
parse_unquoted_string(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff)
{
#define R(LOWER, UPPER) ((LOWER) <= c && c <= (UPPER))
#define CHAR_ERROR(...)\
	do {\
		NEW_ERROR(__VA_ARGS__);\
		error->end = lineoff + (size_t)(raw - raw_);\
		error->start = error->end - 1;\
		tree->processed = PROCESS_LEVEL;\
		goto done;\
	} while (0)

	const char *restrict raw_ = raw;
	char32_t *rc, buf = 0;
	char c;

	while ((c = *raw++)) {
		if (R('0', '9'))    buf = 10 * buf + (c & 15);
		else if (c == '\\') CHAR_ERROR(tree, ERROR, "mixing numericals and escapes is not allowed");
		else if (c == '"')  CHAR_ERROR(tree, ERROR, "mixing numericals and quotes is not allowed");
		else                CHAR_ERROR(tree, ERROR, "stray ‘%c’", c); /* XXX support multibyte */
	}

done:
	fail_if (xmalloc(rc, 2, char32_t));
	return rc[0] = buf, rc[1] = -1, rc;
  
fail:
	return NULL;
#undef CHAR_ERROR
#undef R
}


/**
 * Parse a string
 * 
 * @param   tree     The statement where the string is located
 * @param   raw      The string to parse
 * @param   lineoff  The offset on the line where the string beings
 * @return           The string as pure text, `NULL` on error
 */
static char32_t *
parse_string(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff)
{
	mds_kbdc_tree_t *old_last_value_statement = last_value_statement;
	char32_t *rc = (strchr("\"\\", *raw) ? parse_quoted_string : parse_unquoted_string)(tree, raw, lineoff);
	last_value_statement = old_last_value_statement;
	fail_if (!rc);
	return rc;
fail:
	return NULL;
}


/**
 * Parse a key-combination string
 * 
 * @param   tree     The statement where the string is located
 * @param   raw      The string to parse
 * @param   lineoff  The offset on the line where the string beings
 * @return           The string as pure text, `NULL` on error
 */
static char32_t *
parse_keys(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff)
{
#define GROW_BUF\
	if (buf_ptr == buf_size)\
		fail_if (xxrealloc(old_buf, buf, buf_size += 16, char))
#define COPY\
	n = string_length(subrc);\
	fail_if (xxrealloc(old_rc, rc, rc_ptr + n, char32_t));\
	memcpy(rc + rc_ptr, subrc, n * sizeof(char32_t)), rc_ptr += n;\
	free(subrc), subrc = NULL
#define STORE\
	do {\
		if (buf_ptr) {\
			GROW_BUF;\
			buf[buf_ptr] = '\0', buf_ptr = 0;\
			fail_if (!(subrc = string_decode(buf)));\
			COPY;\
		}\
	} while (0)
#define SPECIAL(VAL)\
	STORE;\
	fail_if (xxrealloc(old_rc, rc, rc_ptr + 1, char32_t));\
	rc[rc_ptr++] = -(VAL + 1)

	mds_kbdc_tree_t *old_last_value_statement = last_value_statement;
	const char *restrict raw_ = raw++;
	char32_t *restrict subrc = NULL;
	char32_t *restrict rc = NULL;
	char32_t *restrict old_rc = NULL;
	char *restrict buf = NULL;
	char *restrict old_buf = NULL;
	size_t rc_ptr = 0, n;
	size_t buf_ptr = 0, buf_size = 0;
	size_t escoff = 0;
	int escape = 0, quote = 0;
	char c;
	int saved_errno;

	/* Parse the string. */
	while (c = *raw++, c && *raw) {
		if (escape && strchr("()[]{}<>\"\\,", c)) {
			/* Buffer UTF-8 text for convertion to UTF-32. */
			GROW_BUF;
			buf[buf_ptr++] = c;
			escape = 0;
		} else if (escape) {
			/* Parse escape. */
			raw -= 2, escoff = lineoff + (size_t)(raw - raw_);
			subrc = parse_escape(tree, raw, escoff, &escape, &raw);
			fail_if (!subrc);
			COPY;
		} else if (c == '\\') {
			/* Convert the buffered UTF-8 text to UTF-32, and start an escape. */
			STORE;
			escape = 1;
		} else if (c == ',' && !quote) {
			/* Sequence in key-combination. */
			SPECIAL(1);
		} else if (c == '"') {
			/* String in key-combination. */
			quote ^= 1;
			SPECIAL(2);
		} else {
			/* Buffer UTF-8 text for convertion to UTF-32. */
			GROW_BUF;
			buf[buf_ptr++] = c;
		}
	}
	STORE;

	/* Check that no escape is incomplete. */
	if (escape && tree->processed != PROCESS_LEVEL) {
		NEW_ERROR(tree, ERROR, "incomplete escape");
		error->start = lineoff + (size_t)(strrchr(raw_, '\\') - raw_);
		error->end = lineoff + strlen(raw_);
		tree->processed = PROCESS_LEVEL;
	}

	/* Check that key-combination is complete. */
	if (c != '>' && tree->processed != PROCESS_LEVEL) {
		NEW_ERROR(tree, ERROR, "key-combination is not closed");
		error->start = lineoff;
		error->end = lineoff + strlen(raw_);
		tree->processed = PROCESS_LEVEL;
	}

	/* Shrink or grow to string to its minimal size, and -1-terminate it. */
	fail_if (xxrealloc(old_rc, rc, rc_ptr + 1, char32_t));
	rc[rc_ptr] = -1;

	free(buf);
	return last_value_statement = old_last_value_statement, rc;
fail:
	saved_errno = errno;
	free(subrc);
	free(old_rc);
	free(old_buf);
	free(rc);
	free(buf);
	errno = saved_errno;
	return last_value_statement = old_last_value_statement, NULL;
#undef SPECIAL
#undef STORE
#undef COPY
#undef GROW_BUF
}


/**
 * Parse a variable string
 * 
 * @param   tree     The statement where the variable is selected
 * @param   raw      The variable string
 * @param   lineoff  The offset on the line where the variable string begins
 * @return           The index of the variable, zero on error
 */
static size_t
parse_variable(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff)
{
	size_t var, n;
	const char *restrict raw_ = raw;
	char *restrict dotless;

	/* The variable must begin with \. */
	if (*raw++ != '\\') goto bad;
	/* Zero is not a valid varible, nor may there be leading zeroes or be empty.  */
	if (*raw == '0')  goto bad;
	if (*raw == '.')  goto bad;
	if (*raw == '\0') goto bad;
	for (; *raw; raw++) {
		/* Check that the variable consists only of digits. */
		if ('0' <= *raw && *raw <= '9')
			/* However, it may end with a dot. */;
		else if (raw[0] == '.' && raw[1] == '\0')
			break;
		else
			goto bad;
	}

	/* Parse the variable string and check that it did not overflow. */
	n = (size_t)(raw - raw_);
	dotless = alloca((n + 1) * sizeof(char));
	memcpy(dotless, raw_, n * sizeof(char)), dotless[n] = '\0';
	var = atoz(dotless + 1);
	if (strlen(dotless + 1) != (size_t)snprintf(NULL, 0, "%zu", var))
		fail_if ((errno = ERANGE));
	if (!var) {
		NEW_ERROR(tree, INTERNAL_ERROR,
		          "parsed a variable string to be 0, which should not be possible");
		error->start = lineoff;
		error->end = lineoff + strlen(raw_);
		tree->processed = PROCESS_LEVEL;
		return 1;
	}
	return var;
fail:
	return 0;

bad:
	/* Report an error but return a variable index if the variable-string is invalid. */
	NEW_ERROR(tree, ERROR, "not a variable");
	error->start = lineoff;
	error->end = lineoff + strlen(raw_);
	tree->processed = PROCESS_LEVEL;
	return 1;
}


/**
 * Parse an argument in a function call
 * 
 * @param   tree     The statement where the function call appear
 * @param   raw      The beginning of the argument for the function call
 * @param   lineoff  The offset for `raw` on line in which it appears
 * @param   end      Output parameter for the end of the argument
 * @param   value    Output parameter for the value to which the argument evaulates
 * @return           Zero on success, -1 on error
 */
static int
parse_function_argument(mds_kbdc_tree_t *restrict tree, const char *restrict raw, size_t lineoff,
                        const char *restrict *restrict end, char32_t **restrict value)
{
	size_t size = strlen(raw), ptr = 0, call_end = 0;
	int escape = 0, quote = 0, saved_errno;
	char *raw_argument = NULL;
	char c;

	/* Find the span of the argument. */
	while (ptr < size) {
		c = raw[ptr++];

		/* Escapes may be longer than one character,
		   but only the first can affect the parsing. */
		if (escape)
			escape = 0;
		/* Nested function and nested quotes can appear. */
		else if (ptr <= call_end)
			;
		/* Quotes end with the same symbols as they start with,
		   and quotes automatically escape brackets. */
		/* \ can either start a functon call or an escape. */
		else if (c == '\\') {
			/* It may not be an escape, but registering it
			   as an escape cannot harm us since we only
			   skip the first character, and a function call
			   cannot be that short. */
			escape = 1;
			/* Nested quotes can appear at function calls. */
			call_end = get_end_of_call(raw, ptr, size);
		}
		/* " is the quote symbol. */
		else if (quote)
			quote = (c != '"');
		else if (c == '"')
			quote = 1;
		/* End of argument? */
		else if (strchr(" )", c)) {
			ptr--;
			break;
		}
	}
	*end = raw + ptr;

	/* Copy the argument so that we have a NUL-terminates string. */
	fail_if (xmalloc(raw_argument, ptr + 1, char));
	memcpy(raw_argument, raw, ptr * sizeof(char));
	raw_argument[ptr] = '\0';

	/* Evaluate argument. */
	*value = parse_string(tree, raw_argument, lineoff);
	fail_if (!*value);

	free(raw_argument);
	return 0;
	FAIL_BEGIN;
	free(raw_argument);
	FAIL_END;
}


/**
 * Store a macro
 * 
 * @param   macro                The macro
 * @param   macro_include_stack  The include-stack for the macro
 * @return                       Zero on success, -1 on error
 */
static int
set_macro(mds_kbdc_tree_macro_t *restrict macro, mds_kbdc_include_stack_t *macro_include_stack)
{
	fail_if (callables_set(macro->name, 0, (mds_kbdc_tree_t *)macro, macro_include_stack));
	return 0;
fail:
	return -1;
}


/**
 * Get a stored macro
 * 
 * @param  name                 The name of the macro, with suffix
 * @param  macro                Output parameter for the macro, `NULL` if not found
 * @param  macro_include_stack  Output parameter for the include-stack for the macro
 */
static void
get_macro_lax(const char *restrict macro_name, mds_kbdc_tree_macro_t **restrict macro,
              mds_kbdc_include_stack_t **restrict macro_include_stack)
{
	callables_get(macro_name, 0, (mds_kbdc_tree_t **)macro, macro_include_stack);
}


/**
 * Get a stored macro
 * 
 * The function is similar to `get_macro_lax`, however, this fucntion
 * will report an error if the macro has not yet been defined, and it
 * will pretend that it has not yet been defined if the macro contained
 * an error in an earlier called to it
 * 
 * @param   macro_call           The macro-call
 * @param   macro                Output parameter for the macro, `NULL` if not found or has an error
 * @param   macro_include_stack  Output parameter for the include-stack for the macro
 * @return                       Zero on success, -1 on error
 */
static int
get_macro(mds_kbdc_tree_macro_call_t *restrict macro_call,
          mds_kbdc_tree_macro_t **restrict macro,
          mds_kbdc_include_stack_t **restrict macro_include_stack)
{
	char *code = result->source_code->lines[macro_call->loc_line];
	char *end = code + strlen(code) - 1;

	get_macro_lax(macro_call->name, macro, macro_include_stack);
	if (!*macro) {
		NEW_ERROR(macro_call, ERROR, "macro ‘%s’ has not been defined yet", macro_call->name);
		while (*end == ' ')
			end--;
		error->end = (size_t)(++end - code);
		macro_call->processed = PROCESS_LEVEL;
		return 0;
	}
	if ((*macro)->processed == PROCESS_LEVEL)
		*macro = NULL;

	return 0;
fail:
	return -1;
}


/**
 * Store a function
 * 
 * @param   function                The function
 * @param   function_include_stack  The include-stack for the function
 * @return                          Zero on success, -1 on error
 */
static int
set_function(mds_kbdc_tree_function_t *restrict function,
             mds_kbdc_include_stack_t *function_include_stack)
{
	char *suffixless = function->name;
	char *suffix_start = strchr(suffixless, '/');
	size_t arg_count = atoz(suffix_start + 1);
	int r;

	*suffix_start = '\0';
	r = callables_set(suffixless, arg_count, (mds_kbdc_tree_t *)function, function_include_stack);
	fail_if (*suffix_start = '/', r);
	return 0;
fail:
	return -1;
}


/**
 * Get a stored function
 * 
 * @param  name                    The name of the function, suffixless
 * @param  arg_count               The number of arguments the function takes
 * @param  function                Output parameter for the function, `NULL` if not found
 * @param  function_include_stack  Output parameter for the include-stack for the function
 */
static void
get_function_lax(const char *restrict function_name, size_t arg_count,
                 mds_kbdc_tree_function_t **restrict function,
                 mds_kbdc_include_stack_t **restrict function_include_stack)
{
	callables_get(function_name, arg_count, (mds_kbdc_tree_t **)function, function_include_stack);
}


/**
 * Store a value for being returned by the current function
 * 
 * @param   value  The value the function should return
 * @return         Zero on success, 1 if no function is currently being called
 */
static int
set_return_value(char32_t *restrict value)
{
	if (!current_return_value)
		return free(value), 1;
	free(*current_return_value);
	*current_return_value = value;
	return 0;
}


static int
add_mapping(mds_kbdc_tree_map_t *restrict mapping, mds_kbdc_include_stack_t *restrict include_stack)
{
	mds_kbdc_tree_free((mds_kbdc_tree_t *)mapping);
	mds_kbdc_include_stack_free(include_stack);
	return 0; /* TODO */
}



/*** Tree-walking. ***/


/**
 * Compile an include-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_include(mds_kbdc_tree_include_t *restrict tree)
{
	void *data;
	int r;
	fail_if (mds_kbdc_include_stack_push(tree, &data));
	r = compile_subtree(tree->inner);
	mds_kbdc_include_stack_pop(data);

	/* For simplicity we set `last_value_statement` on includes,
	 * so we are sure `last_value_statement` has the same
	 * include-stack as its overriding statement. */
	last_value_statement = NULL;

	fail_if (r);
	return 0;
fail:
	return -1;
}


/**
 * Compile a language-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_language(mds_kbdc_tree_information_language_t *restrict tree)
{
	size_t lineoff;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	char32_t *restrict data = NULL;
	char **old = NULL;
	int saved_errno;

	/* Make sure the language-list fits another entry. */
	if (result->languages_ptr == result->languages_size) {
		result->languages_size = result->languages_size ? (result->languages_size << 1) : 1;
		fail_if (xxrealloc(old, result->languages, result->languages_size, char*));
	}

	/* Locate the first character in the language-string. */
	for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
	/* Evaluate function calls, variable dereferences and escapes in the language-string. */
	fail_if (!(data = parse_string((mds_kbdc_tree_t*)tree, tree->data, lineoff)));
	if (tree->processed == PROCESS_LEVEL)
		return free(data), 0;
	/* We want the string in UTF-8, not UTF-16. */
	fail_if (!(code = string_encode(data)));
	free(data);

	/* Add the language to the language-list. */
	result->languages[result->languages_ptr++] = code;

	return 0;
	FAIL_BEGIN;
	free(old);
	free(data);
	FAIL_END;
}


/**
 * Compile a country-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_country(mds_kbdc_tree_information_country_t *restrict tree)
{
	size_t lineoff;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	char32_t *restrict data = NULL;
	char **old = NULL;
	int saved_errno;

	/* Make sure the country-list fits another entry. */
	if (result->countries_ptr == result->countries_size) {
		result->countries_size = result->countries_size ? (result->countries_size << 1) : 1;
		fail_if (xxrealloc(old, result->countries, result->countries_size, char *));
	}

	/* Locate the first character in the country-string. */
	for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
	/* Evaluate function calls, variable dereferences and escapes in the country-string. */
	fail_if (!(data = parse_string((mds_kbdc_tree_t*)tree, tree->data, lineoff)));
	if (tree->processed == PROCESS_LEVEL)
		return free(data), 0;
	/* We want the string in UTF-8, not UTF-16. */
	fail_if (!(code = string_encode(data)));
	free(data);

	/* Add the country to the country-list. */
	result->countries[result->countries_ptr++] = code;

	return 0;
	FAIL_BEGIN;
	free(old);
	free(data);
	FAIL_END;
}


/**
 * Compile a variant-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_variant(mds_kbdc_tree_information_variant_t *restrict tree)
{
	size_t lineoff;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	char32_t *restrict data = NULL;
	int saved_errno;

	/* Make sure the variant has not already been set. */
	if (result->variant) {
		if (!multiple_variants)
			NEW_ERROR(tree, ERROR, "only one ‘variant’ is allowed");
		multiple_variants = 1;
		return 0;
	}

	/* Locate the first character in the variant-string. */
	for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
	/* Evaluate function calls, variable dereferences and escapes in the variant-string. */
	fail_if (!(data = parse_string((mds_kbdc_tree_t*)tree, tree->data, lineoff)));
	if (tree->processed == PROCESS_LEVEL)
		return free(data), 0;
	/* We want the string in UTF-8, not UTF-16. */
	fail_if (!(code = string_encode(data)));
	free(data);

	/* Store the variant. */
	result->variant = code;

	return 0;
	FAIL_BEGIN;
	free(data);
	FAIL_END;
}


/**
 * Compile a have-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_have(mds_kbdc_tree_assumption_have_t *restrict tree)
{
	mds_kbdc_tree_t *node = tree->data;
	char32_t *data = NULL, **old = NULL;
	size_t new_size = 0;
	int saved_errno;

	/* Make sure we can fit all assumption in the assumption list (part 1/2). */
	new_size = (node->type == C(STRING)) ? result->assumed_strings_size : result->assumed_keys_size;
	new_size = new_size ? (new_size << 1) : 1;

	if (node->type == C(STRING)) {
		/* Evaluate function calls, variable dereferences and escapes in the string. */
		fail_if (!(data = parse_string(node, node->string.string, node->loc_start)));
		if (node->processed == PROCESS_LEVEL)
			return free(data), 0;
		/* Make sure we can fit all strings in the assumption list (part 2/2). */
		if (result->assumed_strings_ptr == result->assumed_strings_size)
			{
				fail_if (xxrealloc(old, result->assumed_strings, new_size, char32_t*));
				result->assumed_strings_size = new_size;
			}
		/* Add the assumption to the list. */
		result->assumed_strings[result->assumed_strings_ptr++] = data;
	} else {
		/* Evaluate function calls, variable dereferences and escapes in the key-combination. */
		fail_if (!(data = parse_keys(node, node->keys.keys, node->loc_start)));
		if (node->processed == PROCESS_LEVEL)
			return free(data), 0;
		/* Make sure we can fit all key-combinations in the assumption list (part 2/2). */
		if (result->assumed_keys_ptr == result->assumed_keys_size) {
			fail_if (xxrealloc(old, result->assumed_keys, new_size, char32_t*));
			result->assumed_keys_size = new_size;
		}
		/* Add the assumption to the list. */
		result->assumed_keys[result->assumed_keys_ptr++] = data;
	}

	return 0;
	FAIL_BEGIN;
	free(old);
	free(data);
	FAIL_END;
}


/**
 * Compile a have_chars-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_have_chars(mds_kbdc_tree_assumption_have_chars_t *restrict tree)
{
	size_t lineoff;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	char32_t *restrict data = NULL;
	char32_t **old = NULL;
	char32_t *restrict character;
	size_t n;
	int saved_errno;
  
	/* Locate the first character in the list. */
	for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
	/* Evaluate function calls, variable dereferences
	   and escapes in the charcter list. */
	fail_if (!(data = parse_string((mds_kbdc_tree_t*)tree, tree->chars, lineoff)));
	if (tree->processed == PROCESS_LEVEL)
		return free(data), 0;

	/* Make sure we can fit all characters in the assumption list. */
	for (n = 0; data[n] >= 0; n++);
	if (result->assumed_strings_ptr + n > result->assumed_strings_size) {
		result->assumed_strings_size += n;
		fail_if (xxrealloc(old, result->assumed_strings, result->assumed_strings_size, char32_t *));
	}

	/* Add all characters to the assumption list. */
	while (n--) {
		fail_if (xmalloc(character, 2, char32_t));
		character[0] = data[n];
		character[1] = -1;
		result->assumed_strings[result->assumed_strings_ptr++] = character;
	}

	free(data);
	return 0;
	FAIL_BEGIN;
	free(data);
	free(old);
	FAIL_END;
}


/**
 * Compile a have_range-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_have_range(mds_kbdc_tree_assumption_have_range_t *restrict tree)
{
	size_t lineoff_first;
	size_t lineoff_last;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	char32_t *restrict first = NULL;
	char32_t *restrict last = NULL;
	char32_t **old = NULL;
	char32_t *restrict character;
	size_t n;
	int saved_errno;


	/* Locate the first characters of both bound strings. */
	for (lineoff_first = tree->loc_end; code[lineoff_first] == ' '; lineoff_first++);
	for (lineoff_last = lineoff_first + strlen(tree->first); code[lineoff_last] == ' '; lineoff_last++);
  
	/* Duplicate bounds and evaluate function calls,
	   variable dereferences and escapes in the bounds. */
	fail_if (!(first = parse_string((mds_kbdc_tree_t*)tree, tree->first, lineoff_first)));
	fail_if (!(last  = parse_string((mds_kbdc_tree_t*)tree, tree->last,  lineoff_last)));

	/* Did one of the bound not evaluate, then stop. */
	if (tree->processed == PROCESS_LEVEL)
		goto done;


	/* Check that the primary bound is single-character. */
	if (first[0] == -1 || first[1] != -1) {
		NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");
		error->start = lineoff_first, lineoff_first = 0;
		error->end = error->start + strlen(tree->first);
	}
	/* Check that the secondary bound is single-character. */
	if (last[0] == -1 || last[1] != -1) {
		NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");
		error->start = lineoff_last, lineoff_last = 0;
		error->end = error->start + strlen(tree->last);
	}

	/* Was one of the bounds not single-character, then stop. */
	if (!lineoff_first || !lineoff_last)
		goto done;


	/* If the range is descending, swap the bounds so it is ascending.
	   (This cannot be done in for-loops as that may cause side-effects
	   to be created in the wrong order.) */
	if (*first > *last)
		*first ^= *last, *last ^= *first, *first ^= *last;

	/* Make sure we can fit all characters in the assumption list. */
	n = (size_t)(*last - *first) + 1;
	if (result->assumed_strings_ptr + n > result->assumed_strings_size) {
		result->assumed_strings_size += n;
		fail_if (xxrealloc(old, result->assumed_strings, result->assumed_strings_size, char32_t *));
	}

	/* Add all characters to the assumption list. */
	for (;;) {
		fail_if (xmalloc(character, 2, char32_t));
		character[0] = *first;
		character[1] = -1;
		result->assumed_strings[result->assumed_strings_ptr++] = character;
		/* Bounds are inclusive. */
		if ((*first)++ == *last)
			break;
	}

done:
	free(first);
	free(last);
	return 0;
	FAIL_BEGIN;
	free(first);
	free(last);
	free(old);
	FAIL_END;
}


/**
 * Check that all called macros are already defined
 * 
 * @param   tree  The tree to evaluate
 * @return        Zero on success, -1 on error, 1 if an undefined macro is used
 */
static int
check_marco_calls(mds_kbdc_tree_t *restrict tree)
{
#define t(...) fail_if (rc |= r = (__VA_ARGS__), r < 0)
	mds_kbdc_tree_macro_t *_macro;
	mds_kbdc_include_stack_t *_macro_include_stack;
	void *data;
	int r, rc = 0;

again:
	if (!tree)
		return rc;

	switch (tree->type) {
	case C(INCLUDE):
		t (mds_kbdc_include_stack_push(&(tree->include), &data));
		t (r = check_marco_calls(tree->include.inner), mds_kbdc_include_stack_pop(data), r);
		break;

	case C(FOR):
		t (check_marco_calls(tree->for_.inner));
		break;

	case C(IF):
		t (check_marco_calls(tree->if_.inner));
		t (check_marco_calls(tree->if_.otherwise));
		break;

	case C(MACRO_CALL):
		t (get_macro(&(tree->macro_call), &_macro, &_macro_include_stack));
		break;

	default:
		break;
	}

	tree = tree->next;
	goto again;
fail:
	return -1;
	(void) _macro;
	(void) _macro_include_stack;
#undef t
}


/**
 * Check that all called functions in a for-statement are already defined
 * 
 * @param   tree  The tree to evaluate
 * @return        Zero on success, -1 on error, 1 if an undefined function is used
 */
static int
check_function_calls_in_for(const mds_kbdc_tree_for_t *restrict tree)
{
#define t(...) fail_if (rc |= r = check_function_calls_in_literal(__VA_ARGS__), r < 0)
	size_t lineoff_first;
	size_t lineoff_last;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	int r, rc = 0;
  
	for (lineoff_first = tree->loc_end; code[lineoff_first] == ' '; lineoff_first++);
	for (lineoff_last = lineoff_first + strlen(tree->first); code[lineoff_last] == ' '; lineoff_last++);
	for (lineoff_last += strlen("to"); code[lineoff_last] == ' '; lineoff_last++);

	t ((const mds_kbdc_tree_t *)tree, tree->first, lineoff_first);
	t ((const mds_kbdc_tree_t *)tree, tree->last, lineoff_last);

	return rc;
fail:
	return -1;
#undef t
}


/**
 * Check that all called functions in an if-statement are already defined
 * 
 * @param   tree  The tree to evaluate
 * @return        Zero on success, -1 on error, 1 if an undefined function is used
 */
static int
check_function_calls_in_if(const mds_kbdc_tree_if_t *restrict tree)
{
	size_t lineoff;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	int r;
  
	for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
	r = check_function_calls_in_literal((const mds_kbdc_tree_t *)tree, tree->condition, lineoff);
	fail_if (r < 0);
fail:
	return r;
}


/**
 * Check that all called functions in a key-combination are already defined
 * 
 * @param   tree  The tree to evaluate
 * @return        Zero on success, -1 on error, 1 if an undefined function is used
 */
static int
check_function_calls_in_keys(const mds_kbdc_tree_keys_t *restrict tree)
{
	int r;
	r = check_function_calls_in_literal((const mds_kbdc_tree_t *)tree, tree->keys, tree->loc_start);
	fail_if (r < 0);
fail:
	return r;
}


/**
 * Check that all called functions in a string are already defined
 * 
 * @param   tree  The tree to evaluate
 * @return        Zero on success, -1 on error, 1 if an undefined function is used
 */
static int
check_function_calls_in_string(const mds_kbdc_tree_string_t *restrict tree)
{
	int r;
	r = check_function_calls_in_literal((const mds_kbdc_tree_t *)tree, tree->string, tree->loc_start);
	fail_if (r < 0);
fail:
	return r;
}


/**
 * Check that all called functions are already defined
 * 
 * @param   tree  The tree to evaluate
 * @return        Zero on success, -1 on error, 1 if an undefined function is used
 */
static int
check_function_calls(const mds_kbdc_tree_t *restrict tree)
{
#define t(...) fail_if (rc |= r = (__VA_ARGS__), r < 0)
	void *data;
	int r, rc = 0;

again:
	if (!tree)
		return rc;

	switch (tree->type) {
	case C(INCLUDE):
		t (mds_kbdc_include_stack_push(&(tree->include), &data));
		t (r = check_function_calls(tree->include.inner), mds_kbdc_include_stack_pop(data), r);
		break;

	case C(FOR):
		t (check_function_calls_in_for(&(tree->for_)));
		t (check_function_calls(tree->for_.inner));
		break;

	case C(IF):
		t (check_function_calls_in_if(&(tree->if_)));
		t (check_function_calls(tree->if_.inner));
		t (check_function_calls(tree->if_.otherwise));
		break;

	case C(LET):    t (check_function_calls(tree->let.value));           break;
	case C(ARRAY):  t (check_function_calls(tree->array.elements));      break;
	case C(KEYS):   t (check_function_calls_in_keys(&(tree->keys)));     break;
	case C(STRING): t (check_function_calls_in_string(&(tree->string))); break;
	case C(MAP):    t (check_function_calls(tree->map.sequence));        break;
	default:
		break;
	}

	tree = tree->next;
	goto again;
fail:
	return -1;
#undef t
}


/**
 * Check that a callable's name-suffix is correct
 * 
 * @param   tree  The tree to inspect
 * @return        Zero on sucess, -1 on error, 1 if the name-suffix in invalid
 */
static int
check_name_suffix(struct mds_kbdc_tree_callable *restrict tree)
{
	const char *restrict name = strchr(tree->name, '/');
	const char *restrict code = result->source_code->real_lines[tree->loc_line];

	/* A "/" must exist in the name to tell us how many parameters there are. */
	if (!name) {
		NEW_ERROR(tree, ERROR, "name-suffix is missing");
		goto name_error;
	}
	/* Do not let the suffix by just "/". */
	if (!*++name) {
		NEW_ERROR(tree, ERROR, "empty name-suffix");
		goto name_error;
	}

	/* We are all good if the suffix is simply "/0" */
	if (!strcmp(name, "0"))
		return 0;

	/* The suffix may not have leading zeroes. */
	if (*name == '0') {
		NEW_ERROR(tree, ERROR, "leading zero in name-suffix");
		goto name_error;
	}
	/* The suffix must be a decimal, non-negative number. */
	for (; *name; name++) {
		if (*name < '0' || '9' < *name) {
			NEW_ERROR(tree, ERROR, "name-suffix may only contain digits");
			goto name_error;
		}
	}

	return 0;
fail:
	return -1;
name_error:
	error->start = tree->loc_end;
	while (code[error->start] == ' ')
		error->start++;
	error->end = error->start + strlen(tree->name);
	tree->processed = PROCESS_LEVEL;
	return 1;
}


/**
 * Compile a function
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_function(mds_kbdc_tree_function_t *restrict tree)
{
#define t(expr) do { fail_if (r = (expr), r < 0); if (r) tree->processed = PROCESS_LEVEL; } while (0)
	mds_kbdc_tree_function_t *function;
	mds_kbdc_include_stack_t *function_include_stack;
	mds_kbdc_include_stack_t *our_include_stack = NULL;
	char *suffixless;
	char *suffix_start = NULL;
	size_t arg_count;
	int r, saved_errno;

	/* Check that the suffix if properly formatted. */
	t (check_name_suffix((struct mds_kbdc_tree_callable*)tree));
	if (r)
		return 0;

	/* Get the function's name without suffix, and parse the suffix. */
	suffixless = tree->name;
	suffix_start = strchr(suffixless, '/');
	*suffix_start++ = '\0';
	arg_count = atoz(suffix_start--);

	/* Check that the function is not already defined as a builtin function. */
	if (builtin_function_defined(suffixless, arg_count)) {
		NEW_ERROR(tree, ERROR, "function ‘%s/%zu’ is already defined as a builtin function",
			  tree->name, arg_count);
		*suffix_start = '/';
		return 0;
	}
	/* Check that the function is not already defined,
	   the include-stack is used in the error-clause as
	   well as later when we list the function as defined. */
	get_function_lax(suffixless, arg_count, &function, &function_include_stack);
	fail_if (!(our_include_stack = mds_kbdc_include_stack_save(), our_include_stack));
	if (function) {
		*suffix_start = '/';
		NEW_ERROR(tree, ERROR, "function ‘%s’ is already defined", tree->name);
		fail_if (mds_kbdc_include_stack_restore(function_include_stack));
		NEW_ERROR(function, NOTE, "previously defined here");
		fail_if (mds_kbdc_include_stack_restore(our_include_stack));
		mds_kbdc_include_stack_free(our_include_stack);
		return 0;
	}

	/* Check the the function does not call macros or functions
	 * before they are defined, otherwise they may get defined
	 * between the definition of the function and calls to it. */
	t (check_marco_calls(tree->inner));
	t (check_function_calls(tree->inner));

	/* List the function as defined. */
	*suffix_start = '/', suffix_start = NULL;
	t (set_function(tree, our_include_stack));

	return 0;
	FAIL_BEGIN;
	if (suffix_start)
		*suffix_start = '/';
	mds_kbdc_include_stack_free(our_include_stack);
	FAIL_END;
#undef t
}


/**
 * Compile a macro
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_macro(mds_kbdc_tree_macro_t *restrict tree)
{
#define t(expr) do { fail_if (r = (expr), r < 0); if (r) tree->processed = PROCESS_LEVEL; } while (0)
	mds_kbdc_tree_macro_t *macro;
	mds_kbdc_include_stack_t *macro_include_stack;
	mds_kbdc_include_stack_t *our_include_stack = NULL;
	int r, saved_errno;

	/* Check that the suffix if properly formatted. */
	t (check_name_suffix((struct mds_kbdc_tree_callable*)tree));
	if (r)
		return 0;

	/* Check that the macro is not already defined,
	   the include-stack is used in the error-clause as
	   well as later when we list the macro as defined. */
	fail_if (!(our_include_stack = mds_kbdc_include_stack_save()));
	get_macro_lax(tree->name, &macro, &macro_include_stack);
	if (macro) {
		NEW_ERROR(tree, ERROR, "macro ‘%s’ is already defined", tree->name);
		fail_if (mds_kbdc_include_stack_restore(macro_include_stack));
		NEW_ERROR(macro, NOTE, "previously defined here");
		fail_if (mds_kbdc_include_stack_restore(our_include_stack));
		mds_kbdc_include_stack_free(our_include_stack);
		return 0;
	}

	/* Check the the macro does not call macros or functions
	 * before they are defined, otherwise they may get defined
	 * between the definition of the macro and calls to it. */
	t (check_marco_calls(tree->inner));
	t (check_function_calls(tree->inner));

	/* List the macro as defined. */
	t (set_macro(tree, our_include_stack));

	return 0;
	FAIL_BEGIN;
	mds_kbdc_include_stack_free(our_include_stack);
	FAIL_END;
#undef t
}


/**
 * Compile a for-loop
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_for(mds_kbdc_tree_for_t *restrict tree)
{
	size_t lineoff_first;
	size_t lineoff_last;
	size_t lineoff_var;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	char32_t *restrict first = NULL;
	char32_t *restrict last = NULL;
	char32_t diff;
	char32_t character[2];
	size_t variable;
	int possible_shadow = 1, saved_errno;


	last_value_statement = NULL;


	/* Locate the first character of the primary bound's string. */
	for (lineoff_first = tree->loc_end; code[lineoff_first] == ' '; lineoff_first++);
	/* Locate the first character of the secondary bound's string. */
	for (lineoff_last = lineoff_first + strlen(tree->first); code[lineoff_last] == ' '; lineoff_last++);
	for (lineoff_last += strlen("to"); code[lineoff_last] == ' '; lineoff_last++);
	/* Locate the first character of the select variable. */
	for (lineoff_var = lineoff_last + strlen(tree->last); code[lineoff_var] == ' '; lineoff_var++);
	for (lineoff_var += strlen("as"); code[lineoff_var] == ' '; lineoff_var++);

	/* Duplicate bounds and evaluate function calls,
	   variable dereferences and escapes in the bounds. */
	fail_if (!(first = parse_string((mds_kbdc_tree_t*)tree, tree->first, lineoff_first)));
	fail_if (!(last  = parse_string((mds_kbdc_tree_t*)tree, tree->last,  lineoff_last)));
	/* Get the index of the selected variable. */
	fail_if (variable = parse_variable((mds_kbdc_tree_t*)tree, tree->variable, lineoff_var), variable == 0);

	/* Did one of the bound not evaluate, then stop. */
	if (tree->processed == PROCESS_LEVEL)
		goto done;


	/* Check that the primary bound is single-character. */
	if (first[0] == -1 || first[1] != -1) {
		NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");
		error->start = lineoff_first, lineoff_first = 0;
		error->end = error->start + strlen(tree->first);
	}
	/* Check that the secondary bound is single-character. */
	if (last[0] == -1 || last[1] != -1) {
		NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");
		error->start = lineoff_last, lineoff_last = 0;
		error->end = error->start + strlen(tree->last);
	}

	/* Was one of the bounds not single-character, then stop. */
	if (!lineoff_first || !lineoff_last)
		goto done;


	/* Iterate over the loop for as long as a `return` or `break` has not
	   been encountered (without being caught elsewhere). */
	character[1] = -1;
	for (diff = (*first > *last) ? -1 : +1; break_level < 2; *first += diff) {
		break_level = 0;
		character[0] = *first;
		fail_if (let(variable, character, NULL, (mds_kbdc_tree_t*)tree, lineoff_var, possible_shadow));
		possible_shadow = 0;
		fail_if (compile_subtree(tree->inner));
		/* Bounds are inclusive. */
		if (*first == *last)
			break;
	}
	fail_if (variables_was_used_in_for(variable));

	/* Catch `break` and `continue`, they may not propagate further. */
	if (break_level < 3)
		break_level = 0;


done:
	last_value_statement = NULL;
	free(first);
	free(last);
	return 0;
	FAIL_BEGIN;
	free(first);
	free(last);
	FAIL_END;
}


/**
 * Compile an if-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_if(mds_kbdc_tree_if_t *restrict tree)
{
	size_t lineoff;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	char32_t *restrict data = NULL;
	int ok, saved_errno;
	size_t i;

	last_value_statement = NULL;

	/* Locate the first character in the condition. */
	for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
	/* Evaluate function calls, variable dereferences and escapes in the condition. */
	fail_if (!(data = parse_string((mds_kbdc_tree_t*)tree, tree->condition, lineoff)));
	if (tree->processed == PROCESS_LEVEL)
		return free(data), 0;

	/* Evaluate whether the evaluted value is true. */
	for (ok = 1, i = 0; data[i] >= 0; i++)
		ok &= !!(data[i]);
	free(data), data = NULL;;

	/* Compile the appropriate clause. */
	ok = compile_subtree(ok ? tree->inner : tree->otherwise);
	last_value_statement = NULL;
	fail_if (ok < 0);
	return 0;
	FAIL_BEGIN;
	free(data);
	FAIL_END;
}


/**
 * Compile a let-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_let(mds_kbdc_tree_let_t *restrict tree)
{
	size_t lineoff;
	char *restrict code = result->source_code->real_lines[tree->loc_line];
	mds_kbdc_tree_t* value = NULL;
	size_t variable;
	int saved_errno;

	/* Get the index of the selected variable. */
	for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
	fail_if (!(variable = parse_variable((mds_kbdc_tree_t *)tree, tree->variable, lineoff)));
	if (tree->processed == PROCESS_LEVEL)
		return 0;

	/* Duplicate arguments and evaluate function calls,
	   variable dereferences and escapes in the value. */
	fail_if (!(value = mds_kbdc_tree_dup(tree->value)));
	fail_if (compile_subtree(value));
	if ((tree->processed = value->processed) == PROCESS_LEVEL)
		return mds_kbdc_tree_free(value), 0;

	/* Set the value of the variable. */
	fail_if (let(variable, NULL, value, NULL, 0, 0));

	mds_kbdc_tree_free(value);
	return 0;
	FAIL_BEGIN;
	free(value);
	FAIL_END;
}


/*
 * `compile_keys`, `compile_string`, `compile_array` and `evaluate_element`
 * are do only compilation subprocedures that may alter the compiled nodes.
 * This is because (1) `compile_let`, `compile_map` and `compile_macro_call`
 * needs the compiled values, and (2) only duplicates of nodes of types
 * `C(KEYS)`, `C(STRING)` and `C(ARRAY)` are compiled, as they can only be
 * found with `C(LET)`-, `C(MAP)`- and `C(MACRO_CALL)`-nodes.
 */


/**
 * Evaluate an element or argument in a mapping-, value-, let-statement or macro call
 * 
 * @param   node  The element to evaluate
 * @return        Zero on success, -1 on error, 1 if the element is invalid
 */
static int
evaluate_element(mds_kbdc_tree_t *restrict node)
{
	char32_t *restrict data = NULL;
	int bad = 0;

	for (; node; node = node->next) {
		if (node->type == C(STRING))
			fail_if (!(data = parse_string(node, node->string.string, node->loc_start)));
		if (node->type == C(KEYS))
			fail_if (!(data = parse_keys(node, node->keys.keys, node->loc_start)));
		free(node->string.string);
		node->type = (node->type == C(STRING)) ? C(COMPILED_STRING) : C(COMPILED_KEYS);
		node->compiled_string.string = data;
		bad |= (node->processed == PROCESS_LEVEL);
	}

	return bad;
fail:
	return -1;
}


/**
 * Compile a key-combination
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_keys(mds_kbdc_tree_keys_t *restrict tree)
{
	fail_if (evaluate_element((mds_kbdc_tree_t *)tree) < 0);
	return 0;
fail:
	return -1;
}


/**
 * Compile a string
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_string(mds_kbdc_tree_string_t *restrict tree)
{
	fail_if (evaluate_element((mds_kbdc_tree_t *)tree) < 0);
	return 0;
fail:
	return -1;
}


/**
 * Compile an array
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_array(mds_kbdc_tree_array_t *restrict tree)
{
	int r = evaluate_element(tree->elements);
	fail_if (r < 0);
	if (r)
		tree->processed = PROCESS_LEVEL;
	return 0;
fail:
	return -1;
}


/**
 * Check that a chain of strings and key-combinations
 * does not contain NULL characters
 * 
 * @param   tree  The tree to check
 * @return        Zero on success, -1 on error, 1 if any of
 *                the elements contain a NULL character
 */
static int
check_nonnul(mds_kbdc_tree_t *restrict tree)
{
	const char32_t *restrict string;
	int rc = 0;
again:
	if (!tree)
		return rc;

	for (string = tree->compiled_string.string; *string != -1; string++) {
		if (!*string) {
			NEW_ERROR(tree, ERROR, "NULL characters are not allowed in mappings");
			tree->processed = PROCESS_LEVEL;
			rc = 1;
			break;
		}
	}

	tree = tree->next;
	goto again;
fail:
	return -1;
}


/**
 * Compile a mapping- or value-statement
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_map(mds_kbdc_tree_map_t *restrict tree)
{
	int bad = 0, old_have_side_effect = have_side_effect;
	mds_kbdc_include_stack_t *restrict include_stack = NULL;
	mds_kbdc_tree_t *seq = NULL;
	mds_kbdc_tree_t *res = NULL;
	mds_kbdc_tree_t *old_seq = tree->sequence;
	mds_kbdc_tree_t *old_res = tree->result;
	mds_kbdc_tree_map_t *dup_map = NULL;
	int r, saved_errno;
	mds_kbdc_tree_t *previous_last_value_statement = last_value_statement;

	have_side_effect = 0;

	/* Duplicate arguments and evaluate function calls,
	   variable dereferences and escapes in the mapping
	   input sequence. */
	fail_if (!(seq = mds_kbdc_tree_dup(old_seq)));
	fail_if ((bad |= evaluate_element(seq)) < 0);

	/* Duplicate arguments and evaluate function calls,
	   variable dereferences and escapes in the mapping
	   output sequence, unless this is a value-statement. */
	if (tree->result) {
		fail_if (!(res = mds_kbdc_tree_dup(old_res)));
		fail_if ((bad |= evaluate_element(res)) < 0);
	}

	/* Stop if any of the mapping-arguments could not be evaluated. */
	if (bad)
		goto done;


	if (tree->result) {
		/* Mapping-statement. */

		/* Check for invalid characters in the mapping-arguments. */
		fail_if ((bad |= check_nonnul(seq)) < 0);
		fail_if ((bad |= check_nonnul(res)) < 0);
		if (bad)
			goto done;

		/* Duplicate the mapping-statement but give it the evaluated mapping-arguments. */
		tree->sequence = NULL;
		tree->result   = NULL;
		fail_if (!(dup_map = &mds_kbdc_tree_dup((mds_kbdc_tree_t *)tree)->map));
		tree->sequence = old_seq, dup_map->sequence = seq, seq = NULL;
		tree->result   = old_res, dup_map->result   = res, res = NULL;

		/* Enlist the mapping for assembling. */
		fail_if (!(include_stack = mds_kbdc_include_stack_save()));
		fail_if (add_mapping(dup_map, include_stack));

		goto done;
	}


	/* Value-statement */

	/* Save this statement so we can warn later if it is unnecessary,
	 * `set_return_value` will set it to `NULL` if there are side-effects,
	 * which would make this statement necessary (unless the overridding
	 * statement has identical side-effect, but we will not check for that).
	 * For simplicity, we do not store the include-stack, instead, we reset
	 * `last_value_statement` to `NULL` when we visit an include-statement. */
	last_value_statement = (mds_kbdc_tree_t *)tree;

	/* Add the value statement */
	r = set_return_value(seq->compiled_string.string);
	seq->compiled_string.string = NULL;

	/* Check that the value-statement is inside a function call, or has
	   side-effects by directly or indirectly calling ‘\set/3’ on an
	   array that is not shadowed by an inner function- or macro-call. */
	if (r && !have_side_effect) {
		NEW_ERROR(tree, ERROR, "value-statement outside function without side-effects");
		tree->processed = PROCESS_LEVEL;
	}
	if (have_side_effect)
		last_value_statement = NULL;

	/* Check whether we made a previous value-statement unnecessary. */
	if (previous_last_value_statement) {
		/* For simplicity we set `last_value_statement` on includes,
		 * so we are sure `last_value_statement` has the same include-stack. */

		NEW_ERROR(previous_last_value_statement, WARNING, "value-statement has no effects");
		NEW_ERROR(tree, NOTE, "overridden here");
	}


done:
	mds_kbdc_tree_free(seq);
	mds_kbdc_tree_free(res);
	have_side_effect |= old_have_side_effect;
	return 0;
	FAIL_BEGIN;
	mds_kbdc_include_stack_free(include_stack);
	mds_kbdc_tree_free((mds_kbdc_tree_t*)dup_map);
	mds_kbdc_tree_free(seq);
	mds_kbdc_tree_free(res);
	tree->sequence = old_seq;
	tree->result = old_res;
	have_side_effect = old_have_side_effect;
	FAIL_END;
}


/**
 * Compile a macro call
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_macro_call(mds_kbdc_tree_macro_call_t *restrict tree)
{
	mds_kbdc_tree_t *arg = NULL;
	mds_kbdc_tree_t *arg_;
	mds_kbdc_tree_macro_t *macro;
	mds_kbdc_include_stack_t *macro_include_stack;
	mds_kbdc_include_stack_t *our_include_stack = NULL;
	size_t variable = 0;
	int bad, saved_errno;

	last_value_statement = NULL;

	/* Push call-stack. */
	mds_kbdc_call_stack_push((mds_kbdc_tree_t *)tree, tree->loc_start, tree->loc_end);

	/* Duplicate arguments and evaluate function calls,
	   variable dereferences and escapes in the macro
	   call arguments. */
	if (tree->arguments)
		fail_if (!(arg = mds_kbdc_tree_dup(tree->arguments)));
	fail_if (bad = evaluate_element(arg), bad < 0);
	if (bad)
		return mds_kbdc_tree_free(arg), 0;

	/* Get the macro's subtree and include-stack, if it has
	   not been defined `get_macro` will add an error message
	   and return `NULL`. */
	fail_if (get_macro(tree, &macro, &macro_include_stack));
	if (!macro)
		goto done;


	/* Push variable-stack and set parameters. */
	variables_stack_push();
	for (arg_ = arg; arg_; arg_ = arg_->next)
		fail_if (let(++variable, NULL, arg_, NULL, 0, 0));

	/* Switch include-stack to the macro's. */
	fail_if (!(our_include_stack = mds_kbdc_include_stack_save()));
	fail_if (mds_kbdc_include_stack_restore(macro_include_stack));

	/* Call the macro. */
	fail_if (compile_subtree(macro->inner));

	/* Switch back the include-stack to ours. */
	fail_if (mds_kbdc_include_stack_restore(our_include_stack));
	mds_kbdc_include_stack_free(our_include_stack), our_include_stack = NULL;

	/* Pop variable-stack. */
	variables_stack_pop();

done:
	/* Pop call-stack. */
	mds_kbdc_call_stack_pop();

	last_value_statement = NULL;
	break_level = 0;
	mds_kbdc_tree_free(arg);
	return 0;
	FAIL_BEGIN;
	mds_kbdc_call_stack_pop();
	mds_kbdc_tree_free(arg);
	mds_kbdc_include_stack_free(our_include_stack);
	FAIL_END;
}


/**
 * Compile a subtree
 * 
 * @param   tree  The tree to compile
 * @return        Zero on success, -1 on error
 */
static int
compile_subtree(mds_kbdc_tree_t *restrict tree)
{
#define t(expr)  fail_if ((expr) < 0)
#define c(type)  t (compile_##type(&tree->type))
#define c_(type) t (compile_##type(&tree->type##_))
again:
	if (!tree)
		return 0;

	if (tree->processed == PROCESS_LEVEL)
		/* An error has occurred here before, let's skip it so
		 * we do not deluge the user with errors. */
		goto next;

	switch (tree->type) {
	case C(INFORMATION):
		t (compile_subtree(tree->information.inner));
		break;
	case C(INFORMATION_LANGUAGE):  c (language);    break;
	case C(INFORMATION_COUNTRY):   c (country);     break;
	case C(INFORMATION_VARIANT):   c (variant);     break;
	case C(INCLUDE):               c (include);     break;
	case C(FUNCTION):              c (function);    break;
	case C(MACRO):                 c (macro);       break;
	case C(ASSUMPTION):
		if (!includes_ptr)
			fail_if (compile_subtree(tree->assumption.inner));
		break;
	case C(ASSUMPTION_HAVE):       c (have);        break;
	case C(ASSUMPTION_HAVE_CHARS): c (have_chars);  break;
	case C(ASSUMPTION_HAVE_RANGE): c (have_range);  break;
	case C(FOR):                   c_ (for);        break;
	case C(IF):                    c_ (if);         break;
	case C(LET):                   c (let);         break;
	case C(KEYS):                  c (keys);        break;
	case C(STRING):                c (string);      break;
	case C(ARRAY):                 c (array);       break;
	case C(MAP):                   c (map);         break;
	case C(MACRO_CALL):            c (macro_call);  break;
	case C(RETURN):                break_level = 3; break;
	case C(BREAK):                 break_level = 2; break;
	case C(CONTINUE):              break_level = 1; break;
	default:
		break;
	}

 next:
	if (break_level)
		/* If a `continue`, `break` or `return` has been encountered,
		 * we are done here and should return to whence we came and
		 * let the subcompiler of that construct deal with it. */
		return 0;

	tree = tree->next;
	goto again;
fail:
	return -1;
#undef c_
#undef c
#undef t
}


/**
 * Compile the layout code
 * 
 * @param   result_  `result` from `eliminate_dead_code`, will be updated
 * @return           -1 if an error occursed that cannot be stored in `result`, zero otherwise
 */
int
compile_layout(mds_kbdc_parsed_t *restrict result_)
{
	int r, saved_errno;
	result = result_;
	mds_kbdc_include_stack_begin(result_);
	mds_kbdc_call_stack_begin(result_);
	r = compile_subtree(result_->tree);
	saved_errno = errno;
	mds_kbdc_call_stack_end();
	mds_kbdc_include_stack_end();
	variables_terminate();
	callables_terminate();
	errno = saved_errno;
	fail_if (r);
	return 0;
fail:
	return -1;
}



#undef FAIL_END
#undef FAIL_BEGIN
#undef NEW_ERROR
#undef C
#undef PROCESS_LEVEL