Skip to content

Commit

Permalink
up to first version of arguments.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ducasse committed Dec 29, 2024
1 parent caf7c17 commit 0e430f5
Show file tree
Hide file tree
Showing 3 changed files with 66 additions and 40 deletions.
2 changes: 1 addition & 1 deletion Chapters/06-VariablesAndScopes.md
Original file line number Diff line number Diff line change
Expand Up @@ -259,7 +259,7 @@ Now the tests should pass.
### Evaluating Variables: Global Reads

We finish this chapter with the reading of global variables, which covers two cases: proper global variables and access to classes.
It illustrate the chain of scopes where an instance scope parent is a global scope.
It illustrates the chain of scopes where an instance scope parent is a global scope.

To better control our testing environment, we decided to not use the Pharo environment by default.
Instead, the interpreter will know its global scope in an instance variable and look up globals in it using a simple API, making it possible to use the system's global environment instead if we wanted to.
Expand Down
37 changes: 25 additions & 12 deletions Chapters/07-EvaluatorFirstMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,10 @@ Figure *@callstack@* presents a call stack with two methods. The first method in




### Putting in Place the Stack

We will use the stack implementation available at github://pharo-containers/.

```
Metacello new
baseline: 'ContainersStack';
repository: 'github://pharo-containers/Containers-Stack:v1.0/src';
load.
```

Since methods define a scope with their temporary variables and arguments, we represent frames using a new kind of scope: a method scope.
For now, the method scope will store the current receiver, and later its parent scope, and a set of key-value pairs representing the variables defined in the current method execution: the arguments and temporaries (see Chapter *@@*).
For now, the method scope will store the current receiver, and later its parent scope, and a set of key-value pairs representing the variables defined in the current method execution: the arguments and temporaries (see Chapter *@cha:messageArgs@*).

```
Object << #CMethodScope
Expand All @@ -60,6 +50,20 @@ CMethodScope >> receiver
```




### Putting in Place the Stack

We will use the stack implementation available at github://pharo-containers/.

```
Metacello new
baseline: 'ContainersStack';
repository: 'github://pharo-containers/Containers-Stack:v1.0/src';
load.
```


A first step to introduce stack management without breaking all our previous tests is to replace the single `receiver` instance variable with a stack that will be initialized when the evaluator is created. The top of the stack will represent the current execution, and thus we will take the current receiver at each moment from the stack top. Moreover, each time we tell our interpreter to execute something we need to initialize our stack with a single frame.


Expand Down Expand Up @@ -112,6 +116,15 @@ As the reader may have observed, this stack can only grow.
We will take care of popping frames from the stack later when we revisit method returns.


SD: currentScope is not connected to the stack and this is strange.
Because it is defined as.

```
currentScope ^ CInstanceScope new receiver: self receiver; parentScope: globalScope; yourself
```

Note that we will have to improve the situation because the method `currentScope` (which is creating an instance scope) is not connected with the frame and this will be a problem.

### Evaluating a First Message Send

Let's start as usual by defining a new method exhibiting the scenario we want to work on.
Expand Down Expand Up @@ -328,7 +341,7 @@ We are ready to continue our journey in message-sends.



# Conclusion
### Conclusion

In this chapter, we set the infrastructure to support message execution.
We introduced the important notion of stack frames whose elements represent a given execution.
Expand Down
67 changes: 40 additions & 27 deletions Chapters/08-EvaluatorMessageArg.md
Original file line number Diff line number Diff line change
Expand Up @@ -44,8 +44,16 @@ Since we have not implemented any support for arguments yet, this test should fa

### Enhance Method Scope

The method scope was limited to managing the receiver. It is not enough.
Method scopes should support variables as well. To do so, we basically add a dictionary
The current method scope is limited to managing the receiver. It is not enough.
Method scopes should support variables as well as parsent scope.

We add the `parentScope:` instance variable and its accessors as well as

```
Object << #CMethodScope slots: { #receiver . #parentScope . #variables }; package: 'Champollion'
```

To support variables, we add a dictionary
to hold them and some utility methods.

```
Expand All @@ -61,8 +69,10 @@ CMethodScope >> at: aKey put: aValue
variables at: aKey put: aValue
```

In addition we add support for identifying the scope.
We specify `scopeDefining:` as follows:
In addition, we add support for identifying the scope.
We define the method `scopeDefining:` as follows: It checks whether the looked up name is
a variable one.

```
CMethodScope >> scopeDefining: aString
(variables includesKey: aString)
Expand All @@ -73,7 +83,8 @@ CMethodScope >> scopeDefining: aString

Note that the `scopeDefining:` delegates to its parent scope when the variable is not locally found.

Finally we add an `read:` method using the variable implementation logic.
Finally we add a `read:` method using the variable implementation logic.

```
CMethodScope >> read: aString
^ variables at: aString
Expand All @@ -91,7 +102,8 @@ Implementing argument support requires two main changes:
Let's start with the second step, the callee side, and since all variable reads are concentrated on the scope lookup, we need to add the method scope in the scope chain.

#### Previous situation
Previously the method `execute:withReceiver:` was defined as follows

Previously the method `execute:withReceiver:` was defined as follows:

```
execute: anAST withReceiver: anObject
Expand All @@ -116,30 +128,40 @@ pushNewMethodFrame

#### Improved version

The new version is the following one:

```
CInterpreter >> execute: anAST withReceiver: anObject
| result |
self pushNewMethodFrame.
"Set up the scope chain"
self topFrame parentScope: (CInstanceScope new
receiver: anObject;
parentScope: globalScope;
yourself);
yourself.
yourself).
self topFrame receiver: anObject.
result := self visitNode: anAST.
self popFrame.
^ result
```

After pushing to the stack a new frame representing the method execution, we should make sure that the parent scope
of the method scope is an instance scope. This is what we were doing in the `currentScope` method.

Also notice that the instance scope and the method scope have both a receiver.
SD: When can they be different? And why the instance one is not enough since it is in the parent scope of the method.


We have still to make sure that `currentScope` refers to the top frame of the interpreter.
This is what the following redefinition expresses:

```
CInterpreter >> currentScope
^ self topFrame
```




Then we need to update `visitMessageNode:` to compute the arguments by doing a recursive evaluation, and then use those values during the new method activation.

```
Expand All @@ -152,21 +174,19 @@ CInterpreter >> visitMessageNode: aMessageNode
```


To include arguments in the method activation, let's add a new `arguments` parameter to our method `execute:withReceiver:` to get `execute:withReceiver:withArguments:`.
To include arguments in the method activation, we add a new `arguments` parameter to our method `execute:withReceiver:` to get `execute:withReceiver:withArguments:`.

In addition to adding the receiver to the new frame representing the execution, we add a binding for each parameter (called unfortunately arguments in Pharo AST) with their corresponding value in the argument collection.
We use the message `with:do:` to iterate both the parameter list and actual arguments as pairs.
In addition to adding the receiver to the new frame representing the execution, we add a binding for each parameter (called unfortunately arguments in Pharo AST) with their corresponding value in the argument collection. This binding is added to the variables of the top frame.
The message `with:do:` iterates both the parameter list and actual arguments as pairs.

```
CInterpreter >> execute: anAST withReceiver: anObject andArguments: aCollection
| result |
self pushNewMethodFrame.
"Set up the scope chain"
self topFrame parentScope: (CInstanceScope new
receiver: anObject;
parentScope: globalScope;
yourself);
yourself.
yourself).
self topFrame receiver: anObject.
anAST arguments
Expand All @@ -178,7 +198,8 @@ CInterpreter >> execute: anAST withReceiver: anObject andArguments: aCollection
```


Instead of just removing the old `executeMethod:withReceiver:` method, we redefine it calling the new one with a default empty collection of arguments. This method was used by our tests and is part of our public API, so keeping it will avoid migrating extra code and an empty collection of arguments seems like a sensible and practical default value.
Instead of just removing the old `executeMethod:withReceiver:` method, we redefine it calling the new one with a default empty collection of arguments.
This method was used by our tests and is part of our public API, so keeping it will avoid migrating extra code and an empty collection of arguments is a sensible and practical default value.

```
CInterpreter >> executeMethod: anAST withReceiver: anObject
Expand All @@ -197,16 +218,8 @@ Our tests should all pass now.











### Refactoring the Terrain

@@HERE

Let's now refactor a bit the existing code to clean it up and expose some existing but hidden functionality. Let us extract the code that accesses `self` and the frame parameters into two other methods that make more intention revealing that we are accessing values in the current frame.

Expand Down

0 comments on commit 0e430f5

Please sign in to comment.