Function evaluation

Function 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:

think [add(1,sub(3,2))]foo

#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
2foo

Brackets

Brackets 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.

So this:

  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 functions

Commands 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.)