Inline anonymous function declarations in TorqueScript
by Konrad Kiss · 03/20/2009 (1:52 pm) · 23 comments
After many hours of learning Bison and other stuff I always subconsciously hoped to never have to understand, + with a very helpful hint from Ben Garney, I managed to get anonymous functions (that I'm so fond of from JavaScript and mostly jQuery) right into TorqueScript! Yey.
For those of you, who are not yet familiar with anonymous functions, or how awesome they are, here's some quick info. Consider the following code:
function testFunction() {
%retval = myFuncCall(%variable, "callbackFunction");
}
function callbackFunction(%someparam) {
echo(%someparam);
}What this resource does is make the following possible as an alternative to the above code:
function testFunction() {
%retval = myFuncCall(%variable, function(%someparam) {
echo(%someparam);
});
}This resource makes the anonymous function receive an internal function name, and returns that name to the caller function (here myFuncCall). So in effect, all it does is insert the generated function name into myFuncCall and append the function definition to the file that's being compiled, like this:
function testFunction() {
%retval = myFuncCall(%variable, "autoFN1");
}
function autoFN1(%someparam) {
echo(%someparam);
}If you use it as a callback, you will still need to take care of calling the callback function of course, but this makes it possible to beautify the onMissionDownloadPhase1..2..3 uhm.. feature.
It also lets you have a better overview of your code. This is very handy when dealing with threaded callbacks. HttpObject, cURL, sql resultset callbacks are good uses of this, but it doesn't end there.
Ok, now how to get this into your source.. Note that I've done this in TGEA 1.7.1, but you probably should have no problem implementing this into any other flavor of Torque from TGE 1.5.2.
First of all, back up all your files, with special attention to the following ones:
Quote:
console/ast.h
console/CMDgram.y
console/CMDscan.l
console/codeBlock.cpp
console/console.cpp
platform/platform.cpp
console/ast.h - after
extern StmtNode *statementList;
add
extern StmtNode *inlineFnList;
console/CMDgram.y - after
%union {
char c;
int i;add
int id;
still in console/CMDgram.y - change
Quote:
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
;
slot_acc
to
Quote:
| VAR '[' aidx_expr ']'
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
| rwDEFINE '(' var_list_decl ')' '{' statement_list '}'
{
U32 autoId = yyvsp[-6].id;
String fnname = String("autoFN_" + String::ToString(autoId));
StringTableEntry afnName = StringTable->insert(fnname.c_str());
StmtNode *fndef = FunctionDeclStmtNode::alloc(afnName, NULL, $3, $6);
if(!gInlineFnList) {
gInlineFnList = fndef;
} else {
gInlineFnList->append(fndef);
}
$$ = StrConstNode::alloc((UTF8*)fnname.utf8(), false);
}
;
slot_acc
console/codeBlock.cpp - on both finds, after
statementList = NULL;
add
inlineFnList = NULL;
still in console/codeBlock.cpp - on both finds, after
// Now do some parsing. smCurrentParser->setScanBuffer(script, fileName); smCurrentParser->restart(NULL); smCurrentParser->parse();
add
// here we probably already have an inlineFnList as well
// with all the inline function definitions
// if there's something in it, append it to the statement list
if (statementList) {
if (inlineFnList) {
statementList->append(inlineFnList);
}
}console/console.cpp - after
StmtNode *statementList;
add
StmtNode *inlineFnList;
console/CMDscan.l - change
SPACE [ tvf]
HEXDIGIT [a-fA-F0-9]
%%
;
{SPACE}+ { }to
SPACE [ tvf]
HEXDIGIT [a-fA-F0-9]
%%
CMDlval.id = 0;
{SPACE}+ { }still in console/CMDscan.l - change
"function" { CMDlval.i = lineIndex; return(rwDEFINE); }to
"function" { CMDlval.i = lineIndex;
autoFuncID++;
CMDlval.id = autoFuncID;
return(rwDEFINE); }again in console/CMDscan.l near the top after #define YY_NEVER_INTERACTIVE 1 add:
extern U32 autoFuncID;
and finally in platform/platform.cpp, after the includes:
U32 autoFuncID = 0;
Now, run your console/generateCompiler.bat file to generate the CMD*.cpp and CMD*.h files. This is very important. If you make a syntax error, the files will not be generated, and you won't be able to compile your engine - in this case, your backups will be nice to have.
Then, recompile your engine, and cross your fingers. ;)
Thanks for reading through, enjoy anonymous embedded functions in TorqueScript!
--Konrad
@konradkiss
KonradKiss @ GitHub
konradkiss.com
About the author
http://about.me/konrad.kiss
#2
03/20/2009 (5:47 pm)
Awesome feature, Konrad. This is one of the things that I love about ActionScript.
#4
@Tom: Sounds really cool! Thank you!
03/23/2009 (12:01 am)
Thanks guys for all the awesome comments! :)@Tom: Sounds really cool! Thank you!
#5
04/09/2009 (10:56 am)
I am trying to implement this, and I think something is missing. After generating the compilers I get an error saying 'autoFuncID' is undeclared (and by reviewing the changes, it obviously is).
#6
in platform.cpp, after the includes:
in CMDscan.l near the top after #define YY_NEVER_INTERACTIVE 1 add:
Please let me know if you encounter any more troubles.
I have updated the resource to reflect these changes.
04/09/2009 (11:00 am)
@Manoel: you're right! sorry about that. Add the following:in platform.cpp, after the includes:
U32 autoFuncID = 0;
in CMDscan.l near the top after #define YY_NEVER_INTERACTIVE 1 add:
extern U32 autoFuncID;
Please let me know if you encounter any more troubles.
I have updated the resource to reflect these changes.
#7
You know what that means?
I compiled, but it's not working. My script compiles fine, but the anonymous function isn't called. It also does nothing when I call it manually on the console:
04/09/2009 (11:32 am)
@Konrad: Thanks a lot! It's compiling now. One more thing: generateCompiler.bat returns one warning: cmdgram.y contains 1 shift/reduce conflict.
You know what that means?
I compiled, but it's not working. My script compiles fine, but the anonymous function isn't called. It also does nothing when I call it manually on the console:
function test()
{
%cb = function(%foo) {echo("WORKS " @ %foo);};
echo(%cb); //Correctly prints the function internal name
call(%cb, "HAHAHAHA"); //Does nothing
}
#8
Shift-reduce conflict
If it compiles, I don't think you will have to worry much about it. The nature of inline embedded functions would give an explanation to this warning, so I think you can ignore that.
04/09/2009 (11:40 am)
I can't remember seeing this, no, but I found some docs about it here:Shift-reduce conflict
If it compiles, I don't think you will have to worry much about it. The nature of inline embedded functions would give an explanation to this warning, so I think you can ignore that.
#9
Try this:
04/09/2009 (11:44 am)
This works only if you have the function as a parameter of another function. This is important, because that's how it is defined in the grammar file.Try this:
function test(%callback)
{
echo("test begins.."):
eval(%callback@"("HAHAHA");");
echo("test ends.."):
}
test(function(%hahastr){
echo("this is echoed from the anonymous inline function");
echo("parameter: " @ %hahastr);
});
#10
--EDIT--
LOL, it's the website's fault! The code tag is eating $1, $3 variables, messing with the code.
This:
Looks like this on my unmodified code (1.8.1):
04/09/2009 (11:50 am)
No deal, this is what I get on my console:test begins.. <input> (0): Unable to find function autoFN_14 test ends..
--EDIT--
LOL, it's the website's fault! The code tag is eating $1, $3 variables, messing with the code.
This:
{ $$ = (ExprNode*)VarNode::alloc(, ); }Looks like this on my unmodified code (1.8.1):
Quote:the problem is that I don't know what are the last two arguments on this line:
{ $$ = (ExprNode*)VarNode::alloc($1, $3); }
StmtNode *fndef = FunctionDeclStmtNode::alloc(afnName, NULL, , );
#11
Edit: changed the problem parts to quotes, so nobody has to peek through the web source.
04/09/2009 (12:11 pm)
Yes, I know.. I've been trying to find a way to make it work, but I can't .. oh well, if anyone else does this, make sure you check the website's source for any code that's to be changed in CMDgram.y.Edit: changed the problem parts to quotes, so nobody has to peek through the web source.
#12
This will save my life since I am doing dozens of different HTTP requests and each one needs a callback.
04/09/2009 (12:24 pm)
Ah, now it works. Even my original call() test works too, and it also works with schedule()! =)This will save my life since I am doing dozens of different HTTP requests and each one needs a callback.
#13
Yeah, I'm using this for a gazillion cUrl requests myself. This does make things a lot easier.
04/09/2009 (12:26 pm)
Ha, nice.. well it does make sense afterall, since it is registered as an expression.. I didn't even think about uses like that.. :)Yeah, I'm using this for a gazillion cUrl requests myself. This does make things a lot easier.
#14
in the case of HTTP request callbacks, i generally do something like this:
how would something like that look with via inline anonymous functions ?
cheers,
orion
04/09/2009 (12:45 pm)
hey guys, i'm still trying to wrap my head around why this is useful, maybe you can help me out.in the case of HTTP request callbacks, i generally do something like this:
function getFooBarFromTheBackend()
{
sendRequest("http://blah.com/services?cmd=getMeFooBar", "onRequestCompleted_GetFooBar");
}
function onRequestCompleted_GetFooBar(%request)
{
if (%request.succeeded())
{
%foo = %request.getValue("foo");
%bar = %request.getValue("bar");
// other request-specific stuff
}
else
{
// etc
}
}how would something like that look with via inline anonymous functions ?
cheers,
orion
#15
This assumes that when onRequestCompleted fires, you call the inline function by the name that is passed as the second parameter (but I see that's how it's been anyway). You don't have to name it, and you can declare the callback function right in the calling function. Makes things a bit easier to oversee.
Edit: fix
04/09/2009 (12:50 pm)
Hey Orion,function getFooBarFromTheBackend()
{
sendRequest("http://...", function(%request) {
if (%request.succeeded())
{
%foo = %request.getValue("foo");
%bar = %request.getValue("bar");
// other request-specific stuff
}
else
{
// etc
}
});
}This assumes that when onRequestCompleted fires, you call the inline function by the name that is passed as the second parameter (but I see that's how it's been anyway). You don't have to name it, and you can declare the callback function right in the calling function. Makes things a bit easier to oversee.
Edit: fix
#16
i see the value in not having to name the function, although if you establish a naming convention that's not such a big deal,
and i see the organizational value in having the response handler embedded in the sendRequest() definition, but i guess i'm just not sure it seems like that big a win in exchange for what me seems like somewhat less readable code. .. but that probably just outs me as programmer who's stuck in his ways! ;)
what if i had two different ways i called sendRequest() but wanted the results to be parsed by the same function ? i guess you would want make a wrapper around sendRequest() so that you only call it one way.
04/09/2009 (1:02 pm)
thanks Konrad.i see the value in not having to name the function, although if you establish a naming convention that's not such a big deal,
and i see the organizational value in having the response handler embedded in the sendRequest() definition, but i guess i'm just not sure it seems like that big a win in exchange for what me seems like somewhat less readable code. .. but that probably just outs me as programmer who's stuck in his ways! ;)
what if i had two different ways i called sendRequest() but wanted the results to be parsed by the same function ? i guess you would want make a wrapper around sendRequest() so that you only call it one way.
#17
When you create a deeper tree of callbacks, it would definitely show the pros, as you'd clearly see the structure (and more importantly, depth) in which your callbacks are used.
Some pseudo-code:
It is a lot harder to track as separate functions. This is basically what I'm doing for stuff like data requests, and further conditional data requests, that depend on results of previous requests such as client authentication. I'm not trying to convince you, I just find it really convenient. :)
04/09/2009 (1:10 pm)
Yes, that'd be the way I'd go. I'm just used to this form from the jQuery / Ajax / other scripting languages,and I'm happy to be able to use this form in TorqueScript as well.When you create a deeper tree of callbacks, it would definitely show the pros, as you'd clearly see the structure (and more importantly, depth) in which your callbacks are used.
Some pseudo-code:
function test() {
issueRequest(function(response) {
if (response) {
issueRequest(function(response) {
if (response) {
issueRequest(function(response) {
if (response) {
issueRequest(function(response) {
if (response) {
// communication done
}
});
}
});
}
});
}
});
}It is a lot harder to track as separate functions. This is basically what I'm doing for stuff like data requests, and further conditional data requests, that depend on results of previous requests such as client authentication. I'm not trying to convince you, I just find it really convenient. :)
#18
This is also makes implementing user-input via GUIs much easier. In a past project with tons of GUIs where the user was asked to select stuff I ended up with dozens and dozens of functions which were merely callbacks. It got very annoying to keep track of.
04/09/2009 (2:30 pm)
There isn't much value when you have a couple callbacks, or every request only has a single callback. But when you have loads or requests, or requests which need multiple callbacks (my usual HTTP requests need a callback for line parsing, for connection errors and disconnect cleanups), the code suddenly becomes much more organized since you don't have as many separate functions cluttering your code.This is also makes implementing user-input via GUIs much easier. In a past project with tons of GUIs where the user was asked to select stuff I ended up with dozens and dozens of functions which were merely callbacks. It got very annoying to keep track of.
#19
To this:
09/14/2010 (7:54 pm)
It's been a while, but found a way to allow breakpoints inside inline functions. In codeBlock.cpp, change this:U32 CodeBlock::findFirstBreakLine(U32 lineNumber)
{
if(!lineBreakPairs)
return 0;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
U32 line = (p[0] >> 8);
if( lineNumber <= line )
return line;
}
return 0;
}To this:
U32 CodeBlock::findFirstBreakLine(U32 lineNumber)
{
if(!lineBreakPairs)
return 0;
U32 minLine = U32_MAX;
bool foundLine = false;
for(U32 i = 0; i < lineBreakPairCount; i++)
{
U32 *p = lineBreakPairs + i * 2;
U32 line = (p[0] >> 8);
if( line >= lineNumber )
{
foundLine = true;
minLine = getMin(line, minLine);
}
}
if (foundLine)
return minLine;
else
return 0;
}
#20
funny,
i only in the last couple weeks started using anonymous functions
and just this morning i was thinking about this very thread.
sadly i'm not in torque these days;
i'm working on a 2D casual game in flash for facebook,
and doing a bit of iOS fooling around in my free-time.
ooo
09/14/2010 (8:01 pm)
sweet Manoel.funny,
i only in the last couple weeks started using anonymous functions
and just this morning i was thinking about this very thread.
sadly i'm not in torque these days;
i'm working on a 2D casual game in flash for facebook,
and doing a bit of iOS fooling around in my free-time.
ooo

Associate Manoel Neto
Default Studio Name