/** * mds — A micro-display server * Copyright © 2014, 2015, 2016, 2017 Mattias Andrée (m@maandree.se) * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #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)\ 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