/**
* mds — A micro-display server
* Copyright © 2014 Mattias Andrée (maandree@member.fsf.org)
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "compile-layout.h"
/* TODO add call stack */
/* TODO fix so that for-loops do not generate the same errors/warnings in all iterations [loopy_error]. */
/* TODO test all builtin functions */
/* TODO test function- and macro-overloading */
/* TODO test same-named macros and functions */
#include "include-stack.h"
#include "builtin-functions.h"
#include "string.h"
#include "variables.h"
#include "callables.h"
#include <stdlib.h>
#include <errno.h>
#include <string.h>
/**
* This process's value for `mds_kbdc_tree_t.processed`
*/
#define PROCESS_LEVEL 6
/**
* Tree type constant shortener
*/
#define C(TYPE) MDS_KBDC_TREE_TYPE_##TYPE
/**
* Add an error with “included from here”-notes to the error list
*
* @param NODE:const mds_kbdc_tree_t* The node the triggered the error
* @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(NODE, SEVERITY, ...) \
NEW_ERROR_WITH_INCLUDES(NODE, includes_ptr, SEVERITY, __VA_ARGS__)
/**
* Beginning of failure clause
*/
#define FAIL_BEGIN fail: saved_errno = errno
/**
* End of failure clause
*/
#define FAIL_END return errno = saved_errno, -1
/**
* Variable whether the latest created error is stored
*/
static mds_kbdc_parse_error_t* error;
/**
* The parameter of `compile_layout`
*/
static mds_kbdc_parsed_t* restrict result;
/**
* 3: `return` is being processed
* 2: `break` is being processed
* 1: `continue` is being processed
* 0: Neither is being processed
*/
static int break_level = 0;
/**
* Whether a second variant has already been encountered
*/
static int multiple_variants = 0;
/**
* The previous value-statement, which has no effect
* if we can find another value statemnt that is
* sure to be evaluated
*
* (We will not look too hard.)
*/
static mds_kbdc_tree_t* last_value_statement = NULL;
/**
* Address of the current return value
*/
static char32_t** current_return_value = NULL;
/**
* Whether ‘\set/3’ has been called
*/
static int have_side_effect = 0;
/**
* Compile a subtree
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_subtree(mds_kbdc_tree_t* restrict tree);
/**
* Check that a function used in a part of a literal is defined
*
* @param tree The statement where the literal is located
* @param raw The beginning of the function call in the literal
* @param lineoff The offset on the line where the function call in the literal beings
* @param end Output parameter for the end of the function call
* @param rc Success status output parameter: zero on success,
* -1 on error, 1 if an undefined function is used
*/
static void check_function_call(const mds_kbdc_tree_t* restrict tree, const char* restrict raw,
size_t lineoff, const char* restrict* restrict end, int* restrict rc);
/**
* Parse an argument in a function call
*
* @param tree The statement where the function call appear
* @param raw The beginning of the argument for the function call
* @param lineoff The offset for `raw` on line in which it appears
* @param end Output parameter for the end of the argument
* @param value Output parameter for the value to which the argument evaulates
* @return Zero on success, -1 on error
*/
static int parse_function_argument(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff,
const char* restrict* restrict end, char32_t** restrict value);
/*** Macro-, function- and variable-support, string-parsing and value- and mapping-compilation. ***/
/* (Basically everything except tree-walking.) */
/**
* Assign a value to a variable, and define or shadow it in the process
*
* @param variable The variable index
* @param string The variable's new value, must be `NULL` iff `value != NULL`
* @param value The variable's new value, must be `NULL` iff `string != NULL`
* @param statement The statement where the variable is assigned, may be `NULL`
* @param lineoff The offset of the line for where the string selecting the variable begins
* @param possibile_shadow_attempt Whether `statement` is of a type that does not shadow variables,
* but could easily be mistaked for one that does
* @return Zero on success, -1 on error
*/
static int let(size_t variable, const char32_t* restrict string, const mds_kbdc_tree_t* restrict value,
mds_kbdc_tree_t* restrict statement, size_t lineoff, int possibile_shadow_attempt)
{
mds_kbdc_tree_t* tree = NULL;
int saved_errno;
/* Warn if this is a possible shadow attempt. */
if (possibile_shadow_attempt && variables_let_will_override(variable) &&
statement && (statement->processed != PROCESS_LEVEL))
{
statement->processed = PROCESS_LEVEL;
NEW_ERROR(statement, WARNING, "does not shadow existing definition");/* TODO test */
error->start = lineoff;
error->end = lineoff + (size_t)snprintf(NULL, 0, "\\%zu", variable);
}
/* Duplicate value. */
if (value)
fail_if (tree = mds_kbdc_tree_dup(value), tree == NULL);
if (value == NULL)
{
fail_if (tree = mds_kbdc_tree_create(C(COMPILED_STRING)), tree == NULL);
fail_if ((tree->compiled_string.string = string_dup(string)) == NULL);
}
/* Assign variable. */
fail_if (variables_let(variable, tree));
return 0;
FAIL_BEGIN;
mds_kbdc_tree_free(tree);
FAIL_END;
}
/**
* Check that a call to set/3 or get/2 is valid
*
* @param tree The statement from where the function is called
* @param is_set Whether a call to set/3 is being checked
* @param variable_arg The first argument
* @param index_arg The second argument
* @param start The offset on the line where the function call begins
* @param end The offset on the line where the function call ends
* @return Zero on success, -1 on error, 1 if the call is invalid
*/
static int check_set_3_get_2_call(mds_kbdc_tree_t* restrict tree, int is_set, const char32_t* variable_arg,
const char32_t* index_arg, size_t start, size_t end)
{
#define F (is_set ? "set/3" : "get/2")
#define FUN_ERROR(...) \
do \
{ \
NEW_ERROR(__VA_ARGS__); \
error->start = start; \
error->end = end; \
return 1; \
} \
while(0);
mds_kbdc_tree_t* variable;
mds_kbdc_tree_t* element;
size_t index;
if ((variable_arg[0] <= 0) || (variable_arg[1] != -1))
FUN_ERROR(tree, ERROR, "first argument in call to function ‘%s’ must be a variable index", F);/* TODO test */
if ((index_arg[0] < 0) || (index_arg[1] != -1))
FUN_ERROR(tree, ERROR, "second argument in call to function ‘%s’ must be an element index", F);/* TODO test */
variable = variables_get((size_t)*variable_arg);
if (variable == NULL)
FUN_ERROR(tree, ERROR, "‘\\%zu’ is not declared", (size_t)*variable_arg);/* TODO test */
if (variable->type != C(ARRAY))
FUN_ERROR(tree, ERROR, "‘\\%zu’ is not an array", (size_t)*variable_arg);/* TODO test */
index = (size_t)*index_arg;
element = variable->array.elements;
while (element && index--)
element = element->next;
if (element == NULL)
FUN_ERROR(tree, ERROR, "‘\\%zu’ does not hold %zu elements", (size_t)*variable_arg, (size_t)*index_arg);/* TODO test */
return 0;
fail:
return -1;
#undef FUN_ERROR
#undef F
}
/**
* Call a function
*
* @param tree The statement from where the function is called
* @param name The name of the function, suffixless
* @param arguments The arguments to pass to the function, `NULL`-terminated
* @param start The offset on the line where the function call begins
* @param end The offset on the line where the function call ends
* @param return_value Output parameter for the return value, `NULL` if the
* function did not return, was not defined or otherwise
* invoked an error, which is true will be reported to the
* user from this function and the statement will be marked
* as containing an error
* @return Zero on success, -1 on error
*/
static int call_function(mds_kbdc_tree_t* restrict tree, const char* restrict name,
const char32_t** restrict arguments, size_t start, size_t end,
char32_t** restrict return_value)
{
#define FUN_ERROR(...) \
do \
{ \
NEW_ERROR(__VA_ARGS__); \
error->start = start; \
error->end = end; \
tree->processed = PROCESS_LEVEL; \
free(*return_value), *return_value = NULL; \
goto done; \
} \
while(0);
size_t i, arg_count = 0, empty_count = 0;
char32_t** old_return_value;
mds_kbdc_tree_function_t* function = NULL;
mds_kbdc_include_stack_t* function_include_stack = NULL;
mds_kbdc_include_stack_t* our_include_stack = NULL;
int r, is_set, builtin, saved_errno;
/* Count the number of arguments we have. */
while (arguments[arg_count])
arg_count++;
/* Push return-stack. */
*return_value = NULL;
old_return_value = current_return_value;
current_return_value = return_value;
/* Get function definition. */
builtin = builtin_function_defined(name, arg_count);
if (builtin == 0)
callables_get(name, arg_count, (mds_kbdc_tree_t**)&function, &function_include_stack);
if ((builtin == 0) && (function == NULL))
FUN_ERROR(tree, ERROR, "function ‘%s/%zu’ has not been defined yet", name, arg_count);
/* Call non-builtin function. */
if (builtin == 0)
{
/* Push call stack and set parameters. */
variables_stack_push();
for (i = 0; i < arg_count; i++)
fail_if (let(i, arguments[i], NULL, NULL, 0, 0));
/* Switch include-stack to the function's. */
fail_if (our_include_stack = mds_kbdc_include_stack_save(), our_include_stack == NULL);
fail_if (mds_kbdc_include_stack_restore(function_include_stack));
/* Call the function. */
fail_if (compile_subtree(function->inner));
/* Switch back the include-stack to ours. */
fail_if (mds_kbdc_include_stack_restore(our_include_stack));
mds_kbdc_include_stack_free(our_include_stack), our_include_stack = NULL;
/* Pop call stack. */
variables_stack_pop();
/* Check that the function returned a value. */
if (*return_value == NULL)
FUN_ERROR(tree, ERROR, "function ‘%s/%zu’ did not return a value", name, arg_count);/* TODO test */
goto done;
}
/* Call builtin function. */
/* Check argument sanity. */
is_set = (arg_count == 3) && !strcmp(name, "set");
if (is_set || ((arg_count == 2) && !strcmp(name, "get")))
{
fail_if (r = check_set_3_get_2_call(tree, is_set, arguments[0], arguments[1], start, end), r < 0);
if (r)
{
tree->processed = PROCESS_LEVEL;
free(*return_value), *return_value = NULL;
goto done;
}
}
else
{
for (i = 0; i < arg_count; i++)
empty_count += string_length(arguments[i]) == 0;
if (empty_count && (empty_count != arg_count))
FUN_ERROR(tree, ERROR,
"built-in function ‘%s/%zu’ requires that either none of"
" the arguments are empty strings or that all of them are",
name, arg_count);/* TODO test */
}
/* Call the function. */
*return_value = builtin_function_invoke(name, arg_count, arguments);
fail_if (*return_value == NULL);
have_side_effect |= is_set;
done:
/* Pop return-stack. */
current_return_value = old_return_value;
return 0;
FAIL_BEGIN;
mds_kbdc_include_stack_free(our_include_stack);
free(*return_value), *return_value = NULL;
current_return_value = old_return_value;
FAIL_END;
#undef FUN_ERROR
}
/**
* Parse a function call escape
*
* @param tree The statement where the escape is located
* @param raw The escape to parse
* @param lineoff The offset on the line where the escape beings
* @param escape Will be set to zero if the escape ended,
* will be set to anything but zero otherwise
* @param end Output parameter for the end of the escape
* @return The text the escape represents, `NULL` on error
*/
static char32_t* parse_function_call(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff,
int* restrict escape, const char* restrict* restrict end)
{
#define R(LOWER, UPPER) (((LOWER) <= c) && (c <= (UPPER)))
#define GROW_ARGS \
if (arguments_ptr == arguments_size) \
fail_if (xxrealloc(old_arguments, arguments, arguments_size += 4, char32_t*))
const char* restrict bracket = raw + 1;
char32_t* rc = NULL;
const char32_t** restrict arguments_;
char32_t** restrict arguments = NULL;
char32_t** restrict old_arguments = NULL;
size_t arguments_ptr = 0, arguments_size = 0;
char* restrict name;
char c;
int r, saved_errno = 0;
/* Find the opening bracket associated with the function call and validate the escape. */
for (; (c = *bracket); bracket++)
if ((c == '_') || R('0', '9') || R('a', 'z') || R('A', 'Z'));
else if (c == '(')
break;
else
{
*end = bracket;
if (tree->processed != PROCESS_LEVEL)
NEW_ERROR(tree, ERROR, "invalid escape");/* TODO test */
goto error;
}
/* Copy the name of the function. */
name = alloca((size_t)(bracket - raw) * sizeof(char));
memcpy(name, raw + 1, (size_t)(bracket - raw - 1) * sizeof(char));
name[bracket - raw - 1] = 0;
/* Get arguments. */
for (*end = ++bracket;;)
{
while (**end == ' ')
(*end)++;
GROW_ARGS;
arguments[arguments_ptr] = NULL;
if (**end == ')')
{
*escape = 0;
(*end)++;
arguments_ptr++;
break;
}
r = parse_function_argument(tree, *end, lineoff + (size_t)(*end - raw), end, arguments + arguments_ptr++);
fail_if (r < 0);
}
/* Call the function. */
if (tree->processed == PROCESS_LEVEL)
goto stop;
arguments_ = alloca(arguments_ptr * sizeof(const char32_t*));
memcpy(arguments_, arguments, arguments_ptr * sizeof(const char32_t*));
fail_if (call_function(tree, name, arguments_, lineoff, lineoff + (size_t)(*end - raw), &rc));
if (rc == NULL)
goto stop;
goto done;
error:
error->start = lineoff;
error->end = lineoff + (size_t)(*end - raw);
stop:
*escape = 0;
tree->processed = PROCESS_LEVEL;
fail_if (xmalloc(rc, 1, char32_t));
*rc = -1;
goto done;
fail:
saved_errno = errno;
free(rc);
if (old_arguments)
arguments = old_arguments;
while (arguments_ptr--)
free(arguments[arguments_ptr]);
free(arguments);
rc = NULL;
done:
while (arguments_ptr--)
free(arguments[arguments_ptr]);
free(arguments);
errno = saved_errno;
return rc;
#undef GROW_ARGS
#undef R
}
/**
* Check that all functions used in a part of a literal are defined
*
* @param tree The statement where the literal is located
* @param raw The beginning of the part of the literal to check
* @param lineoff The offset on the line where the part of the literal beings
* @param end Output parameter for the part of the literal
* @param rc Success status output parameter: zero on success,
* -1 on error, 1 if an undefined function is used
* @return The number of arguments the part of the literal contains
*/
static size_t check_function_calls_in_literal_(const mds_kbdc_tree_t* restrict tree,
const char* restrict raw, size_t lineoff,
const char* restrict* restrict end, int* restrict rc)
{
#define R(LOWER, UPPER) (((LOWER) <= c) && (c <= (UPPER)))
const char* restrict raw_ = raw;
size_t count = 0;
int space = 1, quote = 0, escape = 0;
char c;
while ((c = *raw++))
{
if ((c != ' ') && space)
space = 0, count++;
if (escape)
{
escape = 0;
if ((c == '_') || R('a', 'z') || R('A', 'Z'))
if (check_function_call(tree, raw - 2, lineoff + (size_t)(raw - 2 - raw_), &raw, rc), *rc < 0)
break;
}
else if (c == '\\') escape = 1;
else if (c == '"') quote ^= 1;
else if (!quote)
{
space = (c == ' ');
if (c == ')')
break;
}
}
*end = raw;
return count;
#undef R
}
/**
* Check that a function used in a part of a literal is defined
*
* @param tree The statement where the literal is located
* @param raw The beginning of the function call in the literal
* @param lineoff The offset on the line where the function call in the literal beings
* @param end Output parameter for the end of the function call
* @param rc Success status output parameter: zero on success,
* -1 on error, 1 if an undefined function is used
*/
static void check_function_call(const mds_kbdc_tree_t* restrict tree, const char* restrict raw,
size_t lineoff, const char* restrict* restrict end, int* restrict rc)
{
mds_kbdc_tree_t* function = NULL;
mds_kbdc_include_stack_t* _function_include_stack;
char* restrict bracket = strchr(raw, '(');
char* restrict name;
size_t arg_count;
/* Check that it is a function call by check that it has an opening bracket. */
if (bracket == NULL)
{
*end = raw + strlen(raw);
return;
}
/* Copy the name of the function. */
name = alloca((size_t)(bracket - raw) * sizeof(char));
memcpy(name, raw + 1, (size_t)(bracket - raw - 1) * sizeof(char));
name[bracket++ - raw - 1] = 0;
/* Get the number of arguments used, and check function calls there too. */
arg_count = check_function_calls_in_literal_(tree, bracket, lineoff + (size_t)(bracket - raw), end, rc);
if (*rc < 0) return;
/* Check that the function is defined. */
if (builtin_function_defined(name, arg_count))
return;
callables_get(name, arg_count, &function, &_function_include_stack);
if (function != NULL)
return;
*rc |= 1;
NEW_ERROR(tree, ERROR, "function ‘%s/%zu’ has not been defined yet", name, arg_count);
error->start = lineoff;
error->end = lineoff + (size_t)(*end - raw);
return;
fail:
*rc |= -1;
}
/**
* Check that all functions used in a literal are defined
*
* @param tree The statement where the literal is located
* @param raw The literal to check
* @param lineoff The offset on the line where the literal beings
* @return Zero on success, -1 on error, 1 if an undefined function is used
*/
static int check_function_calls_in_literal(const mds_kbdc_tree_t* restrict tree,
const char* restrict raw, size_t lineoff)
{
int rc = 0;
(void) check_function_calls_in_literal_(tree, raw, lineoff, &raw, &rc);
fail_if (rc < 0);
fail:
return rc;
}
/**
* Parse an escape, variable dereference or function call
*
* @param tree The statement where the escape is located
* @param raw The escape to parse
* @param lineoff The offset on the line where the escape beings
* @param escape Will be set to zero if the escape ended,
* will be set to anything but zero otherwise
* @param end Output parameter for the end of the escape
* @return The text the escape represents, `NULL` on error
*/
static char32_t* parse_escape(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff,
int* restrict escape, const char* restrict* restrict end)
{
#define R(LOWER, UPPER) (((LOWER) <= c) && (c <= (UPPER)))
#define CR(COND, LOWER, UPPER) ((*escape == (COND)) && R(LOWER, UPPER))
#define VARIABLE (int)(raw - raw_) - (c == '.'), raw_
#define RETURN_ERROR(...) \
do \
{ \
NEW_ERROR(__VA_ARGS__); \
error->start = lineoff; \
error->end = lineoff + (size_t)(raw - raw_); \
tree->processed = PROCESS_LEVEL; \
*escape = 0; \
if (rc) \
goto done; \
fail_if (rc = malloc(sizeof(char32_t)), rc == NULL); \
*rc = -1; \
goto done; \
} \
while (0)
const char* restrict raw_ = raw++;
char c = *raw++;
uintmax_t numbuf = 0;
char32_t* rc = NULL;
mds_kbdc_tree_t* value;
int have = 0, saved_errno;
/* Get escape type. */
if (c == '0')
/* Octal representation. */
*escape = 8, have = 1;
else if (c == 'u')
/* Hexadecimal representation. */
*escape = 16;
else if (R('1', '9'))
/* Variable dereference. */
*escape = 10, have = 1, numbuf = (uintmax_t)(c - '0');
else if ((c == '_') || R('a', 'z') || R('A', 'Z'))
/* Function call. */
*escape = 100;
else
RETURN_ERROR(tree, ERROR, "invalid escape");/* TODO test */
/* Read escape. */
if (*escape == 100)
/* Function call. */
{
fail_if (rc = parse_function_call(tree, raw_, lineoff, escape, end), rc == NULL);
return rc;
}
/* Octal or hexadecimal representation, or variable dereference. */
for (; (c = *raw); have = 1, raw++)
if (CR( 8, '0', '7')) numbuf = 8 * numbuf + (c & 15);
else if (CR(16, '0', '9')) numbuf = 16 * numbuf + (c & 15);
else if (CR(16, 'a', 'f')) numbuf = 16 * numbuf + (c & 15) + 9;
else if (CR(16, 'A', 'F')) numbuf = 16 * numbuf + (c & 15) + 9;
else if (CR(10, '0', '9')) numbuf = 10 * numbuf + (c & 15);
else break;
if (have == 0)
RETURN_ERROR(tree, ERROR, "invalid escape");/* TODO test */
/* Evaluate escape. */
if (*escape == 10)
{
/* Variable dereference. */
if (value = variables_get((size_t)numbuf), value == NULL)
RETURN_ERROR(tree, ERROR, "variable ‘%.*s’ is not defined", VARIABLE);/* TODO test */
if (value->type == C(ARRAY))
RETURN_ERROR(tree, ERROR, "variable ‘%.*s’ is an array", VARIABLE);/* TODO test */
if (value->type != C(COMPILED_STRING))
NEW_ERROR(tree, INTERNAL_ERROR, "variable ‘%.*s’ is of impossible type", VARIABLE);/* TODO test */
fail_if (rc = string_dup(value->compiled_string.string), rc == NULL);
}
else
{
/* Octal or hexadecimal representation. */
fail_if (xmalloc(rc, 2, char32_t));
rc[0] = (char32_t)numbuf, rc[1] = -1;
}
done:
*escape = 0;
*end = raw;
return rc;
fail:
saved_errno = errno;
free(rc);
return errno = saved_errno, NULL;
#undef RETURN_ERROR
#undef VARIABLE
#undef CR
#undef R
}
/**
* Parse a quoted string
*
* @param tree The statement where the string is located
* @param raw The string to parse
* @param lineoff The offset on the line where the string beings
* @return The string as pure text, `NULL` on error
*/
static char32_t* parse_quoted_string(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff)
{
#define GROW_BUF \
if (buf_ptr == buf_size) \
fail_if (xxrealloc(old_buf, buf, buf_size += 16, char))
#define COPY \
n = string_length(subrc); \
fail_if (xxrealloc(old_rc, rc, rc_ptr + n, char32_t)); \
memcpy(rc + rc_ptr, subrc, n * sizeof(char32_t)), rc_ptr += n; \
free(subrc), subrc = NULL
#define STORE \
if (buf_ptr) \
do \
{ \
GROW_BUF; \
buf[buf_ptr] = '\0', buf_ptr = 0; \
fail_if (subrc = string_decode(buf), subrc == NULL); \
COPY; \
} \
while (0)
#define CHAR_ERROR(...) \
do \
{ \
NEW_ERROR(__VA_ARGS__); \
error->end = lineoff + (size_t)(raw - raw_); \
error->start = error->end - 1; \
} \
while (0)
const char* restrict raw_ = raw;
char32_t* restrict subrc = NULL;
char32_t* restrict rc = NULL;
char32_t* restrict old_rc = NULL;
char* restrict buf = NULL;
char* restrict old_buf = NULL;
size_t rc_ptr = 0, n;
size_t buf_ptr = 0, buf_size = 0;
size_t escoff = 0;
int quote = 0, escape = 0;
char c;
int saved_errno;
/* Parse the string. */
while ((c = *raw++))
if (escape && quote && strchr("()[]{}<>\"\\,", c))
{
/* Buffer UTF-8 text for convertion to UTF-32. */
GROW_BUF;
buf[buf_ptr++] = c;
escape = 0;
}
else if (escape)
{
/* Parse escape. */
raw -= 2, escoff = lineoff + (size_t)(raw - raw_);
subrc = parse_escape(tree, raw, escoff, &escape, &raw);
fail_if (subrc == NULL);
COPY;
}
else if (c == '"')
{
/* Close or open quote, of it got closed, convert the buffered UTF-8 text to UTF-32. */
if (quote ^= 1) continue;
if ((quote == 1) && (raw != raw_ + 1))
CHAR_ERROR(tree, WARNING, "strings should either be unquoted or unclosed in one large quoted");/* TODO test */
STORE;
}
else if (c == '\\')
{
/* Convert the buffered UTF-8 text to UTF-32, and start an escape. */
STORE;
escape = 1;
}
else if ((quote == 0) && (tree->processed != PROCESS_LEVEL))
{
/* Only escapes may be used without quotes, if the string contains quotes. */
if (*raw_ == '"')
CHAR_ERROR(tree, ERROR, "only escapes may be outside quotes in quoted strings");/* TODO test */
else
CHAR_ERROR(tree, ERROR, "mixing numericals and escapes is not allowed");/* TODO test */
tree->processed = PROCESS_LEVEL;
}
else
{
/* Buffer UTF-8 text for convertion to UTF-32. */
GROW_BUF;
buf[buf_ptr++] = c;
}
/* Check that no escape is incomplete. */
if (escape && (tree->processed != PROCESS_LEVEL))
{
NEW_ERROR(tree, ERROR, "incomplete escape");/* TODO test */
error->start = escoff;
error->end = lineoff + strlen(raw_);
tree->processed = PROCESS_LEVEL;
}
/* Check that the quote is complete. */
if (quote && (tree->processed != PROCESS_LEVEL))
{
NEW_ERROR(tree, ERROR, "quote is not closed");/* TODO test */
error->start = lineoff;
error->end = lineoff + strlen(raw_);
tree->processed = PROCESS_LEVEL;
}
/* Shrink or grow to string to its minimal size, and -1-terminate it. */
fail_if (xxrealloc(old_rc, rc, rc_ptr + 1, char32_t));
rc[rc_ptr] = -1;
free(buf);
return rc;
fail:
saved_errno = errno;
free(subrc);
free(old_rc);
free(old_buf);
free(rc);
free(buf);
return errno = saved_errno, NULL;
#undef CHAR_ERROR
#undef STORE
#undef COPY
#undef GROW_BUF
}
/**
* Parse am unquoted string
*
* @param tree The statement where the string is located
* @param raw The string to parse
* @param lineoff The offset on the line where the string beings
* @return The string as pure text, `NULL` on error
*/
static char32_t* parse_unquoted_string(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff)
{
#define R(LOWER, UPPER) (((LOWER) <= c) && (c <= (UPPER)))
#define CHAR_ERROR(...) \
do \
{ \
NEW_ERROR(__VA_ARGS__); \
error->end = lineoff + (size_t)(raw - raw_); \
error->start = error->end - 1; \
tree->processed = PROCESS_LEVEL; \
goto done; \
} \
while (0)
const char* restrict raw_ = raw;
char32_t* rc;
char32_t buf = 0;
char c;
while ((c = *raw++))
if (R('0', '9')) buf = 10 * buf + (c & 15);
else if (c == '\\') CHAR_ERROR(tree, ERROR, "mixing numericals and escapes is not allowed");/* TODO test */
else if (c == '"') CHAR_ERROR(tree, ERROR, "mixing numericals and quotes is not allowed");/* TODO test */
else CHAR_ERROR(tree, ERROR, "stray ‘%c’", c);/* TODO test */
done:
fail_if (rc = malloc(2 * sizeof(char32_t)), rc == NULL);
return rc[0] = buf, rc[1] = -1, rc;
fail:
return NULL;
#undef CHAR_ERROR
#undef R
}
/**
* Parse a string
*
* @param tree The statement where the string is located
* @param raw The string to parse
* @param lineoff The offset on the line where the string beings
* @return The string as pure text, `NULL` on error
*/
static char32_t* parse_string(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff)
{
mds_kbdc_tree_t* old_last_value_statement = last_value_statement;
char32_t* rc = (strchr("\"\\", *raw) ? parse_quoted_string : parse_unquoted_string)(tree, raw, lineoff);
last_value_statement = old_last_value_statement;
fail_if (rc == NULL);
return rc;
fail:
return NULL;
}
/**
* Parse a key-combination string
*
* @param tree The statement where the string is located
* @param raw The string to parse
* @param lineoff The offset on the line where the string beings
* @return The string as pure text, `NULL` on error
*/
static char32_t* parse_keys(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff)
{
#define GROW_BUF \
if (buf_ptr == buf_size) \
fail_if (xxrealloc(old_buf, buf, buf_size += 16, char))
#define COPY \
n = string_length(subrc); \
fail_if (xxrealloc(old_rc, rc, rc_ptr + n, char32_t)); \
memcpy(rc + rc_ptr, subrc, n * sizeof(char32_t)), rc_ptr += n; \
free(subrc), subrc = NULL
#define STORE \
if (buf_ptr) \
do \
{ \
GROW_BUF; \
buf[buf_ptr] = '\0', buf_ptr = 0; \
fail_if (subrc = string_decode(buf), subrc == NULL); \
COPY; \
} \
while (0)
#define SPECIAL(VAL) \
STORE; \
fail_if (xxrealloc(old_rc, rc, rc_ptr + 1, char32_t)); \
rc[rc_ptr++] = -(VAL + 1)
mds_kbdc_tree_t* old_last_value_statement = last_value_statement;
const char* restrict raw_ = raw++;
char32_t* restrict subrc = NULL;
char32_t* restrict rc = NULL;
char32_t* restrict old_rc = NULL;
char* restrict buf = NULL;
char* restrict old_buf = NULL;
size_t rc_ptr = 0, n;
size_t buf_ptr = 0, buf_size = 0;
size_t escoff = 0;
int escape = 0, quote = 0;
char c;
int saved_errno;
/* Parse the string. */
while (c = *raw++, *raw)
if (escape && strchr("()[]{}<>\"\\,", c))
{
/* Buffer UTF-8 text for convertion to UTF-32. */
GROW_BUF;
buf[buf_ptr++] = c;
escape = 0;
}
else if (escape)
{
/* Parse escape. */
raw -= 2, escoff = lineoff + (size_t)(raw - raw_);
subrc = parse_escape(tree, raw, escoff, &escape, &raw);
fail_if (subrc == NULL);
COPY;
}
else if (c == '\\')
{
/* Convert the buffered UTF-8 text to UTF-32, and start an escape. */
STORE;
escape = 1;
}
else if ((c == ',') && !quote)
{
/* Sequence in key-combination. */
SPECIAL(1);
}
else if (c == '"')
{
/* String in key-combination. */
quote ^= 1;
SPECIAL(2);
}
else
{
/* Buffer UTF-8 text for convertion to UTF-32. */
GROW_BUF;
buf[buf_ptr++] = c;
}
STORE;
/* Check that no escape is incomplete. */
if (escape && (tree->processed != PROCESS_LEVEL))
{
NEW_ERROR(tree, ERROR, "incomplete escape");/* TODO test */
error->start = lineoff + (size_t)(strrchr(raw_, '\\') - raw_);
error->end = lineoff + strlen(raw_);
tree->processed = PROCESS_LEVEL;
}
/* Check that key-combination is complete. */
if ((c != '>') && (tree->processed != PROCESS_LEVEL))
{
NEW_ERROR(tree, ERROR, "key-combination is not closed");/* TODO test */
error->start = lineoff;
error->end = lineoff + strlen(raw_);
tree->processed = PROCESS_LEVEL;
}
/* Shrink or grow to string to its minimal size, and -1-terminate it. */
fail_if (xxrealloc(old_rc, rc, rc_ptr + 1, char32_t));
rc[rc_ptr] = -1;
free(buf);
return last_value_statement = old_last_value_statement, rc;
fail:
saved_errno = errno;
free(subrc);
free(old_rc);
free(old_buf);
free(rc);
free(buf);
errno = saved_errno;
return last_value_statement = old_last_value_statement, NULL;
#undef SPECIAL
#undef STORE
#undef COPY
#undef GROW_BUF
}
/**
* Parse a variable string
*
* @param tree The statement where the variable is selected
* @param raw The variable string
* @param lineoff The offset on the line where the variable string begins
* @return The index of the variable, zero on error
*/
static size_t parse_variable(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff)
{
size_t var, n;
const char* restrict raw_ = raw;
char* restrict dotless;
/* The variable must begin with \. */
if (*raw++ != '\\') goto bad;
/* Zero is not a valid varible, nor may there be leading zeroes or be empty. */
if (*raw == '0') goto bad;
if (*raw == '.') goto bad;
if (*raw == '\0') goto bad;
for (raw++; *raw; raw++)
/* Check that the variable consists only of digits. */
if (('0' <= *raw) && (*raw <= '9'));
/* However, it may end with a dot. */
else if ((raw[0] == '.') && (raw[1] == '\0'))
break;
else
goto bad;
/* Parse the variable string and check that it did not overflow. */
n = (size_t)(raw - raw_);
dotless = alloca((n + 1) * sizeof(char));
memcpy(dotless, raw_, n * sizeof(char)), dotless[n] = '\0';
var = (size_t)atoll(dotless + 1);
if (strlen(dotless + 1) != (size_t)snprintf(NULL, 0, "%zu", var))
fail_if ((errno = ERANGE));
if (var == 0)
{
NEW_ERROR(tree, INTERNAL_ERROR,
"parsed a variable string to be 0, which should not be possible");
error->start = lineoff;
error->end = lineoff + strlen(raw_);
tree->processed = PROCESS_LEVEL;
return 1;
}
return var;
fail:
return 0;
bad:
/* Report an error but return a variable index if the variable-string is invalid. */
NEW_ERROR(tree, ERROR, "not a variable");
error->start = lineoff;
error->end = lineoff + strlen(raw_);
tree->processed = PROCESS_LEVEL;
return 1;
}
/**
* Parse an argument in a function call
*
* @param tree The statement where the function call appear
* @param raw The beginning of the argument for the function call
* @param lineoff The offset for `raw` on line in which it appears
* @param end Output parameter for the end of the argument
* @param value Output parameter for the value to which the argument evaulates
* @return Zero on success, -1 on error
*/
static int parse_function_argument(mds_kbdc_tree_t* restrict tree, const char* restrict raw, size_t lineoff,
const char* restrict* restrict end, char32_t** restrict value)
{
size_t size = strlen(raw), ptr = 0, call_end = 0;
int escape = 0, quote = 0;
char* raw_argument = NULL;
int saved_errno;
/* Find the span of the argument. */
while (ptr < size)
{
char c = raw[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(raw, ptr, size);
}
/* " is the quote symbol. */
else if (quote) quote = (c != '"');
else if (c == '"') quote = 1;
/* End of argument? */
else if (strchr(" )", c))
{
ptr--;
break;
}
}
*end = raw + ptr;
/* Copy the argument so that we have a NUL-terminates string. */
fail_if (xmalloc(raw_argument, ptr + 1, char));
memcpy(raw_argument, raw, ptr * sizeof(char));
raw_argument[ptr] = '\0';
/* Evaluate argument. */
*value = parse_string(tree, raw_argument, lineoff);
fail_if (*value == NULL);
free(raw_argument);
return 0;
FAIL_BEGIN;
free(raw_argument);
FAIL_END;
}
/**
* Store a macro
*
* @param macro The macro
* @param macro_include_stack The include-stack for the macro
* @return Zero on success, -1 on error
*/
static int set_macro(mds_kbdc_tree_macro_t* restrict macro,
mds_kbdc_include_stack_t* macro_include_stack)
{
fail_if (callables_set(macro->name, 0, (mds_kbdc_tree_t*)macro, macro_include_stack));
return 0;
fail:
return -1;
}
/**
* Get a stored macro
*
* @param name The name of the macro, with suffix
* @param macro Output parameter for the macro, `NULL` if not found
* @param macro_include_stack Output parameter for the include-stack for the macro
*/
static void get_macro_lax(const char* restrict macro_name, mds_kbdc_tree_macro_t** restrict macro,
mds_kbdc_include_stack_t** restrict macro_include_stack)
{
callables_get(macro_name, 0, (mds_kbdc_tree_t**)macro, macro_include_stack);
}
/**
* Get a stored macro
*
* The function is similar to `get_macro_lax`, however, this fucntion
* will report an error if the macro has not yet been defined, and it
* will pretend that it has not yet been defined if the macro contained
* an error in an earlier called to it
*
* @param macro_call The macro-call
* @param macro Output parameter for the macro, `NULL` if not found or has an error
* @param macro_include_stack Output parameter for the include-stack for the macro
* @return Zero on success, -1 on error
*/
static int get_macro(mds_kbdc_tree_macro_call_t* restrict macro_call,
mds_kbdc_tree_macro_t** restrict macro,
mds_kbdc_include_stack_t** restrict macro_include_stack)
{
char* code = result->source_code->lines[macro_call->loc_line];
char* end = code + strlen(code) - 1;
get_macro_lax(macro_call->name, macro, macro_include_stack);
if (*macro == NULL)
{
NEW_ERROR(macro_call, ERROR, "macro ‘%s’ has not been defined yet", macro_call->name);
while (*end == ' ')
end--;
error->end = (size_t)(++end - code);
macro_call->processed = PROCESS_LEVEL;
return 0;
}
if ((*macro)->processed == PROCESS_LEVEL)
*macro = NULL;
return 0;
fail:
return -1;
}
/**
* Store a function
*
* @param function The function
* @param function_include_stack The include-stack for the function
* @return Zero on success, -1 on error
*/
static int set_function(mds_kbdc_tree_function_t* restrict function,
mds_kbdc_include_stack_t* function_include_stack)
{
char* suffixless = function->name;
char* suffix_start = strchr(suffixless, '/');
size_t arg_count = (size_t)atoll(suffix_start + 1);
int r;
*suffix_start = '\0';
r = callables_set(suffixless, arg_count, (mds_kbdc_tree_t*)function, function_include_stack);
fail_if (*suffix_start = '/', r);
return 0;
fail:
return -1;
}
/**
* Get a stored function
*
* @param name The name of the function, suffixless
* @param arg_count The number of arguments the function takes
* @param function Output parameter for the function, `NULL` if not found
* @param function_include_stack Output parameter for the include-stack for the function
*/
static void get_function_lax(const char* restrict function_name, size_t arg_count,
mds_kbdc_tree_function_t** restrict function,
mds_kbdc_include_stack_t** restrict function_include_stack)
{
callables_get(function_name, arg_count, (mds_kbdc_tree_t**)function, function_include_stack);
}
/**
* Store a value for being returned by the current function
*
* @param value The value the function should return
* @return Zero on success, 1 if no function is currently being called
*/
static int set_return_value(char32_t* restrict value)
{
if (current_return_value == NULL)
return free(value), 1;
free(*current_return_value);
*current_return_value = value;
return 0;
}
static int add_mapping(mds_kbdc_tree_map_t* restrict mapping, mds_kbdc_include_stack_t* restrict include_stack)
{
mds_kbdc_tree_free((mds_kbdc_tree_t*)mapping);
mds_kbdc_include_stack_free(include_stack);
return 0; /* TODO */
}
/*** Tree-walking. ***/
/**
* Compile an include-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_include(mds_kbdc_tree_include_t* restrict tree)
{
void* data;
int r;
fail_if (mds_kbdc_include_stack_push(tree, &data));
r = compile_subtree(tree->inner);
mds_kbdc_include_stack_pop(data);
/* For simplicity we set `last_value_statement` on includes,
* so we are sure `last_value_statement` has the same
* include-stack as its overriding statement. */
last_value_statement = NULL;
fail_if (r);
return 0;
fail:
return -1;
}
/**
* Compile a language-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_language(mds_kbdc_tree_information_language_t* restrict tree)
{
size_t lineoff;
char* restrict code = result->source_code->real_lines[tree->loc_line];
char32_t* restrict data = NULL;
char** old = NULL;
int saved_errno;
/* Make sure the language-list fits another entry. */
if (result->languages_ptr == result->languages_size)
{
result->languages_size = result->languages_size ? (result->languages_size << 1) : 1;
fail_if (xxrealloc(old, result->languages, result->languages_size, char*));
}
/* Locate the first character in the language-string. */
for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
/* Evaluate function calls, variable dereferences and escapes in the language-string. */
fail_if (data = parse_string((mds_kbdc_tree_t*)tree, tree->data, lineoff), data == NULL);
if (tree->processed == PROCESS_LEVEL)
return free(data), 0;
/* We want the string in UTF-8, not UTF-16. */
fail_if (code = string_encode(data), code == NULL);
free(data);
/* Add the language to the language-list. */
result->languages[result->languages_ptr++] = code;
return 0;
FAIL_BEGIN;
free(old);
free(data);
FAIL_END;
}
/**
* Compile a country-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_country(mds_kbdc_tree_information_country_t* restrict tree)
{
size_t lineoff;
char* restrict code = result->source_code->real_lines[tree->loc_line];
char32_t* restrict data = NULL;
char** old = NULL;
int saved_errno;
/* Make sure the country-list fits another entry. */
if (result->countries_ptr == result->countries_size)
{
result->countries_size = result->countries_size ? (result->countries_size << 1) : 1;
fail_if (xxrealloc(old, result->countries, result->countries_size, char*));
}
/* Locate the first character in the country-string. */
for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
/* Evaluate function calls, variable dereferences and escapes in the country-string. */
fail_if (data = parse_string((mds_kbdc_tree_t*)tree, tree->data, lineoff), data == NULL);
if (tree->processed == PROCESS_LEVEL)
return free(data), 0;
/* We want the string in UTF-8, not UTF-16. */
fail_if (code = string_encode(data), code == NULL);
free(data);
/* Add the country to the country-list. */
result->countries[result->countries_ptr++] = code;
return 0;
FAIL_BEGIN;
free(old);
free(data);
FAIL_END;
}
/**
* Compile a variant-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_variant(mds_kbdc_tree_information_variant_t* restrict tree)
{
size_t lineoff;
char* restrict code = result->source_code->real_lines[tree->loc_line];
char32_t* restrict data = NULL;
int saved_errno;
/* Make sure the variant has not already been set. */
if (result->variant)
{
if (multiple_variants == 0)
NEW_ERROR(tree, ERROR, "only one ‘variant’ is allowed");
multiple_variants = 1;
return 0;
}
/* Locate the first character in the variant-string. */
for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
/* Evaluate function calls, variable dereferences and escapes in the variant-string. */
fail_if (data = parse_string((mds_kbdc_tree_t*)tree, tree->data, lineoff), data == NULL);
if (tree->processed == PROCESS_LEVEL)
return free(data), 0;
/* We want the string in UTF-8, not UTF-16. */
fail_if (code = string_encode(data), code == NULL);
free(data);
/* Store the variant. */
result->variant = code;
return 0;
FAIL_BEGIN;
free(data);
FAIL_END;
}
/**
* Compile a have-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_have(mds_kbdc_tree_assumption_have_t* restrict tree)
{
mds_kbdc_tree_t* node = tree->data;
char32_t* data = NULL;
char32_t** old = NULL;
size_t new_size = 0;
int saved_errno;
/* Make sure we can fit all assumption in the assumption list (part 1/2). */
new_size = (node->type == C(STRING)) ? result->assumed_strings_size : result->assumed_keys_size;
new_size = new_size ? (new_size << 1) : 1;
if (node->type == C(STRING))
{
/* Evaluate function calls, variable dereferences and escapes in the string. */
fail_if (data = parse_string(node, node->string.string, node->loc_start), data == NULL);
if (node->processed == PROCESS_LEVEL)
return free(data), 0;
/* Make sure we can fit all strings in the assumption list (part 2/2). */
if (result->assumed_strings_ptr == result->assumed_strings_size)
{
fail_if (xxrealloc(old, result->assumed_strings, new_size, char32_t*));
result->assumed_strings_size = new_size;
}
/* Add the assumption to the list. */
result->assumed_strings[result->assumed_strings_ptr++] = data;
}
else
{
/* Evaluate function calls, variable dereferences and escapes in the key-combination. */
fail_if (data = parse_keys(node, node->keys.keys, node->loc_start), data == NULL);
if (node->processed == PROCESS_LEVEL)
return free(data), 0;
/* Make sure we can fit all key-combinations in the assumption list (part 2/2). */
if (result->assumed_keys_ptr == result->assumed_keys_size)
{
fail_if (xxrealloc(old, result->assumed_keys, new_size, char32_t*));
result->assumed_keys_size = new_size;
}
/* Add the assumption to the list. */
result->assumed_keys[result->assumed_keys_ptr++] = data;
}
return 0;
FAIL_BEGIN;
free(old);
free(data);
FAIL_END;
}
/**
* Compile a have_chars-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_have_chars(mds_kbdc_tree_assumption_have_chars_t* restrict tree)
{
size_t lineoff;
char* restrict code = result->source_code->real_lines[tree->loc_line];
char32_t* restrict data = NULL;
char32_t** old = NULL;
char32_t* restrict character;
size_t n;
int saved_errno;
/* Locate the first character in the list. */
for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
/* Evaluate function calls, variable dereferences
and escapes in the charcter list. */
fail_if (data = parse_string((mds_kbdc_tree_t*)tree, tree->chars, lineoff), data == NULL);
if (tree->processed == PROCESS_LEVEL)
return free(data), 0;
/* Make sure we can fit all characters in the assumption list. */
for (n = 0; data[n] >= 0; n++);
if (result->assumed_strings_ptr + n > result->assumed_strings_size)
{
result->assumed_strings_size += n;
fail_if (xxrealloc(old, result->assumed_strings, result->assumed_strings_size, char32_t*));
}
/* Add all characters to the assumption list. */
while (n--)
{
fail_if (xmalloc(character, 2, char32_t));
character[0] = data[n];
character[1] = -1;
result->assumed_strings[result->assumed_strings_ptr++] = character;
}
free(data);
return 0;
FAIL_BEGIN;
free(data);
free(old);
FAIL_END;
}
/**
* Compile a have_range-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_have_range(mds_kbdc_tree_assumption_have_range_t* restrict tree)
{
size_t lineoff_first;
size_t lineoff_last;
char* restrict code = result->source_code->real_lines[tree->loc_line];
char32_t* restrict first = NULL;
char32_t* restrict last = NULL;
char32_t** old = NULL;
char32_t* restrict character;
size_t n;
int saved_errno;
/* Locate the first characters of both bound strings. */
for (lineoff_first = tree->loc_end; code[lineoff_first] == ' '; lineoff_first++);
for (lineoff_last = lineoff_first + strlen(tree->first); code[lineoff_last] == ' '; lineoff_last++);
/* Duplicate bounds and evaluate function calls,
variable dereferences and escapes in the bounds. */
fail_if (first = parse_string((mds_kbdc_tree_t*)tree, tree->first, lineoff_first), first == NULL);
fail_if (last = parse_string((mds_kbdc_tree_t*)tree, tree->last, lineoff_last), last == NULL);
/* Did one of the bound not evaluate, then stop. */
if (tree->processed == PROCESS_LEVEL)
goto done;
/* Check that the primary bound is single-character. */
if ((first[0] == -1) || (first[1] != -1))
{
NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");/* TODO test */
error->start = lineoff_first, lineoff_first = 0;
error->end = error->start + strlen(tree->first);
}
/* Check that the secondary bound is single-character. */
if ((last[0] == -1) || (last[1] != -1))
{
NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");/* TODO test */
error->start = lineoff_last, lineoff_last = 0;
error->end = error->start + strlen(tree->last);
}
/* Was one of the bounds not single-character, then stop. */
if ((lineoff_first == 0) || (lineoff_last == 0))
goto done;
/* If the range is descending, swap the bounds so it is ascending.
(This cannot be done in for-loops as that may cause side-effects
to be created in the wrong order.) */
if (*first > *last)
*first ^= *last, *last ^= *first, *first ^= *last;
/* Make sure we can fit all characters in the assumption list. */
n = (size_t)(*last - *first) + 1;
if (result->assumed_strings_ptr + n > result->assumed_strings_size)
{
result->assumed_strings_size += n;
fail_if (xxrealloc(old, result->assumed_strings, result->assumed_strings_size, char32_t*));
}
/* Add all characters to the assumption list. */
for (;;)
{
fail_if (xmalloc(character, 2, char32_t));
character[0] = *first;
character[1] = -1;
result->assumed_strings[result->assumed_strings_ptr++] = character;
/* Bounds are inclusive. */
if ((*first)++ == *last)
break;
}
done:
free(first);
free(last);
return 0;
FAIL_BEGIN;
free(first);
free(last);
free(old);
FAIL_END;
}
/**
* Check that all called macros are already defined
*
* @param tree The tree to evaluate
* @return Zero on success, -1 on error, 1 if an undefined macro is used
*/
static int check_marco_calls(mds_kbdc_tree_t* restrict tree)
{
#define t(...) fail_if (rc |= r = (__VA_ARGS__), r < 0)
mds_kbdc_tree_macro_t* _macro;
mds_kbdc_include_stack_t* _macro_include_stack;
void* data;
int r, rc = 0;
again:
if (tree == NULL)
return rc;
switch (tree->type)
{
case C(INCLUDE):
t (mds_kbdc_include_stack_push(&(tree->include), &data));
t (r = check_marco_calls(tree->include.inner), mds_kbdc_include_stack_pop(data), r);
break;
case C(FOR):
t (check_marco_calls(tree->for_.inner));
break;
case C(IF):
t (check_marco_calls(tree->if_.inner));
t (check_marco_calls(tree->if_.otherwise));
break;
case C(MACRO_CALL):
t (get_macro(&(tree->macro_call), &_macro, &_macro_include_stack));
break;
default:
break;
}
tree = tree->next;
goto again;
fail:
return -1;
(void) _macro;
(void) _macro_include_stack;
#undef t
}
/**
* Check that all called functions in a for-statement are already defined
*
* @param tree The tree to evaluate
* @return Zero on success, -1 on error, 1 if an undefined function is used
*/
static int check_function_calls_in_for(const mds_kbdc_tree_for_t* restrict tree)
{
#define t(...) fail_if (rc |= r = check_function_calls_in_literal(__VA_ARGS__), r < 0)
size_t lineoff_first;
size_t lineoff_last;
char* restrict code = result->source_code->real_lines[tree->loc_line];
int r, rc = 0;
for (lineoff_first = tree->loc_end; code[lineoff_first] == ' '; lineoff_first++);
for (lineoff_last = lineoff_first + strlen(tree->first); code[lineoff_last] == ' '; lineoff_last++);
for (lineoff_last += strlen("to"); code[lineoff_last] == ' '; lineoff_last++);
t ((const mds_kbdc_tree_t*)tree, tree->first, lineoff_first);
t ((const mds_kbdc_tree_t*)tree, tree->last, lineoff_last);
return rc;
fail:
return -1;
#undef t
}
/**
* Check that all called functions in an if-statement are already defined
*
* @param tree The tree to evaluate
* @return Zero on success, -1 on error, 1 if an undefined function is used
*/
static int check_function_calls_in_if(const mds_kbdc_tree_if_t* restrict tree)
{
size_t lineoff;
char* restrict code = result->source_code->real_lines[tree->loc_line];
int r;
for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
r = check_function_calls_in_literal((const mds_kbdc_tree_t*)tree, tree->condition, lineoff);
fail_if (r < 0);
fail:
return r;
}
/**
* Check that all called functions in a key-combination are already defined
*
* @param tree The tree to evaluate
* @return Zero on success, -1 on error, 1 if an undefined function is used
*/
static int check_function_calls_in_keys(const mds_kbdc_tree_keys_t* restrict tree)
{
int r;
r = check_function_calls_in_literal((const mds_kbdc_tree_t*)tree, tree->keys, tree->loc_start);
fail_if (r < 0);
fail:
return r;
}
/**
* Check that all called functions in a string are already defined
*
* @param tree The tree to evaluate
* @return Zero on success, -1 on error, 1 if an undefined function is used
*/
static int check_function_calls_in_string(const mds_kbdc_tree_string_t* restrict tree)
{
int r;
r = check_function_calls_in_literal((const mds_kbdc_tree_t*)tree, tree->string, tree->loc_start);
fail_if (r < 0);
fail:
return r;
}
/**
* Check that all called functions are already defined
*
* @param tree The tree to evaluate
* @return Zero on success, -1 on error, 1 if an undefined function is used
*/
static int check_function_calls(const mds_kbdc_tree_t* restrict tree)
{
#define t(...) fail_if (rc |= r = (__VA_ARGS__), r < 0)
void* data;
int r, rc = 0;
again:
if (tree == NULL)
return rc;
switch (tree->type)
{
case C(INCLUDE):
t (mds_kbdc_include_stack_push(&(tree->include), &data));
t (r = check_function_calls(tree->include.inner), mds_kbdc_include_stack_pop(data), r);
break;
case C(FOR):
t (check_function_calls_in_for(&(tree->for_)));
t (check_function_calls(tree->for_.inner));
break;
case C(IF):
t (check_function_calls_in_if(&(tree->if_)));
t (check_function_calls(tree->if_.inner));
t (check_function_calls(tree->if_.otherwise));
break;
case C(LET): t (check_function_calls(tree->let.value)); break;
case C(ARRAY): t (check_function_calls(tree->array.elements)); break;
case C(KEYS): t (check_function_calls_in_keys(&(tree->keys))); break;
case C(STRING): t (check_function_calls_in_string(&(tree->string))); break;
case C(MAP): t (check_function_calls(tree->map.sequence)); break;
default:
break;
}
tree = tree->next;
goto again;
fail:
return -1;
#undef t
}
/**
* Check that a callable's name-suffix is correct
*
* @param tree The tree to inspect
* @return Zero on sucess, -1 on error, 1 if the name-suffix in invalid
*/
static int check_name_suffix(struct mds_kbdc_tree_callable* restrict tree)
{
const char* restrict name = strchr(tree->name, '/');
const char* restrict code = result->source_code->real_lines[tree->loc_line];
/* A "/" must exist in the name to tell us how many parameters there are. */
if (name == NULL)
{
NEW_ERROR(tree, ERROR, "name-suffix is missing");
goto name_error;
}
/* Do not let the suffix by just "/". */
if (*++name == '\0')
{
NEW_ERROR(tree, ERROR, "empty name-suffix");
goto name_error;
}
/* We are all good if the suffix is simply "/0" */
if (!strcmp(name, "0"))
return 0;
/* The suffix may not have leading zeroes. */
if (*name == '0')
{
NEW_ERROR(tree, ERROR, "leading zero in name-suffix");
goto name_error;
}
/* The suffix must be a decimal, non-negative number. */
for (; *name; name++)
if ((*name < '0') || ('9' < *name))
{
NEW_ERROR(tree, ERROR, "name-suffix may only contain digits");
goto name_error;
}
return 0;
fail:
return -1;
name_error:
error->start = tree->loc_end;
while (code[error->start] == ' ')
error->start++;
error->end = error->start + strlen(tree->name);
tree->processed = PROCESS_LEVEL;
return 1;
}
/**
* Compile a function
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_function(mds_kbdc_tree_function_t* restrict tree)
{
#define t(expr) fail_if (r = (expr), r < 0); if (r) tree->processed = PROCESS_LEVEL
mds_kbdc_tree_function_t* function;
mds_kbdc_include_stack_t* function_include_stack;
mds_kbdc_include_stack_t* our_include_stack = NULL;
char* suffixless;
char* suffix_start = NULL;
size_t arg_count;
int r, saved_errno;
/* Check that the suffix if properly formatted. */
t (check_name_suffix((struct mds_kbdc_tree_callable*)tree));
if (r) return 0;
/* Get the function's name without suffix, and parse the suffix. */
suffixless = tree->name;
suffix_start = strchr(suffixless, '/');
*suffix_start++ = '\0';
arg_count = (size_t)atoll(suffix_start--);
/* Check that the function is not already defined as a builtin function. */
if (builtin_function_defined(suffixless, arg_count))
{
NEW_ERROR(tree, ERROR, "function ‘%s/%zu’ is already defined as a builtin function",
tree->name, arg_count);
*suffix_start = '/';
return 0;
}
/* Check that the function is not already defined,
the include-stack is used in the error-clause as
well as later when we list the function as defined. */
get_function_lax(suffixless, arg_count, &function, &function_include_stack);
fail_if (our_include_stack = mds_kbdc_include_stack_save(), our_include_stack == NULL);
if (function)
{
*suffix_start = '/';
NEW_ERROR(tree, ERROR, "function ‘%s’ is already defined", tree->name);
fail_if (mds_kbdc_include_stack_restore(function_include_stack));
NEW_ERROR(function, NOTE, "previously defined here");
fail_if (mds_kbdc_include_stack_restore(our_include_stack));
mds_kbdc_include_stack_free(our_include_stack);
return 0;
}
/* Check the the function does not call macros or functions
* before they are defined, otherwise they may get defined
* between the definition of the function and calls to it. */
t (check_marco_calls(tree->inner));
t (check_function_calls(tree->inner));
/* List the function as defined. */
*suffix_start = '/', suffix_start = NULL;
t (set_function(tree, our_include_stack));
return 0;
FAIL_BEGIN;
if (suffix_start)
*suffix_start = '/';
mds_kbdc_include_stack_free(our_include_stack);
FAIL_END;
#undef t
}
/**
* Compile a macro
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_macro(mds_kbdc_tree_macro_t* restrict tree)
{
#define t(expr) fail_if (r = (expr), r < 0); if (r) tree->processed = PROCESS_LEVEL
mds_kbdc_tree_macro_t* macro;
mds_kbdc_include_stack_t* macro_include_stack;
mds_kbdc_include_stack_t* our_include_stack = NULL;
int r, saved_errno;
/* Check that the suffix if properly formatted. */
t (check_name_suffix((struct mds_kbdc_tree_callable*)tree));
if (r) return 0;
/* Check that the macro is not already defined,
the include-stack is used in the error-clause as
well as later when we list the macro as defined. */
fail_if (our_include_stack = mds_kbdc_include_stack_save(), our_include_stack == NULL);
get_macro_lax(tree->name, ¯o, ¯o_include_stack);
if (macro)
{
NEW_ERROR(tree, ERROR, "macro ‘%s’ is already defined", tree->name);
fail_if (mds_kbdc_include_stack_restore(macro_include_stack));
NEW_ERROR(macro, NOTE, "previously defined here");
fail_if (mds_kbdc_include_stack_restore(our_include_stack));
mds_kbdc_include_stack_free(our_include_stack);
return 0;
}
/* Check the the macro does not call macros or functions
* before they are defined, otherwise they may get defined
* between the definition of the macro and calls to it. */
t (check_marco_calls(tree->inner));
t (check_function_calls(tree->inner));
/* List the macro as defined. */
t (set_macro(tree, our_include_stack));
return 0;
FAIL_BEGIN;
mds_kbdc_include_stack_free(our_include_stack);
FAIL_END;
#undef t
}
/**
* Compile a for-loop
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_for(mds_kbdc_tree_for_t* restrict tree)
{
size_t lineoff_first;
size_t lineoff_last;
size_t lineoff_var;
char* restrict code = result->source_code->real_lines[tree->loc_line];
char32_t* restrict first = NULL;
char32_t* restrict last = NULL;
char32_t diff;
char32_t character[2];
size_t variable;
int possible_shadow = 1, saved_errno;
last_value_statement = NULL;
/* Locate the first character of the primary bound's string. */
for (lineoff_first = tree->loc_end; code[lineoff_first] == ' '; lineoff_first++);
/* Locate the first character of the secondary bound's string. */
for (lineoff_last = lineoff_first + strlen(tree->first); code[lineoff_last] == ' '; lineoff_last++);
for (lineoff_last += strlen("to"); code[lineoff_last] == ' '; lineoff_last++);
/* Locate the first character of the select variable. */
for (lineoff_var = lineoff_last + strlen(tree->last); code[lineoff_var] == ' '; lineoff_var++);
for (lineoff_var += strlen("as"); code[lineoff_var] == ' '; lineoff_var++);
/* Duplicate bounds and evaluate function calls,
variable dereferences and escapes in the bounds. */
fail_if (first = parse_string((mds_kbdc_tree_t*)tree, tree->first, lineoff_first), first == NULL);
fail_if (last = parse_string((mds_kbdc_tree_t*)tree, tree->last, lineoff_last), last == NULL);
/* Get the index of the selected variable. */
fail_if (variable = parse_variable((mds_kbdc_tree_t*)tree, tree->variable, lineoff_var), variable == 0);
/* Did one of the bound not evaluate, then stop. */
if (tree->processed == PROCESS_LEVEL)
goto done;
/* Check that the primary bound is single-character. */
if ((first[0] == -1) || (first[1] != -1))
{
NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");/* TODO test */
error->start = lineoff_first, lineoff_first = 0;
error->end = error->start + strlen(tree->first);
}
/* Check that the secondary bound is single-character. */
if ((last[0] == -1) || (last[1] != -1))
{
NEW_ERROR(tree, ERROR, "iteration boundary must be a single character string");/* TODO test */
error->start = lineoff_last, lineoff_last = 0;
error->end = error->start + strlen(tree->last);
}
/* Was one of the bounds not single-character, then stop. */
if ((lineoff_first == 0) || (lineoff_last == 0))
goto done;
/* Iterate over the loop for as long as a `return` or `break` has not
been encountered (without being caught elsewhere). */
character[1] = -1;
for (diff = (*first > *last) ? -1 : +1; break_level < 2; *first += diff)
{
break_level = 0;
character[0] = *first;
fail_if (let(variable, character, NULL, (mds_kbdc_tree_t*)tree, lineoff_var, possible_shadow));
possible_shadow = 0;
fail_if (compile_subtree(tree->inner));
/* Bounds are inclusive. */
if (*first == *last)
break;
}
/* Catch `break` and `continue`, they may not propagate further. */
if (break_level < 3)
break_level = 0;
done:
last_value_statement = NULL;
free(first);
free(last);
return 0;
FAIL_BEGIN;
free(first);
free(last);
FAIL_END;
}
/**
* Compile an if-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_if(mds_kbdc_tree_if_t* restrict tree)
{
size_t lineoff;
char* restrict code = result->source_code->real_lines[tree->loc_line];
char32_t* restrict data = NULL;
int ok, saved_errno;
size_t i;
last_value_statement = NULL;
/* Locate the first character in the condition. */
for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
/* Evaluate function calls, variable dereferences and escapes in the condition. */
fail_if (data = parse_string((mds_kbdc_tree_t*)tree, tree->condition, lineoff), data == NULL);
if (tree->processed == PROCESS_LEVEL)
return free(data), 0;
/* Evaluate whether the evaluted value is true. */
for (ok = 1, i = 0; data[i] >= 0; i++)
ok &= !!(data[i]);
free(data), data = NULL;;
/* Compile the appropriate clause. */
ok = compile_subtree(ok ? tree->inner : tree->otherwise);
last_value_statement = NULL;
fail_if (ok < 0);
return 0;
FAIL_BEGIN;
free(data);
FAIL_END;
}
/**
* Compile a let-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_let(mds_kbdc_tree_let_t* restrict tree)
{
size_t lineoff;
char* restrict code = result->source_code->real_lines[tree->loc_line];
mds_kbdc_tree_t* value = NULL;
size_t variable;
int saved_errno;
/* Get the index of the selected variable. */
for (lineoff = tree->loc_end; code[lineoff] == ' '; lineoff++);
fail_if (variable = parse_variable((mds_kbdc_tree_t*)tree, tree->variable, lineoff), variable == 0);
if (tree->processed == PROCESS_LEVEL)
return 0;
/* Duplicate arguments and evaluate function calls,
variable dereferences and escapes in the value. */
fail_if (value = mds_kbdc_tree_dup(tree->value), value == NULL);
fail_if (compile_subtree(value));
if ((tree->processed = value->processed) == PROCESS_LEVEL)
return mds_kbdc_tree_free(value), 0;
/* Set the value of the variable. */
fail_if (let(variable, NULL, value, NULL, 0, 0));
mds_kbdc_tree_free(value);
return 0;
FAIL_BEGIN;
free(value);
FAIL_END;
}
/*
* `compile_keys`, `compile_string`, `compile_array` and `evaluate_element`
* are do only compilation subprocedures that may alter the compiled nodes.
* This is because (1) `compile_let`, `compile_map` and `compile_macro_call`
* needs the compiled values, and (2) only duplicates of nodes of types
* `C(KEYS)`, `C(STRING)` and `C(ARRAY)` are compiled, as they can only be
* found with `C(LET)`-, `C(MAP)`- and `C(MACRO_CALL)`-nodes.
*/
/**
* Evaluate an element or argument in a mapping-, value-, let-statement or macro call
*
* @param node The element to evaluate
* @return Zero on success, -1 on error, 1 if the element is invalid
*/
static int evaluate_element(mds_kbdc_tree_t* restrict node)
{
char32_t* restrict data = NULL;
int bad = 0;
for (; node; node = node->next)
{
if (node->type == C(STRING))
fail_if (data = parse_string(node, node->string.string, node->loc_start), data == NULL);
if (node->type == C(KEYS))
fail_if (data = parse_keys(node, node->keys.keys, node->loc_start), data == NULL);
free(node->string.string);
node->type = (node->type == C(STRING)) ? C(COMPILED_STRING) : C(COMPILED_KEYS);
node->compiled_string.string = data;
bad |= (node->processed == PROCESS_LEVEL);
}
return bad;
fail:
return -1;
}
/**
* Compile a key-combination
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_keys(mds_kbdc_tree_keys_t* restrict tree)
{
fail_if (evaluate_element((mds_kbdc_tree_t*)tree));
return 0;
fail:
return -1;
}
/**
* Compile a string
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_string(mds_kbdc_tree_string_t* restrict tree)
{
fail_if (evaluate_element((mds_kbdc_tree_t*)tree));
return 0;
fail:
return -1;
}
/**
* Compile an array
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_array(mds_kbdc_tree_array_t* restrict tree)
{
int r = evaluate_element(tree->elements);
fail_if (r < 0);
if (r)
tree->processed = PROCESS_LEVEL;
return 0;
fail:
return -1;
}
/**
* Check that a chain of strings and key-combinations
* does not contain NULL characters
*
* @param tree The tree to check
* @return Zero on success, -1 on error, 1 if any of
* the elements contain a NULL character
*/
static int check_nonnul(mds_kbdc_tree_t* restrict tree)
{
const char32_t* restrict string;
int rc = 0;
again:
if (tree == NULL)
return rc;
for (string = tree->compiled_string.string; *string != -1; string++)
if (*string == 0)
{
NEW_ERROR(tree, ERROR, "NULL characters are not allowed in mappings");
tree->processed = PROCESS_LEVEL;
rc = 1;
break;
}
tree = tree->next;
goto again;
fail:
return -1;
}
/**
* Compile a mapping- or value-statement
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_map(mds_kbdc_tree_map_t* restrict tree)
{
int bad = 0, old_have_side_effect = have_side_effect;
mds_kbdc_include_stack_t* restrict include_stack = NULL;
mds_kbdc_tree_t* seq = NULL;
mds_kbdc_tree_t* res = NULL;
mds_kbdc_tree_t* old_seq = tree->sequence;
mds_kbdc_tree_t* old_res = tree->result;
mds_kbdc_tree_map_t* dup_map = NULL;
int r, saved_errno;
mds_kbdc_tree_t* previous_last_value_statement = last_value_statement;
have_side_effect = 0;
/* Duplicate arguments and evaluate function calls,
variable dereferences and escapes in the mapping
input sequence. */
fail_if (seq = mds_kbdc_tree_dup(old_seq), seq == NULL);
fail_if (bad |= evaluate_element(seq), bad < 0);
/* Duplicate arguments and evaluate function calls,
variable dereferences and escapes in the mapping
output sequence, unless this is a value-statement. */
if (tree->result)
{
fail_if (res = mds_kbdc_tree_dup(old_res), res == NULL);
fail_if (bad |= evaluate_element(res), bad < 0);
}
/* Stop if any of the mapping-arguments could not be evaluated. */
if (bad)
goto done;
if (tree->result)
{
/* Mapping-statement. */
/* Check for invalid characters in the mapping-arguments. */
fail_if (bad |= check_nonnul(seq), bad < 0);
fail_if (bad |= check_nonnul(res), bad < 0);
if (bad)
goto done;
/* Duplicate the mapping-statement but give it the evaluated mapping-arguments. */
tree->sequence = NULL;
tree->result = NULL;
fail_if (dup_map = &(mds_kbdc_tree_dup((mds_kbdc_tree_t*)tree)->map), dup_map == NULL);
tree->sequence = old_seq, dup_map->sequence = seq, seq = NULL;
tree->result = old_res, dup_map->result = res, res = NULL;
/* Enlist the mapping for assembling. */
fail_if (include_stack = mds_kbdc_include_stack_save(), include_stack == NULL);
fail_if (add_mapping(dup_map, include_stack));
goto done;
}
/* Value-statement */
/* Save this statement so we can warn later if it is unnecessary,
* `set_return_value` will set it to `NULL` if there are side-effects,
* which would make this statement necessary (unless the overridding
* statement has identical side-effect, but we will not check for that).
* For simplicity, we do not store the include-stack, instead, we reset
* `last_value_statement` to `NULL` when we visit an include-statement. */
last_value_statement = (mds_kbdc_tree_t*)tree;
/* Add the value statement */
r = set_return_value(seq->compiled_string.string);
seq->compiled_string.string = NULL;
/* Check that the value-statement is inside a function call, or has
side-effects by directly or indirectly calling ‘\set/3’ on an
array that is not shadowed by an inner function- or macro-call. */
if (r && !have_side_effect)
{
NEW_ERROR(tree, ERROR, "value-statement outside function without side-effects");/* TODO test */
tree->processed = PROCESS_LEVEL;
}
if (have_side_effect)
last_value_statement = NULL;
/* Check whether we made a previous value-statement unnecessary. */
if (previous_last_value_statement)
{
/* For simplicity we set `last_value_statement` on includes,
* so we are sure `last_value_statement` has the same include-stack. */
NEW_ERROR(previous_last_value_statement, WARNING, "value-statement has no effects");/* TODO test */
NEW_ERROR(tree, NOTE, "overridden here");
}
done:
mds_kbdc_tree_free(seq);
mds_kbdc_tree_free(res);
have_side_effect = old_have_side_effect;
return 0;
FAIL_BEGIN;
mds_kbdc_include_stack_free(include_stack);
mds_kbdc_tree_free((mds_kbdc_tree_t*)dup_map);
mds_kbdc_tree_free(seq);
mds_kbdc_tree_free(res);
tree->sequence = old_seq;
tree->result = old_res;
have_side_effect = old_have_side_effect;
FAIL_END;
}
/**
* Compile a macro call
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_macro_call(mds_kbdc_tree_macro_call_t* restrict tree)
{
mds_kbdc_tree_t* arg = NULL;
mds_kbdc_tree_t* arg_;
mds_kbdc_tree_macro_t* macro;
mds_kbdc_include_stack_t* macro_include_stack;
mds_kbdc_include_stack_t* our_include_stack = NULL;
size_t variable = 0;
int bad, saved_errno;
last_value_statement = NULL;
/* Duplicate arguments and evaluate function calls,
variable dereferences and escapes in the macro
call arguments. */
fail_if (arg = mds_kbdc_tree_dup(tree->arguments), arg == NULL);
fail_if (bad = evaluate_element(arg), bad < 0);
if (bad)
return 0;
/* Get the macro's subtree and include-stack, if it has
not been defined `get_macro` will add an error message
and return `NULL`. */
fail_if (get_macro(tree, ¯o, ¯o_include_stack));
if (macro == NULL)
goto done;
/* Push call stack and set parameters. */
variables_stack_push();
for (arg_ = arg; arg_; arg_ = arg_->next)
fail_if (let(++variable, NULL, arg_, NULL, 0, 0));
/* Switch include-stack to the macro's. */
fail_if (our_include_stack = mds_kbdc_include_stack_save(), our_include_stack == NULL);
fail_if (mds_kbdc_include_stack_restore(macro_include_stack));
/* Call the macro. */
fail_if (compile_subtree(macro->inner));
/* Switch back the include-stack to ours. */
fail_if (mds_kbdc_include_stack_restore(our_include_stack));
mds_kbdc_include_stack_free(our_include_stack), our_include_stack = NULL;
/* Pop call stack. */
variables_stack_pop();
done:
last_value_statement = NULL;
break_level = 0;
mds_kbdc_tree_free(arg);
return 0;
FAIL_BEGIN;
mds_kbdc_tree_free(arg);
mds_kbdc_include_stack_free(our_include_stack);
FAIL_END;
}
/**
* Compile a subtree
*
* @param tree The tree to compile
* @return Zero on success, -1 on error
*/
static int compile_subtree(mds_kbdc_tree_t* restrict tree)
{
#define t(expr) fail_if ((expr) < 0)
#define c(type) t (compile_##type(&(tree->type)))
#define c_(type) t (compile_##type(&(tree->type##_)))
again:
if (tree == NULL)
return 0;
if (tree->processed == PROCESS_LEVEL)
/* An error has occurred here before, lets skip it so
* we do not deluge the user with errors. */
goto next;
switch (tree->type)
{
case C(INFORMATION):
t (compile_subtree(tree->information.inner));
break;
case C(INFORMATION_LANGUAGE): c (language); break;
case C(INFORMATION_COUNTRY): c (country); break;
case C(INFORMATION_VARIANT): c (variant); break;
case C(INCLUDE): c (include); break;
case C(FUNCTION): c (function); break;
case C(MACRO): c (macro); break;
case C(ASSUMPTION):
t ((includes_ptr == 0) && compile_subtree(tree->assumption.inner));
break;
case C(ASSUMPTION_HAVE): c (have); break;
case C(ASSUMPTION_HAVE_CHARS): c (have_chars); break;
case C(ASSUMPTION_HAVE_RANGE): c (have_range); break;
case C(FOR): c_ (for); break;
case C(IF): c_ (if); break;
case C(LET): c (let); break;/* TODO test */
case C(KEYS): c (keys); break;/* TODO test */
case C(STRING): c (string); break;/* TODO test */
case C(ARRAY): c (array); break;/* TODO test */
case C(MAP): c (map); break;
case C(MACRO_CALL): c (macro_call); break;/* TODO test */
case C(RETURN): break_level = 3; break;/* TODO test */
case C(BREAK): break_level = 2; break;
case C(CONTINUE): break_level = 1; break;
default:
break;
}
next:
if (break_level)
/* If a `continue`, `break` or `return` has been encountered,
* we are done here and should return to whence we came and
* let the subcompiler of that construct deal with it. */
return 0;
tree = tree->next;
goto again;
fail:
return -1;
#undef c_
#undef c
#undef t
}
/**
* Compile the layout code
*
* @param result_ `result` from `eliminate_dead_code`, will be updated
* @return -1 if an error occursed that cannot be stored in `result`, zero otherwise
*/
int compile_layout(mds_kbdc_parsed_t* restrict result_)
{
int r, saved_errno;
mds_kbdc_include_stack_begin(result = result_);
r = compile_subtree(result_->tree);
saved_errno = errno;
mds_kbdc_include_stack_end();
variables_terminate();
callables_terminate();
errno = saved_errno;
fail_if (r);
return 0;
fail:
return -1;
}
#undef FAIL_END
#undef FAIL_BEGIN
#undef NEW_ERROR
#undef C
#undef PROCESS_LEVEL