From c6c790e50d7c99a661ad80bad3be69a2c16a72ab Mon Sep 17 00:00:00 2001 From: Miguel Lechon Date: Tue, 7 Sep 2021 18:16:57 +0200 Subject: [PATCH] expression parser; first pass. --- bin/all_dev_checks.sh | 0 bin/bld_core.sh | 0 bin/bld_init.sh | 0 bin/build_examples.sh | 0 bin/build_tests.sh | 1 + bin/run_examples.sh | 0 bin/run_tests.sh | 1 + source/md.c | 221 ++++++++++++++++++++++++++++++++-- source/md.h | 14 ++- tests/expression_tests.c | 254 +++++++++++++++++++++++++++++++++++++++ 10 files changed, 478 insertions(+), 13 deletions(-) mode change 100644 => 100755 bin/all_dev_checks.sh mode change 100644 => 100755 bin/bld_core.sh mode change 100644 => 100755 bin/bld_init.sh mode change 100644 => 100755 bin/build_examples.sh mode change 100644 => 100755 bin/build_tests.sh mode change 100644 => 100755 bin/run_examples.sh mode change 100644 => 100755 bin/run_tests.sh create mode 100644 tests/expression_tests.c diff --git a/bin/all_dev_checks.sh b/bin/all_dev_checks.sh old mode 100644 new mode 100755 diff --git a/bin/bld_core.sh b/bin/bld_core.sh old mode 100644 new mode 100755 diff --git a/bin/bld_init.sh b/bin/bld_init.sh old mode 100644 new mode 100755 diff --git a/bin/build_examples.sh b/bin/build_examples.sh old mode 100644 new mode 100755 diff --git a/bin/build_tests.sh b/bin/build_tests.sh old mode 100644 new mode 100755 index 8dbe1ba..65d48be --- a/bin/build_tests.sh +++ b/bin/build_tests.sh @@ -12,6 +12,7 @@ bin/bld_core.sh show_ctx bin/bld_core.sh unit sanity_tests tests/sanity_tests.c bin/bld_core.sh unit unicode_test tests/unicode_test.c bin/bld_core.sh unit cpp_build_test tests/cpp_build_test.cpp +bin/bld_core.sh unit expression_tests tests/expression_tests.c echo diff --git a/bin/run_examples.sh b/bin/run_examples.sh old mode 100644 new mode 100755 diff --git a/bin/run_tests.sh b/bin/run_tests.sh old mode 100644 new mode 100755 index d972d56..3c43a07 --- a/bin/run_tests.sh +++ b/bin/run_tests.sh @@ -10,6 +10,7 @@ mkdir -p build cd build ./sanity_tests.exe ./unicode_test.exe +./expression_tests.exe ###### Restore Path ########################################################### cd $og_path diff --git a/source/md.c b/source/md.c index e46acb8..e103a79 100644 --- a/source/md.c +++ b/source/md.c @@ -2973,7 +2973,7 @@ MD_NodeDeepMatch(MD_Node *a, MD_Node *b, MD_MatchFlags flags) //~ Expression Parsing -MD_FUNCTION void +MD_FUNCTION_IMPL void MD_ExprOperatorPush(MD_Arena *arena, MD_ExprOperatorList *list, MD_u32 op_id, MD_ExprOperatorKind kind, MD_u64 precedence, MD_Node *md_node){ @@ -2986,18 +2986,221 @@ MD_ExprOperatorPush(MD_Arena *arena, MD_ExprOperatorList *list, node->op.md_node = md_node; } -MD_FUNCTION MD_ExprOperatorTable +MD_FUNCTION_IMPL MD_ExprOperatorTable MD_ExprBakeOperatorTableFromList(MD_Arena *arena, MD_ExprOperatorList *list){ - MD_ExprOperatorTable result = {0}; - // TODO(allen): @expr_parser + MD_ExprOperatorTable result = MD_ZERO_STRUCT; + + for(MD_ExprOperatorNode *op_node = list->first; op_node; op_node = op_node->next){ + MD_ExprOperatorList *list = result.table+op_node->op.kind; + MD_ExprOperatorNode *op_node_copy = MD_PushArray(arena, MD_ExprOperatorNode, 1); + MD_QueuePush(list->first, list->last, op_node_copy); + list->count += 1; + op_node_copy->op = op_node->op; + + if(op_node->op.kind == MD_ExprOperatorKind_Postfix && + MD_S8Match(op_node->op.md_node->string, MD_S8Lit("()"), 0)){ + result.call_op = &op_node_copy->op; + } + else if(op_node->op.kind == MD_ExprOperatorKind_Binary && + MD_S8Match(op_node->op.md_node->string, MD_S8Lit("[]"), 0)){ + result.subscript_op = &op_node_copy->op; + } + + // TODO(mal): Check: non-repeat operators, same precedence binary ops should share associativity + } + return(result); } -MD_FUNCTION MD_ExprParseResult -MD_ExprParse(MD_Arena *arena, MD_ExprOperatorTable *op_table, - MD_Node *first, MD_Node *one_past_last){ - MD_ExprParseResult result = {0}; - // TODO(allen): @expr_parser +typedef struct _MD_ExprParseCtx _MD_ExprParseCtx; +struct _MD_ExprParseCtx{ + MD_ExprOperatorTable *op_table; + MD_Node *first; + MD_Node *one_past_last; +}; + +MD_FUNCTION_IMPL MD_ExprParseResult +_MD_ExprParse_Ctx_MinPrecedence(MD_Arena *arena, _MD_ExprParseCtx *ctx, MD_u32 min_precedence); + +MD_FUNCTION_IMPL MD_ExprNode * _MD_Expr_Make(MD_Arena *arena, MD_ExprOperator *op, MD_Node *op_node, + MD_ExprNode *left, MD_ExprNode *right){ + MD_ExprNode *result = MD_PushArrayZero(arena, MD_ExprNode, 1); + result->is_op = 1; + result->op_id = op->op_id; + result->md_op_node = op->md_node; + result->md_node = op_node; + result->left = left; + result->right = right; + result->left->parent = result; + if(result->right){ // TODO(mal): Introduce Nil expr node? + result->right->parent = result; + } + return result; +} + +MD_FUNCTION_IMPL MD_ExprOperator * +_MD_ExprOperatorMatch(MD_ExprOperatorTable *table, MD_ExprOperatorKind kind, MD_String8 s){ + // NOTE(mal): Look for operator on one or all (kind == MD_ExprOperatorKind_Null) tables + MD_ExprOperator *result = 0; + for(MD_ExprOperatorKind cur_kind = (MD_ExprOperatorKind)(MD_ExprOperatorKind_Null+1); + !result && cur_kind < MD_ExprOperatorKind_COUNT; + cur_kind = (MD_ExprOperatorKind)(cur_kind+1)){ + if(kind == MD_ExprOperatorKind_Null || kind == cur_kind){ + MD_ExprOperatorList *op_list = table->table+cur_kind; + for(MD_ExprOperatorNode *op_node = op_list->first; !result && op_node; op_node = op_node->next) + { + if(MD_S8Match(op_node->op.md_node->string, s, 0)){ + result = &op_node->op; + } + } + } + } + return result; +} + +MD_FUNCTION_IMPL void _MD_CtxAdvance(_MD_ExprParseCtx *ctx){ + ctx->first = ctx->first->next; +} + +MD_FUNCTION_IMPL MD_b32 _MD_ExprOperatorConsumed(_MD_ExprParseCtx *ctx, MD_ExprOperatorKind kind, MD_u32 min_precedence, + MD_ExprOperator **op_out){ + MD_b32 result = 0; + if(!MD_NodeIsNil(ctx->first)){ + MD_Node *node = ctx->first; + MD_ExprOperator *op = _MD_ExprOperatorMatch(ctx->op_table, kind, node->string); + if(op && op->precedence >= min_precedence){ + result = 1; + *op_out = op; + _MD_CtxAdvance(ctx); + } + } + return result; +} + +MD_FUNCTION_IMPL _MD_ExprParseCtx +_MD_ExprParse_MakeContext(MD_ExprOperatorTable *op_table, MD_Node *first, MD_Node *one_past_last){ + _MD_ExprParseCtx result = MD_ZERO_STRUCT; + result.op_table = op_table; + result.first = first; + result.one_past_last = one_past_last; + return result; +} + +MD_FUNCTION_IMPL MD_ExprParseResult +_MD_ExprParse_Atom(MD_Arena *arena, _MD_ExprParseCtx *ctx){ + MD_ExprParseResult result = MD_ZERO_STRUCT; + + MD_Node *node = ctx->first; + MD_ExprOperator *op = 0; + + if(node->flags & MD_NodeFlag_HasParenLeft && node->flags & MD_NodeFlag_HasParenRight){ // NOTE(mal): Parens + _MD_CtxAdvance(ctx); + _MD_ExprParseCtx sub_ctx = _MD_ExprParse_MakeContext(ctx->op_table, node->first_child, node->last_child->next); + result = _MD_ExprParse_Ctx_MinPrecedence(arena, &sub_ctx, 0); + } + else if((node->flags & MD_NodeFlag_HasBracketLeft && node->flags & MD_NodeFlag_HasBracketRight) || + (node->flags & MD_NodeFlag_HasBraceLeft && node->flags & MD_NodeFlag_HasBraceRight)){ + _MD_CtxAdvance(ctx); + // NOTE(mal): Unparsed leaf sets ({ ... }, [ ... ]) + result.node = MD_PushArrayZero(arena, MD_ExprNode, 1); + result.node->md_node = node; + } + else if(_MD_ExprOperatorConsumed(ctx, MD_ExprOperatorKind_Prefix, 1, &op)){ + MD_u32 min_precedence = op->precedence + 1; + MD_ExprParseResult sub_parse_result = _MD_ExprParse_Ctx_MinPrecedence(arena, ctx, min_precedence); + if(sub_parse_result.errors.max_message_kind == MD_MessageKind_Null){ + result.node = _MD_Expr_Make(arena, op, node, sub_parse_result.node, 0); + } + else{ + result.errors = sub_parse_result.errors; + } + } + else if(_MD_ExprOperatorConsumed(ctx, MD_ExprOperatorKind_Null, 1, &op)){ + MD_String8 error_str = MD_S8Fmt(arena, "Expected leaf. Got operator \"%.*s\".", MD_S8VArg(node->string)); + MD_Message *error = MD_MakeNodeError(arena, node, MD_MessageKind_Error, error_str); + MD_MessageListPush(&result.errors, error); + } + else{ // NOTE(mal): leaf + _MD_CtxAdvance(ctx); + result.node = MD_PushArrayZero(arena, MD_ExprNode, 1); + result.node->md_node = node; + } + + return result; +} + +MD_FUNCTION_IMPL MD_ExprParseResult +_MD_ExprParse_Ctx_MinPrecedence(MD_Arena *arena, _MD_ExprParseCtx *ctx, MD_u32 min_precedence){ + MD_ExprParseResult result = MD_ZERO_STRUCT; + + MD_ExprOperator *subscript_op = ctx->op_table->subscript_op; + + result = _MD_ExprParse_Atom(arena, ctx); + if(result.errors.max_message_kind == MD_MessageKind_Null){ + while(!MD_NodeIsNil(ctx->first) && ctx->first != ctx->one_past_last){ + MD_Node *node = ctx->first; + MD_ExprOperator *op; + + if(subscript_op && subscript_op->precedence >= min_precedence && + node->flags & MD_NodeFlag_HasBracketLeft && node->flags & MD_NodeFlag_HasBracketRight){ + _MD_CtxAdvance(ctx); + + // NOTE(mal): Array subscript + _MD_ExprParseCtx sub_ctx = _MD_ExprParse_MakeContext(ctx->op_table, + node->first_child, node->last_child->next); + MD_ExprParseResult sub_parse_result = _MD_ExprParse_Ctx_MinPrecedence(arena, &sub_ctx, 0); + if(sub_parse_result.errors.max_message_kind == MD_MessageKind_Null){ + result.node = _MD_Expr_Make(arena, subscript_op, node, result.node, sub_parse_result.node); + } + else{ + result.errors = sub_parse_result.errors; + break; + } + } + else if(_MD_ExprOperatorConsumed(ctx, MD_ExprOperatorKind_Binary, min_precedence, &op) || + _MD_ExprOperatorConsumed(ctx, MD_ExprOperatorKind_BinaryRightAssociative, min_precedence, &op)){ + MD_u32 next_min_precedence = op->precedence + (op->kind == MD_ExprOperatorKind_Binary); + MD_ExprParseResult sub_parse_result = _MD_ExprParse_Ctx_MinPrecedence(arena, ctx, next_min_precedence); + if(sub_parse_result.errors.max_message_kind == MD_MessageKind_Null){ + result.node = _MD_Expr_Make(arena, op, node, result.node, sub_parse_result.node); + } + else{ + result.errors = sub_parse_result.errors; + break; + } + } + else if(ctx->op_table->call_op && ctx->op_table->call_op->precedence >= min_precedence && + node->flags & MD_NodeFlag_HasParenLeft && node->flags & MD_NodeFlag_HasParenRight){ // NOTE: call + _MD_CtxAdvance(ctx); + result.node = _MD_Expr_Make(arena, ctx->op_table->call_op, node, result.node, 0); + } + else if(_MD_ExprOperatorConsumed(ctx, MD_ExprOperatorKind_Postfix, min_precedence, &op)){ + result.node = _MD_Expr_Make(arena, op, node, result.node, 0); + } + else{ + break; // NOTE: Due to lack of progress + } + } + } + + return result; +} + +MD_FUNCTION_IMPL MD_ExprParseResult +MD_ExprParse(MD_Arena *arena, MD_ExprOperatorTable *op_table, MD_Node *first, MD_Node *one_past_last){ + MD_ExprParseResult result = MD_ZERO_STRUCT; + + _MD_ExprParseCtx ctx = _MD_ExprParse_MakeContext(op_table, first, one_past_last); + result = _MD_ExprParse_Ctx_MinPrecedence(arena, &ctx, 0); + if(result.errors.max_message_kind == MD_MessageKind_Null){ + if(ctx.first != ctx.one_past_last){ + MD_String8 error_str = + MD_S8Fmt(arena, "Partial parse. Expected binary or unary postfix operator."); // TODO(mal): More detail? + MD_Message *error = MD_MakeNodeError(arena, ctx.first, MD_MessageKind_Error, error_str); + MD_MessageListPush(&result.errors, error); + } + } + return(result); } diff --git a/source/md.h b/source/md.h index 06092c2..373c7c8 100644 --- a/source/md.h +++ b/source/md.h @@ -681,11 +681,13 @@ struct MD_ParseResult typedef enum MD_ExprOperatorKind { - // TODO(mal): We could improve this naming scheme + MD_ExprOperatorKind_Null, + // TODO(mal): Improve this naming scheme ? MD_ExprOperatorKind_Prefix, MD_ExprOperatorKind_Postfix, MD_ExprOperatorKind_Binary, MD_ExprOperatorKind_BinaryRightAssociative, + MD_ExprOperatorKind_COUNT, } MD_ExprOperatorKind; typedef struct MD_ExprOperator MD_ExprOperator; @@ -715,8 +717,12 @@ struct MD_ExprOperatorList typedef struct MD_ExprOperatorTable MD_ExprOperatorTable; struct MD_ExprOperatorTable { - // TODO(allen): @expr_parser fill this in however needed - int foo; + // TODO(mal): Something faster; arrays indexed by op kind or hash table... + MD_ExprOperatorList table[MD_ExprOperatorKind_COUNT]; + + MD_ExprOperator *call_op; + MD_ExprOperator *subscript_op; + MD_MessageList errors; }; @@ -826,7 +832,7 @@ struct MD_FileIter ((l)->next=(n),(l)=(n),zset((n)->next))) #define MD_QueuePop_NZ(f,l,next,zset) ((f)==(l)?\ (zset(f),zset(l)):\ -(f)=(f)->next) +((f)=(f)->next)) #define MD_StackPush_N(f,n,next) ((n)->next=(f),(f)=(n)) #define MD_StackPop_NZ(f,next,zchk) (zchk(f)?0:(f)=(f)->next) diff --git a/tests/expression_tests.c b/tests/expression_tests.c new file mode 100644 index 0000000..ed8beb3 --- /dev/null +++ b/tests/expression_tests.c @@ -0,0 +1,254 @@ +//$ exe // + +#if 0 +#define ASSERT(expr) {if(!(expr)){fprintf(stderr, "Assertion failed: %s, line %d (%s)\n", __FILE__, __LINE__, #expr); __builtin_debugtrap();}} +#define HARD_ASSERT(expr) {if(!(expr)){fprintf(stderr, "Assertion failed: %s, line %d (%s)\n", __FILE__, __LINE__, #expr); __builtin_debugtrap();}} +#define BP ASSERT(!"Break point"); +#define NotTested ASSERT(!"Not tested"); +#define STATIC_ASSERT(expr) U8 MACRO_CONCAT(static_assert_, __COUNTER__)[(expr)?(1):(-1)] +#define NotImplemented ASSERT(!"Not implemented"); +#endif + + +#include "md.h" +#include "md.c" + +MD_Arena *arena = 0; + +typedef struct Expression_QA Expression_QA; +struct Expression_QA +{ + char *q; + char *a; +}; + +typedef struct OperatorDescription OperatorDescription; +struct OperatorDescription{ + MD_String8 s; + MD_ExprOperator op; +}; + +// NOTE(mal): Based on https://en.wikipedia.org/wiki/Operators_in_C_and_C%2B%2B#Operator_precedence 2021-09-05 +// Precedences are flipped. The ones listed here are 20-x where x is the precedence on the wikipedia table +#define OPERATORS \ + X(PostFixIncrement, "++", Postfix, 18) \ + X(PostFixDecrement, "--", Postfix, 18) \ + X(Call, "()", Postfix, 18) \ + X(ArraySubscript, "[]", Binary, 18) \ + X(Member, ".", Binary, 18) \ + X(PointerMember, "->", Binary, 18) \ + X(PreFixIncrement, "++", Prefix, 17) \ + X(PreFixDecrement, "--", Prefix, 17) \ + X(UnaryPlus, "+", Prefix, 17) \ + X(UnaryMinus, "-", Prefix, 17) \ + X(LogicalNot, "!", Prefix, 17) \ + X(BitwiseNot, "~", Prefix, 17) \ + X(Dereference, "*", Prefix, 17) \ + X(AddressOf, "&", Prefix, 17) \ + X(SizeOf, "sizeof", Prefix, 17) /* NOTE(mal); Just for fun*/ \ + X(Multiplication, "*", Binary, 15) \ + X(Division, "/", Binary, 15) \ + X(Modulo, "%", Binary, 15) \ + X(Addition, "+", Binary, 14) \ + X(Subtraction, "-", Binary, 14) \ + X(LeftShift, "<<", Binary, 13) \ + X(RightShift, ">>", Binary, 13) \ + X(LessThan, "<", Binary, 11) \ + X(LessThanOrEqual, "<=", Binary, 11) \ + X(GreaterThan, ">", Binary, 11) \ + X(GreaterThanOrEqual, ">=", Binary, 11) \ + X(Equal, "==", Binary, 10) \ + X(NotEqual, "!=", Binary, 10) \ + X(BitwiseAnd, "&", Binary, 9) \ + X(BitwiseXor, "^", Binary, 8) \ + X(BitwiseOr, "|", Binary, 7) \ + X(LogicalAnd, "&&", Binary, 6) \ + X(LogicalOr, "||", Binary, 5) \ + X(Assign, "=", BinaryRightAssociative, 4) \ + X(AssignAddition, "+=", BinaryRightAssociative, 4) \ + X(AssignSubtraction, "-=", BinaryRightAssociative, 4) \ + X(AssignMultiplication,"*=", BinaryRightAssociative, 4) \ + X(AssignDivision, "/=", BinaryRightAssociative, 4) \ + X(AssignModulo, "%=", BinaryRightAssociative, 4) \ + X(AssignLeftShift, "<<=", BinaryRightAssociative, 4) \ + X(AssignRightShift, ">>=", BinaryRightAssociative, 4) \ + X(AssignBitwiseAnd, "&=", BinaryRightAssociative, 4) \ + X(AssignBitwiseXor, "^=", BinaryRightAssociative, 4) \ + X(AssignBitwiseOr, "|=", BinaryRightAssociative, 4) + // X(Cast "()", Prefix, 17) + // X(Comma, ",", Binary, 3) + +#define X(name, token, kind, prec) Op_##name, +typedef enum{ + Op_Null, + OPERATORS + Op_COUNT +} Op; +#undef X + + +static MD_String8 node_raw_contents(MD_Node *node, MD_b32 exclude_outer){ + MD_String8 result = {0}; + + MD_u64 beg = node->offset; + if(exclude_outer && !MD_NodeIsNil(node->first_child)){ + beg = node->first_child->offset; + } + + MD_u64 end = beg; { + MD_Node *last_descendant = node; + while(!MD_NodeIsNil(last_descendant->last_child)){ + last_descendant = last_descendant->last_child; + } + end = last_descendant->offset + last_descendant->raw_string.size; + } + + MD_Node *root = node; + while(!MD_NodeIsNil(root->parent)){ + root = root->parent; + } + + result = MD_S8Substring(root->raw_string, beg, end); + return result; +} + +static void parenthesize_exclude_outer(MD_Arena *arena, OperatorDescription *descs, MD_String8List *l, + MD_ExprNode *node, MD_b32 exclude_outer_parens){ + if(node->is_op){ + if(!exclude_outer_parens){ + MD_S8ListPush(arena, l, MD_S8Lit("(")); + } + + MD_ExprOperator *op = &descs[node->op_id].op; + if(op->kind == MD_ExprOperatorKind_Binary || op->kind == MD_ExprOperatorKind_BinaryRightAssociative){ + + parenthesize_exclude_outer(arena, descs, l, node->left, 0); + + MD_b32 is_subscript = (node->op_id == Op_ArraySubscript); + if(is_subscript){ + MD_S8ListPush(arena, l, MD_S8Lit("[")); + parenthesize_exclude_outer(arena, descs, l, node->right, 1); + MD_S8ListPush(arena, l, MD_S8Lit("]")); + } + else{ + MD_S8ListPush(arena, l, MD_S8Lit(" ")); + MD_S8ListPush(arena, l, node->md_node->string); + MD_S8ListPush(arena, l, MD_S8Lit(" ")); + parenthesize_exclude_outer(arena, descs, l, node->right, 0); + } + } + else if(op->kind == MD_ExprOperatorKind_Prefix){ + MD_S8ListPush(arena, l, node->md_node->string); + MD_u8 last_op_c = MD_S8Suffix(node->md_node->string, 1).str[0]; + + if(MD_CharIsAlpha(last_op_c) || MD_CharIsDigit(last_op_c)){ // NOTE: Keyword prefix operator (e.g. sizeof) + MD_S8ListPush(arena, l, MD_S8Lit(" ")); + } + + parenthesize_exclude_outer(arena, descs, l, node->left, 0); + } + else if(op->kind == MD_ExprOperatorKind_Postfix){ + parenthesize_exclude_outer(arena, descs, l, node->left, 0); + + MD_b32 is_call = (node->op_id == Op_Call); + if(is_call){ + MD_S8ListPush(arena, l, MD_S8Lit("(...)")); + } + else{ + MD_S8ListPush(arena, l, node->md_node->string); + } + } + else{ + MD_S8ListPush(arena, l, MD_S8Lit("--- Can't print expression ---")); + } + + if(!exclude_outer_parens){ + MD_S8ListPush(arena, l, MD_S8Lit(")")); + } + } + else{ + if(MD_NodeIsNil(node->md_node->first_child)){ + MD_S8ListPush(arena, l, node_raw_contents(node->md_node, 0)); + } + else{ + if(node->md_node->flags & MD_NodeFlag_HasBracketLeft && + node->md_node->flags & MD_NodeFlag_HasBracketRight){ + MD_S8ListPush(arena, l, MD_S8Lit("[...]")); + } + else if(node->md_node->flags & MD_NodeFlag_HasBraceLeft && + node->md_node->flags & MD_NodeFlag_HasBraceRight){ + MD_S8ListPush(arena, l, MD_S8Lit("{...}")); + } + else{ + MD_S8ListPush(arena, l, MD_S8Lit("???")); + } + } + } +} + +static MD_String8 parenthesize(MD_Arena *arena, OperatorDescription *descs, MD_ExprNode *node){ + MD_String8 result = {0}; + MD_String8List l = {0}; + parenthesize_exclude_outer(arena, descs, &l, node, 1); + result = MD_S8ListJoin(arena, l, 0); + return result; +} + + +int main(void){ + OperatorDescription operator_array[Op_COUNT] = {0}; +#define X(name, token, kind_, prec) \ + operator_array[Op_##name].s = MD_S8Lit(token); \ + operator_array[Op_##name].op = (MD_ExprOperator){ .op_id = Op_##name, .kind = MD_ExprOperatorKind_##kind_, .precedence = prec }; + OPERATORS +#undef X + + arena = MD_ArenaAlloc(1ull << 40); + MD_ExprOperatorList operator_list = {0}; + + for(Op op= Op_Null+1; op < Op_COUNT; ++op){ + OperatorDescription *desc = operator_array + op; + MD_Node *node = MD_MakeNode(arena, MD_NodeKind_Main, desc->s, desc->s, 0); + MD_ExprOperatorPush(arena, &operator_list, op, desc->op.kind, desc->op.precedence, node); + } + + MD_ExprOperatorTable op_table = MD_ExprBakeOperatorTableFromList(arena, &operator_list); + + Expression_QA tests[] = { + { .q = "a + b + c", .a = "(a + b) + c" }, + { .q = "a + (b + c)", .a = "a + (b + c)" }, + { .q = "-a * b + c", .a = "((-a) * b) + c" }, + { .q = "a * (b + c)", .a = "a * (b + c)" }, + { .q = "a = b + c", .a = "a = (b + c)" }, + { .q = "a(b,c)", .a = "a(...)" }, + { .q = "(a + b)()", .a = "(a + b)(...)" }, + { .q = "sizeof a + b", .a = "(sizeof a) + b" }, + { .q = "[1, 100] * n", .a = "[...] * n" }, + { .q = "a[b+c]", .a = "a[b + c]" }, + { .q = "a++ + b", .a = "(a++) + b" }, + }; + + for(MD_u32 i_test = 0; i_test < MD_ArrayCount(tests); i_test+=1){ + Expression_QA test = tests[i_test]; + MD_String8 q = MD_S8CString(test.q); + MD_String8 a = MD_S8CString(test.a); + + MD_ParseResult parse = MD_ParseWholeString(arena, MD_S8Lit("test"), q); + MD_Assert(parse.errors.max_message_kind == MD_MessageKind_Null); + MD_ExprParseResult expr_parse = MD_ExprParse(arena, &op_table, parse.node->first_child, MD_NilNode()); + + if(expr_parse.errors.max_message_kind){ + printf("Example %d : \"%.*s\". Got error \"%.*s\"\n", i_test, MD_S8VArg(q), + MD_S8VArg(expr_parse.errors.first[0].string)); + } + else{ + MD_String8 parser_answer = parenthesize(arena, operator_array, expr_parse.node); + if(expr_parse.errors.max_message_kind || !MD_S8Match(parser_answer, a, 0)){ + printf("Example %d : Expected answer for %.*s is %.*s. Got %.*s\n", + i_test, MD_S8VArg(q), MD_S8VArg(a), MD_S8VArg(parser_answer)); + } + } + } + + return 0; +}