Parser:Type_resolver - hpgDesigns/hpgdesigns-dev.io GitHub Wiki
Many aspects of EDL as inherited from GML conflict or seem inefficient when compared with a C++ environment. Also, C++ isn't exactly a jewel in many cases, either. For example, C++ requires pointer-based access to use -> instead of the traditional dot. GML offers switch() of multiple types, but is sadly inefficient at doing so, employing an O(N) segment of if()s. GML's dot operator treats the left-hand operand as an integer, and accesses the right-hand member, where C++ expects a const& instance to the left and accesses the member named to the right in that.
These conflicts could be resolved to improve upon both languages with the use of a good type resolution system.
Classes
The parser is stack-based, and operates off of a structure containing a number of identifying variables. The structure is as follows:
{
string op; // What would follow the word "operator" if overloaded
externs *type; // The type we would return if this were it for the stack: this tracks all referencing!
unsigned otype; // The kind of operator this is; binary or unary, for example
unsigned short prec; // The precedence integer of this level.
unsigned short pad; // The number of & referencers on top of our existing ref stack
rf_node *deref; // The rf_node of the next dereference needed, or NULL if conked out
}
Design
The image to the right shows a sample expression being evaluated for type. Red gradated cells are the ones being operated on. Green gradated cells show the result. Result cells that are being operated on are shaded yellow. The columns show the type being stored (if any), the symbol the operator represents (if any), and the precedence of the operator represented (if there is an operator).
Here are a few simple rules implemented when evaluating an expression:
- The stack starts with one blank item (both type and operator fields NULL)
- An operator writes to the current item if it is NULL.
- Otherwise, it must be unary, and a new item is pushed with the type field NULL.
- A typed identifier can modify the top node, but it will never push a
new one.
- This is fine as, at the mention of an identifier, the type field must be NULL or a cast.
- Before push, the stack is traversed, and all items of higher
precedence than the new operator are evaluated.
- When a parenthesis is pushed, old stack items are not popped.
- The parenthesis is pushed with precedence zero to prevent evaluation of lower items.
- A closing parenthesis behaves specially. This is the behavior:
- Search for the opening parenthesis, popping everything in between.
- On the level containing the starting parenthesis:
- If the type is NULL, overwrite it.
- If the type was named explicitly, leave it alone.
- If the type was named as an identifier, execute operator() on it, or leave it alone.
- When the stack is popped, the second-to-top item on the stack ("low") is operated against the top item ("high"), in the fashion `[low type] [low op] [high type]`.