/**
* 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 .
*/
#include "make-tree.h"
#include "raw-data.h"
#include
#include
#include
#include
#include
#include
#include
#include
#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)
/**
* Wrapper around `asprintf` that makes sure that first
* argument gets set to `NULL` on error and that zero is
* returned on success rather than the number of printed
* characters
*
* @param VAR:char** The output parameter for the string
* @param ...:const char*, ... The format string and arguments
* @return :int Zero on success, -1 on error
*/
#define xasprintf(VAR, ...) \
(asprintf(&(VAR), __VA_ARGS__) < 0 ? (VAR = NULL, -1) : 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 \
(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 the to 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
*/
#define NEW_ERROR(ERROR_IS_IN_FILE, SEVERITY, ...) \
do \
{ \
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 - LINE); \
error->end = (size_t)(end - LINE); \
fail_if ((error->pathname = strdup(pathname)) == NULL); \
if (ERROR_IS_IN_FILE) \
fail_if ((error->code = strdup(source_code.real_lines[line_i])) == NULL); \
fail_if (xasprintf(error->description, __VA_ARGS__)); \
} \
while (0)
/**
* 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) \
do \
{ \
line += strlen(line); \
*end = prev_end_char, prev_end_char = '\0'; \
SKIP_SPACES(line); \
if (*line) \
{ \
end = line + strlen(line); \
NEW_ERROR(1, ERROR, "extra token after ‘%s’", KEYWORD); \
} \
} \
while (0)
/**
* 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) \
do \
{ \
line += strlen(line); \
*end = prev_end_char, prev_end_char = '\0'; \
SKIP_SPACES(line); \
if (*line == '\0') \
{ \
line = original, end = line + strlen(line); \
NEW_ERROR(1, ERROR, "a name is expected"); \
} \
else \
{ \
char* name_end = line; \
char* test; \
int stray_char = 0; \
while (*name_end && is_name_char(*name_end)) \
name_end++; \
if (*name_end && (*name_end != ' ')) \
{ \
char* 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 ((node->var = strdup(line)) == NULL); \
} \
} \
while (0)
/**
* Suppress the next `line += strlen(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) \
do \
{ \
if (too_few) \
break; \
line += strlen(line); \
*end = prev_end_char, prev_end_char = '\0'; \
SKIP_SPACES(line); \
if (*line == '\0') \
{ \
line = original, end = line + strlen(line); \
NEW_ERROR(1, ERROR, "too few parameters"); \
line = end, too_few = 1; \
} \
else \
{ \
char* arg_end = line; \
char* call_end = arg_end; \
int escape = 0, quote = 0; \
while (*arg_end) \
{ \
char 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 ((node->var = strdup(line)) == NULL); \
line = end; \
} \
} \
while (0)
/**
* 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 \
do \
{ \
char* line_ = line; \
line += strlen(line); \
*end = prev_end_char; \
SKIP_SPACES(line); \
if (*line && (*line != '"')) \
{ \
char* 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_; \
} \
while (0)
/**
* 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
*
* @parma KEYWORD:const char* The keyword
*/
#define TEST_FOR_KEYWORD(KEYWORD) \
do \
{ \
if (too_few) \
break; \
line += strlen(line); \
*end = prev_end_char, prev_end_char = '\0'; \
SKIP_SPACES(line); \
if (*line == '\0') \
{ \
line = original, end = line + strlen(line); \
NEW_ERROR(1, ERROR, "too few parameters"); \
line = end, too_few = 1; \
} \
else \
{ \
int ok = (strstr(line, KEYWORD) == line); \
line += strlen(KEYWORD); \
ok = ok && ((*line == '\0') || (*line == ' ')); \
if (ok) \
{ \
end = line; \
prev_end_char = *end, *end = '\0'; \
break; \
} \
line -= strlen(KEYWORD); \
end = line; \
SKIP_SPACES(end); \
prev_end_char = *end, *end = '\0'; \
NEW_ERROR(1, ERROR, "expecting keyword ‘%s’", KEYWORD); \
} \
} \
while (0)
/**
* 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) \
do \
{ \
if (too_few) \
break; \
line += strlen(line); \
*end = prev_end_char, prev_end_char = '\0'; \
SKIP_SPACES(line); \
if (*line == '\0') \
{ \
line = original, end = line + strlen(line); \
NEW_ERROR(1, ERROR, "too few parameters"); \
line = end, too_few = 1; \
} \
else \
{ \
char* arg_end = line; \
char* call_end = arg_end; \
int escape = 0, quote = 0, triangle = (*arg_end == '<'); \
while (*arg_end) \
{ \
char 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); \
node->var = (mds_kbdc_tree_t*)subnode; \
fail_if ((subnode->keys = strdup(line)) == NULL); \
} \
else \
{ \
NEW_SUBNODE(string, STRING); \
node->var = (mds_kbdc_tree_t*)subnode; \
fail_if ((subnode->string = strdup(line)) == NULL); \
} \
line = end; \
} \
} \
while (0)
/**
* 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) \
do \
{ \
if (too_few) \
break; \
line += strlen(line); \
*end = prev_end_char, prev_end_char = '\0'; \
SKIP_SPACES(line); \
if (*line == '\0') \
{ \
line = original, end = line + strlen(line); \
NEW_ERROR(1, ERROR, "too few parameters"); \
line = end, too_few = 1; \
} \
else \
{ \
char* arg_end = line; \
char* call_end = arg_end; \
int escape = 0, quote = 0, triangle = (*arg_end == '<'); \
while (*arg_end) \
{ \
char 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 ((node->var = strdup(line)) == NULL); \
end = arg_end, line = end; \
} \
} \
while (0)
/**
* 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 == '\0') || (*line == (mapseq ? ':' : ')'))) \
break; \
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); \
} \
} \
while (1)
/**
* Change the scopes created in `SEQUENCE` has all been popped
*
* @param stack_orig:size_t The size of the stack when `SEQUENCE` was called
*/
#define SEQUENCE_FULLY_POPPED(stack_orig) \
do \
{ \
if (stack_ptr == stack_orig) \
break; \
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; \
} \
} \
while (0)
/**
* 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)
/**
* Parse a file into a syntax 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)
{
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, in_array = 0;
*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. */
{
/* The maxium line-length is needed because lines can have there own stacking,
* like sequence mapping lines, additionally, let statements can have one array. */
size_t max_line_length = 0, cur_line_length;
for (line_i = 0, line_n = 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, source_code.line_count + max_line_length, const char*));
fail_if (xmalloc(tree_stack, source_code.line_count + max_line_length + 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 = LINE;
char* end;
char prev_end_char;
char* original;
int too_few = 0;
SKIP_SPACES(line);
if (end = strchrnul(line, ' '), end == line)
continue;
prev_end_char = *end, *end = '\0';
original = line;
redo:
if (in_array)
{
for (;;)
{
SKIP_SPACES(line);
if (*line == '\0')
break;
else if (*line == '}')
{
line++;
end = line + strlen(line);
END;
line = end, prev_end_char = '\0';
in_array = 0;
stack_ptr -= 2;
NEXT;
break;
}
else
{
#define node subnode
NEW_NODE(string, STRING);
NO_JUMP;
CHARS(string);
LEAF;
node->loc_end = (size_t)(end - LINE);
*end = prev_end_char;
line = end;
#undef node
}
}
continue;
}
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"))
{
size_t i;
if (stack_ptr == 0)
{
NEW_ERROR(1, ERROR, "runaway ‘else’ statement");
goto next;
}
line += strlen(line);
*end = prev_end_char, prev_end_char = '\0';
end = line + strlen(line);
SKIP_SPACES(line);
i = stack_ptr - 1;
while (keyword_stack[i] == NULL)
i--;
if (strcmp(keyword_stack[i], "if"))
{
stack_ptr--;
line = original, end = line + strlen(line);
NEW_ERROR(1, ERROR, "runaway ‘else’ statement");
}
else if (*line == '\0')
{
/* 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] == '\0')))
{
/* 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--;
}
}
else if (!strcmp(line, "for"))
{
NEW_NODE(for, FOR);
CHARS(first);
TEST_FOR_KEYWORD("to");
CHARS(last);
TEST_FOR_KEYWORD("as");
CHARS(variable);
END;
BRANCH("for");
}
else if (!strcmp(line, "let"))
{
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 == '\0')
{
line = original, end = line + strlen(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++;
goto redo;
}
}
else if (!strcmp(line, "end"))
{
if (stack_ptr == 0)
{
NEW_ERROR(1, ERROR, "runaway ‘end’ statement");
goto next;
}
line += strlen(line);
*end = prev_end_char, prev_end_char = '\0';
SKIP_SPACES(line);
while (keyword_stack[--stack_ptr] == NULL);
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);
NEXT;
}
else if (strchr("\"<([0123456789", *line))
{
size_t stack_orig = stack_ptr + 1;
#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(stack_orig);
#define node supernode
#define inner result
stack_ptr--;
*end = prev_end_char;
SKIP_SPACES(line);
if (*line++ != ':')
{
LEAF;
continue; /* Not an error in macros. */
}
BRANCH(":");
#undef inner
#undef node
SEQUENCE(1);
SEQUENCE_FULLY_POPPED(stack_orig);
stack_ptr--;
*end = prev_end_char;
SKIP_SPACES(line);
#define node supernode
LEAF;
#undef node
if (*line == '\0')
continue;
end = line + strlen(line), prev_end_char = *end;
NEW_ERROR(1, ERROR, "too many parameters");
}
else
{
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(stack_orig);
#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;
goto next;
#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);
}
next:
*end = prev_end_char;
}
/* Check that all scopes have been popped. */
if (stack_ptr)
{
char* line = NULL;
char* end = NULL;
while (stack_ptr && keyword_stack[stack_ptr - 1] == NULL)
stack_ptr--;
if (stack_ptr)
{
NEW_ERROR(0, ERROR, "premature end of file");
while (stack_ptr--)
{
if (keyword_stack[stack_ptr] == NULL)
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]);
}
}
}
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 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 xasprintf
#undef PRINT_STACK
#undef DEBUG_PROC