Function evaluationFunction evaluation javelin Sat, 2002-11-30 18:27
The expression parser is responsible for evaluating functions (as well as doing other kinds of parsing). It's recursive in design, which means that during evaluation, the parser often calls itself to evaluate subportions of an expression.
For example, here's how [add(1,sub(3,2))]foo is evaluated:
[add(1,sub(3,2))]foo The parser starts with the first character. It's a [, which causes the parser to recurse, and turns on mandatory function evaluation -- the next thing we expect to see is a function. The next evaluation will terminate when it reaches a ]. add(1,sub(3,2))]foo The level-2 parser starts with the first character ('a') and keeps reading until it comes to a special character, the '(', which marks the end of a function's name. It looks to see if 'add' is a valid function, which it is, and now recurses to parse the first argument. The next evaluation will terminate when it reaches a comma, which is the argument separator. 1,sub(3,2))]foo The level-3 parser starts with the first character ('1') and keeps reading until it comes to a special character, the comma, which is the terminator it was expecting. Because no other special characters have been reached, it returns what it's got so far ('1') as a literal. The level-2 parser recurses again to evaluate the second argument to add(): sub(3,2))]foo The level-3 parser starts with the first character ('s') and keeps reading until it comes to a special character, the '('. It checks that 'sub' is a function, and recurses to parse its first argument. 3,2))]foo The level-4 parser returns the literal '3' The level-3 parser recurses again to evaluate the second argument to sub(): 2))]foo The level-4 parser returns the literal '2'. It returns because it hits a ')', which denotes the end of an argument list. ))]foo The level-3 parser now calls the internal sub() function, with arguments '3' and '2'. That function returns '1', and this parser level returns with that value. )]foo The level-2 parser now reads an ')', which terminates the argument list to add(). It calls the internal add() function, with arguments '1' and '1'. That function returns '2'. ]foo The top-level parser now expects to see a ']', marking the end of its recursion for mandatory function evaluation. It gets one, and the overall result of the recursive parsing was '2'. foo The top-level parser continutes to work its way through the characters. No more special characters are encountered, so 'foo' is copied into the return buffer. The top-level parser returns '2foo', and parsing is done.
A player set DEBUG and evaluating the expression above would see very similar output, though DEBUG, for brevity, does not show evaluations at the level of literals:
#7! [add(1,sub(3,2))]foo :
#7! add(1,sub(3,2)) :
#7! sub(3,2) => 1
#7! add(1,sub(3,2)) => 2
#7! [add(1,sub(3,2))]foo => 2foo
BracketsBrackets javelin Sat, 2002-11-30 18:29
Square brackets signal the expression parser that it should recurse, and must parse whatever it finds for functions. This means that you should use brackets in situations where the parser isn't expecting to parse for functions. Currently, the parser expects to parse for functions in the first word of whatever it's parsing, and nowhere else.
think add(1,1) foo
returns '2 foo', but this:
think foo add(1,1)
returns 'foo add(1,1)'. If you wanted to get 'foo 2', you'd have to:
think foo [add(1,1)]
Because the first word of what's being parsed by a level of the parser is automatically checked for functions, you don't need to do this:
start [add([sub([mul(3,3)],2)],1)] end
because the sub (and the mul) are the first words of expressions that one of the recursing parsers will evaluate. So you can write the above as:
start [add(sub(mul(3,3),2),1)] end
In addition to saving a few characters, this is more readable. It also saves a couple of extra recursions, which might be important if you're running into the parser's call_limit. (The impact on code speed is usually negligible, however).
Commands aren't functionsCommands aren't functions javelin Sat, 2002-11-30 18:34
The command parser calls the function parser when evaluating arguments, but the function parser never directly calls the command parser (although side-effect functions can indirectly result in commands being
run). This insures the synchrony of the function parser, and resembles the way procedural languages like C are parsed. This means that code like the following is wrong (if you meant for it to @pemit Yes to the enactor):
think [if(1,@pemit %#=Yes,@pemit %#=No)]
(By the way, implementing functions like wait(), trigger(), and force() breaks this fundamental paradigm. This is one reason why the PennMUSH developers do not intend to put these functions into standard PennMUSH.)