aboutsummaryrefslogblamecommitdiffstats
path: root/src/mds-kbdc/make-tree.c
blob: f5bdf559bb3a77f31efbdb4cac0026f038c0d1f4 (plain) (tree)
1
2
3

                                 
                                                                         















                                                                        

                  








                   


                               
                                          







                                                          







                                                                     
 
 
   






                                                                        

                                                  

 





                                                                                                  

                                                                                     

 
   

                                               

                                            





                                                      

                                                                 


   
                                 
   



                                                                                            
   


                                                                               

 





                                                                              






                                                                   

 
   




                                                                              






                                                                      


   








                                                                       



                                                             

 







                                                          


                                                            

 
   



                                        


                                    


   

                                                 
                                           
   

                                        

 






                                                                           

                                     

 
   
                                          
   




                               


   



                                                                           

                          


   





                                                                           

                                   

 


                                         







                                                                    

 


                                            

                          

 






                                                                           





                            

 


                                                 
                                           
   

                                           

 






                                                                           

                                  
 
 
   





                                                                           

                                       
 


   
                                


                                                                    
   







                                                             


   
                                                                  
   

                                                   

 






                                                                                        





                                                

 






                                                                                        





                                                 

 

   
                                                      
   
                                     
 


                                          
                                          
 



                                
 


                                           
                                           
 


                                        
                                              

   
                                         
   
                         





                                             



                                                
                         



                                                      
                        









                                                
                      






                                          

 

                                   


                                           
   
                                                     
                                                                                                           
   

                                           
 





























                                                                                                       



   

                                           
                                        
   

                     
 















                                                                                               



   

                                      
                                        
   

                      
 





                                                                               


 



                                    



                                                            
                                        
   

                                     
 

















                                                                                                                        
         



                  







                                                         
                                        
   

                                 
 






                                                       


 
 








                                                 

                                           
 










                                                                           











                                                                

                            
 




































                                                                                  
         



                  










                                                                

                          
 


































                                                                                                  
         



                  







                                            

            
 















                                                                   







                                                                           

                          
 













                                                          








                                                 

                                              
 























                                                                   










                                                                     

                                    
 



























                                                                                          
         














                                                                 











                                                                

                              
 



























                                                                                          
         






                                                  










                                                                        

                                       
 







































                                                                                                                          
         

                 
      
                  







                                                                       

                                        
 













                                                                                            



 


                               
   
                                      
   
                                                                             
   

                
 



                                                                    
         







































                                                                                   







                                                                             

               
 











                               







                                                                             

               
 





                              
                   
                             
            





                                                                          
                    



                                                     
           


                                                        
                    
                      


                                                    

            







                             







                                                                             

               
 


















                                                                                                          







                                                                             

               
 

                                          

                      


                                        

            

                              

                      









                                                                                                 

            





                                                  
                      
             
           












                                                      







                                                                             

                      
 






                                               

                       







                                                                 

            

                                      
                      














                                                                                    
           











                                                                     







                                                                             

                          
 
























                                                                               
         








                       







                                        

                
 





































                                                                                                                                       
        


 



                                   



                                                        
                                                              

                                                                                                

                                                                                 
 




















































                                                                                               



 

                  


                            
           
                       
               
             
          
            

              
              
                    
                  

             
               
                

           

                   

                  
/**
 * mds — A micro-display server
 * Copyright © 2014, 2015, 2016, 2017  Mattias Andrée (maandree@kth.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 "make-tree.h"

#include "paths.h"

#include <limits.h>
#include <stdlib.h>
#include <libgen.h>
#include <errno.h>
#include <unistd.h>
#include <string.h>
#include <stdio.h>


#ifndef DEBUG
# define DEBUG_PROC(statements)
#else
# define DEBUG_PROC(statements) statements
#endif


/**
 * Print the current keyword stack, this is intended to
 * as a compiler debugging feature and should be used from
 * inside `DEBUG_PROC`
 */
#define PRINT_STACK\
	do {\
		size_t i = stack_ptr;\
		fprintf(stderr, "stack {\n");\
		while (i--)\
			fprintf(stderr, "  %s\n", keyword_stack[i]);\
		fprintf(stderr, "}\n");\
	} while (0)


/**
 * Check whether a value is inside a closed range
 * 
 * @param   LOWER:¿T?  The lower bound, inclusive
 * @param   VALUE:¿T?  The value to test
 * @param   UPPER:¿T?  The upper bound, inclusive
 * @return  :int       1 if `LOWER` ≤ `VALUE` ≤ `UPPER`, otherwise 0
 */
#define in_range(LOWER, VALUE, UPPER)\
	((LOWER) <= (VALUE) && (VALUE) <= (UPPER))


/**
 * Check whether a character is a valid callable name character, forward slash is accepted
 * 
 * @param   C:char  The character
 * @return  :int    Zero if `C` is a valid callable name character or a forward slash, otherwise 0
 */
#define is_name_char(C)\
	(in_range('a', C, 'z') || in_range('A', C, 'Z') || strchr("0123456789_/", C))


/**
 * Pointer to the beginning of the current line
 */
#define LINE\
	(result->source_code->lines[line_i])


/**
 * Update the tip of the stack to point to the address
 * of the current stack's tip's `next`-member
 */
#define NEXT\
	tree_stack[stack_ptr] = &(tree_stack[stack_ptr][0]->next)


/**
 * Add an error to the error list
 * 
 * @param  ERROR_IS_IN_FILE:int           Whether the error is in the layout code
 * @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(ERROR_IS_IN_FILE, SEVERITY, ...)\
	NEW_ERROR_(result, SEVERITY, ERROR_IS_IN_FILE, line_i,\
	           (size_t)(line - LINE), (size_t)(end - LINE), 1, __VA_ARGS__)


/**
 * Create a new node
 * 
 * @param  LOWERCASE:identifier  The keyword, for the node type, in lower case
 * @param  UPPERCASE:identifier  The keyword, for the node type, in upper case
 */
#define NEW_NODE(LOWERCASE, UPPERCASE)\
	mds_kbdc_tree_##LOWERCASE##_t *node;\
	fail_if (xcalloc(node, 1, mds_kbdc_tree_##LOWERCASE##_t));\
	node->type = MDS_KBDC_TREE_TYPE_##UPPERCASE;\
	node->loc_line  = line_i;\
	node->loc_start = (size_t)(line - LINE);\
	node->loc_end   = (size_t)(end  - LINE)


/**
 * Create a new node named `subnode`
 * 
 * @param  LOWERCASE:identifier  The keyword, for the node type, in lower case
 * @param  UPPERCASE:identifier  The keyword, for the node type, in upper case
 */
#define NEW_SUBNODE(LOWERCASE, UPPERCASE)\
	mds_kbdc_tree_##LOWERCASE##_t *subnode;\
	fail_if (xcalloc(subnode, 1, mds_kbdc_tree_##LOWERCASE##_t));\
	subnode->type = MDS_KBDC_TREE_TYPE_##UPPERCASE;\
	subnode->loc_line  = line_i;\
	subnode->loc_start = (size_t)(line - LINE);\
	subnode->loc_end   = (size_t)(end  - LINE)


/**
 * Update the tip of the tree stack with the current node
 * and change the pointer at the tip to the pointer to the
 * current node's down pointer
 * 
 * This is what should be done when a branch node has
 * been created and should be added to the result tree
 * 
 * @param  KEYWORD:const char*  The keyword for the current node's type
 */
#define BRANCH(KEYWORD)\
	(*(tree_stack[stack_ptr]) = (mds_kbdc_tree_t *)node,\
	 tree_stack[stack_ptr + 1] = &(node->inner),\
	 keyword_stack[stack_ptr++] = KEYWORD)


/**
 * Update the tip of the tree stack with the current node
 * and change the pointer at the tip to the pointer to the
 * current node's next pointer
 * 
 * This is what should be done when a leaf node has been
 * created and should be added to the result tree
 */
#define LEAF\
	(*(tree_stack[stack_ptr]) = (mds_kbdc_tree_t*)node,\
	 NEXT)


/**
 * Skip all blank spaces
 * 
 * @param  var:const char*  The variable
 */
#define SKIP_SPACES(var)\
	while (*var && *var == ' ')\
		var++


/**
 * Check that there are no tokens after a keyword
 * 
 * @param  KEYWORD:const char*  The keyword
 */
#define NO_PARAMETERS(KEYWORD)\
	fail_if (no_parameters(KEYWORD))


/**
 * Take next parameter, which should be a name of a callable,
 * and store it in the current node
 * 
 * @param  var:identifier  The name of the member variable, for the current
 *                         node, where the parameter should be stored
 */
#define NAMES_1(var)\
	fail_if (names_1(&node->var))


/**
 * Suppress the next `line = STREND(line)`
 */
#define NO_JUMP\
	(*end = prev_end_char,\
	 end = line,\
	 prev_end_char = *end,\
	 *end = '\0')


/**
 * Check whether a character ends a strings, whilst not being being a quote
 * 
 * @param  c:char  The character
 */
#define IS_END(c)\
	strchr(" >}])", c)


/**
 * Take next parameter, which should be a string or numeral,
 * and store it in the current node
 * 
 * @param  var:identifier  The name of the member variable, for the current
 *                         node, where the parameter should be stored
 */
#define CHARS(var)\
	fail_if (chars(&node->var))


/**
 * Test that there are no more parameters
 */
#define END\
	do {\
		SKIP_SPACES(line);\
		if (*line) {\
			NEW_ERROR(1, ERROR, "too many parameters");\
			error->end = strlen(LINE);\
		}\
	} while (0)


/**
 * Test that the next parameter is in quotes
 */
#define QUOTES\
	fail_if (quotes())


/**
 * Check that there is exactly one parameter, that it is in
 * quotes, and add it to the current node
 * 
 * @param  var:identifier  The name of the member variable, for the current
 *                         node, where the parameter should be stored
 */
#define QUOTES_1(var)\
	do {\
		QUOTES;\
		CHARS(var);\
		END;\
	} while (0)


/**
 * Check that the next word is a specific keyword
 * 
 * @param  KEYWORD:const char*  The keyword
 */
#define TEST_FOR_KEYWORD(KEYWORD)\
	fail_if (test_for_keyword(KEYWORD))


/**
 * Take next parameter, which should be a key combination or strings,
 * and store it in the current node
 * 
 * @param  var:identifier  The name of the member variable, for the current
 *                         node, where the parameter should be stored
 */
#define KEYS(var)\
	fail_if (keys(&node->var))


/**
 * Take next parameter, which should be a key combination,
 * and store it in the current node
 * 
 * @param  var:identifier  The name of the member variable, for the current
 *                         node, where the parameter should be stored
 */
#define PURE_KEYS(var)\
	fail_if (pure_keys(&node->var))



/**
 * Parse a sequence in a mapping
 * 
 * @param  mapseq:int  Whether this is a mapping sequence, otherwise
 *                     it is treated as macro call arguments
 */
#define SEQUENCE(mapseq)\
	do /* for(;;) */ {\
		*end = prev_end_char;\
		SKIP_SPACES(line);\
		if (!*line || *line == (mapseq ? ':' : ')'))\
			break;\
		fail_if (sequence(mapseq, stack_orig));\
	} while (1)


/**
 * Check that the scopes created in `SEQUENCE` has all been popped
 */
#define SEQUENCE_FULLY_POPPED\
	fail_if (sequence_fully_popped(stack_orig))


/**
 * Create new leaf and update the stack accordingly
 * 
 * @param  LOWERCASE:identifier  The keyword, for the node type, in lower case
 * @param  UPPERCASE:identifier  The keyword, for the node type, in upper case
 * @param  PARSE:expression      Statement, without final semicolon, to retrieve members
 */
#define MAKE_LEAF(LOWERCASE, UPPERCASE, PARSE)\
	do {\
		NEW_NODE(LOWERCASE, UPPERCASE);\
		PARSE;\
		LEAF;\
	} while (0)


/**
 * Create new branch and update the stack accordingly
 * 
 * @param  LOWERCASE:identifier  The keyword, for the node type, in lower case
 * @param  UPPERCASE:identifier  The keyword, for the node type, in upper case
 * @param  PARSE:expression      Statement, without final semicolon, to retrieve members
 */
#define MAKE_BRANCH(LOWERCASE, UPPERCASE, PARSE)\
	do {\
		NEW_NODE(LOWERCASE, UPPERCASE);\
		PARSE;\
		BRANCH(#LOWERCASE);\
	} while (0)



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

/**
 * Output parameter for the parsing result
 */
static mds_kbdc_parsed_t *restrict result;

/**
 * The head of the parsing-stack
 */
static size_t stack_ptr;

/**
 * The keyword portion of the parsing-stack
 */
static const char **restrict keyword_stack;

/**
 * The tree portion of the parsing-stack
 */
static mds_kbdc_tree_t ***restrict tree_stack;

/**
 * The index of the currently parsed line
 */
static size_t line_i = 0;

/**
 * Whether an array is currently being parsed
 */
static int in_array;

/**
 * The beginning of what has not get been parsed
 * on the current line
 */
static char *line = NULL;

/**
 * The end of what has been parsed on the current line
 */
static char *end = NULL;

/**
 * The previous value of `*end`
 */
static char prev_end_char;

/**
 * Pointer to the first non-whitespace character
 * on the current line
 */
static char *original;

/**
 * Whether it has been identified that the
 * current line has too few parameters
 */
static int too_few;



/***  Pre-parsing procedures.  ***/


/**
 * Get the pathname name of the parsed file
 * 
 * @param   filename  The filename of the parsed file
 * @return            The value the caller should return, or 1 if the caller should not return, -1 on error
 */
static int
get_pathname(const char *restrict filename)
{
	char *cwd = NULL;
	int saved_errno;

	/* Get a non-relative pathname for the file, relative filenames
	 * can be misleading as the program can have changed working
	 * directory to be able to resolve filenames. */
	result->pathname = abspath(filename);
	if (!result->pathname) {
		fail_if (errno != ENOENT);
		saved_errno = errno;
		fail_if (!(cwd = curpath()));
		fail_if (xstrdup(result->pathname, filename));
		NEW_ERROR_(result, ERROR, 0, 0, 0, 0, 1, "no such file or directory in ‘%s’", cwd);
		free(cwd);
		return 0;
	}

	/* Check that the file exists and can be read. */
	if (access(result->pathname, R_OK) < 0) {
		saved_errno = errno;
		NEW_ERROR_(result, ERROR, 0, 0, 0, 0, 0, NULL);
		fail_if (xstrdup(error->description, strerror(saved_errno)));
		return 0;
	}

	return 1;
fail:
	saved_errno = errno;
	free(cwd);
	return errno = saved_errno, -1;
}


/**
 * Allocate stacks needed to parse the tree
 * 
 * @return  Zero on success, -1 on error
 */
static int
allocate_stacks(void)
{
	size_t max_line_length = 0, cur_line_length, line_n;

	/* The maximum line-length is needed because lines can have there own stacking,
	 * like sequence mapping lines, additionally, let statements can have one array. */
	for (line_i = 0, line_n = result->source_code->line_count; line_i < line_n; line_i++) {
		cur_line_length = strlen(LINE);
		if (max_line_length < cur_line_length)
			max_line_length = cur_line_length;
	}

	fail_if (xmalloc(keyword_stack, line_n + max_line_length, const char*));
	fail_if (xmalloc(tree_stack, line_n + max_line_length + 1, mds_kbdc_tree_t**));

	return 0;
fail:
	return -1;
}


/**
 * Read the file and simplify it a bit
 * 
 * @return  Zero on success, -1 on error
 */
static int
read_source_code(void)
{
	/* Read the file and simplify it a bit. */
	fail_if (read_source_lines(result->pathname, result->source_code) < 0);

	return 0;
fail:
	return -1;
}



/***  Post-parsing procedures.  ***/


/**
 * Check that a the file did not end prematurely by checking
 * that the stack has been fully popped
 * 
 * @return  Zero on success, -1 on error
 */
static int
check_for_premature_end_of_file(void)
{
	/* Check that all scopes have been popped. */
	if (stack_ptr) {
		while (stack_ptr && !keyword_stack[stack_ptr - 1])
			stack_ptr--;
		if (stack_ptr) {
			NEW_ERROR(0, ERROR, "premature end of file");
			while (stack_ptr--) {
				if (!keyword_stack[stack_ptr])
					continue;
				line_i = tree_stack[stack_ptr][0]->loc_line;
				line = LINE + tree_stack[stack_ptr][0]->loc_start;
				end  = LINE + tree_stack[stack_ptr][0]->loc_end;
				if (!strcmp(keyword_stack[stack_ptr], "}"))
					NEW_ERROR(1, NOTE, "missing associated ‘%s’", keyword_stack[stack_ptr]);
				else
					NEW_ERROR(1, NOTE, "missing associated ‘end %s’", keyword_stack[stack_ptr]);
			}
		}
	}

	return 0;
fail:
	return -1;
}


/**
 * Check whether the parsed file did not contain any code
 * and generate a warning if that is the case, comments
 * and whitespace is ignored
 * 
 * @return  Zero on success, -1 on error
 */
static int
check_whether_file_is_empty(void)
{
	/* Warn about empty files. */
	if (!result->tree && !result->errors_ptr)
		NEW_ERROR(0, WARNING, "file is empty");

	return 0;
fail:
	return -1;
}



/***  Parsing subprocedures.  ***/


/**
 * Check that there are no tokens after a keyword
 * 
 * @param   keyword  The keyword
 * @return           Zero on success, -1 on error
 */
static int
no_parameters(const char *restrict keyword)
{
	line = STREND(line);
	*end = prev_end_char, prev_end_char = '\0';
	SKIP_SPACES(line);
	if (*line) {
		end = STREND(line);
		NEW_ERROR(1, ERROR, "extra token after ‘%s’", keyword);
	}

	return 0;
fail:
	return -1;
}



/**
 * Take next parameter, which should be a name of a callable,
 * and store it in the current node
 * 
 * @param   var  Address of the member variable, for the current
 *               node, where the parameter should be stored
 * @return       Zero on success, -1 on error
 */
static int
names_1(char **restrict var)
{
	char *name_end;
	char *test;
	int stray_char = 0;
	char *end_end;

	line = STREND(line);
	*end = prev_end_char, prev_end_char = '\0';
	SKIP_SPACES(line);
	if (!*line) {
		line = original, end = STREND(line);
		NEW_ERROR(1, ERROR, "a name is expected");
	} else {
		name_end = line;
		while (*name_end && is_name_char(*name_end))
			name_end++;
		if (*name_end && (*name_end != ' ')) {
			end_end = name_end + 1;
			while ((*end_end & 0xC0) == 0x80)
				end_end++;
			prev_end_char = *end_end, *end_end = '\0';
			NEW_ERROR(1, ERROR, "stray ‘%s’ character", name_end);
			error->start = (size_t)(name_end - LINE);
			error->end   = (size_t)(end_end  - LINE);
			*end_end = prev_end_char;
			stray_char = 1;
		}
		test = name_end;
		SKIP_SPACES(test);
		if (*test && !stray_char) {
			NEW_ERROR(1, ERROR, "too many parameters");
			error->start = (size_t)(test - LINE);
			error->end   = strlen(LINE);
		}
		end = name_end;
		prev_end_char = *end;
		*end = '\0';
		fail_if (xstrdup(*var, line));
	}

	return 0;
fail:
	return -1;
}


/**
 * Take next parameter, which should be a string or numeral,
 * and store it in the current node
 * 
 * @param   var  Address of the member variable, for the current
 *               node, where the parameter should be stored
 * @return       Zero on success, -1 on error
 */
static int
chars(char **restrict var)
{
	char *arg_end, *call_end, c;
	int escape, quote;
	if (too_few)
		return 0;
	line = STREND(line);
	*end = prev_end_char, prev_end_char = '\0';
	SKIP_SPACES(line);
	if (!*line) {
		line = original, end = STREND(line);
		NEW_ERROR(1, ERROR, "too few parameters");
		line = end, too_few = 1;
	} else {
		arg_end = line;
		call_end = arg_end;
		escape = quote = 0;
		while (*arg_end) {
			c = *arg_end++;
			if (escape) {
				escape = 0;
			} else if (arg_end <= call_end) { ;
			} else if (c == '\\') {
				escape = 1;
				call_end = arg_end + get_end_of_call(arg_end, 0, strlen(arg_end));
			} else if (quote) {
				quote = c != '"';
			} else if (IS_END(c)) {
				arg_end--;
				break;
			} else {
				quote = c == '"';
			}
		}
		prev_end_char = *arg_end, *arg_end = '\0', end = arg_end;
		fail_if (xstrdup(*var, line));
		line = end;
	}

	return 0;
fail:
	return -1;
}


/**
 * Test that the next parameter is in quotes
 * 
 * @return  Zero on success, -1 on error
 */
static int
quotes(void)
{
	char *line_ = line, *arg_end;
	line = STREND(line);
	*end = prev_end_char;
	SKIP_SPACES(line);
	if (*line && *line != '"') {
		arg_end = line;
		SKIP_SPACES(arg_end);
		NEW_ERROR(1, ERROR, "parameter must be in quotes");
		error->end = (size_t)(arg_end - LINE);
	}
	*end = '\0';
	line = line_;

	return 0;
fail:
	return -1;
}


/**
 * Check whether the currently line has unparsed parameters
 * 
 * @return  Whether the currently line has unparsed parameters, -1 on error
 */
static int
have_more_parameters(void)
{
	if (too_few)
		return 0;
	line = STREND(line);
	*end = prev_end_char, prev_end_char = '\0';
	SKIP_SPACES(line);
	if (!*line) {
		line = original, end = STREND(line);
		NEW_ERROR(1, ERROR, "too few parameters");
		line = end, too_few = 1;
		return 0;
	}
	return 1;
fail:
	return -1;
}


/**
 * Check that the next word is a specific keyword
 * 
 * @param   keyword  The keyword
 * @return           Zero on success, -1 on error
 */
static int
test_for_keyword(const char *restrict keyword)
{
	int ok, r = have_more_parameters();
	fail_if (r < 0);
	if (!r)
		return 0;

	ok = (strstr(line, keyword) == line);
	line += strlen(keyword);
	ok = ok && (!*line || *line == ' ');
	if (ok) {
		end = line;
		prev_end_char = *end;
		*end = '\0';
		return 0;
	}
	line -= strlen(keyword);
	end = line;
	SKIP_SPACES(end);
	prev_end_char = *end, *end = '\0';
	NEW_ERROR(1, ERROR, "expecting keyword ‘%s’", keyword);
	error->end = error->start + 1;

	return 0;
fail:
	return -1;
}


/**
 * Take next parameter, which should be a key combination or strings,
 * and store it in the current node
 * 
 * @param   var  Address of the member variable, for the current
 *               node, where the parameter should be stored
 * @return       Zero on success, -1 on error
 */
static int
keys(mds_kbdc_tree_t **restrict var)
{
	char *arg_end, *call_end, c;
	int r, escape = 0, quote = 0, triangle;
	r = have_more_parameters();
	fail_if (r < 0);
	if (!r)
		return 0;

	arg_end = line;
	call_end = arg_end;
	triangle = *arg_end == '<';
	while (*arg_end) {
		c = *arg_end++;
		if (escape) {
			escape = 0;
		} else if (arg_end <= call_end) { ;
		} else if (c == '\\') {
			escape = 1;
			call_end = arg_end + get_end_of_call(arg_end, 0, strlen(arg_end));
		} else if (quote) {
			quote = c != '"';
		} else if (c == '\"') {
			quote = 1;
		} else if (c == '>') {
			triangle = 0;
		} else if (IS_END(c) && !triangle) {
			arg_end--;
			break;
		}
	}
	prev_end_char = *arg_end, *arg_end = '\0', end = arg_end;
	if (*line == '<') {
		NEW_SUBNODE(keys, KEYS);
		*var = (mds_kbdc_tree_t *)subnode;
		fail_if (xstrdup(subnode->keys, line));
	} else {
		NEW_SUBNODE(string, STRING);
		*var = (mds_kbdc_tree_t *)subnode;
		fail_if (xstrdup(subnode->string, line));
	}
	line = end;

	return 0;
fail:
	return -1;
}



/**
 * Take next parameter, which should be a key combination,
 * and store it in the current node
 * 
 * @param   var  Address of the member variable, for the current
 *               node, where the parameter should be stored
 * @return       Zero on success, -1 on error
 */
static int
pure_keys(char **restrict var)
{
	char *arg_end, *call_end, c;
	int r, escape = 0, quote = 0, triangle;
	r = have_more_parameters();
	fail_if (r < 0);
	if (!r)
		return 0;

	arg_end = line;
	call_end = arg_end;
	triangle = *arg_end == '<';
	while (*arg_end) {
		c = *arg_end++;
		if (escape) {
			escape = 0;
		} else if (arg_end <= call_end) { ;
		} else if (c == '\\') {
			escape = 1;
			call_end = arg_end + get_end_of_call(arg_end, 0, strlen(arg_end));
		} else if (quote) {
			quote = c != '"';
		} else if (c == '\"') {
			quote = 1;
		} else if (c == '>') {
			triangle = 0;
		} else if (IS_END(c) && !triangle) {
			arg_end--;
			break;
		}
	}
	prev_end_char = *arg_end, *arg_end = '\0';
	fail_if (xstrdup(*var, line));
	end = arg_end, line = end;

	return 0;
fail:
	return -1;
}


/**
 * Parse an element of a sequence in a mapping
 * 
 * @param   mapseq      Whether this is a mapping sequence, otherwise
 *                      it is treated as macro call arguments
 * @param   stack_orig  The size of the stack when `SEQUENCE` was called
 * @return              Zero on success, -1 on error
 */
static int
sequence(int mapseq, size_t stack_orig)
{
	if (mapseq && *line == '(') {
		NEW_NODE(unordered, UNORDERED);
		node->loc_end = node->loc_start + 1;
		BRANCH(")");
		line++;
	} else if (*line == '[') {
		NEW_NODE(alternation, ALTERNATION);
		node->loc_end = node->loc_start + 1;
		BRANCH("]");
		line++;
	} else if (*line == '.') {
		NEW_NODE(nothing, NOTHING);
		node->loc_end = node->loc_start + 1;
		LEAF;
		line++;
	} else if (strchr("])", *line)) {
		end = line + 1;
		prev_end_char = *end, *end = '\0';
		if (stack_ptr == stack_orig) {
			NEW_ERROR(1, ERROR, "runaway ‘%s’", line);
		} else {
			stack_ptr--;
			if (strcmp(line, keyword_stack[stack_ptr]))
				NEW_ERROR(1, ERROR, "expected ‘%s’ but got ‘%s’", keyword_stack[stack_ptr], line);
			NEXT;
		}
		*end = prev_end_char;
		line++;
	} else if (*line == '<') {
		NEW_NODE(keys, KEYS);
		NO_JUMP;
		PURE_KEYS(keys);
		LEAF;
		node->loc_end = (size_t)(line - LINE);
	} else {
		NEW_NODE(string, STRING);
		NO_JUMP;
		CHARS(string);
		LEAF;	
		node->loc_end = (size_t)(line - LINE);
	}

	return 0;
 fail:
	return -1;
}


/**
 * Check that the scopes created in `SEQUENCE` has all been popped
 * 
 * @param  stack_orig  The size of the stack when `SEQUENCE` was called
 */
static int
sequence_fully_popped(size_t stack_orig)
{
	if (stack_ptr == stack_orig)
		return 0;
	end = line + 1;
	NEW_ERROR(1, ERROR, "premature end of sequence");
	while (stack_ptr > stack_orig) {
		stack_ptr--;
		NEW_ERROR(1, NOTE, "missing associated ‘%s’", keyword_stack[stack_ptr]);
		error->start = tree_stack[stack_ptr][0]->loc_start;
		error->end   = tree_stack[stack_ptr][0]->loc_end;
	}

	return 0;
fail:
	return -1;
}



/***  Parsing procedures.  ***/


/**
 * Parse an else- or else if-statement
 * 
 * @return  Zero on success, -1 on error, 1 if the caller should go to `redo`
 */
static int
parse_else(void)
{
	size_t i;
	if (!stack_ptr) {
		NEW_ERROR(1, ERROR, "runaway ‘else’ statement");
		return 0;
	}
	line = STREND(line);
	*end = prev_end_char, prev_end_char = '\0';
	end = STREND(line);
	SKIP_SPACES(line);
	i = stack_ptr - 1;
	while (!keyword_stack[i])
		i--;
	if (strcmp(keyword_stack[i], "if")) {
		stack_ptr--;
		line = original, end = STREND(line);
		NEW_ERROR(1, ERROR, "runaway ‘else’ statement");
	} else if (!*line) {
		/* else */
		mds_kbdc_tree_if_t *supernode = &tree_stack[stack_ptr - 1][0]->if_;
		if (supernode->otherwise) {
			line = strstr(LINE, "else");
			end = line + 4, prev_end_char = *end;
			NEW_ERROR(1, ERROR, "multiple ‘else’ statements");
			mds_kbdc_tree_free(supernode->otherwise);
			supernode->otherwise = NULL;
		}
		tree_stack[stack_ptr] = &supernode->otherwise;
	} else if (strstr(line, "if") == line && (line[2] == ' ' || !line[2])) {
		/* else if */
		mds_kbdc_tree_if_t *supernode = &tree_stack[stack_ptr - 1][0]->if_;
		NEW_NODE(if, IF);
		node->loc_end = node->loc_start + 2;
		end = line += 2, prev_end_char = *end, *end = '\0';
		CHARS(condition);
		END;
		tree_stack[stack_ptr] = &(supernode->otherwise);
		BRANCH(NULL);
	} else {
		NEW_ERROR(1, ERROR, "expecting nothing or ‘if’");
		stack_ptr--;
	}

	return 0;
fail:
	return -1;
}


/**
 * Parse a for-statement
 * 
 * @return  Zero on success, -1 on error, 1 if the caller should go to `redo`
 */
static int
parse_for(void)
{
	NEW_NODE(for, FOR);
	CHARS(first);
	TEST_FOR_KEYWORD("to");
	CHARS(last);
	TEST_FOR_KEYWORD("as");
	CHARS(variable);
	END;
	BRANCH("for");

	return 0;
fail:
	return -1;
}


/**
 * Parse a let-statement
 * 
 * @return  Zero on success, -1 on error, 1 if the caller should go to `redo`
 */
static int
parse_let(void)
{
	NEW_NODE(let, LET);
	CHARS(variable);
	TEST_FOR_KEYWORD(":");
	*end = prev_end_char;
	SKIP_SPACES(line);
	if (*line == '{')
#define inner value
		BRANCH(NULL);
#undef inner
	else
		LEAF;
	if (!*line) {
		line = original, end = STREND(line), prev_end_char = '\0';
		NEW_ERROR(1, ERROR, "too few parameters");
	} else if (*line != '{') {
#define node subnode
		NEW_NODE(string, STRING);
		NO_JUMP;
		CHARS(string);
		node->loc_end = (size_t)(end - LINE);
#undef node
		node->value = (mds_kbdc_tree_t*)subnode;
		END;
	} else {
#define node subnode
#define inner elements
		NEW_NODE(array, ARRAY);
		BRANCH("}");
		node->loc_end = node->loc_start + 1;
#undef inner
#undef node
		in_array = 1;
		line++;
		return 1;
	}

	return 0;
fail:
	return -1;
}


/**
 * Parse an end-statement
 * 
 * @return  Zero on success, -1 on error, 1 if the caller should go to `redo`
 */
static int
parse_end(void)
{
	if (!stack_ptr) {
		NEW_ERROR(1, ERROR, "runaway ‘end’ statement");
		return 0;
	}
	line = STREND(line);
	*end = prev_end_char, prev_end_char = '\0';
	SKIP_SPACES(line);
	while (!keyword_stack[--stack_ptr]);
	if (!*line) {
		line = original, end = STREND(line);
		NEW_ERROR(1, ERROR, "expecting a keyword after ‘end’");
	} else if (strcmp(line, keyword_stack[stack_ptr])) {
		NEW_ERROR(1, ERROR, "expected ‘%s’ but got ‘%s’", keyword_stack[stack_ptr], line);
	}
	NEXT;

	return 0;
fail:
	return -1;
}


/**
 * Parse a mapping- or value-statement
 * 
 * @return  Zero on success, -1 on error, 1 if the caller should go to `redo`
 */
static int
parse_map(void)
{
	size_t stack_orig = stack_ptr + 1;
	char *colon;
#define node supernode
#define inner sequence
	NEW_NODE(map, MAP);
	node->loc_end = node->loc_start;
	BRANCH(":");
#undef inner
#undef node
	SEQUENCE(1);
	SEQUENCE_FULLY_POPPED;
#define node supernode
#define inner result
	stack_ptr--;
	*end = prev_end_char;
	supernode->loc_end = (size_t)(end - LINE);
	SKIP_SPACES(line);
	if (colon = line, *line++ != ':') {
		LEAF;
		prev_end_char = *end;
		return 0; /* Not an error in functions, or if \set is access, even indirectly. */
	}
	BRANCH(":");
#undef inner
#undef node
	SEQUENCE(1);
	SEQUENCE_FULLY_POPPED;
	stack_ptr--;
	*end = prev_end_char;
	supernode->loc_end = (size_t)(end - LINE);
	SKIP_SPACES(line);
#define node supernode
	LEAF;
#undef node
	if (!supernode->result) {
		NEW_ERROR(1, ERROR, "output missing");
		error->start = (size_t)(colon - LINE);
		error->end = error->start + 1;
	}
	if (!*line)
		return prev_end_char = *end, 0;
	end = STREND(line), prev_end_char = *end;
	NEW_ERROR(1, ERROR, "too many parameters");

	return 0;
fail:
	return -1;
}


/**
 * Parse a macro call
 * 
 * @return  Zero on success, -1 on error, 1 if the caller should go to `redo`
 */
static int
parse_macro_call(void)
{
	char *old_end = end;
	char old_prev_end_char = prev_end_char;
	size_t stack_orig = stack_ptr + 1;
	*end = prev_end_char;
	end = strchrnul(line, '(');
	prev_end_char = *end, *end = '\0';
	if (prev_end_char) {
#define node supernode
#define inner arguments
		NEW_NODE(macro_call, MACRO_CALL);
		old_end = end, old_prev_end_char = prev_end_char;
		NO_JUMP;
		*old_end = '\0';
		CHARS(name);
		BRANCH(NULL);
		end = old_end, prev_end_char = old_prev_end_char;
		line++;
#undef inner
#undef node
		SEQUENCE(0);
		SEQUENCE_FULLY_POPPED;
#define node supernode
		if (*line == ')') {
			line++;
			SKIP_SPACES(line);
			if (*line) {
				NEW_ERROR(1, ERROR, "extra token after macro call");
				error->end = strlen(LINE);
			}
		} else {
			NEW_ERROR(1, ERROR, "missing ‘)’");
			error->start = (size_t)(strchr(LINE, '(') - LINE);
			error->end   = error->start + 1;
		}
		stack_ptr--;
		NEXT;
		return 0;
#undef node
	}
	*old_end = '\0';
	end = old_end;
	prev_end_char = old_prev_end_char;
	if (strchr("}", *line))
		NEW_ERROR(1, ERROR, "runaway ‘%c’", *line);
	else
		NEW_ERROR(1, ERROR, "invalid syntax ‘%s’", line);

	return 0;
fail:
	return -1;
}


/**
 * Parse a line of an array
 * 
 * @return  Zero on success, -1 on error, 1 if the caller should go to `redo`
 */
static int
parse_array_elements(void)
{
	for (;;) {
		SKIP_SPACES(line);
		if (!*line) {
			return 0;
		} else if (*line == '}') {
			line++;
			end = STREND(line);
			END;
			line = end, prev_end_char = '\0';
			goto done;
		} else {
			NEW_NODE(string, STRING);
			if (strchr("[]()<>{}", *line)) {
				mds_kbdc_tree_free((mds_kbdc_tree_t*)node);
				NEW_ERROR(1, ERROR, "x-stray ‘%c’", *line);
				error->end = error->start + 1;
				goto done;
			}
			NO_JUMP;
			CHARS(string);
			LEAF;
			node->loc_end = (size_t)(end - LINE);
			*end = prev_end_char;
			line = end;
		}
	}

fail:
	return -1;

done:
	in_array = 0;
	stack_ptr -= 2;
	NEXT;
	return 0;
}


/**
 * Parse a line
 * 
 * @return  Zero on success, -1 on error
 */
static int
parse_line(void)
{
#define p(function)\
	do {\
		fail_if ((r = function()) < 0);\
		if (r > 0)\
			goto redo;\
	} while (0)

	int r;

redo:
	if      (in_array)                     p (parse_array_elements);
	else if (!strcmp(line, "have_chars"))  MAKE_LEAF(assumption_have_chars, ASSUMPTION_HAVE_CHARS, QUOTES_1(chars));
	else if (!strcmp(line, "have_range"))  MAKE_LEAF(assumption_have_range, ASSUMPTION_HAVE_RANGE, CHARS(first); CHARS(last); END);
	else if (!strcmp(line, "have"))        MAKE_LEAF(assumption_have, ASSUMPTION_HAVE, KEYS(data); END);
	else if (!strcmp(line, "information")) MAKE_BRANCH(information, INFORMATION, NO_PARAMETERS("information"));
	else if (!strcmp(line, "assumption"))  MAKE_BRANCH(assumption, ASSUMPTION, NO_PARAMETERS("assumption"));
	else if (!strcmp(line, "return"))      MAKE_LEAF(return, RETURN, NO_PARAMETERS("return"));
	else if (!strcmp(line, "continue"))    MAKE_LEAF(continue, CONTINUE, NO_PARAMETERS("continue"));
	else if (!strcmp(line, "break"))       MAKE_LEAF(break, BREAK, NO_PARAMETERS("break"));
	else if (!strcmp(line, "language"))    MAKE_LEAF(information_language, INFORMATION_LANGUAGE, QUOTES_1(data));
	else if (!strcmp(line, "country"))     MAKE_LEAF(information_country, INFORMATION_COUNTRY, QUOTES_1(data));
	else if (!strcmp(line, "variant"))     MAKE_LEAF(information_variant, INFORMATION_VARIANT, QUOTES_1(data));
	else if (!strcmp(line, "include"))     MAKE_LEAF(include, INCLUDE, QUOTES_1(filename));
	else if (!strcmp(line, "function"))    MAKE_BRANCH(function, FUNCTION, NAMES_1(name));
	else if (!strcmp(line, "macro"))       MAKE_BRANCH(macro, MACRO, NAMES_1(name));
	else if (!strcmp(line, "if"))          MAKE_BRANCH(if, IF, CHARS(condition); END);
	else if (!strcmp(line, "else"))              p (parse_else);
	else if (!strcmp(line, "for"))               p (parse_for);
	else if (!strcmp(line, "let"))               p (parse_let);
	else if (!strcmp(line, "end"))               p (parse_end);
	else if (strchr("\\\"<([0123456789", *line)) p (parse_map);
	else                                         p (parse_macro_call);

	*end = prev_end_char;

	return 0;
fail:
	return -1;
#undef p
}



/***  Parsing root-procedure.  ***/


/**
 * Parse a file into a syntax tree
 * 
 * @param   filename   The filename of the file to parse
 * @param   result_    Output parameter for the parsing result
 * @return             -1 if an error occursed that cannot be stored in `result`, zero otherwise
 */
int
parse_to_tree(const char *restrict filename, mds_kbdc_parsed_t *restrict result_)
{
	size_t line_n;
	int r, saved_errno;


	/* Prepare parsing. */
	result = result_;
	stack_ptr = 0;
	keyword_stack = NULL;
	tree_stack = NULL;
	in_array = 0;

	fail_if (xmalloc(result->source_code, 1, mds_kbdc_source_code_t));
	mds_kbdc_source_code_initialise(result->source_code);

	fail_if ((r = get_pathname(filename)) < 0);
	if (!r)
		return 0;

	fail_if (read_source_code());
	fail_if (allocate_stacks());


	/* Create a node-slot for the tree root. */
	*tree_stack = &(result->tree);

	/* Parse the file. */
	for (line_i = 0, line_n = result->source_code->line_count; line_i < line_n; line_i++) {
		line = LINE;
		SKIP_SPACES(line);
		if ((end = strchrnul(line, ' ')) == line)
			continue;
		prev_end_char = *end, *end = '\0';
		original = line;
		too_few = 0;

		parse_line();
	}


	/* Check parsing state. */
	fail_if (check_for_premature_end_of_file());
	fail_if (check_whether_file_is_empty());

	/* Clean up. */
	free(keyword_stack);
	free(tree_stack);
	return 0;

fail:
	saved_errno = errno;
	free(keyword_stack);
	free(tree_stack);
	return errno = saved_errno, -1;
}



#undef MAKE_BRANCH
#undef MAKE_LEAF
#undef SEQUENCE_FULLY_POPPED
#undef SEQUENCE
#undef PURE_KEYS
#undef KEYS
#undef TEST_FOR_KEYWORD
#undef QUOTES_1
#undef QUOTES
#undef END
#undef CHARS
#undef IS_END
#undef NO_JUMP
#undef NAMES_1
#undef NO_PARAMETERS
#undef SKIP_SPACES
#undef LEAF
#undef BRANCH
#undef NEW_NODE
#undef NEW_ERROR
#undef NEXT
#undef LINE
#undef is_name_char
#undef in_range
#undef PRINT_STACK
#undef DEBUG_PROC