aboutsummaryrefslogblamecommitdiffstats
path: root/src/mds-kbdc/make-tree.c
blob: f99201c3869352c6cc19acb462e6d0adf79392bf (plain) (tree)











































                                                                                                                
                                                                                       

















                                                                                         



                                                                 
                                                                 


                                                                 
                                                                 














                                                                                 

                                





                                             
                                    
                                       
                       

                  
                 
































                                                                 
                                                 
                                                                        


                                                                               
  













                                                                              

                                       

                                             
                                
         

                                           

                                           
                               




                                       
               




                                         
               




                                      
               
         





                                               


                                               




                                               
         





                                                               

                                                     
                      


                                         
             
                                                         
                                                                         
             
                                                          
             
                                                                                                        
             
                                                                    
         




                                                                
      
         



                           
                      
                   





                                    
                      
                   


                                                         
                                              
                                 
  
                    

             
               

                

 
/**
 * mds — A micro-display server
 * Copyright © 2014  Mattias Andrée (maandree@member.fsf.org)
 * 
 * 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 "raw-data.h"

#include <libmdsserver/macros.h>

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



/**
 * Parse a file into a syntex tree
 * 
 * @param   filename  The filename of the file to parse
 * @param   result    Output parameter for the root of the tree, `NULL` if -1 is returned
 * @param   errors    `NULL`-terminated list of found error, `NULL` if no errors were found or if -1 is returned
 * @return            -1 if an error occursed that cannot be stored in `*errors`, zero otherwise
 */
int parse_to_tree(const char* restrict filename, mds_kbdc_tree_t** restrict result,
		  mds_kbdc_parse_error_t*** restrict errors)
{
#define xasprintf(VAR, ...)  (asprintf(&(VAR), __VA_ARGS__) < 0 ? (VAR = NULL, -1) : 0)
#define NEW_ERROR(ERROR_IS_IN_FILE, SEVERITY, ...)					\
  if (errors_ptr + 1 >= errors_size)							\
    {											\
      errors_size = errors_size ? (errors_size << 1) : 2;				\
      fail_if (xxrealloc(old_errors, *errors, errors_size, mds_kbdc_parse_error_t*));	\
    }											\
  fail_if (xcalloc(error, 1, mds_kbdc_parse_error_t));					\
  (*errors)[errors_ptr + 0] = error;							\
  (*errors)[errors_ptr + 1] = NULL;							\
  errors_ptr++;										\
  error->line  = line_i;								\
  error->severity = MDS_KBDC_PARSE_ERROR_##SEVERITY;					\
  error->error_is_in_file = ERROR_IS_IN_FILE;						\
  error->start = (size_t)(line - source_code.lines[line_i]);				\
  error->end   = (size_t)(end  - source_code.lines[line_i]);				\
  fail_if ((error->pathname = strdup(pathname)) == NULL);				\
  fail_if ((error->code = strdup(source_code.real_lines[line_i])) == NULL);		\
  fail_if (xasprintf(error->description, __VA_ARGS__))
#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
#define BRANCH(KEYWORD)						\
  *(tree_stack[stack_ptr]) = (mds_kbdc_tree_t*)node;		\
  tree_stack[stack_ptr + 1] = &(node->inner);			\
  keyword_stack[stack_ptr++] = KEYWORD
#define LEAF							\
  *(tree_stack[stack_ptr]) = (mds_kbdc_tree_t*)node;		\
  tree_stack[stack_ptr] = &(tree_stack[stack_ptr][0]->next)
#define NO_PARAMETERS(KEYWORD)						\
  line += strlen(line);							\
  *end = prev_end_char, prev_end_char = '\0';				\
  while (*line && (*line == ' '))					\
    line++;								\
  do									\
    if (*line)								\
      {									\
	end = line + strlen(line);					\
	NEW_ERROR(1, ERROR, "extra token after ‘%s’", KEYWORD);		\
      }									\
  while (0)
  
  
  mds_kbdc_parse_error_t* error;
  mds_kbdc_parse_error_t** old_errors = NULL;
  char* pathname;
  source_code_t source_code;
  size_t errors_size = 0;
  size_t errors_ptr = 0;
  size_t line_i, line_n;
  const char** keyword_stack = NULL;
  mds_kbdc_tree_t*** tree_stack = NULL;
  size_t stack_ptr = 0;
  int saved_errno;
  
  *result = NULL;
  *errors = NULL;
  source_code_initialise(&source_code);
  
  /* Get a non-relative pathname for the file, relative filenames
   * can be misleading as the program can have changed working
   * directroy to be able to resolve filenames. */
  pathname = realpath(filename, NULL);
  fail_if (pathname == NULL);
  
  /* Check that the file exists and can be read. */
  if (access(pathname, R_OK) < 0)
    {
      saved_errno = errno;
      fail_if (xmalloc(*errors, 2, mds_kbdc_parse_error_t*));
      fail_if (xmalloc(**errors, 1, mds_kbdc_parse_error_t));
      (*errors)[1] = NULL;
      
      (**errors)->severity = MDS_KBDC_PARSE_ERROR_ERROR;
      (**errors)->error_is_in_file = 0;
      (**errors)->pathname = pathname, pathname = NULL;
      (**errors)->line = 0;
      (**errors)->start = 0;
      (**errors)->end = 0;
      (**errors)->code = NULL;
      (**errors)->description = strdup(strerror(saved_errno));
      fail_if ((**errors)->description == NULL);
      return 0;
    }
  
  /* Read the file and simplify it a bit. */
  fail_if (read_source_lines(pathname, &source_code) < 0);
  /* TODO '\t':s should be expanded into ' ':s. */
  
  /* Allocate stacks needed to parse the tree. */
  fail_if (xmalloc(keyword_stack, source_code.line_count, const char*));
  fail_if (xmalloc(tree_stack, source_code.line_count + 1, mds_kbdc_tree_t**));
  /* Create a node-slot for the tree root. */
  *tree_stack = result;
  
  for (line_i = 0, line_n = source_code.line_count; line_i < line_n; line_i++)
    {
      char* line = source_code.lines[line_i];
      char* end;
      char prev_end_char;
      
      while (*line && (*line == ' '))
	line++;
      end = strchrnul(line, ' ');
      if (end == line)
	continue;
      prev_end_char = *end;
      *end = '\0';
      
      if (!strcmp(line, "information"))
	{
	  NEW_NODE(information, INFORMATION);
	  NO_PARAMETERS("information");
	  BRANCH("information");
	}
      else if (!strcmp(line, "assumption"))
	{
	  NEW_NODE(assumption, ASSUMPTION);
	  NO_PARAMETERS("assumption");
	  BRANCH("assumption");
	}
      else if (!strcmp(line, "return"))
	{
	  NEW_NODE(return, RETURN);
	  NO_PARAMETERS("return");
	  LEAF;
	}
      else if (!strcmp(line, "continue"))
	{
	  NEW_NODE(continue, CONTINUE);
	  NO_PARAMETERS("continue");
	  LEAF;
	}
      else if (!strcmp(line, "break"))
	{
	  NEW_NODE(break, BREAK);
	  NO_PARAMETERS("break");
	  LEAF;
	}
      else if (!strcmp(line, "language"))     ;
      else if (!strcmp(line, "country"))      ;
      else if (!strcmp(line, "variant"))      ;
      else if (!strcmp(line, "include"))      ;
      else if (!strcmp(line, "function"))     ;
      else if (!strcmp(line, "macro"))        ;
      else if (!strcmp(line, "if"))           ;
      else if (!strcmp(line, "else"))         ;
      else if (!strcmp(line, "for"))          ;
      else if (!strcmp(line, "let"))          ;
      else if (!strcmp(line, "have"))         ;
      else if (!strcmp(line, "have_chars"))   ;
      else if (!strcmp(line, "have_range"))   ;
      else if (!strcmp(line, "end"))
	{
	  char* original = line;
	  if (stack_ptr == 0)
	    {
	      NEW_ERROR(1, ERROR, "stray ‘end’ statement");
	      goto next;
	    }
	  line += strlen(line);
	  *end = prev_end_char, prev_end_char = '\0';
	  stack_ptr--;
	  while (*line && (*line == ' '))
	    line++;
	  if (*line == '\0')
	    {
	      line = original, end = line + strlen(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);
	    }
	  tree_stack[stack_ptr] = &(tree_stack[stack_ptr][0]->next);
	}
      else if (strchr("\"<([", *line) || strchr(line, '('))  ;
      else
	{
	  NEW_ERROR(1, ERROR, "invalid keyword ‘%s’", line);
	}
      
    next:
      *end = prev_end_char;
    }
  
  free(pathname);
  free(keyword_stack);
  free(tree_stack);
  source_code_destroy(&source_code);
  return 0;
  
 pfail:
  saved_errno = errno;
  free(pathname);
  free(keyword_stack);
  free(tree_stack);
  source_code_destroy(&source_code);
  mds_kbdc_parse_error_free_all(old_errors);
  mds_kbdc_parse_error_free_all(*errors), *errors = NULL;
  mds_kbdc_tree_free(*result), *result = NULL;
  return errno = saved_errno, -1;
  
#undef NO_PARAMETERS
#undef LEAF
#undef BRANCH
#undef NEW_NODE
#undef NEW_ERROR
#undef xasprintf
}