/** * 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 "paths.h" #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) /** * 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) \ 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 * * @param 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) /** * 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; /** * Whether an array is currently being parsed */ static int in_array; /*** 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 */ 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 == NULL) { fail_if (errno != ENOENT); saved_errno = errno; fail_if ((cwd = curpath(), cwd == NULL)); result->pathname = strdup(filename); fail_if (result->pathname == NULL); 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); error->description = strdup(strerror(saved_errno)); fail_if (error->description == NULL); return 0; } return 1; pfail: 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; pfail: 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; pfail: 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) { char* line = NULL; char* end = NULL; /* Check that all scopes have been popped. */ if (stack_ptr) { 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]); } } } return 0; pfail: 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) { char* line = NULL; char* end = NULL; /* Warn about empty files. */ if (result->tree == NULL) if (result->errors_ptr == 0) NEW_ERROR(0, WARNING, "file is empty"); return 0; pfail: return -1; } /*** Parsing procedures. ***/ /** * Parse a line * * @return Zero on success, -1 on error */ static int parse_line(void) { /* TODO make this function less complex */ char* line = LINE; char* end; char prev_end_char; char* original; int too_few = 0; SKIP_SPACES(line); if (end = strchrnul(line, ' '), end == line) return 0; 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 } } return 0; } 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; 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(stack_orig); #define node supernode #define inner result stack_ptr--; *end = prev_end_char; SKIP_SPACES(line); if (colon = line, *line++ != ':') { LEAF; 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_orig); stack_ptr--; *end = prev_end_char; SKIP_SPACES(line); #define node supernode LEAF; #undef node if (supernode->result == NULL) { NEW_ERROR(1, ERROR, "output missing"); error->start = (size_t)(colon - LINE); error->end = error->start + 1; } if (*line == '\0') return 0; 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; return 0; pfail: return -1; } /*** 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; line_i = 0; in_array = 0; fail_if (xmalloc(result->source_code, 1, mds_kbdc_source_code_t)); mds_kbdc_source_code_initialise(result->source_code); if (r = get_pathname(filename), r <= 0) return r; 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++) 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; pfail: 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