3149 lines
103 KiB
C
3149 lines
103 KiB
C
#include <stdio.h>
|
|
#include <ctype.h>
|
|
#include <stdlib.h>
|
|
#include <string.h>
|
|
#include <stdint.h>
|
|
#include <stdbool.h>
|
|
|
|
typedef enum {
|
|
ASTNodeType_template = 0, /* Template: entire template */
|
|
ASTNodeType_text, /* Raw text outside of tags */
|
|
ASTNodeType_comment, /* Comment: {# ... #} */
|
|
ASTNodeType_output_expr, /* Output: {{ expression }} */
|
|
ASTNodeType_set_stmt, /* Set statement: {% set member_or_var.here = expr %} */
|
|
ASTNodeType_unset_stmt, /* Unset statement (only nonmembers): {% unset var %} */
|
|
ASTNodeType_unmacro_stmt, /* Unmacro statement (only macros): {% unmacro name %} */
|
|
ASTNodeType_conditional, /* If: {% if ... %} ... {% elif ... %} ... {% end %} or ternary: cond ?
|
|
a : b */
|
|
ASTNodeType_macro_stmt, /* Block: {% macro ... %} ... {% end %} */
|
|
ASTNodeType_namespace_stmt, /* Namespace: {% namespace ... %} ... {% end %} */
|
|
ASTNodeType_import_stmt, /* Import: {% import "filename.tpl" %} */
|
|
ASTNodeType_for_loop, /* For loop: {% for x : y %} */
|
|
ASTNodeType_while_loop, /* While loop: {% while expr %} */
|
|
ASTNodeType_binary_expr, /* Binary expression: a + b */
|
|
ASTNodeType_unary_expr, /* Unary expression: !x, -x */
|
|
ASTNodeType_function_call, /* Function call: lower(x) */
|
|
ASTNodeType_variable, /* Variable: x */
|
|
ASTNodeType_literal_string, /* Literal string: "..." or r"..." */
|
|
ASTNodeType_fmt_string, /* Literal format string: f"... {expr} ..." */
|
|
ASTNodeType_literal_number, /* Literal number: 123 (base 10) (or 0d123), 0x123 (base 16), 0o123
|
|
(base 8), 0b110 (base 2) */
|
|
ASTNodeType_literal_array, /* Literal array: [..., ...] */
|
|
ASTNodeType_literal_boolean, /* Literal boolean: true or false */
|
|
ASTNodeType_literal_null, /* Literal null: null */
|
|
ASTNodeType_literal_float, /* Literal float: 3.14159 or 12e-3 (0.012) */
|
|
ASTNodeType_literal_inf, /* Literal infinity: inf */
|
|
ASTNodeType_parent_stmt, /* Parent stmt: parent */
|
|
ASTNodeType_lookup, /* Lookup: [...] */
|
|
ASTNodeType_member_lookup, /* Member lookup: a.b */
|
|
ASTNodeType_finish_stmt, /* Finish render: {% finish %} */
|
|
ASTNodeType_break_stmt, /* Break statement: {% break %} */
|
|
ASTNodeType_continue_stmt, /* Continue statement: {% continue %} */
|
|
ASTNodeType_block_stmt, /* Block statement: {% block name %} {% end %} */
|
|
ASTNodeType_range, /* Range: a:b/step */
|
|
ASTNodeType_literal_hashmap, /* Literal hashmao: {a: b} */
|
|
} ASTNodeType;
|
|
|
|
typedef enum {
|
|
BinaryOperator_add = 0, /* + */
|
|
BinaryOperator_sub, /* - */
|
|
BinaryOperator_mul, /* * */
|
|
BinaryOperator_div, /* / */
|
|
BinaryOperator_mod, /* % */
|
|
BinaryOperator_keyword, /* = (for keyword arguments and default values) */
|
|
BinaryOperator_eq, /* == */
|
|
BinaryOperator_neq, /* != */
|
|
BinaryOperator_lt, /* < */
|
|
BinaryOperator_gt, /* > */
|
|
BinaryOperator_leq, /* <= */
|
|
BinaryOperator_geq, /* >= */
|
|
BinaryOperator_and, /* && */
|
|
BinaryOperator_or, /* || */
|
|
BinaryOperator_band, /* & */
|
|
BinaryOperator_bor, /* | */
|
|
BinaryOperator_bxor, /* ^ */
|
|
BinaryOperator_shl, /* << */
|
|
BinaryOperator_shr, /* >> */
|
|
BinaryOperator_in, /* @ */
|
|
} BinaryOperator;
|
|
|
|
typedef enum {
|
|
AssignmentOperator_simple = 0, /* = */
|
|
AssignmentOperator_add, /* += */
|
|
AssignmentOperator_sub, /* -= */
|
|
AssignmentOperator_mul, /* *= */
|
|
AssignmentOperator_div, /* /= */
|
|
AssignmentOperator_mod, /* %= */
|
|
AssignmentOperator_band, /* &= */
|
|
AssignmentOperator_bor, /* |= */
|
|
AssignmentOperator_bxor, /* ^= */
|
|
AssignmentOperator_shl, /* <<= */
|
|
AssignmentOperator_shr, /* >>= */
|
|
} AssignmentOperator;
|
|
|
|
typedef enum {
|
|
UnaryOperator_neg = 0, /* - */
|
|
UnaryOperator_pos, /* + */
|
|
UnaryOperator_not, /* ! */
|
|
UnaryOperator_bnot, /* ~ */
|
|
UnaryOperator_spread, /* * */
|
|
UnaryOperator_kspread, /* ** */
|
|
} UnaryOperator;
|
|
|
|
typedef struct ASTNode {
|
|
ASTNodeType type;
|
|
union {
|
|
struct {
|
|
char *text;
|
|
size_t len;
|
|
} text;
|
|
struct {
|
|
char *text;
|
|
size_t len;
|
|
} comment;
|
|
struct {
|
|
struct ASTNode *expr;
|
|
} output;
|
|
struct {
|
|
struct ASTNode *name;
|
|
AssignmentOperator eq;
|
|
struct ASTNode *expr;
|
|
bool trim; /* trim value */
|
|
} set_stmt;
|
|
struct {
|
|
char *name;
|
|
size_t name_len;
|
|
} unset_stmt;
|
|
struct {
|
|
char *name;
|
|
size_t name_len;
|
|
} unmacro_stmt;
|
|
struct {
|
|
struct ASTNode *condition;
|
|
struct ASTNode *body;
|
|
struct ASTNode *else_clause;
|
|
bool trim_body, trim_clause;
|
|
} conditional;
|
|
struct {
|
|
char *name;
|
|
size_t name_len;
|
|
struct ASTNode **args;
|
|
size_t args_count;
|
|
struct ASTNode *body;
|
|
bool trim; /* trim macro body */
|
|
} macro_stmt;
|
|
struct {
|
|
char *name;
|
|
size_t name_len;
|
|
struct ASTNode *body;
|
|
bool trim; /* trim macro body */
|
|
} namespace_stmt;
|
|
struct {
|
|
struct ASTNode *filename;
|
|
char *namespace;
|
|
size_t namespace_size;
|
|
bool trim; /* trim template body */
|
|
} import_stmt;
|
|
struct {
|
|
char *varname;
|
|
struct ASTNode *iterable;
|
|
struct ASTNode *body;
|
|
bool trim; /* trim loop body */
|
|
} for_loop;
|
|
struct {
|
|
struct ASTNode *condition;
|
|
struct ASTNode *body;
|
|
bool trim; /* trim loop body */
|
|
} while_loop;
|
|
struct {
|
|
BinaryOperator op;
|
|
struct ASTNode *left;
|
|
struct ASTNode *right;
|
|
} binary;
|
|
struct {
|
|
UnaryOperator op;
|
|
struct ASTNode *operand;
|
|
} unary;
|
|
struct {
|
|
struct ASTNode *name;
|
|
struct ASTNode **args;
|
|
size_t args_count;
|
|
} function_call;
|
|
struct {
|
|
char *name;
|
|
size_t name_len;
|
|
} variable;
|
|
struct {
|
|
int64_t value;
|
|
} number;
|
|
struct {
|
|
bool raw;
|
|
char *value;
|
|
size_t value_len;
|
|
} string;
|
|
struct {
|
|
struct ASTNode *string;
|
|
} fmt_string;
|
|
struct {
|
|
struct ASTNode *operand;
|
|
struct ASTNode *index;
|
|
} lookup;
|
|
struct {
|
|
struct ASTNode *operand;
|
|
char *member;
|
|
size_t member_len;
|
|
} member_lookup;
|
|
struct {
|
|
struct ASTNode **items;
|
|
size_t items_count;
|
|
} array;
|
|
struct {
|
|
bool value;
|
|
} boolean;
|
|
struct {
|
|
double value;
|
|
} floatnum;
|
|
struct {
|
|
char *name;
|
|
size_t name_len;
|
|
struct ASTNode *body;
|
|
bool trim; /* trim block body */
|
|
} block;
|
|
struct {
|
|
struct ASTNode *from;
|
|
struct ASTNode *to;
|
|
struct ASTNode *step;
|
|
} range;
|
|
struct {
|
|
struct ASTNode **keys;
|
|
struct ASTNode **values;
|
|
size_t count;
|
|
} hashmap;
|
|
} data;
|
|
struct ASTNode *next;
|
|
} ASTNode;
|
|
|
|
/* BEGIN FORWARD DECLARATIONS */
|
|
|
|
static void free_ast(ASTNode *node);
|
|
static const char *binary_op_str(BinaryOperator op);
|
|
static char *unary_op_str(UnaryOperator op);
|
|
static const char *eq_op_str(AssignmentOperator op);
|
|
|
|
static ASTNode *new_text_node(const char *start, size_t len);
|
|
static ASTNode *new_comment_node(const char *start, size_t len);
|
|
static ASTNode *new_output_node(ASTNode *expr);
|
|
static ASTNode *new_literal_number_node(int64_t value);
|
|
static ASTNode *new_fmt_string_node(ASTNode *expr);
|
|
static ASTNode *new_literal_string_node(const char *start, size_t len, bool raw);
|
|
static ASTNode *new_variable_node(const char *start, size_t len);
|
|
static ASTNode *new_function_call_node(ASTNode *name, ASTNode **args, size_t args_count);
|
|
static ASTNode *new_lookup_node(ASTNode *operand, ASTNode *index);
|
|
static ASTNode *new_member_lookup_node(ASTNode *operand, const char *member, size_t len);
|
|
static ASTNode *new_binary_node(BinaryOperator op, ASTNode *left, ASTNode *right);
|
|
static ASTNode *new_import_node(ASTNode *filename, const char *namespace, size_t len, bool trim);
|
|
static ASTNode *new_set_node(ASTNode *name, AssignmentOperator eq, ASTNode *expr, bool trim);
|
|
static ASTNode *new_unset_node(const char *start, size_t len);
|
|
static ASTNode *new_unmacro_node(const char *start, size_t len);
|
|
static ASTNode *new_macro_node(
|
|
const char *start, size_t len, ASTNode **args, size_t args_count, ASTNode *body, bool trim);
|
|
static ASTNode *new_namespace_node(const char *start, size_t len, ASTNode *body, bool trim);
|
|
static ASTNode *new_while_node(ASTNode *condition, ASTNode *body, bool trim);
|
|
static ASTNode *
|
|
new_for_node(const char *start, size_t len, ASTNode *iterable, ASTNode *body, bool trim);
|
|
static ASTNode *new_conditional_node(
|
|
ASTNode *condition, ASTNode *body, ASTNode *else_clause, bool trim_body, bool trim_clause);
|
|
static ASTNode *new_array_node(ASTNode **items, size_t items_count);
|
|
static ASTNode *new_hashmap_node(ASTNode **keys, ASTNode **values, size_t count);
|
|
static ASTNode *new_finish_stmt_node(void);
|
|
static ASTNode *new_unary_node(UnaryOperator op, ASTNode *operand);
|
|
static ASTNode *new_boolean_node(bool value);
|
|
static ASTNode *new_null_node(void);
|
|
static ASTNode *new_inf_node(void);
|
|
static ASTNode *new_break_node(void);
|
|
static ASTNode *new_continue_node(void);
|
|
static ASTNode *new_literal_float_node(double value);
|
|
static ASTNode *new_block_node(const char *start, size_t len, ASTNode *body, bool trim);
|
|
static ASTNode *new_range_node(ASTNode *from, ASTNode *to, ASTNode *step);
|
|
|
|
static void skip_whitespace(const char **p, const char *end);
|
|
static ASTNode *parse_number(const char **p, const char *end);
|
|
static ASTNode *parse_fmt_string(const char **p, const char *end);
|
|
static ASTNode *parse_string(const char **p, const char *end);
|
|
static ASTNode *parse_array(const char **p, const char *end);
|
|
static ASTNode *parse_hashmap(const char **p, const char *end);
|
|
static ASTNode *parse_identifier(const char **p, const char *end);
|
|
static ASTNode *parse_primary_expr(const char **p, const char *end);
|
|
static ASTNode *parse_postfix_expr(ASTNode *base, const char **p, const char *end);
|
|
static bool match_binary_operator(const char **p, const char *end, BinaryOperator *op);
|
|
static bool match_unary_operator(const char **p, const char *end, UnaryOperator *op);
|
|
static uint8_t get_operator_precedence(BinaryOperator op);
|
|
static ASTNode *
|
|
parse_binary_expr_rhs(uint8_t min_prec, ASTNode *lhs, const char **p, const char *end);
|
|
static ASTNode *parse_unary_expr_lhs(const char **p, const char *end);
|
|
static ASTNode *parse_conditional_expr(ASTNode *condition, const char **p, const char *end);
|
|
static ASTNode *parse_expression(const char **p, const char *end);
|
|
static bool match_assignment_operator(const char **p, const char *end, AssignmentOperator *op);
|
|
static ASTNode *parse_set_stmt(const char *args_begin, const char *args_end, bool trim);
|
|
static ASTNode *parse_unset_stmt(const char *args_begin, const char *args_end);
|
|
static ASTNode *parse_unmacro_stmt(const char *args_begin, const char *args_end);
|
|
static ASTNode *parse_import_stmt(const char *args_begin, const char *args_end, bool trim);
|
|
static ASTNode *
|
|
parse_while_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim);
|
|
static ASTNode *
|
|
parse_macro_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim);
|
|
static ASTNode *
|
|
parse_namespace_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim);
|
|
static ASTNode *
|
|
parse_for_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim);
|
|
static ASTNode **parse_list(const char **p, const char *end, char close_char, size_t *out_argc);
|
|
static ASTNode *
|
|
parse_block_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim);
|
|
static ASTNode *parse_range(ASTNode *from, const char **p, const char *end);
|
|
|
|
static void print_indent(size_t indent);
|
|
static void print_ast(const ASTNode *node, size_t indent);
|
|
static bool parse_template(ASTNode *root, const char *input, size_t len);
|
|
|
|
/* END FORWARD DECLARATIONS */
|
|
|
|
static void free_ast(ASTNode *node) {
|
|
while (node) {
|
|
ASTNode *next = node->next;
|
|
|
|
if (node->type == ASTNodeType_text) {
|
|
free(node->data.text.text);
|
|
} else if (node->type == ASTNodeType_comment) {
|
|
free(node->data.comment.text);
|
|
}
|
|
|
|
free(node);
|
|
node = next;
|
|
}
|
|
}
|
|
|
|
static const char *binary_op_str(BinaryOperator op) {
|
|
switch (op) {
|
|
case BinaryOperator_add:
|
|
return "+";
|
|
case BinaryOperator_sub:
|
|
return "-";
|
|
case BinaryOperator_mul:
|
|
return "*";
|
|
case BinaryOperator_div:
|
|
return "/";
|
|
case BinaryOperator_mod:
|
|
return "%";
|
|
case BinaryOperator_keyword:
|
|
return "=";
|
|
case BinaryOperator_eq:
|
|
return "==";
|
|
case BinaryOperator_neq:
|
|
return "!=";
|
|
case BinaryOperator_lt:
|
|
return "<";
|
|
case BinaryOperator_gt:
|
|
return ">";
|
|
case BinaryOperator_leq:
|
|
return "<=";
|
|
case BinaryOperator_geq:
|
|
return ">=";
|
|
case BinaryOperator_and:
|
|
return "&&";
|
|
case BinaryOperator_or:
|
|
return "||";
|
|
case BinaryOperator_band:
|
|
return "&";
|
|
case BinaryOperator_bor:
|
|
return "|";
|
|
case BinaryOperator_bxor:
|
|
return "^";
|
|
case BinaryOperator_shl:
|
|
return "<<";
|
|
case BinaryOperator_shr:
|
|
return ">>";
|
|
case BinaryOperator_in:
|
|
return "@";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
static char *unary_op_str(UnaryOperator op) {
|
|
switch (op) {
|
|
case UnaryOperator_neg:
|
|
return "-";
|
|
case UnaryOperator_pos:
|
|
return "+";
|
|
case UnaryOperator_not:
|
|
return "!";
|
|
case UnaryOperator_bnot:
|
|
return "~";
|
|
case UnaryOperator_spread:
|
|
return "*";
|
|
case UnaryOperator_kspread:
|
|
return "**";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
static const char *eq_op_str(AssignmentOperator op) {
|
|
switch (op) {
|
|
case AssignmentOperator_simple:
|
|
return "=";
|
|
case AssignmentOperator_add:
|
|
return "+=";
|
|
case AssignmentOperator_sub:
|
|
return "-=";
|
|
case AssignmentOperator_mul:
|
|
return "*=";
|
|
case AssignmentOperator_div:
|
|
return "/=";
|
|
case AssignmentOperator_mod:
|
|
return "%=";
|
|
case AssignmentOperator_band:
|
|
return "&=";
|
|
case AssignmentOperator_bor:
|
|
return "|=";
|
|
case AssignmentOperator_bxor:
|
|
return "^=";
|
|
case AssignmentOperator_shl:
|
|
return "<<=";
|
|
case AssignmentOperator_shr:
|
|
return ">>=";
|
|
default:
|
|
return "?";
|
|
}
|
|
}
|
|
|
|
/* BEGIN HELPERS */
|
|
|
|
static ASTNode *new_text_node(const char *start, size_t len) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_text;
|
|
node->data.text.text = malloc(len + 1);
|
|
memcpy(node->data.text.text, start, len);
|
|
node->data.text.text[len] = '\0';
|
|
node->data.text.len = len;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_comment_node(const char *start, size_t len) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_comment;
|
|
node->data.comment.text = malloc(len + 1);
|
|
memcpy(node->data.comment.text, start, len);
|
|
node->data.comment.text[len] = '\0';
|
|
node->data.comment.len = len;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_output_node(ASTNode *expr) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
if (!node) {
|
|
return NULL;
|
|
}
|
|
node->type = ASTNodeType_output_expr;
|
|
node->data.output.expr = expr;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_literal_number_node(int64_t value) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_number;
|
|
node->data.number.value = value;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_literal_string_node(const char *start, size_t len, bool raw) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_string;
|
|
node->data.string.value = malloc(len + 1);
|
|
memcpy(node->data.string.value, start, len);
|
|
node->data.string.value[len] = '\0';
|
|
node->data.string.raw = raw;
|
|
node->data.string.value_len = len;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_fmt_string_node(ASTNode *expr) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_fmt_string;
|
|
node->data.fmt_string.string = expr;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_variable_node(const char *start, size_t len) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_variable;
|
|
node->data.variable.name = malloc(len + 1);
|
|
memcpy(node->data.variable.name, start, len);
|
|
node->data.variable.name[len] = '\0';
|
|
node->data.variable.name_len = len;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_function_call_node(ASTNode *name, ASTNode **args, size_t args_count) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_function_call;
|
|
node->data.function_call.name = name;
|
|
node->data.function_call.args = args;
|
|
node->data.function_call.args_count = args_count;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_lookup_node(ASTNode *operand, ASTNode *index) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_lookup;
|
|
node->data.lookup.operand = operand;
|
|
node->data.lookup.index = index;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_member_lookup_node(ASTNode *operand, const char *member, size_t len) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_member_lookup;
|
|
node->data.member_lookup.operand = operand;
|
|
node->data.member_lookup.member = malloc(len + 1);
|
|
memcpy(node->data.member_lookup.member, member, len);
|
|
node->data.member_lookup.member[len] = '\0';
|
|
node->data.member_lookup.member_len = len;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_binary_node(BinaryOperator op, ASTNode *left, ASTNode *right) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_binary_expr;
|
|
node->data.binary.op = op;
|
|
node->data.binary.left = left;
|
|
node->data.binary.right = right;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_import_node(ASTNode *filename, const char *namespace, size_t len, bool trim) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_import_stmt;
|
|
node->data.import_stmt.filename = filename;
|
|
node->data.import_stmt.namespace = malloc(len + 1);
|
|
memcpy(node->data.import_stmt.namespace, namespace, len);
|
|
node->data.import_stmt.namespace[len] = '\0';
|
|
node->data.import_stmt.namespace_size = len;
|
|
node->data.import_stmt.trim = trim;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_set_node(ASTNode *name, AssignmentOperator eq, ASTNode *expr, bool trim) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_set_stmt;
|
|
node->data.set_stmt.name = name;
|
|
node->data.set_stmt.eq = eq;
|
|
node->data.set_stmt.expr = expr;
|
|
node->data.set_stmt.trim = trim;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_unset_node(const char *start, size_t len) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_unset_stmt;
|
|
node->data.unset_stmt.name = malloc(len + 1);
|
|
memcpy(node->data.unset_stmt.name, start, len);
|
|
node->data.unset_stmt.name[len] = '\0';
|
|
node->data.unset_stmt.name_len = len;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_unmacro_node(const char *start, size_t len) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_unmacro_stmt;
|
|
node->data.unmacro_stmt.name = malloc(len + 1);
|
|
memcpy(node->data.unmacro_stmt.name, start, len);
|
|
node->data.unmacro_stmt.name[len] = '\0';
|
|
node->data.unmacro_stmt.name_len = len;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_macro_node(
|
|
const char *start, size_t len, ASTNode **args, size_t args_count, ASTNode *body, bool trim) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_macro_stmt;
|
|
node->data.macro_stmt.name = malloc(len + 1);
|
|
memcpy(node->data.macro_stmt.name, start, len);
|
|
node->data.macro_stmt.name[len] = '\0';
|
|
node->data.macro_stmt.body = body;
|
|
node->data.macro_stmt.name_len = len;
|
|
node->data.macro_stmt.args = args;
|
|
node->data.macro_stmt.args_count = args_count;
|
|
node->data.macro_stmt.trim = trim;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_namespace_node(const char *start, size_t len, ASTNode *body, bool trim) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_namespace_stmt;
|
|
node->data.namespace_stmt.name = malloc(len + 1);
|
|
memcpy(node->data.namespace_stmt.name, start, len);
|
|
node->data.namespace_stmt.name[len] = '\0';
|
|
node->data.namespace_stmt.body = body;
|
|
node->data.namespace_stmt.name_len = len;
|
|
node->data.namespace_stmt.trim = trim;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_while_node(ASTNode *condition, ASTNode *body, bool trim) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_while_loop;
|
|
node->data.while_loop.condition = condition;
|
|
node->data.while_loop.body = body;
|
|
node->data.while_loop.trim = trim;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *
|
|
new_for_node(const char *start, size_t len, ASTNode *iterable, ASTNode *body, bool trim) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_for_loop;
|
|
node->data.for_loop.varname = malloc(len + 1);
|
|
memcpy(node->data.for_loop.varname, start, len);
|
|
node->data.for_loop.varname[len] = '\0';
|
|
node->data.for_loop.iterable = iterable;
|
|
node->data.for_loop.body = body;
|
|
node->data.for_loop.trim = trim;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_conditional_node(
|
|
ASTNode *condition, ASTNode *body, ASTNode *else_clause, bool trim_body, bool trim_clause) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_conditional;
|
|
node->data.conditional.condition = condition;
|
|
node->data.conditional.body = body;
|
|
node->data.conditional.else_clause = else_clause;
|
|
node->data.conditional.trim_body = trim_body;
|
|
node->data.conditional.trim_clause = trim_clause;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_array_node(ASTNode **items, size_t items_count) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_array;
|
|
node->data.array.items = items;
|
|
node->data.array.items_count = items_count;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_hashmap_node(ASTNode **keys, ASTNode **values, size_t count) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_hashmap;
|
|
node->data.hashmap.keys = keys;
|
|
node->data.hashmap.values = values;
|
|
node->data.hashmap.count = count;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_finish_stmt_node(void) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_finish_stmt;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_unary_node(UnaryOperator op, ASTNode *operand) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_unary_expr;
|
|
node->data.unary.op = op;
|
|
node->data.unary.operand = operand;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_boolean_node(bool value) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_boolean;
|
|
node->data.boolean.value = value;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_null_node(void) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_null;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_inf_node(void) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_inf;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_parent_node(void) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_parent_stmt;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_break_node(void) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_break_stmt;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_continue_node(void) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_continue_stmt;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_literal_float_node(double value) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_literal_float;
|
|
node->data.floatnum.value = value;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_block_node(const char *start, size_t len, ASTNode *body, bool trim) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_block_stmt;
|
|
if (len == 0) {
|
|
node->data.block.name = NULL;
|
|
} else {
|
|
node->data.block.name = malloc(len + 1);
|
|
memcpy(node->data.block.name, start, len);
|
|
node->data.block.name[len] = '\0';
|
|
}
|
|
node->data.block.name_len = len;
|
|
node->data.block.body = body;
|
|
node->data.block.trim = trim;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
static ASTNode *new_range_node(ASTNode *from, ASTNode *to, ASTNode *step) {
|
|
ASTNode *node = malloc(sizeof(ASTNode));
|
|
node->type = ASTNodeType_range;
|
|
node->data.range.from = from;
|
|
node->data.range.to = to;
|
|
node->data.range.step = step;
|
|
node->next = NULL;
|
|
return node;
|
|
}
|
|
|
|
/* END HELPERS */
|
|
|
|
/* BEGIN PARSERS */
|
|
|
|
static void skip_whitespace(const char **p, const char *end) {
|
|
while (*p < end && isspace(**p)) {
|
|
++(*p);
|
|
}
|
|
}
|
|
|
|
static inline bool is_valid_digit(char c, uint8_t base) {
|
|
if (base <= 10) {
|
|
return (c >= '0' && c < '0' + base);
|
|
} else {
|
|
/* base > 10, allow digits and letters up to base */
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
return (c - '0') < base;
|
|
}
|
|
|
|
if (c >= 'a' && c < 'a' + base - 10) {
|
|
return true;
|
|
}
|
|
|
|
if (c >= 'A' && c < 'A' + base - 10) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static inline int64_t str_to_int64(const char *start, const char *end, uint8_t base) {
|
|
int64_t result = 0;
|
|
int64_t sign = 1;
|
|
|
|
if (start < end && *start == '-') {
|
|
sign = -1;
|
|
++start;
|
|
}
|
|
|
|
while (start < end) {
|
|
char c = *start++;
|
|
int digit = 0;
|
|
|
|
if (c >= '0' && c <= '9') {
|
|
digit = c - '0';
|
|
} else if (c >= 'a' && c <= 'f') {
|
|
digit = 10 + (c - 'a');
|
|
} else if (c >= 'A' && c <= 'F') {
|
|
digit = 10 + (c - 'A');
|
|
} else {
|
|
break;
|
|
}
|
|
|
|
if (digit >= base) {
|
|
break;
|
|
}
|
|
|
|
result = result * base + digit;
|
|
}
|
|
|
|
return sign * result;
|
|
}
|
|
|
|
static inline double power10(int64_t exponent) {
|
|
if (exponent == 0) {
|
|
return 1.0;
|
|
}
|
|
|
|
int64_t abs_exp = exponent < 0 ? -exponent : exponent;
|
|
double result = 1.0;
|
|
double base = 10.0;
|
|
|
|
while (abs_exp) {
|
|
if (abs_exp & 1) {
|
|
result *= base;
|
|
}
|
|
|
|
base *= base;
|
|
abs_exp /= 2;
|
|
}
|
|
|
|
return exponent < 0 ? 1.0 / result : result;
|
|
}
|
|
|
|
static inline double str_to_double(const char *start, const char *end) {
|
|
const char *p = start;
|
|
double sign = 1.0;
|
|
|
|
if (p < end && (*p == '+' || *p == '-')) {
|
|
sign = (*p == '-') ? -1.0 : 1.0;
|
|
++p;
|
|
}
|
|
|
|
double integer_part = 0.0;
|
|
double fractional_part = 0.0;
|
|
bool has_value = false;
|
|
|
|
while (p < end && isdigit(*p)) {
|
|
integer_part = integer_part * 10.0 + (*p - '0');
|
|
has_value = true;
|
|
++p;
|
|
}
|
|
|
|
if (p < end && *p == '.') {
|
|
double divisor = 10.0;
|
|
|
|
++p;
|
|
|
|
while (p < end && isdigit(*p)) {
|
|
fractional_part += (*p - '0') / divisor;
|
|
divisor *= 10.0;
|
|
has_value = 1;
|
|
p++;
|
|
}
|
|
}
|
|
|
|
if (!has_value) {
|
|
return 0.0;
|
|
}
|
|
|
|
double combined = integer_part + fractional_part;
|
|
int64_t exponent_val = 0;
|
|
|
|
if (p < end && (*p == 'e' || *p == 'E')) {
|
|
p++;
|
|
int64_t exp_sign = 1;
|
|
|
|
if (p < end && (*p == '+' || *p == '-')) {
|
|
exp_sign = (*p == '-') ? -1 : 1;
|
|
++p;
|
|
}
|
|
|
|
while (p < end && isdigit(*p)) {
|
|
exponent_val = exponent_val * 10 + (*p - '0');
|
|
++p;
|
|
}
|
|
|
|
exponent_val *= exp_sign;
|
|
}
|
|
|
|
return sign * combined * power10(exponent_val);
|
|
}
|
|
|
|
static ASTNode *parse_number(const char **p, const char *end) {
|
|
skip_whitespace(p, end);
|
|
|
|
uint8_t base = 10;
|
|
bool has_exp = false;
|
|
|
|
/* Check for base prefix only if it is NOT a float */
|
|
if (**p == '0' && ((*p) + 1) < end && isalpha(*(*p + 1))) {
|
|
char next_char = *(*p + 1);
|
|
|
|
if (next_char == 'b' || next_char == 'B' || next_char == 'o' || next_char == 'O' ||
|
|
next_char == 'd' || next_char == 'D' || next_char == 'x' || next_char == 'X' ||
|
|
next_char == 'e' || next_char == 'E') {
|
|
if (next_char == 'e' || next_char == 'E') {
|
|
base = 10;
|
|
has_exp = true;
|
|
} else {
|
|
switch (next_char) {
|
|
case 'b':
|
|
case 'B':
|
|
base = 2;
|
|
break;
|
|
case 'o':
|
|
case 'O':
|
|
base = 8;
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
base = 10;
|
|
break;
|
|
case 'x':
|
|
case 'X':
|
|
base = 16;
|
|
break;
|
|
}
|
|
|
|
*p += 2;
|
|
}
|
|
} else {
|
|
fprintf(stderr, "Error: Invalid number literal base identifier: %c\n", next_char);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
/* Now parse digits and optionally decimal point and exponent */
|
|
const char *start = *p;
|
|
const char *scan = *p;
|
|
bool has_dot = false;
|
|
|
|
while (scan < end) {
|
|
char c = *scan;
|
|
|
|
if (c == '.') {
|
|
if (has_dot || has_exp) {
|
|
break; /* Only one dot allowed, no dot after exponent */
|
|
}
|
|
|
|
if (base != 10) {
|
|
break; /* Float only allowed in base 10 */
|
|
}
|
|
|
|
has_dot = true;
|
|
++scan;
|
|
} else if (c == 'e' || c == 'E') {
|
|
if (has_exp || base != 10) {
|
|
break; /* Only one exponent allowed, only base 10 */
|
|
}
|
|
|
|
has_exp = true;
|
|
++scan;
|
|
|
|
/* Optional sign after exponent */
|
|
if (scan < end && (*scan == '+' || *scan == '-')) {
|
|
++scan;
|
|
}
|
|
} else if (is_valid_digit(c, base)) {
|
|
++scan;
|
|
} else {
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (start == scan) {
|
|
fprintf(stderr, "Error: Invalid number literal at '%s'\n", *p);
|
|
return NULL;
|
|
}
|
|
|
|
/* If float detected, base must be 10 (ignore any prefix) */
|
|
if (has_dot || has_exp) {
|
|
base = 10;
|
|
*p = scan;
|
|
return new_literal_float_node(str_to_double(start, scan));
|
|
} else {
|
|
*p = scan;
|
|
return new_literal_number_node(str_to_int64(start, scan, base));
|
|
}
|
|
}
|
|
|
|
static ASTNode *parse_fmt_string(const char **p, const char *end) {
|
|
const char *s = *p;
|
|
const char *lit_start = s; /* Start of literal text */
|
|
ASTNode *head = NULL;
|
|
ASTNode **current = &head;
|
|
|
|
while (s < end) {
|
|
if (*s == '{') {
|
|
if (s + 1 < end && s[1] == '{') {
|
|
/* Escaped {{ */
|
|
|
|
/* Add any literal text before {{ */
|
|
if (lit_start < s) {
|
|
ASTNode *text =
|
|
new_literal_string_node(lit_start, (size_t)(s - lit_start), false);
|
|
*current = text;
|
|
current = &text->next;
|
|
}
|
|
|
|
/* Add literal '{' */
|
|
ASTNode *text = new_literal_string_node("{", 1, true);
|
|
*current = text;
|
|
current = &text->next;
|
|
|
|
s += 2;
|
|
lit_start = s;
|
|
continue;
|
|
}
|
|
|
|
/* Single '{' - parse expression */
|
|
if (lit_start < s) {
|
|
ASTNode *text = new_literal_string_node(lit_start, (size_t)(s - lit_start), false);
|
|
*current = text;
|
|
current = &text->next;
|
|
}
|
|
|
|
const char *expr_start = s + 1;
|
|
const char *expr_end = expr_start;
|
|
int brace_count = 1;
|
|
|
|
while (expr_end < end && brace_count > 0) {
|
|
if (*expr_end == '{') {
|
|
brace_count++;
|
|
} else if (*expr_end == '}') {
|
|
brace_count--;
|
|
}
|
|
if (brace_count > 0) {
|
|
expr_end++;
|
|
}
|
|
}
|
|
|
|
if (brace_count != 0 || expr_end >= end) {
|
|
fprintf(stderr, "Error: Unterminated expression in format string\n");
|
|
free_ast(head);
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse expression inside {} */
|
|
const char *tmp = expr_start;
|
|
ASTNode *expr = parse_expression(&tmp, expr_end);
|
|
if (!expr) {
|
|
fprintf(stderr, "Error: Invalid expression in format string\n");
|
|
free_ast(head);
|
|
return NULL;
|
|
}
|
|
|
|
*current = expr;
|
|
while (*current) {
|
|
current = &(*current)->next;
|
|
}
|
|
|
|
s = expr_end + 1;
|
|
lit_start = s;
|
|
*p = s;
|
|
} else if (*s == '}') {
|
|
if (s + 1 < end && s[1] == '}') {
|
|
/* Escaped }} */
|
|
if (lit_start < s) {
|
|
ASTNode *text =
|
|
new_literal_string_node(lit_start, (size_t)(s - lit_start), false);
|
|
*current = text;
|
|
current = &text->next;
|
|
}
|
|
ASTNode *text = new_literal_string_node("}", 1, true);
|
|
*current = text;
|
|
current = &text->next;
|
|
|
|
s += 2;
|
|
lit_start = s;
|
|
*p = s;
|
|
continue;
|
|
} else {
|
|
/* Unescaped } without matching { - treat as literal */
|
|
s++;
|
|
}
|
|
} else {
|
|
s++;
|
|
}
|
|
}
|
|
|
|
/* Add any remaining literal text */
|
|
if (lit_start < s) {
|
|
ASTNode *text = new_literal_string_node(lit_start, (size_t)(s - lit_start), false);
|
|
*current = text;
|
|
current = &text->next;
|
|
}
|
|
|
|
*p = s;
|
|
return new_fmt_string_node(head);
|
|
}
|
|
|
|
static ASTNode *parse_string(const char **p, const char *end) {
|
|
skip_whitespace(p, end);
|
|
|
|
if (*p >= end) {
|
|
return NULL;
|
|
}
|
|
|
|
char fmt = '"';
|
|
bool is_not_raw = true;
|
|
|
|
if (**p == 'r' || **p == 'f' || **p == 'R' || **p == 'F') {
|
|
fmt = **p;
|
|
is_not_raw = (fmt != 'r') && (fmt != 'R');
|
|
++(*p);
|
|
}
|
|
|
|
if (**p != '"') {
|
|
fprintf(stderr, "Error: Expected 'r' ('R') or 'f' ('F') for string type, got '%c'\n", **p);
|
|
return NULL;
|
|
}
|
|
|
|
++(*p); /* skip opening quote */
|
|
|
|
const char *start = *p;
|
|
while (*p < end && **p != '"') {
|
|
if (is_not_raw && **p == '\\') {
|
|
++(*p); /* Skip past backslash */
|
|
|
|
if (*p >= end) {
|
|
/* No next character (invalid) */
|
|
fprintf(stderr, "Error: Unterminated backslash\n");
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
++(*p);
|
|
}
|
|
|
|
if (*p >= end || **p != '"') {
|
|
fprintf(stderr, "Error: Unterminated string\n");
|
|
return NULL;
|
|
}
|
|
|
|
const char *s_end = *p;
|
|
|
|
size_t len = (size_t)(s_end - start);
|
|
++(*p); /* skip closing quote */
|
|
|
|
switch (fmt) {
|
|
case 'r':
|
|
case 'R':
|
|
return new_literal_string_node(start, len, true);
|
|
case 'f':
|
|
case 'F':
|
|
return parse_fmt_string(&start, s_end);
|
|
default:
|
|
return new_literal_string_node(start, len, false);
|
|
}
|
|
}
|
|
|
|
static ASTNode *parse_array(const char **p, const char *end) {
|
|
size_t argc = 0;
|
|
++(*p); /* skip [ */
|
|
ASTNode **items = parse_list(p, end, ']', &argc);
|
|
return new_array_node(items, argc);
|
|
}
|
|
|
|
static ASTNode *parse_identifier(const char **p, const char *end) {
|
|
skip_whitespace(p, end);
|
|
|
|
if (*p >= end || !(isalpha(**p) || **p == '_' || **p == '$')) {
|
|
fprintf(stderr, "Error: Invalid identifier start\n");
|
|
return NULL;
|
|
}
|
|
|
|
const char *start = *p;
|
|
while (*p < end && (isalnum(**p) || **p == '_')) {
|
|
++(*p);
|
|
}
|
|
const size_t len = (size_t)(*p - start);
|
|
|
|
if (len == 4 && strncmp(start, "true", 4) == 0) {
|
|
return new_boolean_node(true);
|
|
} else if (len == 5 && strncmp(start, "false", 5) == 0) {
|
|
return new_boolean_node(false);
|
|
} else if (len == 4 && strncmp(start, "null", 4) == 0) {
|
|
return new_null_node();
|
|
} else if (len == 3 && strncmp(start, "inf", 3) == 0) {
|
|
return new_inf_node();
|
|
}
|
|
|
|
return new_variable_node(start, len);
|
|
}
|
|
|
|
static ASTNode *parse_primary_expr(const char **p, const char *end) {
|
|
skip_whitespace(p, end);
|
|
|
|
if (*p >= end) {
|
|
return NULL;
|
|
}
|
|
|
|
if (**p == ':') {
|
|
return parse_range(NULL, p, end);
|
|
}
|
|
|
|
if (isdigit(**p)) {
|
|
return parse_number(p, end);
|
|
}
|
|
|
|
if (**p == '[') {
|
|
return parse_array(p, end);
|
|
}
|
|
|
|
if (**p == '{') {
|
|
return parse_hashmap(p, end);
|
|
}
|
|
|
|
if (**p == '"' || ((**p == 'r' || **p == 'f' || **p == 'R' || **p == 'F') && (*p)[1] == '"')) {
|
|
return parse_string(p, end);
|
|
}
|
|
|
|
if (isalpha(**p) || **p == '_' || **p == '$') {
|
|
return parse_identifier(p, end);
|
|
}
|
|
|
|
/* Parenthesised expression */
|
|
if (**p == '(') {
|
|
++(*p); /* Skip initial '(' */
|
|
|
|
const char *start = *p;
|
|
int paren_depth = 1;
|
|
|
|
while (*p < end && paren_depth > 0) {
|
|
if (**p == '(') {
|
|
++paren_depth;
|
|
} else if (**p == ')') {
|
|
--paren_depth;
|
|
}
|
|
++(*p);
|
|
}
|
|
|
|
if (paren_depth != 0) {
|
|
return NULL; /* Unbalanced parentheses */
|
|
}
|
|
|
|
const char *subexpr_end = *p - 1; /* Points to the matching ')' */
|
|
|
|
/* Parse the subexpression */
|
|
const char *subexpr_ptr = start;
|
|
ASTNode *expr = parse_expression(&subexpr_ptr, subexpr_end);
|
|
if (!expr) {
|
|
return NULL;
|
|
}
|
|
|
|
skip_whitespace(p, end);
|
|
return expr;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ASTNode *parse_postfix_expr(ASTNode *base, const char **p, const char *end) {
|
|
while (*p < end) {
|
|
skip_whitespace(p, end);
|
|
|
|
if (*p >= end)
|
|
break;
|
|
|
|
if (**p == '[') {
|
|
++(*p); /* skip '[' */
|
|
|
|
/* Find matching ']' to respect nested brackets */
|
|
const char *bracket_start = *p;
|
|
int bracket_depth = 1;
|
|
const char *bracket_end = bracket_start;
|
|
|
|
while (bracket_end < end && bracket_depth > 0) {
|
|
if (*bracket_end == '[') {
|
|
++bracket_depth;
|
|
} else if (*bracket_end == ']') {
|
|
--bracket_depth;
|
|
if (bracket_depth == 0) {
|
|
break;
|
|
}
|
|
}
|
|
++bracket_end;
|
|
}
|
|
|
|
if (bracket_depth != 0 || bracket_end >= end) {
|
|
fprintf(stderr, "Error: Missing closing ']' in postfix expression\n");
|
|
free_ast(base);
|
|
return NULL;
|
|
}
|
|
|
|
ASTNode *index = parse_expression(p, bracket_end);
|
|
if (!index) {
|
|
free_ast(base);
|
|
return NULL;
|
|
}
|
|
|
|
*p = bracket_end + 1; /* skip ']' */
|
|
|
|
base = new_lookup_node(base, index);
|
|
continue;
|
|
}
|
|
|
|
if (**p == '.') {
|
|
++(*p); /* skip '.' */
|
|
skip_whitespace(p, end);
|
|
|
|
if (*p >= end || !(isalpha(**p) || **p == '_')) {
|
|
fprintf(stderr, "Error: Invalid member name after '.'\n");
|
|
free_ast(base);
|
|
return NULL;
|
|
}
|
|
|
|
const char *member_start = *p;
|
|
while (*p < end && (isalnum(**p) || **p == '_')) {
|
|
++(*p);
|
|
}
|
|
|
|
base = new_member_lookup_node(base, member_start, (size_t)(*p - member_start));
|
|
continue;
|
|
}
|
|
|
|
if (**p == '(') {
|
|
++(*p); /* skip '(' */
|
|
|
|
size_t argc = 0;
|
|
ASTNode **args = parse_list(p, end, ')', &argc);
|
|
if (!args && argc > 0) {
|
|
free_ast(base);
|
|
return NULL;
|
|
}
|
|
|
|
base = new_function_call_node(base, args, argc);
|
|
continue;
|
|
}
|
|
|
|
/* No more postfix expressions */
|
|
break;
|
|
}
|
|
|
|
return base;
|
|
}
|
|
|
|
static bool match_binary_operator(const char **p, const char *end, BinaryOperator *op) {
|
|
if (*p >= end) {
|
|
return false;
|
|
}
|
|
|
|
#define TRY_MATCH(op_str, op_enum) \
|
|
do { \
|
|
size_t len = strlen(op_str); \
|
|
if ((size_t)(end - *p) >= len && strncmp(*p, op_str, len) == 0) { \
|
|
*op = op_enum; \
|
|
*p += len; \
|
|
return true; \
|
|
} \
|
|
} while (0)
|
|
|
|
TRY_MATCH("==", BinaryOperator_eq);
|
|
TRY_MATCH("<=", BinaryOperator_leq);
|
|
TRY_MATCH(">=", BinaryOperator_geq);
|
|
TRY_MATCH("&&", BinaryOperator_and);
|
|
TRY_MATCH("||", BinaryOperator_or);
|
|
TRY_MATCH("<<", BinaryOperator_shl);
|
|
TRY_MATCH(">>", BinaryOperator_shr);
|
|
TRY_MATCH("!=", BinaryOperator_neq);
|
|
|
|
TRY_MATCH("=", BinaryOperator_keyword);
|
|
TRY_MATCH("+", BinaryOperator_add);
|
|
TRY_MATCH("-", BinaryOperator_sub);
|
|
TRY_MATCH("*", BinaryOperator_mul);
|
|
TRY_MATCH("/", BinaryOperator_div);
|
|
TRY_MATCH("%", BinaryOperator_mod);
|
|
TRY_MATCH("<", BinaryOperator_lt);
|
|
TRY_MATCH(">", BinaryOperator_gt);
|
|
TRY_MATCH("&", BinaryOperator_band);
|
|
TRY_MATCH("|", BinaryOperator_bor);
|
|
TRY_MATCH("^", BinaryOperator_bxor);
|
|
TRY_MATCH("@", BinaryOperator_in);
|
|
|
|
#undef TRY_MATCH
|
|
|
|
return false;
|
|
}
|
|
|
|
static bool match_unary_operator(const char **p, const char *end, UnaryOperator *op) {
|
|
if (*p >= end)
|
|
return false;
|
|
|
|
switch (**p) {
|
|
case '-':
|
|
*op = UnaryOperator_neg;
|
|
++(*p);
|
|
return true;
|
|
case '+':
|
|
*op = UnaryOperator_pos;
|
|
++(*p);
|
|
return true;
|
|
case '!':
|
|
*op = UnaryOperator_not;
|
|
++(*p);
|
|
return true;
|
|
case '~':
|
|
*op = UnaryOperator_bnot;
|
|
++(*p);
|
|
return true;
|
|
case '*':
|
|
*op = UnaryOperator_spread;
|
|
++(*p);
|
|
if (*p < end && **p == '*') {
|
|
*op = UnaryOperator_kspread;
|
|
++(*p);
|
|
}
|
|
return true;
|
|
default:
|
|
return false;
|
|
}
|
|
}
|
|
|
|
static uint8_t get_operator_precedence(BinaryOperator op) {
|
|
switch (op) {
|
|
/* Multiplicative */
|
|
case BinaryOperator_mul:
|
|
case BinaryOperator_div:
|
|
case BinaryOperator_mod:
|
|
return 11;
|
|
|
|
/* Additive */
|
|
case BinaryOperator_add:
|
|
case BinaryOperator_sub:
|
|
return 10;
|
|
|
|
/* Shift */
|
|
case BinaryOperator_shl:
|
|
case BinaryOperator_shr:
|
|
return 9;
|
|
|
|
/* Relational */
|
|
case BinaryOperator_lt:
|
|
case BinaryOperator_gt:
|
|
case BinaryOperator_leq:
|
|
case BinaryOperator_geq:
|
|
case BinaryOperator_in:
|
|
return 8;
|
|
|
|
/* Equality */
|
|
case BinaryOperator_eq:
|
|
case BinaryOperator_neq:
|
|
return 7;
|
|
|
|
/* Bitwise AND */
|
|
case BinaryOperator_band:
|
|
return 6;
|
|
|
|
/* Bitwise XOR */
|
|
case BinaryOperator_bxor:
|
|
return 5;
|
|
|
|
/* Bitwise OR */
|
|
case BinaryOperator_bor:
|
|
return 4;
|
|
|
|
/* Logical AND */
|
|
case BinaryOperator_and:
|
|
return 3;
|
|
|
|
/* Logical OR */
|
|
case BinaryOperator_or:
|
|
return 2;
|
|
|
|
/* Keyword */
|
|
case BinaryOperator_keyword:
|
|
return 1;
|
|
|
|
default:
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
static ASTNode *
|
|
parse_binary_expr_rhs(uint8_t min_prec, ASTNode *lhs, const char **p, const char *end) {
|
|
while (true) {
|
|
skip_whitespace(p, end);
|
|
BinaryOperator op;
|
|
const char *op_start = *p;
|
|
|
|
if (!match_binary_operator(p, end, &op)) {
|
|
break;
|
|
}
|
|
|
|
uint8_t prec = get_operator_precedence(op);
|
|
if (prec < min_prec) {
|
|
*p = op_start;
|
|
break;
|
|
}
|
|
|
|
ASTNode *rhs = parse_unary_expr_lhs(p, end);
|
|
if (!rhs) {
|
|
return NULL;
|
|
}
|
|
|
|
rhs = parse_postfix_expr(rhs, p, end);
|
|
rhs = parse_binary_expr_rhs(prec + 1, rhs, p, end);
|
|
|
|
lhs = new_binary_node(op, lhs, rhs);
|
|
}
|
|
|
|
return lhs;
|
|
}
|
|
|
|
static ASTNode *parse_unary_expr_lhs(const char **p, const char *end) {
|
|
skip_whitespace(p, end);
|
|
|
|
UnaryOperator op;
|
|
if (match_unary_operator(p, end, &op)) {
|
|
ASTNode *operand = parse_unary_expr_lhs(p, end);
|
|
if (!operand) {
|
|
return NULL;
|
|
}
|
|
return new_unary_node(op, operand);
|
|
}
|
|
|
|
/* No unary operator, parse primary + postfix */
|
|
ASTNode *primary = parse_primary_expr(p, end);
|
|
if (!primary) {
|
|
return NULL;
|
|
}
|
|
|
|
return parse_postfix_expr(primary, p, end);
|
|
}
|
|
|
|
static ASTNode *parse_conditional_expr(ASTNode *condition, const char **p, const char *end) {
|
|
skip_whitespace(p, end);
|
|
|
|
if (*p >= end || **p != '?') {
|
|
return condition;
|
|
}
|
|
|
|
++(*p);
|
|
|
|
const char *colon_pos = NULL;
|
|
|
|
int paren = 0, brack = 0;
|
|
const char *cur = *p;
|
|
while (cur < end) {
|
|
char c = *cur;
|
|
if (c == '(')
|
|
++paren;
|
|
else if (c == ')') {
|
|
if (paren > 0)
|
|
--paren;
|
|
} else if (c == '[')
|
|
++brack;
|
|
else if (c == ']') {
|
|
if (brack > 0)
|
|
--brack;
|
|
} else if (c == ':' && paren == 0 && brack == 0) {
|
|
colon_pos = cur;
|
|
break;
|
|
}
|
|
cur++;
|
|
}
|
|
|
|
if (!colon_pos) {
|
|
fprintf(stderr, "Error: Expected ':' in conditional expression\n");
|
|
free_ast(condition);
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse the true branch expression (from *p to colon) */
|
|
const char *true_expr_end = colon_pos;
|
|
ASTNode *true_expr = parse_expression(p, true_expr_end);
|
|
if (!true_expr) {
|
|
free_ast(condition);
|
|
return NULL;
|
|
}
|
|
|
|
*p = colon_pos + 1; // skip past ':'
|
|
skip_whitespace(p, end);
|
|
|
|
// Parse the false branch
|
|
ASTNode *false_expr = parse_expression(p, end);
|
|
if (!false_expr) {
|
|
free_ast(condition);
|
|
free_ast(true_expr);
|
|
return NULL;
|
|
}
|
|
|
|
return new_conditional_node(condition, true_expr, false_expr, false, false);
|
|
}
|
|
|
|
static ASTNode *parse_range(ASTNode *from, const char **p, const char *end) {
|
|
ASTNode *start = from;
|
|
ASTNode *stop = NULL;
|
|
ASTNode *step = NULL;
|
|
|
|
if (*p >= end || **p != ':') {
|
|
return from;
|
|
}
|
|
|
|
++(*p); /* consume ':' */
|
|
skip_whitespace(p, end);
|
|
|
|
const char *stop_start = *p;
|
|
int paren_depth = 0;
|
|
|
|
/* scan to next ':' or '/' or end */
|
|
while (*p < end) {
|
|
if (**p == '(') {
|
|
++paren_depth;
|
|
} else if (**p == ')') {
|
|
--paren_depth;
|
|
if (paren_depth < 0) {
|
|
return NULL;
|
|
}
|
|
} else if (**p == '/' && paren_depth == 0) {
|
|
break;
|
|
}
|
|
++(*p);
|
|
}
|
|
|
|
const char *stop_end = *p;
|
|
if (stop_start < *p) {
|
|
stop = parse_expression(&stop_start, *p);
|
|
|
|
if (!stop || stop_start != stop_end) {
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
if (*p < end && **p == '/') {
|
|
/* support a:b/c form */
|
|
++(*p); /* consume '/' */
|
|
skip_whitespace(p, end);
|
|
|
|
paren_depth = 0;
|
|
const char *scan = *p;
|
|
while (scan < end) {
|
|
if (*scan == '(') {
|
|
++paren_depth;
|
|
} else if (*scan == ')') {
|
|
--paren_depth;
|
|
if (paren_depth < 0) {
|
|
return NULL;
|
|
}
|
|
}
|
|
++scan;
|
|
}
|
|
|
|
if (paren_depth != 0) {
|
|
return NULL; /* unbalanced parentheses in step */
|
|
}
|
|
|
|
step = parse_expression(p, end);
|
|
if (!step) {
|
|
return NULL; /* failed to parse step expression */
|
|
}
|
|
}
|
|
|
|
return new_range_node(start, stop, step);
|
|
}
|
|
|
|
static ASTNode *parse_expression(const char **p, const char *end) {
|
|
ASTNode *lhs = parse_unary_expr_lhs(p, end);
|
|
if (!lhs)
|
|
return NULL;
|
|
|
|
ASTNode *binary_expr = parse_binary_expr_rhs(1, lhs, p, end);
|
|
if (!binary_expr) {
|
|
free_ast(lhs);
|
|
return NULL;
|
|
}
|
|
|
|
ASTNode *cond = parse_conditional_expr(binary_expr, p, end);
|
|
|
|
return parse_range(cond, p, end);
|
|
}
|
|
|
|
static bool match_assignment_operator(const char **p, const char *end, AssignmentOperator *op) {
|
|
if (*p >= end)
|
|
return false;
|
|
|
|
#define TRY_MATCH(op_str, op_enum) \
|
|
do { \
|
|
size_t len = strlen(op_str); \
|
|
if ((size_t)(end - *p) >= len && strncmp(*p, op_str, len) == 0) { \
|
|
*op = op_enum; \
|
|
*p += len; \
|
|
return true; \
|
|
} \
|
|
} while (0)
|
|
|
|
TRY_MATCH("<<=", AssignmentOperator_shl);
|
|
TRY_MATCH(">>=", AssignmentOperator_shr);
|
|
TRY_MATCH("+=", AssignmentOperator_add);
|
|
TRY_MATCH("-=", AssignmentOperator_sub);
|
|
TRY_MATCH("*=", AssignmentOperator_mul);
|
|
TRY_MATCH("/=", AssignmentOperator_div);
|
|
TRY_MATCH("%=", AssignmentOperator_mod);
|
|
TRY_MATCH("&=", AssignmentOperator_band);
|
|
TRY_MATCH("|=", AssignmentOperator_bor);
|
|
TRY_MATCH("^=", AssignmentOperator_bxor);
|
|
TRY_MATCH("=", AssignmentOperator_simple);
|
|
|
|
#undef TRY_MATCH
|
|
return false;
|
|
}
|
|
|
|
static ASTNode *parse_set_stmt(const char *args_begin, const char *end, bool trim) {
|
|
skip_whitespace(&args_begin, end);
|
|
const char *varname_start = args_begin;
|
|
const char *op_pos = NULL;
|
|
AssignmentOperator op;
|
|
|
|
/* Scan for assignment operator at the top level only */
|
|
int paren_depth = 0;
|
|
const char *p = args_begin;
|
|
|
|
while (p < end) {
|
|
if (*p == '(') {
|
|
++paren_depth;
|
|
++p;
|
|
} else if (*p == ')') {
|
|
--paren_depth;
|
|
++p;
|
|
} else if (paren_depth == 0) {
|
|
const char *q = p;
|
|
|
|
if (match_assignment_operator(&q, end, &op)) {
|
|
op_pos = p;
|
|
p = q; /* advance past operator */
|
|
break;
|
|
}
|
|
|
|
++p;
|
|
} else {
|
|
++p;
|
|
}
|
|
}
|
|
|
|
if (paren_depth > 0) {
|
|
fprintf(stderr, "Error: Unmatched `(` in assignment expression.\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (!op_pos) {
|
|
fprintf(stderr, "Error: No assignment operator found for set.\n");
|
|
return NULL;
|
|
}
|
|
|
|
/* Parse the variable name (left-hand side) */
|
|
ASTNode *name = parse_expression(&varname_start, op_pos);
|
|
|
|
/* p is after the assignment operator */
|
|
skip_whitespace(&p, end);
|
|
if (p >= end) {
|
|
free_ast(name);
|
|
fprintf(stderr, "Error: No value for set.\n");
|
|
return NULL;
|
|
}
|
|
|
|
ASTNode *expr = parse_expression(&p, end);
|
|
if (!expr) {
|
|
free_ast(name);
|
|
return NULL;
|
|
}
|
|
|
|
return new_set_node(name, op, expr, trim);
|
|
}
|
|
|
|
static ASTNode *parse_unset_stmt(const char *args_begin, const char *args_end) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
const char *name_start = args_begin;
|
|
while (args_begin < args_end && (isalnum(*args_begin) || *args_begin == '_')) {
|
|
++args_begin;
|
|
}
|
|
const size_t len = (size_t)(args_begin - name_start);
|
|
skip_whitespace(&args_begin, args_end);
|
|
|
|
return new_unset_node(name_start, len);
|
|
}
|
|
|
|
static ASTNode *parse_unmacro_stmt(const char *args_begin, const char *args_end) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
const char *name_start = args_begin;
|
|
while (args_begin < args_end && (isalnum(*args_begin) || *args_begin == '_')) {
|
|
++args_begin;
|
|
}
|
|
const size_t len = (size_t)(args_begin - name_start);
|
|
skip_whitespace(&args_begin, args_end);
|
|
|
|
return new_unmacro_node(name_start, len);
|
|
}
|
|
|
|
static ASTNode *parse_import_stmt(const char *args_begin, const char *args_end, bool trim) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
if (args_begin >= args_end) {
|
|
fprintf(stderr, "Error: No import expression supplied\n");
|
|
return NULL;
|
|
}
|
|
|
|
const char *p = args_begin;
|
|
int paren = 0, brack = 0;
|
|
const char *split = NULL;
|
|
|
|
while (p < args_end) {
|
|
char c = *p;
|
|
if (c == '(')
|
|
++paren;
|
|
else if (c == ')') {
|
|
if (paren > 0)
|
|
--paren;
|
|
} else if (c == '[')
|
|
++brack;
|
|
else if (c == ']') {
|
|
if (brack > 0)
|
|
--brack;
|
|
} else if (c == ':' && paren == 0 && brack == 0) {
|
|
split = p;
|
|
break;
|
|
}
|
|
p++;
|
|
}
|
|
|
|
const char *expr_end = split ? split : args_end;
|
|
const char *expr_ptr = args_begin;
|
|
ASTNode *template_expr = parse_expression(&expr_ptr, expr_end);
|
|
if (!template_expr) {
|
|
fprintf(stderr, "Error: Invalid import expression\n");
|
|
return NULL;
|
|
}
|
|
|
|
if (!split) {
|
|
return new_import_node(template_expr, NULL, 0, trim);
|
|
}
|
|
|
|
const char *ns = split + 1;
|
|
skip_whitespace(&ns, args_end);
|
|
if (ns >= args_end || !(isalpha(*ns) || *ns == '_')) {
|
|
fprintf(stderr, "Error: Missing or invalid namespace for import\n");
|
|
return NULL;
|
|
}
|
|
|
|
const char *ns_end = ns;
|
|
while (ns_end < args_end && (isalnum(*ns_end) || *ns_end == '_')) {
|
|
ns_end++;
|
|
}
|
|
|
|
return new_import_node(template_expr, ns, (size_t)(ns_end - ns), trim);
|
|
}
|
|
|
|
static ASTNode *
|
|
parse_while_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
if (args_begin >= args_end) {
|
|
fprintf(stderr, "Error: No condition for while loop supplied\n");
|
|
return NULL;
|
|
}
|
|
ASTNode *condition = parse_expression(&args_begin, args_end);
|
|
if (!condition) {
|
|
fprintf(stderr, "Error: No condition in while loop.\n");
|
|
return NULL;
|
|
}
|
|
|
|
return new_while_node(condition, body, trim);
|
|
}
|
|
|
|
static ASTNode *
|
|
parse_macro_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
if (args_begin >= args_end) {
|
|
fprintf(stderr, "Error: No name for macro supplied\n");
|
|
return NULL;
|
|
}
|
|
const char *name_start = args_begin;
|
|
while (args_begin < args_end && (isalnum(*args_begin) || *args_begin == '_')) {
|
|
++args_begin;
|
|
}
|
|
const size_t len = (size_t)(args_begin - name_start);
|
|
skip_whitespace(&args_begin, args_end);
|
|
|
|
if (*args_begin == '(') {
|
|
++args_begin;
|
|
skip_whitespace(&args_begin, args_end);
|
|
size_t argc = 0;
|
|
ASTNode **args = parse_list(&args_begin, args_end, ')', &argc);
|
|
return new_macro_node(name_start, len, args, argc, body, trim);
|
|
} else {
|
|
return new_macro_node(name_start, len, NULL, 0, body, trim);
|
|
}
|
|
}
|
|
|
|
static ASTNode *
|
|
parse_namespace_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
if (args_begin >= args_end) {
|
|
fprintf(stderr, "Error: No name for namespace supplied\n");
|
|
return NULL;
|
|
}
|
|
const char *name_start = args_begin;
|
|
while (args_begin < args_end && (isalnum(*args_begin) || *args_begin == '_')) {
|
|
++args_begin;
|
|
}
|
|
const size_t len = (size_t)(args_begin - name_start);
|
|
skip_whitespace(&args_begin, args_end);
|
|
|
|
return new_namespace_node(name_start, len, body, trim);
|
|
}
|
|
|
|
static ASTNode *
|
|
parse_for_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
if (args_begin >= args_end) {
|
|
fprintf(stderr, "Error: No for loop expression supplied\n");
|
|
return NULL;
|
|
}
|
|
|
|
const char *varname_start = args_begin;
|
|
while (args_begin < args_end && (isalnum(*args_begin) || *args_begin == '_')) {
|
|
++args_begin;
|
|
}
|
|
const size_t len = (size_t)(args_begin - varname_start);
|
|
|
|
skip_whitespace(&args_begin, args_end);
|
|
if (args_begin >= args_end || *args_begin != ':') {
|
|
fprintf(stderr, "Error: No for loop iterable supplied\n");
|
|
return NULL;
|
|
}
|
|
|
|
++args_begin; /* skip past : */
|
|
skip_whitespace(&args_begin, args_end);
|
|
ASTNode *expr = parse_expression(&args_begin, args_end);
|
|
|
|
if (!expr) {
|
|
fprintf(stderr, "Error: Invalid iterable\n");
|
|
return NULL;
|
|
}
|
|
|
|
return new_for_node(varname_start, len, expr, body, trim);
|
|
}
|
|
|
|
static ASTNode **parse_list(const char **p, const char *end, char close_char, size_t *out_argc) {
|
|
ASTNode **args = NULL;
|
|
size_t capacity = 0, count = 0;
|
|
*out_argc = 0;
|
|
|
|
skip_whitespace(p, end);
|
|
|
|
/* Handle empty list case */
|
|
if (*p < end && **p == close_char) {
|
|
++(*p);
|
|
return NULL;
|
|
}
|
|
|
|
while (*p < end) {
|
|
/* Parse argument expression */
|
|
ASTNode *arg = parse_expression(p, end);
|
|
if (!arg) {
|
|
/* Cleanup on parse error */
|
|
for (size_t idx = 0; idx < count; ++idx) {
|
|
free_ast(args[idx]);
|
|
}
|
|
free(args);
|
|
return NULL;
|
|
}
|
|
|
|
/* Dynamically grow arguments array */
|
|
if (count >= capacity) {
|
|
capacity = capacity ? capacity * 2 : 4;
|
|
ASTNode **new_args = realloc(args, capacity * sizeof(ASTNode *));
|
|
if (!new_args) {
|
|
free_ast(arg);
|
|
|
|
for (size_t idx = 0; idx < count; ++idx) {
|
|
free_ast(args[idx]);
|
|
}
|
|
|
|
free(args);
|
|
return NULL;
|
|
}
|
|
args = new_args;
|
|
}
|
|
args[count++] = arg;
|
|
|
|
skip_whitespace(p, end);
|
|
|
|
/* Check for terminator or separator */
|
|
if (*p < end && **p == close_char) {
|
|
++(*p);
|
|
break;
|
|
}
|
|
|
|
if (*p < end && **p == ',') {
|
|
++(*p);
|
|
skip_whitespace(p, end);
|
|
} else if (*p >= end) {
|
|
fprintf(stderr, "Error: Unexpected end of input in list\n");
|
|
break;
|
|
} else {
|
|
fprintf(stderr, "Error: Expected ',' or '%c'\n", close_char);
|
|
for (size_t idx = 0; idx < count; ++idx) {
|
|
free_ast(args[idx]);
|
|
}
|
|
free(args);
|
|
return NULL;
|
|
}
|
|
}
|
|
|
|
*out_argc = count;
|
|
return args;
|
|
}
|
|
|
|
static inline const char *find_hashmap_key_value_colon(const char *start, const char *end) {
|
|
int paren = 0, bracket = 0, brace = 0;
|
|
bool in_single_quote = false, in_double_quote = false;
|
|
|
|
for (const char *c = start; c < end; ++c) {
|
|
char ch = *c;
|
|
|
|
if (ch == '\'' && !in_double_quote) {
|
|
in_single_quote = !in_single_quote;
|
|
continue;
|
|
}
|
|
if (ch == '"' && !in_single_quote) {
|
|
in_double_quote = !in_double_quote;
|
|
continue;
|
|
}
|
|
if (in_single_quote || in_double_quote) {
|
|
continue;
|
|
}
|
|
|
|
if (ch == '(') {
|
|
paren++;
|
|
} else if (ch == ')') {
|
|
paren--;
|
|
} else if (ch == '[') {
|
|
bracket++;
|
|
} else if (ch == ']') {
|
|
bracket--;
|
|
} else if (ch == '{') {
|
|
brace++;
|
|
} else if (ch == '}') {
|
|
brace--;
|
|
}
|
|
|
|
if (ch == ':' && paren == 0 && bracket == 0 && brace == 0) {
|
|
return c;
|
|
}
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static ASTNode *parse_hashmap(const char **p, const char *end) {
|
|
ASTNode **keys = NULL, **values = NULL;
|
|
size_t capacity = 0, count = 0;
|
|
int error = 0;
|
|
|
|
++(*p); /* Skip opening '{' */
|
|
skip_whitespace(p, end);
|
|
|
|
/* Handle empty hashmap */
|
|
if (*p < end && **p == '}') {
|
|
++(*p);
|
|
return new_hashmap_node(NULL, NULL, 0);
|
|
}
|
|
|
|
while (*p < end && !error) {
|
|
const char *colon_pos = find_hashmap_key_value_colon(*p, end);
|
|
if (!colon_pos) {
|
|
fprintf(stderr, "Error: Expected ':' after key in hashmap\n");
|
|
error = 1;
|
|
break;
|
|
}
|
|
|
|
/* Parse key expression within [*p, colon_pos) */
|
|
const char *key_end = colon_pos;
|
|
const char *key_start = *p;
|
|
|
|
ASTNode *key = parse_expression(&key_start, key_end);
|
|
if (!key) {
|
|
error = 1;
|
|
break;
|
|
}
|
|
|
|
/* Advance *p to colon_pos + 1 (skip colon) */
|
|
*p = colon_pos + 1;
|
|
skip_whitespace(p, end);
|
|
|
|
/* Now parse value normally */
|
|
ASTNode *value = parse_expression(p, end);
|
|
if (!value) {
|
|
free_ast(key);
|
|
error = 1;
|
|
break;
|
|
}
|
|
|
|
if (count >= capacity) {
|
|
capacity = capacity ? capacity * 2 : 4;
|
|
ASTNode **new_keys = realloc(keys, capacity * sizeof(ASTNode *));
|
|
ASTNode **new_values = realloc(values, capacity * sizeof(ASTNode *));
|
|
|
|
if (!new_keys || !new_values) {
|
|
free_ast(key);
|
|
free_ast(value);
|
|
error = 1;
|
|
break;
|
|
}
|
|
keys = new_keys;
|
|
values = new_values;
|
|
}
|
|
|
|
keys[count] = key;
|
|
values[count] = value;
|
|
count++;
|
|
|
|
skip_whitespace(p, end);
|
|
|
|
/* Check terminator or separator */
|
|
if (*p < end && **p == '}') {
|
|
++(*p); /* Skip closing character */
|
|
break;
|
|
}
|
|
|
|
if (*p < end && **p == ',') {
|
|
++(*p);
|
|
skip_whitespace(p, end);
|
|
} else {
|
|
fprintf(stderr, "Error: Expected ',' or '}'\n");
|
|
error = 1;
|
|
}
|
|
}
|
|
|
|
if (error) {
|
|
for (size_t idx = 0; idx < count; ++idx) {
|
|
free_ast(keys[idx]);
|
|
free_ast(values[idx]);
|
|
}
|
|
|
|
free(keys);
|
|
free(values);
|
|
return NULL;
|
|
}
|
|
|
|
return new_hashmap_node(keys, values, count);
|
|
}
|
|
|
|
static ASTNode *
|
|
parse_block_stmt(const char *args_begin, const char *args_end, ASTNode *body, bool trim) {
|
|
skip_whitespace(&args_begin, args_end);
|
|
const char *name_start = args_begin;
|
|
while (args_begin < args_end && (isalnum(*args_begin) || *args_begin == '_')) {
|
|
++args_begin;
|
|
}
|
|
const size_t len = (size_t)(args_begin - name_start);
|
|
|
|
return new_block_node(name_start, len, body, trim);
|
|
}
|
|
|
|
/* END PARSERS */
|
|
|
|
static void print_indent(size_t indent) {
|
|
for (size_t idx = 0; idx < indent; ++idx) {
|
|
printf(" ");
|
|
}
|
|
}
|
|
|
|
static void print_ast(const ASTNode *node, size_t indent) {
|
|
if (!node) {
|
|
print_indent(indent);
|
|
puts("(null)");
|
|
return;
|
|
}
|
|
|
|
while (node) {
|
|
print_indent(indent);
|
|
|
|
switch (node->type) {
|
|
case ASTNodeType_template:
|
|
printf("Template:\n");
|
|
print_ast(node->next, indent + 1);
|
|
return;
|
|
|
|
case ASTNodeType_text:
|
|
printf("Text [%zu]: \"%s\"\n", node->data.text.len, node->data.text.text);
|
|
break;
|
|
|
|
case ASTNodeType_comment:
|
|
printf("Comment [%zu]: {#%s#}\n", node->data.comment.len, node->data.comment.text);
|
|
break;
|
|
|
|
case ASTNodeType_output_expr:
|
|
printf("OutputExpr:\n");
|
|
print_ast(node->data.output.expr, indent + 1);
|
|
break;
|
|
|
|
case ASTNodeType_set_stmt:
|
|
printf("SetStmt%s: '%s'\n",
|
|
node->data.set_stmt.trim ? " (trimmed)" : "",
|
|
eq_op_str(node->data.set_stmt.eq));
|
|
print_indent(indent + 1);
|
|
printf("Name:\n");
|
|
print_ast(node->data.set_stmt.name, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Expr:\n");
|
|
print_ast(node->data.set_stmt.expr, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_unset_stmt:
|
|
printf("UnsetStmt [%zu]: %s\n",
|
|
node->data.unset_stmt.name_len,
|
|
node->data.unset_stmt.name);
|
|
break;
|
|
|
|
case ASTNodeType_unmacro_stmt:
|
|
printf("UnmacroStmt [%zu]: %s\n",
|
|
node->data.unmacro_stmt.name_len,
|
|
node->data.unmacro_stmt.name);
|
|
break;
|
|
|
|
case ASTNodeType_conditional:
|
|
printf("Conditional:\n");
|
|
print_indent(indent + 1);
|
|
printf("Condition:\n");
|
|
print_ast(node->data.conditional.condition, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Body%s:\n", node->data.conditional.trim_body ? " (trimmed)" : "");
|
|
print_ast(node->data.conditional.body, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("ElseClause%s:\n", node->data.conditional.trim_clause ? " (trimmed)" : "");
|
|
print_ast(node->data.conditional.else_clause, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_for_loop:
|
|
printf("ForLoop%s: varname='%s'\n",
|
|
node->data.for_loop.trim ? " (trimmed)" : "",
|
|
node->data.for_loop.varname);
|
|
print_indent(indent + 1);
|
|
printf("Iterable:\n");
|
|
print_ast(node->data.for_loop.iterable, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Body:\n");
|
|
print_ast(node->data.for_loop.body, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_while_loop:
|
|
printf("WhileLoop%s:\n", node->data.while_loop.trim ? " (trimmed)" : "");
|
|
print_indent(indent + 1);
|
|
printf("Condition:\n");
|
|
print_ast(node->data.while_loop.condition, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Body:\n");
|
|
print_ast(node->data.while_loop.body, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_binary_expr:
|
|
printf("BinaryExpr: op='%s'\n", binary_op_str(node->data.binary.op));
|
|
print_indent(indent + 1);
|
|
printf("Left:\n");
|
|
print_ast(node->data.binary.left, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Right:\n");
|
|
print_ast(node->data.binary.right, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_unary_expr:
|
|
printf("UnaryExpr: op='%s'\n", unary_op_str(node->data.unary.op));
|
|
print_indent(indent + 1);
|
|
printf("Operand:\n");
|
|
print_ast(node->data.unary.operand, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_function_call:
|
|
printf("FunctionCall:\n");
|
|
print_indent(indent + 1);
|
|
printf("Name:\n");
|
|
print_ast(node->data.function_call.name, indent + 2);
|
|
if (node->data.function_call.args_count == 0) {
|
|
print_indent(indent + 1);
|
|
printf("Args: (null)\n");
|
|
} else {
|
|
for (size_t arg = 0; arg < node->data.function_call.args_count; ++arg) {
|
|
print_indent(indent + 1);
|
|
printf("Args [%zu]:\n", arg);
|
|
print_ast(node->data.function_call.args[arg], indent + 2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ASTNodeType_variable:
|
|
printf("Variable [%zu]: name='%s'\n",
|
|
node->data.variable.name_len,
|
|
node->data.variable.name);
|
|
break;
|
|
|
|
case ASTNodeType_literal_string:
|
|
printf("LiteralString [%zu]: %s\"%s\"\n",
|
|
node->data.string.value_len,
|
|
node->data.string.raw ? "r" : "",
|
|
node->data.string.value);
|
|
break;
|
|
|
|
case ASTNodeType_fmt_string:
|
|
printf("FmtString:\n");
|
|
print_ast(node->data.fmt_string.string, indent + 1);
|
|
break;
|
|
|
|
case ASTNodeType_literal_number:
|
|
printf("LiteralNumber: %ju\n", node->data.number.value);
|
|
break;
|
|
|
|
case ASTNodeType_literal_float:
|
|
printf("LiteralFloat: %.15lf\n", node->data.floatnum.value);
|
|
break;
|
|
|
|
case ASTNodeType_literal_boolean:
|
|
printf("LiteralBoolean: %s\n", node->data.boolean.value ? "true" : "false");
|
|
break;
|
|
|
|
case ASTNodeType_literal_null:
|
|
printf("LiteralNull\n");
|
|
break;
|
|
|
|
case ASTNodeType_literal_inf:
|
|
printf("LiteralInf\n");
|
|
break;
|
|
|
|
case ASTNodeType_parent_stmt:
|
|
printf("ParentStmt\n");
|
|
break;
|
|
|
|
case ASTNodeType_import_stmt:
|
|
printf("ImportStmt%s:\n", node->data.import_stmt.trim ? " (trimmed)" : "");
|
|
print_indent(indent + 1);
|
|
printf("Name:\n");
|
|
print_ast(node->data.import_stmt.filename, indent + 1);
|
|
print_indent(indent + 1);
|
|
if (node->data.import_stmt.namespace_size == 0) {
|
|
puts("Namespace: <global>");
|
|
} else {
|
|
printf("Namespace [%zu]: %s\n",
|
|
node->data.import_stmt.namespace_size,
|
|
node->data.import_stmt.namespace);
|
|
}
|
|
break;
|
|
|
|
case ASTNodeType_lookup:
|
|
printf("Lookup:\n");
|
|
print_indent(indent + 1);
|
|
printf("Operand:\n");
|
|
print_ast(node->data.lookup.operand, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Index:\n");
|
|
print_ast(node->data.lookup.index, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_member_lookup:
|
|
printf("MemberLookup:\n");
|
|
print_indent(indent + 1);
|
|
printf("Operand:\n");
|
|
print_ast(node->data.member_lookup.operand, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Member [%zu]: membername='%s'\n",
|
|
node->data.member_lookup.member_len,
|
|
node->data.member_lookup.member);
|
|
break;
|
|
|
|
case ASTNodeType_macro_stmt:
|
|
printf("MacroStmt%s:\n", node->data.macro_stmt.trim ? " (trimmed)" : "");
|
|
print_indent(indent + 1);
|
|
printf(
|
|
"Name [%zu]: %s\n", node->data.macro_stmt.name_len, node->data.macro_stmt.name);
|
|
if (node->data.macro_stmt.args_count == 0) {
|
|
print_indent(indent + 1);
|
|
printf("Args: (null)\n");
|
|
} else {
|
|
for (size_t idx = 0; idx < node->data.macro_stmt.args_count; ++idx) {
|
|
print_indent(indent + 1);
|
|
printf("Args [%zu]:\n", idx);
|
|
print_ast(node->data.macro_stmt.args[idx], indent + 2);
|
|
}
|
|
}
|
|
print_indent(indent + 1);
|
|
printf("Body:\n");
|
|
print_ast(node->data.macro_stmt.body, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_namespace_stmt:
|
|
printf("NamespaceStmt%s:\n", node->data.namespace_stmt.trim ? " (trimmed)" : "");
|
|
print_indent(indent + 1);
|
|
printf("Name [%zu]: %s\n",
|
|
node->data.namespace_stmt.name_len,
|
|
node->data.namespace_stmt.name);
|
|
print_indent(indent + 1);
|
|
printf("Body:\n");
|
|
print_ast(node->data.namespace_stmt.body, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_literal_array:
|
|
printf("LiteralArray:\n");
|
|
if (node->data.array.items_count == 0) {
|
|
print_indent(indent + 1);
|
|
printf("Items: (null)\n");
|
|
} else {
|
|
for (size_t idx = 0; idx < node->data.array.items_count; ++idx) {
|
|
print_indent(indent + 1);
|
|
printf("Items [%zu]:\n", idx);
|
|
print_ast(node->data.array.items[idx], indent + 2);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ASTNodeType_literal_hashmap:
|
|
printf("LiteralHashmap:\n");
|
|
if (node->data.array.items_count == 0) {
|
|
print_indent(indent + 1);
|
|
printf("Pairs: (null)\n");
|
|
} else {
|
|
for (size_t idx = 0; idx < node->data.hashmap.count; ++idx) {
|
|
print_indent(indent + 1);
|
|
printf("Pairs [%zu]:\n", idx);
|
|
print_indent(indent + 2);
|
|
printf("Key:\n");
|
|
print_ast(node->data.hashmap.keys[idx], indent + 3);
|
|
print_indent(indent + 2);
|
|
printf("Value:\n");
|
|
print_ast(node->data.hashmap.values[idx], indent + 3);
|
|
}
|
|
}
|
|
break;
|
|
|
|
case ASTNodeType_finish_stmt:
|
|
printf("FinishStmt\n");
|
|
break;
|
|
|
|
case ASTNodeType_break_stmt:
|
|
printf("BreakStmt\n");
|
|
break;
|
|
|
|
case ASTNodeType_continue_stmt:
|
|
printf("ContinueStmt\n");
|
|
break;
|
|
|
|
case ASTNodeType_block_stmt:
|
|
printf("BlockStmt%s [%zu]: %s\n",
|
|
node->data.block.trim ? " (trimmed)" : "",
|
|
node->data.block.name_len,
|
|
node->data.block.name ? node->data.block.name : "<whitespace control>");
|
|
print_ast(node->data.block.body, indent + 2);
|
|
break;
|
|
|
|
case ASTNodeType_range:
|
|
printf("Range:\n");
|
|
print_indent(indent + 1);
|
|
printf("From:\n");
|
|
print_ast(node->data.range.from, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("To:\n");
|
|
print_ast(node->data.range.to, indent + 2);
|
|
print_indent(indent + 1);
|
|
printf("Step:\n");
|
|
print_ast(node->data.range.step, indent + 2);
|
|
break;
|
|
|
|
default:
|
|
printf("Unknown ASTNodeType: %d\n", node->type);
|
|
}
|
|
|
|
node = node->next;
|
|
}
|
|
}
|
|
|
|
static bool parse_template(ASTNode *root, const char *input, size_t len) {
|
|
ASTNode **current = &root->next; /* pointer to next field to append nodes */
|
|
|
|
const char *p = input;
|
|
const char *end = input + len;
|
|
const char *text_start = p;
|
|
|
|
while (p <= end && *p) {
|
|
if (p[0] == '{' && p[1] == '#') {
|
|
/* Comment node */
|
|
|
|
if (p > text_start) {
|
|
*current = new_text_node(text_start, (size_t)(p - text_start));
|
|
current = &(*current)->next;
|
|
}
|
|
|
|
/* Find comment end #} */
|
|
p += 2; /* skip {# */
|
|
const char *comment_start = p;
|
|
while (*p && !(p[0] == '#' && p[1] == '}')) {
|
|
++p;
|
|
}
|
|
|
|
if (*p == '\0') {
|
|
fprintf(stderr, "Error: unterminated comment. Expected #}\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
const size_t comment_len = (size_t)(p - comment_start);
|
|
*current = new_comment_node(comment_start, comment_len);
|
|
current = &(*current)->next;
|
|
|
|
p += 2; /* skip #} */
|
|
text_start = p;
|
|
} else if (p[0] == '{' && p[1] == '{') {
|
|
/* Output node */
|
|
|
|
if (p > text_start) {
|
|
*current = new_text_node(text_start, (size_t)(p - text_start));
|
|
current = &(*current)->next;
|
|
}
|
|
|
|
p += 2; /* skip {{ */
|
|
const char *expr_start = p;
|
|
/* Find closing "}}" */
|
|
while (*p && !(p[0] == '}' && p[1] == '}')) {
|
|
if (*p == '"') {
|
|
++p;
|
|
|
|
while (*p) {
|
|
if (*p == '\\') {
|
|
p += 2;
|
|
} else if (*p == '"') {
|
|
++p;
|
|
break;
|
|
} else {
|
|
++p;
|
|
}
|
|
}
|
|
|
|
if (*(p - 1) != '"') {
|
|
fprintf(stderr, "Error: Unterminated string literal\n");
|
|
return false;
|
|
}
|
|
continue;
|
|
}
|
|
++p;
|
|
}
|
|
|
|
if (*p == '\0') {
|
|
fprintf(stderr, "Error: unterminated output expression. Expected }}\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *expr_node = parse_expression(&expr_start, p);
|
|
if (!expr_node) {
|
|
fprintf(stderr, "Error: invalid expression inside {{ }}\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = new_output_node(expr_node);
|
|
current = &(*current)->next;
|
|
|
|
p += 2; /* skip }} */
|
|
text_start = p;
|
|
} else if (p[0] == '{' && p[1] == '%') {
|
|
/* Statement node */
|
|
|
|
if (p > text_start) {
|
|
*current = new_text_node(text_start, (size_t)(p - text_start));
|
|
current = &(*current)->next;
|
|
}
|
|
|
|
p += 2; /* skip {% */
|
|
int trim = 0;
|
|
if (*p == '-') {
|
|
trim = 1;
|
|
++p;
|
|
}
|
|
skip_whitespace(&p, end); /* skip any whitespace after {% */
|
|
|
|
/* const char *stmt_start = p; */
|
|
|
|
/* Parse the keyword */
|
|
const char *kw_start = p;
|
|
while (p < end && isalpha(*p)) {
|
|
++p;
|
|
}
|
|
const char *kw_end = p;
|
|
|
|
if (kw_start == kw_end) {
|
|
fprintf(stderr, "Error: expected statement keyword after `{%%`\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
size_t kw_len = (size_t)(kw_end - kw_start);
|
|
skip_whitespace(&p, end);
|
|
|
|
/* Find end of the {% ... %} tag */
|
|
const char *tag_close = strstr(p, trim ? "-%}" : "%}");
|
|
|
|
if (!tag_close) {
|
|
fprintf(stderr, "Error: unterminated `{%% ... %%}` statement\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
const char *stmt_keyword = kw_start;
|
|
|
|
if (kw_len == 3 && strncmp(stmt_keyword, "set", 3) == 0) {
|
|
ASTNode *stmt = parse_set_stmt(kw_end, tag_close, trim);
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 5 && strncmp(stmt_keyword, "unset", 5) == 0) {
|
|
if (trim) {
|
|
fprintf(stderr, "Error: `unset` cannot be trimmed.\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *stmt = parse_unset_stmt(kw_end, tag_close);
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 7 && strncmp(stmt_keyword, "unmacro", 7) == 0) {
|
|
if (trim) {
|
|
fprintf(stderr, "Error: `unmacro` cannot be trimmed.\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *stmt = parse_unmacro_stmt(kw_end, tag_close);
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 6 && strncmp(stmt_keyword, "import", 6) == 0) {
|
|
ASTNode *stmt = parse_import_stmt(kw_end, tag_close, trim);
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 6 && strncmp(stmt_keyword, "finish", 6) == 0) {
|
|
if (trim) {
|
|
fprintf(stderr, "Error: `finish` cannot be trimmed.\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *stmt = new_finish_stmt_node();
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 5 && strncmp(stmt_keyword, "break", 5) == 0) {
|
|
if (trim) {
|
|
fprintf(stderr, "Error: `break` cannot be trimmed.\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *stmt = new_break_node();
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 8 && strncmp(stmt_keyword, "continue", 8) == 0) {
|
|
if (trim) {
|
|
fprintf(stderr, "Error: `continue` cannot be trimmed.\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *stmt = new_continue_node();
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 6 && strncmp(stmt_keyword, "parent", 6) == 0) {
|
|
ASTNode *stmt = new_parent_node();
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
p = tag_close + 2 + trim; /* skip %} */
|
|
} else if (kw_len == 2 && strncmp(stmt_keyword, "if", 2) == 0) {
|
|
ASTNode *condition = parse_expression(&kw_end, tag_close);
|
|
if (!condition) {
|
|
fprintf(stderr, "Error: invalid expression in if statement\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
const char *block_start = tag_close + 2 + trim;
|
|
const char *block_end = block_start;
|
|
int nest_level = 1;
|
|
|
|
ASTNode *root_if = NULL;
|
|
ASTNode *current_if = NULL; /* Current if/elif node being processed */
|
|
ASTNode **current_else_ptr = NULL; /* Pointer to next else_clause slot */
|
|
|
|
/* Initialize first if node */
|
|
current_if = new_conditional_node(condition, NULL, NULL, trim, trim);
|
|
root_if = current_if;
|
|
current_else_ptr = ¤t_if->data.conditional.else_clause;
|
|
|
|
while (block_end < end) {
|
|
if (block_end[0] == '{' && block_end[1] == '%') {
|
|
const char *tmp = block_end + 2;
|
|
int local_trim = 0;
|
|
if (*tmp == '-') {
|
|
local_trim = 1;
|
|
++tmp;
|
|
}
|
|
skip_whitespace(&tmp, end);
|
|
|
|
const char *inner_kw = tmp;
|
|
while (tmp < end && isalpha(*tmp))
|
|
++tmp;
|
|
size_t inner_kw_len = (size_t)(tmp - inner_kw);
|
|
|
|
if (inner_kw_len == 2 && strncmp(inner_kw, "if", 2) == 0) {
|
|
++nest_level;
|
|
} else if (inner_kw_len == 5 && strncmp(inner_kw, "while", 5) == 0) {
|
|
++nest_level;
|
|
} else if (inner_kw_len == 3 && strncmp(inner_kw, "for", 3) == 0) {
|
|
++nest_level;
|
|
} else if (inner_kw_len == 3 && strncmp(inner_kw, "end", 3) == 0) {
|
|
skip_whitespace(&tmp, end);
|
|
if (nest_level == 1) {
|
|
if (trim) {
|
|
++tmp;
|
|
}
|
|
} else {
|
|
if (local_trim) {
|
|
++tmp;
|
|
}
|
|
}
|
|
|
|
if (tmp + 1 < end && tmp[0] == '%' && tmp[1] == '}') {
|
|
--nest_level;
|
|
|
|
if (nest_level == 0) {
|
|
/* Parse final body segment */
|
|
ASTNode *final_body = malloc(sizeof(ASTNode));
|
|
final_body->next = NULL;
|
|
if (!parse_template(final_body,
|
|
block_start,
|
|
(size_t)(block_end - block_start))) {
|
|
fprintf(stderr, "Error: Invalid final block\n");
|
|
free(final_body);
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
if (condition != NULL) {
|
|
/* If/elif branch: set body of current node */
|
|
if (current_if) {
|
|
current_if->data.conditional.body = final_body->next;
|
|
} else {
|
|
fprintf(stderr, "Error: no current if node\n");
|
|
free(final_body);
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
} else {
|
|
/* Else branch: attach to else_clause */
|
|
*current_else_ptr = final_body->next;
|
|
}
|
|
|
|
free(final_body);
|
|
|
|
p = tmp + 2;
|
|
break;
|
|
}
|
|
}
|
|
} else if (nest_level == 1 && inner_kw_len == 4 &&
|
|
strncmp(inner_kw, "elif", 4) == 0) {
|
|
/* Parse elif condition */
|
|
skip_whitespace(&tmp, end);
|
|
const char *elif_tag_close = strstr(tmp, local_trim ? "-%}" : "%}");
|
|
if (!elif_tag_close) {
|
|
fprintf(stderr, "Error: unterminated elif tag\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *elif_condition = parse_expression(&tmp, elif_tag_close);
|
|
if (!elif_condition) {
|
|
fprintf(stderr, "Error: invalid expression in elif\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
/* Parse body for current if/elif */
|
|
ASTNode *body = malloc(sizeof(ASTNode));
|
|
body->next = NULL;
|
|
if (!parse_template(
|
|
body, block_start, (size_t)(block_end - block_start))) {
|
|
fprintf(stderr, "Error: Invalid if/elif block\n");
|
|
free(body);
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
/* Assign body to current if node */
|
|
current_if->data.conditional.body = body->next;
|
|
current_if->data.conditional.trim_clause = local_trim;
|
|
free(body);
|
|
|
|
/* Create new node for elif */
|
|
ASTNode *elif_node =
|
|
new_conditional_node(elif_condition, NULL, NULL, local_trim, trim);
|
|
*current_else_ptr = elif_node; /* Attach to previous else_clause */
|
|
current_else_ptr =
|
|
&elif_node->data.conditional.else_clause; /* Update pointer */
|
|
current_if = elif_node; /* Set as current node */
|
|
condition = elif_condition; /* Update condition state */
|
|
|
|
block_start =
|
|
elif_tag_close + 2 + local_trim; /* Start after {% elif %} */
|
|
} else if (nest_level == 1 && inner_kw_len == 4 &&
|
|
strncmp(inner_kw, "else", 4) == 0) {
|
|
/* Parse body for current if/elif */
|
|
ASTNode *body = malloc(sizeof(ASTNode));
|
|
body->next = NULL;
|
|
if (!parse_template(
|
|
body, block_start, (size_t)(block_end - block_start))) {
|
|
fprintf(stderr, "Error: Invalid if/else block\n");
|
|
free(body);
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
/* Assign body to current node */
|
|
current_if->data.conditional.body = body->next;
|
|
current_if->data.conditional.trim_clause = local_trim;
|
|
free(body);
|
|
|
|
/* Prepare for else clause */
|
|
current_else_ptr = ¤t_if->data.conditional.else_clause;
|
|
condition = NULL; /* Mark as else branch */
|
|
current_if = NULL; /* Clear current node */
|
|
|
|
/* Locate else tag end */
|
|
const char *else_tag_close = strstr(tmp, local_trim ? "-%}" : "%}");
|
|
if (!else_tag_close) {
|
|
fprintf(stderr, "Error: unterminated else tag\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
block_start = else_tag_close + 2 + local_trim;
|
|
}
|
|
|
|
/* Advance to next tag */
|
|
const char *inner_tag_close = strstr(tmp, local_trim ? "-%}" : "%}");
|
|
if (!inner_tag_close) {
|
|
fprintf(stderr, "Error: unterminated tag inside if-block\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
block_end = inner_tag_close + 2 + local_trim;
|
|
continue;
|
|
}
|
|
++block_end;
|
|
}
|
|
|
|
if (nest_level > 0) {
|
|
fprintf(stderr, "Error: unmatched if statement\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
if (!root_if) {
|
|
fprintf(stderr, "Error: no if statement created\n");
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
*current = root_if;
|
|
current = &(*current)->next;
|
|
} else {
|
|
/* Simple blocks */
|
|
|
|
const char *block_start = tag_close + 2 + trim;
|
|
const char *block_end = block_start;
|
|
int nest_level = 1;
|
|
|
|
while (block_end < end) {
|
|
if (block_end[0] == '{' && block_end[1] == '%') {
|
|
const char *tmp = block_end + 2; /* After `{%` */
|
|
int local_trim = 0;
|
|
if (*tmp == '-') {
|
|
local_trim = 1;
|
|
++tmp;
|
|
}
|
|
skip_whitespace(&tmp, end); /* Skip whitespace before keyword */
|
|
|
|
if (strncmp(tmp, "end", 3) == 0) {
|
|
tmp += 3; /* Move past "end" */
|
|
skip_whitespace(&tmp, end); /* Skip whitespace after "end" */
|
|
|
|
if (local_trim) {
|
|
++tmp;
|
|
}
|
|
|
|
/* Now next chars should be `%}` to close the tag */
|
|
if (tmp[0] == '%' && tmp[1] == '}') {
|
|
--nest_level;
|
|
|
|
if (nest_level == 0) {
|
|
break;
|
|
}
|
|
}
|
|
} else {
|
|
/* Check if this is a nested start block */
|
|
const char *inner_kw = tmp;
|
|
while (*tmp && isalpha(*tmp)) {
|
|
++tmp;
|
|
}
|
|
size_t inner_kw_len = (size_t)(tmp - inner_kw);
|
|
|
|
/* block keywords that can nest (macros and namespaces cannot nest) */
|
|
if ((inner_kw_len == 5 && strncmp(inner_kw, "while", 5) == 0) ||
|
|
(inner_kw_len == 3 && strncmp(inner_kw, "for", 3) == 0) ||
|
|
(inner_kw_len == 2 && strncmp(inner_kw, "if", 2) == 0) ||
|
|
(inner_kw_len == 5 && strncmp(inner_kw, "block", 5) == 0)) {
|
|
++nest_level;
|
|
}
|
|
}
|
|
}
|
|
|
|
++block_end;
|
|
}
|
|
|
|
if (nest_level > 0) {
|
|
fprintf(stderr,
|
|
"Error: unmatched `{%% %.*s %%}` block\n",
|
|
(int)kw_len,
|
|
stmt_keyword);
|
|
free_ast(root);
|
|
return false;
|
|
}
|
|
|
|
ASTNode *body = malloc(sizeof(ASTNode));
|
|
if (!body) {
|
|
return NULL;
|
|
}
|
|
body->type = ASTNodeType_template;
|
|
body->next = NULL;
|
|
if (!parse_template(body, block_start, (size_t)(block_end - block_start))) {
|
|
free_ast(root);
|
|
return NULL;
|
|
}
|
|
|
|
/* Pass body (excluding {% end %}) to corresponding parser */
|
|
ASTNode *stmt = NULL;
|
|
|
|
if (kw_len == 5 && strncmp(stmt_keyword, "while", 5) == 0) {
|
|
stmt = parse_while_stmt(kw_end, tag_close, body->next, trim);
|
|
} else if (kw_len == 5 && strncmp(stmt_keyword, "macro", 5) == 0) {
|
|
stmt = parse_macro_stmt(kw_end, tag_close, body->next, trim);
|
|
} else if (kw_len == 9 && strncmp(stmt_keyword, "namespace", 9) == 0) {
|
|
stmt = parse_namespace_stmt(kw_end, tag_close, body->next, trim);
|
|
} else if (kw_len == 3 && strncmp(stmt_keyword, "for", 3) == 0) {
|
|
stmt = parse_for_stmt(kw_end, tag_close, body->next, trim);
|
|
} else if (kw_len == 5 && strncmp(stmt_keyword, "block", 5) == 0) {
|
|
stmt = parse_block_stmt(kw_end, tag_close, body->next, trim);
|
|
} else {
|
|
fprintf(stderr,
|
|
"Error: unknown statement `{%% %.*s %%}`\n",
|
|
(int)kw_len,
|
|
stmt_keyword);
|
|
free_ast(root);
|
|
free_ast(body);
|
|
return false;
|
|
}
|
|
|
|
if (!stmt) {
|
|
free_ast(root);
|
|
free_ast(body);
|
|
return false;
|
|
}
|
|
|
|
*current = stmt;
|
|
current = &(*current)->next;
|
|
|
|
/* Skip to after {% end %} */
|
|
const char *end_tag = strstr(block_end, trim ? "-%}" : "%}");
|
|
if (!end_tag) {
|
|
fprintf(stderr, "Error: unterminated `{%% end %%}`\n");
|
|
free_ast(root);
|
|
free_ast(body);
|
|
return false;
|
|
}
|
|
p = end_tag + 2 + trim; /* skip final %} */
|
|
|
|
free(body);
|
|
}
|
|
|
|
text_start = p;
|
|
} else {
|
|
++p;
|
|
}
|
|
}
|
|
|
|
/* Add trailing text */
|
|
if (p > text_start) {
|
|
*current = new_text_node(text_start, (size_t)(p - text_start));
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
#if 1
|
|
int main(void) {
|
|
const char *template_text = "{{ 1.2e3 @ hi }}";
|
|
puts(template_text);
|
|
ASTNode *root = malloc(sizeof(ASTNode));
|
|
root->type = ASTNodeType_template;
|
|
root->next = NULL;
|
|
if (parse_template(root, template_text, strlen(template_text))) {
|
|
print_ast(root, 0);
|
|
free_ast(root);
|
|
} else {
|
|
puts("NOOOOOOOO");
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#else
|
|
int main(void) {
|
|
const char *filename = "../doc/temple-example/templates/dashboard.lpt";
|
|
FILE *file = fopen(filename, "rb");
|
|
if (!file) {
|
|
perror("Failed to open file");
|
|
return 1;
|
|
}
|
|
|
|
if (fseek(file, 0, SEEK_END) != 0) {
|
|
perror("Failed to seek file");
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
|
|
long filesize = ftell(file);
|
|
if (filesize < 0) {
|
|
perror("Failed to get file size");
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
|
|
rewind(file);
|
|
|
|
char *template_text = malloc(filesize + 1);
|
|
if (!template_text) {
|
|
perror("Failed to allocate memory");
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
|
|
size_t read_size = fread(template_text, 1, filesize, file);
|
|
if (read_size != (size_t)filesize) {
|
|
perror("Failed to read file");
|
|
free(template_text);
|
|
fclose(file);
|
|
return 1;
|
|
}
|
|
|
|
template_text[filesize] = '\0';
|
|
|
|
fclose(file);
|
|
|
|
ASTNode *root = malloc(sizeof(ASTNode));
|
|
if (!root) {
|
|
perror("Failed to allocate ASTNode");
|
|
free(template_text);
|
|
return 1;
|
|
}
|
|
|
|
root->type = ASTNodeType_template;
|
|
root->next = NULL;
|
|
|
|
if (parse_template(root, template_text, filesize)) {
|
|
print_ast(root, 0);
|
|
free_ast(root);
|
|
} else {
|
|
puts("NOOOOOOOO");
|
|
}
|
|
|
|
free(template_text);
|
|
|
|
return 0;
|
|
}
|
|
#endif
|