KSP-KOS/KOS — pull request #280 by Dunbaratu (2014-10-19 03:52:59+00:00)
Title: Methods and properties interchangable Resolves #172 and #278

Before this change, the way it worked was this:
--------------------------

Calling someobject:somesuffix_A. Somesuffix_A is NOT meant to use
parentheses (it was made with 'new Suffix..' rather than 'new NoArgsSuffix..')
creates this:
```
push $someobject
push somesuffix_a
getmember // leaves result of GetSuffix() on the stack, which is
// an object atop the stack that the kos script can use.
```
Calling someobject:somesuffix_B(). Somesuffix_B IS meant to use
parentheses (it was made with 'new NoArgsSuffix..' rather than 'new Suffix..')
creates this:
```
push $someobject
push somesuffix_b
getmember // leaves result of GetSuffix() on the stack, which is
// an object of type Delegate that hasn't been invoked yet.
push $ // Marker for the bottom of the args on the stack
call // Consumes the (lack of) arguments on the stack, and the
// $ marker, then Invokes the Delegate, leaving
// its return val on the stack.
```

The change is two-way:
-------

### 1: For allowing thing:suffix() when it's supposed to be thing:suffix :

Thing:suffix() will build code looking like the "somesuffix_b" example
above, even though the value that getMember put on the stack isn't
really a delegate, so it fails when it tries to call that "delegate".
The fix is to have OpcodeCall notice when it's being used on an indirect
call despite the fact that said object isn't a delegate. In that case it
should just consume the stack down until that non-delegate object is on top,
and just quit. The non-delegate that GetMember left on the stack IS the
return value of the expression.

### 2: For allowing thing:suffix when it's supposed to be thing:suffix() :

Thing:suffix will build code looking like the "somesuffix_a" example
above, even though the value that getMember put on the stack is
really a Delegate that hasn't been invoked yet, and the value that
is passed back to the kos script on the stack is meant to be the result
of that invocation that hasn't occured. To fix this, OpcodeGetMember needs
to divert the execution path over into OpcodeCall and get OpcodeCall to
invoke the delegate as if it had been built using the "somesuffix_b"
pattern seen above. But it has to know *when* to do this, which is the
messy part. Because as you can see the code for "somesuffix_a" and
for "somesuffix_b" look identical during the first 3 instructions. Until
it peeks ahead at the next instructions, it doesn't know if it's being
called as part of a method invocation or not. It should only divert
into OpcodeCall when it knows an OpcodeCall isn't about to come up next.
To make this work without peeking ahead in the program to see, I subclassed
OpcodeGetMethod with OpcodeGetMember - it does the exact same thing, except
that when the instrucion is an OpcodeGetMember, it knows that the compiler
built the upcoming OpcodeCall already, and when it's an OpcodeGetMember,
it knows the compiler didn't. Only when it's an OpcodeGetMember should
it perform the logic to divert into OpcodeCall upon seeing a delegate.
If it's an OpcodeGetMethod, it knows such an OpcodeCall is on its way.