Long, long ago, in a land far-- actually, it was in this chair. Or some chair that looked similar. Be it as it may, a while back, I took a shortcut and thought, "If this ever comes back to bite me, it'll be after the rest of the project is stable and I can fix it without fear." Well, that mostly came true. There are no detrimental effects at this time, simply because the amount of skill required for the malefaction to manifest simply isn't being exhibited by ENIGMA users at this time. For those who wish to be weary, this is the issue:
When I designed the C++ parser, I did not implement a proper overloading system. Overloads were, instead of instantiated as children of a main instance as I considered doing, applied to the current copy by way of modifying the bounds for acceptable parameter numbers. For instance,
string func(int a) { return "test"; }
double func(int a, int b) { return "test2"; }
is stored only as a function returning double that takes either 1 or 2 parameters. Had the second function taken three parameters, the choices would be 1, 2, or 3, even though 3 was actually an invalid option. This makes for a blazingly inaccurate syntax check in rare situations (a GML user would never come close to noticing).
It also means a less pleasant inaccuracy on a more important system.
The following expressions were passed to a new type resolution system of mine:
room_speed
var
*var
room
room + room
room + 1
room - 1
var[5]
The results were, respectively, as follows:
Let's see if I can't waste some space and make a nicer table.
room_speed | => | int |
var | => | var |
*var | => | double |
room | => | roomv |
room + room | => | variant |
room + 1 | => | variant |
room - 1 | => | double |
var[5] | => | variant |
The more curious of you have a few questions. I'll do my best to answer those here:
room_speed and the like are being left integers. I see no point in wasting memory and speed so users can set some random array element in global scalars.
The type resolution system doesn't distinguish between cast and type at the moment (by choice, so I can debug without thinking of a global by that type.
)
The type "roomv" is a special type with an overloaded operator= that calls room_goto(). It inherits from variant.
Because "roomv" is a special variant, room + room will return variant. This is correct. Room + anything will, in fact, so you can keep adding things such as strings.
However, room - 1 is double because subtraction only works on such. It wasn't worth it to avoid a warning on re-cast. Maybe I'll change my mind later, but in any case, the resolver was accurate.
Calls to [] on var are indeed variants.
So what's the problem? Look at item three, "*var". The parser can't distinguish between binary and unary *. Even though it knew it wanted the unary version, it couldn't get the accurate type, because I only store the type of the last declared overload, and so the parser was given `double`, the type returned by the multiplication operator.
When I need, I will store all overloads with argument counts and types, but that is a job for after the basics are all working. I want one more thing to work before I proceed, and it is indeed a trick; I would like for templated types to work, and if possible (by which I mean, it shall pass), templated casts. This way, if you have map<int,int> amap, you can say amap[whatever].x and have it behave correctly. That has been important to me since square one.
Another nice thing I can implement with the help of this system is the switch() optimization, as well as something brand new.
It befuddles me that neither C++ nor GML have a foreach construct. ENIGMA will be given one. It will work rather uniquely, since neither language was designed for it. Basically, it will have the following syntaxes:
foreach (array as element)
foreach (element in array)
foreach (element : array) //Java-esque
Trouble is getting it to work around this little issue: "in" and "as" are common variable names. So, I'm going to work around that thanks to how carefree my parser is when it operates.
foreach in in as
do_something()
will work fine.
To ease the sadistic and curious, that will look like this in intermediate parse:
foreach(in)in;as;do_something();
sssssss(nn)nn;nn;nnnnnnnnnnnn();
Which makes it very easy to parse out. The first item, in (), is either what I'll be iterating, or the element name. The second, of form "nn;", will determine which; it should be "in" or "as". The third set, denoted easily by regexp /;(^;);/, is the corresponding element of the foreach.
So, how will that work from there? Simple. I determine which is the container to iterate. I then check for the following:
- Typedef by name "iterator"; iterator container::begin(); iterator container::end(); iterator::operator++() or iterator::operator++(void)
- container::operator[](int); size_t container::size() or typeof(container::operator[](int))::operator bool()
Depending on which of those I find, the foreach will be replaced with code to iterate each. The real trick is managing to proxy the "as" component in as a method of retrieving the element itself, and not the iterator type. Mind all of you, I'm not afraid to implement a macro if I must. But I imagine I can find a creative way to cram more instructions into the mix using operator, .
Anyway, I'll be busy. Thanks for your patience. I know this is taking a while, but the level of detail Ism and I've put into this goes far beyond the previous releases' quicker development time.
Peace.