mirror of
https://github.com/Ed94/metadesk.git
synced 2026-08-07 16:18:49 +00:00
[examples] filled out the expression intro example and added commentary
This commit is contained in:
+151
-10
@@ -1,7 +1,9 @@
|
|||||||
/*
|
/*
|
||||||
** Example: expressions intro
|
** Example: expressions intro
|
||||||
**
|
**
|
||||||
** TODO
|
** This example shows how to use expression parsing in Metadesk. There is
|
||||||
|
** commentary about the setup as well as the features and limits of the
|
||||||
|
** expression parser.
|
||||||
**
|
**
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@@ -14,10 +16,14 @@ static MD_Arena *arena = 0;
|
|||||||
|
|
||||||
//~ expression setup and helpers //////////////////////////////////////////////
|
//~ expression setup and helpers //////////////////////////////////////////////
|
||||||
|
|
||||||
|
// @notes A common easy setup is to give each operator a statically known
|
||||||
|
// integer code. We can switch on these integer codes later to interpret the
|
||||||
|
// operator nodes in expressions.
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
OpAdd,
|
OpAdd,
|
||||||
OpMul,
|
OpMul,
|
||||||
|
OpIllegal,
|
||||||
};
|
};
|
||||||
|
|
||||||
void
|
void
|
||||||
@@ -26,9 +32,16 @@ print_expression(FILE *out, MD_Expr *expr)
|
|||||||
MD_ExprOpr *op = expr->op;
|
MD_ExprOpr *op = expr->op;
|
||||||
if (op == 0)
|
if (op == 0)
|
||||||
{
|
{
|
||||||
|
// @notes Any MD_Expr that doesn't have an operator attached must be a
|
||||||
|
// leaf of the expression. In this example we don't want to recognize
|
||||||
|
// any nodes with set delimiters as leaves.
|
||||||
|
//
|
||||||
|
// Every expression node (MD_Expr) has an `md_node` regardless of
|
||||||
|
// whether it is a leaf or an operator. This node gives us a way to
|
||||||
|
// see information about this leaf, and also gives a way to create an
|
||||||
|
// MD_CodeLoc for error messages. The same works on operator nodes.
|
||||||
MD_Node *node = expr->md_node;
|
MD_Node *node = expr->md_node;
|
||||||
if (node->raw_string.size != 0 &&
|
if ((node->flags & MD_NodeFlag_MaskSetDelimiters) == 0)
|
||||||
MD_NodeIsNil(node->first_child))
|
|
||||||
{
|
{
|
||||||
fprintf(out, "%.*s", MD_S8VArg(node->raw_string));
|
fprintf(out, "%.*s", MD_S8VArg(node->raw_string));
|
||||||
}
|
}
|
||||||
@@ -41,19 +54,88 @@ print_expression(FILE *out, MD_Expr *expr)
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
// @notes Any MD_Expr that does have an operator attached is an
|
||||||
|
// internal node of the expression. In this example we only setup
|
||||||
|
// binary operators, so we don't bother looking at what kind of
|
||||||
|
// operator this is.
|
||||||
fprintf(out, "(");
|
fprintf(out, "(");
|
||||||
print_expression(out, expr->left);
|
print_expression(out, expr->left);
|
||||||
fprintf(out, " %.*s ", MD_S8VArg(op->string));
|
fprintf(out, " %.*s ", MD_S8VArg(op->string));
|
||||||
print_expression(out, expr->right);
|
print_expression(out, expr->right);
|
||||||
fprintf(out, ")");
|
fprintf(out, ")");
|
||||||
|
|
||||||
|
if (op->op_id == OpIllegal)
|
||||||
|
{
|
||||||
|
MD_CodeLoc loc = MD_CodeLocFromNode(expr->md_node);
|
||||||
|
MD_PrintMessage(stderr, loc, MD_MessageKind_Error,
|
||||||
|
MD_S8Lit("this operator is not actually legal"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// @notes Commonly a useful thing to do with an expression system is to
|
||||||
|
// evaluate the expressions. Here's a quick sketch of what that might look
|
||||||
|
// like.
|
||||||
|
|
||||||
|
MD_Map eval_map = {0};
|
||||||
|
|
||||||
|
int
|
||||||
|
eval_expression(MD_Expr *expr)
|
||||||
|
{
|
||||||
|
int result = 0;
|
||||||
|
MD_ExprOpr *op = expr->op;
|
||||||
|
if (op == 0)
|
||||||
|
{
|
||||||
|
MD_Node *node = expr->md_node;
|
||||||
|
if (node->flags & MD_NodeFlag_Numeric)
|
||||||
|
{
|
||||||
|
result = MD_CStyleIntFromString(node->string);
|
||||||
|
}
|
||||||
|
else if (node->flags & MD_NodeFlag_Identifier)
|
||||||
|
{
|
||||||
|
MD_MapSlot *slot = MD_MapLookup(&eval_map, MD_MapKeyStr(node->string));
|
||||||
|
if (slot != 0)
|
||||||
|
{
|
||||||
|
result = (int)(MD_u64)slot->val;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int l = eval_expression(expr->left);
|
||||||
|
int r = eval_expression(expr->right);
|
||||||
|
|
||||||
|
// @notes The `op_id` on this op pointer is carried to use from the
|
||||||
|
// operator setup where we used the OpAdd and OpMul enum to assign
|
||||||
|
// assign static integers to each operator.
|
||||||
|
switch (op->op_id)
|
||||||
|
{
|
||||||
|
case OpAdd:
|
||||||
|
{
|
||||||
|
result = l + r;
|
||||||
|
}break;
|
||||||
|
case OpMul:
|
||||||
|
{
|
||||||
|
result = l * r;
|
||||||
|
}break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return(result);
|
||||||
|
}
|
||||||
|
|
||||||
//~ main //////////////////////////////////////////////////////////////////////
|
//~ main //////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
int main(int argc, char **argv)
|
int main(int argc, char **argv)
|
||||||
{
|
{
|
||||||
|
#if 1
|
||||||
|
char *argv_dummy[2] = {
|
||||||
|
0,
|
||||||
|
"W:\\metadesk\\examples\\expr\\expr_intro.mdesk",
|
||||||
|
};
|
||||||
|
argc = 2;
|
||||||
|
argv = argv_dummy;
|
||||||
|
#endif
|
||||||
|
|
||||||
// setup the global arena
|
// setup the global arena
|
||||||
arena = MD_ArenaAlloc();
|
arena = MD_ArenaAlloc();
|
||||||
|
|
||||||
@@ -85,20 +167,72 @@ int main(int argc, char **argv)
|
|||||||
// setup the expression system
|
// setup the expression system
|
||||||
MD_ExprOprTable table = {0};
|
MD_ExprOprTable table = {0};
|
||||||
{
|
{
|
||||||
MD_ExprOprList list = {0};
|
// @notes To start using the expression system we have to decide what
|
||||||
|
// the expression operator table is going to look like. To do this we
|
||||||
|
// build up a list of operators and then bake that list into an
|
||||||
|
// optimized operator table for the parser to use.
|
||||||
|
//
|
||||||
|
// An operator string can be any string that parses as exactly one
|
||||||
|
// main node in metadesk. So it must count as a single token, and it
|
||||||
|
// cannot be a tag "@", set delimiter "()[]{}", or separator ",;".
|
||||||
|
//
|
||||||
|
// The system does have one special case for operator strings. A
|
||||||
|
// postfix operator may be created with "()", "[]", "{}", "[)" or "(]"
|
||||||
|
// these get specially interpreted to mean that a set with those
|
||||||
|
// delimiters may be used as a postfix operator. This can be used to
|
||||||
|
// parse things like array indexers and function calls.
|
||||||
|
//
|
||||||
|
// Here we just use symbol tokens as operators, but identifiers as
|
||||||
|
// operators are also allowed.
|
||||||
|
//
|
||||||
|
// Here we can attach user data in two forms (the last two parameters)
|
||||||
|
// The first is intended for static integers like enum values here.
|
||||||
|
// The second is intended for non-static user data like a pointer to
|
||||||
|
// another data structure. Both are optional.
|
||||||
|
//
|
||||||
|
// The bake function converts the list into a table optimized for
|
||||||
|
// parsing, but first it also checks the operator list. These checks
|
||||||
|
// include checking the names as described above, making sure
|
||||||
|
// there are no ambiguities from colliding operators, and making sure
|
||||||
|
// there are no mismatches of precedence levels and operator kinds
|
||||||
|
// that cannot be resolved.
|
||||||
|
//
|
||||||
|
// The memory used in the list is also used by the table, so they
|
||||||
|
// should be on the same arena, or at the very least the list's arena
|
||||||
|
// should not be cleared while the table is still in use.
|
||||||
|
|
||||||
|
MD_ExprOprList list = {0};
|
||||||
MD_ExprOprPush(arena, &list, MD_ExprOprKind_Binary, 1, MD_S8Lit("+"), OpAdd, 0);
|
MD_ExprOprPush(arena, &list, MD_ExprOprKind_Binary, 1, MD_S8Lit("+"), OpAdd, 0);
|
||||||
MD_ExprOprPush(arena, &list, MD_ExprOprKind_Binary, 2, MD_S8Lit("*"), OpMul, 0);
|
MD_ExprOprPush(arena, &list, MD_ExprOprKind_Binary, 2, MD_S8Lit("*"), OpMul, 0);
|
||||||
|
MD_ExprOprPush(arena, &list, MD_ExprOprKind_Binary, 3, MD_S8Lit("&"), OpIllegal, 0);
|
||||||
|
|
||||||
table = MD_ExprBakeOperatorTableFromList(arena, &list);
|
table = MD_ExprBakeOperatorTableFromList(arena, &list);
|
||||||
}
|
}
|
||||||
|
|
||||||
// print the verbose parse results
|
// apply expression parsing to each top level node
|
||||||
for (MD_EachNode(root_it, list->first_child))
|
for (MD_EachNode(root_it, list->first_child))
|
||||||
{
|
{
|
||||||
|
// init eval map
|
||||||
|
eval_map = MD_MapMake(arena);
|
||||||
|
|
||||||
MD_Node *root = MD_ResolveNodeFromReference(root_it);
|
MD_Node *root = MD_ResolveNodeFromReference(root_it);
|
||||||
for (MD_EachNode(node, root->first_child))
|
for (MD_EachNode(node, root->first_child))
|
||||||
{
|
{
|
||||||
|
// @notes An expression parse is an extra stage of analysis on top
|
||||||
|
// of the initial Metadesk parse. It takes in a range of Metadesk
|
||||||
|
// nodes specified as (first, one-past-last). Here we want to
|
||||||
|
// parse all of the children of the top-level node as a single
|
||||||
|
// expression, so we use the node's `first_child` as the first and
|
||||||
|
// nil as the one-past-last.
|
||||||
|
//
|
||||||
|
// The parser can return a list of new error messages, and an
|
||||||
|
// expression tree.
|
||||||
|
//
|
||||||
|
// The tree holds pointers back to the original operator data from
|
||||||
|
// the setup, so the tree should be on the same arena, or on at
|
||||||
|
// at least the operator memory should outlive the tree.
|
||||||
|
|
||||||
|
// run the expression parse
|
||||||
MD_ExprParseResult parse = MD_ExprParse(arena, &table, node->first_child, MD_NilNode());
|
MD_ExprParseResult parse = MD_ExprParse(arena, &table, node->first_child, MD_NilNode());
|
||||||
|
|
||||||
// print errors
|
// print errors
|
||||||
@@ -110,13 +244,20 @@ int main(int argc, char **argv)
|
|||||||
MD_PrintMessage(stdout, code_loc, message->kind, message->string);
|
MD_PrintMessage(stdout, code_loc, message->kind, message->string);
|
||||||
}
|
}
|
||||||
|
|
||||||
// print the expression
|
if (parse.expr != 0)
|
||||||
if (node->string.size != 0)
|
|
||||||
{
|
{
|
||||||
fprintf(stdout, "%.*s = ", MD_S8VArg(node->string));
|
// evaluate the expression
|
||||||
|
int eval_result = eval_expression(parse.expr);
|
||||||
|
MD_MapInsert(arena, &eval_map, MD_MapKeyStr(node->string), (void*)(MD_u64)eval_result);
|
||||||
|
|
||||||
|
// print the expression
|
||||||
|
if (node->string.size != 0)
|
||||||
|
{
|
||||||
|
fprintf(stdout, "%.*s = ", MD_S8VArg(node->string));
|
||||||
|
}
|
||||||
|
print_expression(stdout, parse.expr);
|
||||||
|
fprintf(stdout, "; (%d)\n", eval_result);
|
||||||
}
|
}
|
||||||
print_expression(stdout, parse.expr);
|
|
||||||
fprintf(stdout, ";\n");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -8,7 +8,32 @@ a: 1;
|
|||||||
b: 2;
|
b: 2;
|
||||||
c: 3;
|
c: 3;
|
||||||
|
|
||||||
w: 100;
|
// @notes See expr_c_like.mdesk for an explanation of why these have to be
|
||||||
|
// wrapped in parentheses in some cases.
|
||||||
|
l: 5*5 + 1;
|
||||||
|
m: (5*(5 + 1));
|
||||||
|
n: ((5 + 1)*(5 + 1));
|
||||||
|
|
||||||
|
w: 0x100;
|
||||||
x: a;
|
x: a;
|
||||||
y: b + w*a;
|
y: b + w*a;
|
||||||
z: c + w*b + w*w*a;
|
z: c + w*b + w*w*a;
|
||||||
|
|
||||||
|
// @notes The Metadesk expression parser will automatically accept any set with
|
||||||
|
// delimiters as a leaf. In this example we are doing an extra custom check to
|
||||||
|
// disallow these cases.
|
||||||
|
range: ([0,64));
|
||||||
|
origin: ({50,100});
|
||||||
|
p: (origin + {0, -20});
|
||||||
|
|
||||||
|
// @notes We can also generate errors that point at operators just as easily:
|
||||||
|
`no_&`: 5000 & 0xFF;
|
||||||
|
|
||||||
|
// @notes The expression parser will produce an error if the series of nodes
|
||||||
|
// that form the expression cannot form a complete expression.
|
||||||
|
bad_1: a b c;
|
||||||
|
bad_2: z % b;
|
||||||
|
|
||||||
|
// @notes Surprising things might count as expressions! Anything can be a leaf
|
||||||
|
// so long as it is not recognized as an operator.
|
||||||
|
`good?`: ! + ? * ==;
|
||||||
|
|||||||
+8
-1
@@ -561,21 +561,28 @@ enum
|
|||||||
MD_NodeFlag_HasBraceLeft = (1<<4),
|
MD_NodeFlag_HasBraceLeft = (1<<4),
|
||||||
MD_NodeFlag_HasBraceRight = (1<<5),
|
MD_NodeFlag_HasBraceRight = (1<<5),
|
||||||
|
|
||||||
|
MD_NodeFlag_MaskSetDelimiters = (0x3F<<0),
|
||||||
|
|
||||||
MD_NodeFlag_IsBeforeSemicolon = (1<<6),
|
MD_NodeFlag_IsBeforeSemicolon = (1<<6),
|
||||||
MD_NodeFlag_IsAfterSemicolon = (1<<7),
|
MD_NodeFlag_IsAfterSemicolon = (1<<7),
|
||||||
|
|
||||||
MD_NodeFlag_IsBeforeComma = (1<<8),
|
MD_NodeFlag_IsBeforeComma = (1<<8),
|
||||||
MD_NodeFlag_IsAfterComma = (1<<9),
|
MD_NodeFlag_IsAfterComma = (1<<9),
|
||||||
|
|
||||||
|
MD_NodeFlag_MaskSeperators = (0xF<<6),
|
||||||
|
|
||||||
MD_NodeFlag_StringSingleQuote = (1<<10),
|
MD_NodeFlag_StringSingleQuote = (1<<10),
|
||||||
MD_NodeFlag_StringDoubleQuote = (1<<11),
|
MD_NodeFlag_StringDoubleQuote = (1<<11),
|
||||||
MD_NodeFlag_StringTick = (1<<12),
|
MD_NodeFlag_StringTick = (1<<12),
|
||||||
MD_NodeFlag_StringTriplet = (1<<13),
|
MD_NodeFlag_StringTriplet = (1<<13),
|
||||||
|
|
||||||
|
MD_NodeFlag_MaskStringDelimiters = (0xF<<10),
|
||||||
|
|
||||||
MD_NodeFlag_Numeric = (1<<14),
|
MD_NodeFlag_Numeric = (1<<14),
|
||||||
MD_NodeFlag_Identifier = (1<<15),
|
MD_NodeFlag_Identifier = (1<<15),
|
||||||
MD_NodeFlag_StringLiteral = (1<<16),
|
MD_NodeFlag_StringLiteral = (1<<16),
|
||||||
MD_NodeFlag_Symbol = (1<<17),
|
MD_NodeFlag_Symbol = (1<<17),
|
||||||
|
|
||||||
|
MD_NodeFlag_MaskLabelKind = (0xF<<14),
|
||||||
};
|
};
|
||||||
|
|
||||||
typedef struct MD_Node MD_Node;
|
typedef struct MD_Node MD_Node;
|
||||||
|
|||||||
Reference in New Issue
Block a user