/** * mds — A micro-display server * Copyright © 2014, 2015 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 "raw-data.h" #include "globals.h" #include "string.h" #include <libmdsserver/macros.h> #include <stdlib.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <errno.h> #include <unistd.h> #include <stdio.h> #include <string.h> #include <stdint.h> /** * Initialise a `mds_kbdc_source_code_t*` * * @param this The `mds_kbdc_source_code_t*` */ void mds_kbdc_source_code_initialise(mds_kbdc_source_code_t* restrict this) { this->lines = NULL; this->real_lines = NULL; this->content = NULL; this->real_content = NULL; this->line_count = 0; this->duplicates = 0; } /** * Release all data in a `mds_kbdc_source_code_t*` * * @param this The `mds_kbdc_source_code_t*` */ void mds_kbdc_source_code_destroy(mds_kbdc_source_code_t* restrict this) { if (this == NULL) return; if (this->duplicates--) return; free(this->lines), this->lines = NULL; free(this->real_lines), this->real_lines = NULL; free(this->content), this->content = NULL; free(this->real_content), this->real_content = NULL; } /** * Release all data in a `mds_kbdc_source_code_t*`, and free it * * @param this The `mds_kbdc_source_code_t*` */ void mds_kbdc_source_code_free(mds_kbdc_source_code_t* restrict this) { if (this == NULL) return; if (this->duplicates--) return; free(this->lines); free(this->real_lines); free(this->content); free(this->real_content); free(this); } /** * Create a duplicate of a `mds_kbdc_source_code_t*` * * @param this The `mds_kbdc_source_code_t*` * @return `this` is returned */ mds_kbdc_source_code_t* mds_kbdc_source_code_dup(mds_kbdc_source_code_t* restrict this) { this->duplicates++; return this; } /** * Read the content of a file, ignoring interruptions * * @param pathname The file to read * @param size Output parameter for the size of the read content, in char:s * @return The read content, `NULL` on error */ static char* read_file(const char* restrict pathname, size_t* restrict size) { size_t buf_size = 8096; size_t buf_ptr = 0; char* restrict content = NULL; char* restrict old = NULL; int fd = -1; ssize_t got; /* Allocate buffer for the file's content. */ fail_if (xmalloc(content, buf_size, char)); /* Open the file to compile. */ fail_if ((fd = open(pathname, O_RDONLY)) < 0); /* Read the file to compile. */ for (;;) { /* Make sure the buffer is not small. */ if (buf_size - buf_ptr < 2048) fail_if (xxrealloc(old, content, buf_size <<= 1, char)); /* Read a chunk of the file. */ got = read(fd, content + buf_ptr, (buf_size - buf_ptr) * sizeof(char)); if ((got < 0) && (errno == EINTR)) continue; if (got == 0) break; fail_if (got < 0); buf_ptr += (size_t)got; } /* Shrink the buffer so it is not excessively large. */ if (buf_ptr) /* Simplest way to handle empty files: let the have the initial allocation size. */ fail_if (xxrealloc(old, content, buf_ptr, char)); /* Close file decriptor for the file. */ xclose(fd); *size = buf_ptr; return content; fail: xperror(*argv); free(old); free(content); if (fd >= 0) xclose(fd); return NULL; } /** * Find the end of a function call * * @param content The code * @param offset The index after the first character after the backslash * that triggered this call * @param size The length of `code` * @return The index of the character after the bracket that closes * the function call (may be outside the code by one character), * or `size` if the call do not end (that is, the code ends * prematurely), or zero if there is no function call at `offset` */ size_t get_end_of_call(const char* restrict content, size_t offset, size_t size) { #define C content[ptr] #define r(lower, upper) (((lower) <= C) && (C <= (upper))) size_t ptr = offset, call_end = 0; int escape = 0, quote = 0; /* Skip to end of function name. */ while ((ptr < size) && (r('a', 'z') || r('A', 'Z') || r('0', '9') || (C == '_'))) ptr++; /* Check that it is a function call. */ if ((ptr == size) || (ptr == offset) || (C != '(')) return 0; /* Find the end of the function call. */ while (ptr < size) { char c = content[ptr++]; /* Escapes may be longer than one character, but only the first can affect the parsing. */ if (escape) escape = 0; /* Nested function and nested quotes can appear. */ else if (ptr <= call_end) ; /* Quotes end with the same symbols as they start with, and quotes automatically escape brackets. */ /* \ can either start a functon call or an escape. */ else if (c == '\\') { /* It may not be an escape, but registering it as an escape cannot harm us since we only skip the first character, and a function call cannot be that short. */ escape = 1; /* Nested quotes can appear at function calls. */ call_end = get_end_of_call(content, ptr, size); } else if (quote) quote = (c != '"'); /* End of function call, end of fun. */ else if (c == ')') break; /* " is the quote symbol. */ else if (c == '"') quote = 1; } return ptr; #undef r #undef C } /** * Remove comments from the content * * @param content The code to shrink * @param size The size of `content`, in char:s * @return The new size of `content`, in char:s; this function cannot fail */ static size_t remove_comments(char* restrict content, size_t size) { #define t content[n_ptr++] = c size_t n_ptr = 0, o_ptr = 0, call_end = 0; int comment = 0, quote = 0, escape = 0; while (o_ptr < size) { char c = content[o_ptr++]; /* Remove comment. */ if (comment) { if (c == '\n') t, comment = 0; } /* Escapes may be longer than one character, but only the first can affect the parsing. */ else if (escape) t, escape = 0; /* Nested quotes can appear at function calls. */ else if (o_ptr <= call_end) t; /* \ can either start a functon call or an escape. */ else if (c == '\\') { t; /* It may not be an escape, but registering it as an escape cannot harm us since we only skip the first character, and a function call cannot be that short. */ escape = 1; /* Nested quotes can appear at function calls. */ call_end = get_end_of_call(content, o_ptr, size); } /* Quotes end with the same symbols as they start with, and quotes automatically escape comments. */ else if (quote) { t; if (strchr("\"\n", c)) quote = 0; } /* # is the comment symbol. */ else if (c == '#') comment = 1; /* " is the quote symbol. */ else if (c == '"') t, quote = 1; /* Code and whitespace. */ else t; } return n_ptr; #undef t } /** * Create an array of each line in a text * * @param content The text to split, it must end with an LF. * LF:s are treated as line endings rather than * new lines, this means that the final LF will * not create a new line in the returned array. * Each LF will be replaced by a NUL-character. * @param length The length of `content`. * @return An array of each line in `content`. This * array will be `NULL`-terminated. It will also * reuse the allocate of `content`. This means * that each element must not be free:d, rather * you should simply free this returned allocation * and the allocation of `content`. On error * `NULL` is returned, and `content` will not * have been modified. */ static char** line_split(char* content, size_t length) { char** restrict lines = NULL; size_t count = 0; size_t i, j; int new_line = 1; for (i = 0; i < length; i++) if (content[i] == '\n') count++; fail_if (xmalloc(lines, count + 1, char*)); lines[count] = NULL; for (i = j = 0; i < length; i++) { if (new_line) new_line = 0, lines[j++] = content + i; if (content[i] == '\n') { new_line = 1; content[i] = '\0'; } } return lines; fail: xperror(*argv); return NULL; } /** * Translate all tab spaces into blank spaces * * @param content Input and output parameter for the file's content * @param content_size Input and output parameter for the size of the file's content * @return Zero on success, -1 on error */ static int expand(char** restrict content, size_t* restrict content_size) { size_t extra = 0, added = 0, ptr, col, n = *content_size; char* restrict data = *content; /* Calculate the new size of the file. */ for (ptr = col = 0; ptr < n; ptr++) if (data[ptr] == '\n') col = 0; else if (data[ptr] == '\t') extra += 8 - (col % 8) - 1; /* Extend the allocation. */ if (extra == 0) return 0; *content_size += extra; fail_if (xrealloc(data, *content_size, char)); *content = data; /* Expand tab spaces. */ memmove(data + extra, data, n); for (ptr = 0; ptr < n; ptr++, added--) if (data[ptr + extra] == '\n') data[ptr + added++] = data[ptr + extra], col = 0; else if (data[ptr + extra] != '\t') data[ptr + added++] = data[ptr + extra], col++; else do data[ptr + added++] = ' '; while (++col % 8); return 0; fail: return -1; } /** * Read lines of a source file * * @param pathname The pathname of the source file * @param source_code Output parameter for read data * @return Zero on success, -1 on error */ int read_source_lines(const char* restrict pathname, mds_kbdc_source_code_t* restrict source_code) { char* content = NULL; char* real_content = NULL; char* old = NULL; size_t content_size; size_t real_content_size; char** lines = NULL; char** real_lines = NULL; size_t line_count = 0; /* Read the file. */ content = read_file(pathname, &content_size); fail_if (content == NULL); /* Expand tab spaces. */ fail_if (expand(&content, &content_size)); /* Make sure the content ends with a new line. */ if (!content_size || (content[content_size - 1] != '\n')) { fail_if (xxrealloc(old, content, content_size + 1, char)); content[content_size++] = '\n'; } /* Simplify file. */ fail_if (xmemdup(real_content, content, content_size, char)); real_content_size = content_size; content_size = remove_comments(content, content_size); fail_if (xxrealloc(old, content, content_size, char)); /* Split by line. */ fail_if ((lines = line_split(content, content_size)) == NULL); fail_if ((real_lines = line_split(real_content, real_content_size)) == NULL); /* Count the number of lines. */ while (lines[line_count] != NULL) line_count++; source_code->lines = lines; source_code->real_lines = real_lines; source_code->content = content; source_code->real_content = real_content; source_code->line_count = line_count; return 0; fail: xperror(*argv); free(old); free(content); free(real_content); free(lines); free(real_lines); return -1; } /** * Encode a character in UTF-8 * * @param buffer The buffer where the character should be stored * @param character The character * @return The of the character in `buffer`, `NULL` on error */ static char* encode_utf8(char* buffer, char32_t character) { char32_t text[2]; char* restrict str; char* restrict str_; text[0] = character; text[1] = -1; fail_if (str_ = str = string_encode(text), str == NULL); while (*str) *buffer++ = *str++; free(str_); return buffer; fail: return NULL; } /** * Parse a quoted and escaped string that may not include function calls or variable dereferences * * @param string The string * @return The string in machine-readable format, `NULL` on error */ char* parse_raw_string(const char* restrict string) { #define r(cond, lower, upper) ((cond) && ((lower) <= c) && (c <= (upper))) char* rc; char* p; int escape = 0; char32_t buf = 0; char c; /* We know that the output string can only be shorter because * it is surrounded by 2 quotes and escape can only be longer * then what they escape, for example \uA0, is four characters, * but when parsed it generateds 2 bytes in UTF-8, and their * is not code point whose UTF-8 encoding is longer than its * hexadecimal representation. */ fail_if (xmalloc(p = rc, strlen(string), char)); while ((c = *string++)) if (r(escape == 8, '0', '7')) buf = (buf << 3) | (c & 15); else if (r(escape == 16, '0', '9')) buf = (buf << 4) | (c & 15); else if (r(escape == 16, 'a', 'f')) buf = (buf << 4) | ((c & 15) + 9); else if (r(escape == 16, 'A', 'F')) buf = (buf << 4) | ((c & 15) + 9); else if (escape > 1) { escape = 0; fail_if ((p = encode_utf8(p, buf), p == NULL)); if (c != '.') *p++ = c; } else if (escape == 1) { escape = 0, buf = 0; switch (c) { case '0': escape = 8; break; case 'u': escape = 16; break; default: *p++ = c; break; } } else if (c == '\\') escape = 1; else if (c != '\"') *p++ = c; *p = '\0'; return rc; fail: free(rc); return NULL; #undef r }