Skip to content

Commit

Permalink
use methodClass: super lookup pass + section renames
Browse files Browse the repository at this point in the history
  • Loading branch information
Ducasse committed Jan 10, 2025
1 parent 85b456e commit e348fc8
Show file tree
Hide file tree
Showing 7 changed files with 37 additions and 31 deletions.
10 changes: 5 additions & 5 deletions Chapters/02-AST.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
## Representing Code with Abstract Syntax Trees
## Code Representation with Abstract Syntax Trees
@cha:ast

To execute actual code on top of our ObjVlisp kernel, we need code to execute.
Expand Down Expand Up @@ -45,7 +45,7 @@ Fortunately, Pharo also includes a parser that does exactly this: the `Parser`.
The `Parser` class implements a parser for Pharo code.
It has two main modes of working: parsing expressions and parsing methods.

#### For the Purists: abstract vs concrete trees
#### For the Purists: Abstract vs Concrete Trees


People tend to make the distinction between abstract and concrete syntax trees.
Expand All @@ -55,7 +55,7 @@ This is similar for variable definition delimiters (pipes) or statement delimite
A concrete tree on the other hand keeps such information because tools may need it.
From that perspective, the Pharo AST is in between both. The tree structure contains no information about the concrete elements of the syntax, but these informations are remembered by the nodes so the source code can be rebuilt as similar as the original code as possible. However, we make a bit of language abuse and we refer to them as ASTs.

### Parsing Expressions
### Parse Expressions


Expressions are constructs that can be evaluated to a value.
Expand Down Expand Up @@ -499,7 +499,7 @@ In this section, we will explore some core-messages of Pharo's AST, that allow c
Most of these manipulations are rather primitive and simple.
In the next chapter, we will see how the visitor pattern in conjunction with ASTs empower us, and gives us the possibility to build more complex applications such as concrete and abstract evaluators as we will see in the next chapters.

#### Iterating over an AST
#### AST Iteration


ASTs are indeed trees, and we can traverse them as any other tree.
Expand All @@ -512,7 +512,7 @@ ASTs provide several protocols for accessing and iterating any AST node in a gen
- `aNode methodNode`: returns the method node that is the root of the tree. For consistency, expression nodes parsed using `parseExpression:` are contained within a method node too.


#### Storing Properties
#### Property Store

Some manipulations require storing meta-data associated to AST nodes.
Pharo ASTs provide a set of messages for storing arbitrary properties inside a node.
Expand Down
2 changes: 1 addition & 1 deletion Chapters/03-ASTVisitors.md
Original file line number Diff line number Diff line change
Expand Up @@ -358,7 +358,7 @@ The following test checks that we identify message selectors.
SearchVisitorTest >> testTokenInMessage
| tree visitor |
tree := Parser parseMethod: 'one
tree := OCParser parseMethod: 'one
self pharo2 pharoVar: 11.
'.
Expand Down
2 changes: 1 addition & 1 deletion Chapters/03-Visitors.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ The main purpose of the Visitor pattern is to externalize an operation from a da
In addition, it supports the modular definition of operation (independent from each other) and encapsulating their own data.


### A mini filesystem
### A Mini Filesystem


For example, let's consider a file system implemented with the composite pattern, where nodes can be files or directories.
Expand Down
17 changes: 9 additions & 8 deletions Chapters/05-EvaluatorStructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ This first test is worth one comment: since our evaluator is an AST interpreter,
In other words, we need to get the AST of the `returnInteger` method.
Instead of invoking the parser to get an AST from source code, we will use Pharo's reflective API to get the AST of an already existing method.

### Making the test pass: a First Literal Evaluator
### Making the Test Pass: a First Literal Evaluator


Executing our first test fails first because our test does not understand `interpreter`, meaning we need to implement a method for it in our test class.
Expand Down Expand Up @@ -167,7 +167,7 @@ CInterpreter >> visitLiteralValueNode: aLiteralValueNode

Our first test is now green and we are ready to continue our journey.

### Evaluating Literals: Floats
### Literal Evaluation: Floats


For completeness, let's implement support for literal floats.
Expand Down Expand Up @@ -206,11 +206,13 @@ Second, some would argue that this test is somehow repeating code from the previ
Since we will write many tests with a similar structure during this book, it comes in handy to share some logic between them. The two tests we wrote so far show a good candidate of logic to share as repeated code we can extract.

The method `executeSelector:` extracts some common logic that will make our tests easier to read and understand: it obtains the AST of a method from its selector, evaluates it, and returns the value of the execution.
We also take the opportunity to set in the AST the class from which the method is originating. It will help us in the future.

```
CHInterpreterTest >> executeSelector: aSymbol
CInterpreterTest >> executeSelector: aSymbol
| ast |
ast := OCParser parseMethod: (CInterpretable >> aSymbol) sourceCode.
ast methodClass: CInterpretable.
^ self interpreter execute: ast
```

Expand All @@ -235,13 +237,13 @@ CInterpreterTest >> testReturnFloat

We are ready to write tests for the other constants efficiently.

### Evaluating booleans
### Boolean Evaluation

Boolean literals are the` false` and `true` objects, typically used for conditionals and control flow statements.
In the previous sections we implemented support for numbers, now we introduce support for returning boolean values as follows:

```
CHInterpretable >> returnBoolean
CInterpretable >> returnBoolean
^ false
```

Expand All @@ -261,8 +263,7 @@ If everything goes ok, this test will be automatically green, without the need f
This is because booleans are represented in the AST with literal value nodes, which we have already implemented.


### Evaluating Literals: Arrays

### Literal Evaluation: Arrays

Now that we support simple literals such as booleans and numbers, let's introduce literal arrays.
Literal arrays are arrays that are defined inline in methods with all their elements being other literals.
Expand Down Expand Up @@ -298,7 +299,7 @@ CInterpreterTest >> testReturnRecursiveLiteralArray
```


### Making the test pass: visiting literal array nodes
### Make the Test Pass: visiting literal array nodes

We have to implement the method `visitLiteralArrayNode:` to visit literal arrays.
Literal arrays contain an array of literal nodes, representing the elements inside the literal array.
Expand Down
20 changes: 12 additions & 8 deletions Chapters/07-EvaluatorFirstMessage.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ In the previous chapters, we focused on structural evaluation: reading literal o
The work we did in the previous chapter is nevertheless important to set up the stage: we have a better taste of the visitor pattern, we started a first testing infrastructure, and eventually, message-sends need to carry out some work by using literal objects or reading and writing variables.


### Message concerns
### Message Concerns

Message-sends deserve a chapter on their own because they introduce many different concerns.
On the one hand, each message-send is resolved in two steps:
Expand All @@ -32,7 +32,7 @@ Figure *@callstack@* presents a call stack with two methods. The first method in



### Putting in Place the Stack
### Method Scope

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 *@cha:messageArgs@*).
Expand All @@ -52,7 +52,7 @@ CMethodScope >> receiver



### Putting in Place the Stack
### Put in Place the Stack

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

Expand Down Expand Up @@ -125,7 +125,7 @@ currentScope ^ CInstanceScope new receiver: self receiver; parentScope: glo

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
### A First Message Send Evaluation

Let's start as usual by defining a new method exhibiting the scenario we want to work on.
In this case, we want to start by extending our evaluator to correctly evaluate return values of message sends.
Expand Down Expand Up @@ -179,17 +179,21 @@ CInterpreter >> visitMessageNode: aMessageNode

All our tests should pass and in particular `testSelfSend`.

### Consolidating AST Access Logic
### AST Access Logic Consolidation

Pharo provides a way to get an AST from a compiled method, but we do not want to use
because the AST it returns is different from the one we want for this book (the variables are resolved based on a semantical analysis).
This is why we use `OCParser parseMethod: method sourceCode.`

To encapsulate such a decision we define the method `astOf:` and use it.
In addition we take the opportunity to set the class from which the method AST is originating.

```
astOf: aCompiledMethod
^ OCParser parseMethod: aCompiledMethod sourceCode.
| ast |
ast := OCParser parseMethod: aCompiledMethod sourceCode.
ast methodClass: aCompiledMethod methodClass.
^ ast
```

```
Expand All @@ -200,7 +204,7 @@ CInterpreter >> visitMessageNode: aMessageNode
^ self execute: (self astOf: method) withReceiver: newReceiver
```

### Balancing the Stack
### Balance the Stack

We mentioned earlier that when the execution of a method is finished and the execution returns to its caller method, its frame should be also discarded from the stack. The current implementation clearly does not do it.
Indeed, we also said that our initial implementation of the stack only grows: it is clear by reading our code that we never pop frames from the stack.
Expand Down Expand Up @@ -266,7 +270,7 @@ CInterpreterTest >> setUp
```


#### Making the Test Pass
#### Make the Test Pass

Executing this test breaks because the access to the instance variable `x` returns nil, showing the limits of our current implementation. This is due to the fact that evaluating message send `returnInstanceVariableX` creates a new frame with the collaborator as receiver, and since that frame is not popped from of the stack, when the method returns, the access to the `x` instance variable accesses the one of the uninitialized collaborator instead of the caller object.

Expand Down
10 changes: 5 additions & 5 deletions Chapters/08-EvaluatorMessageArg.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ In this chapter, we will extend this message passing implementation to support p
We will take advantage of the


### Supporting Message Arguments
### Message Argument Evaluation

So far we have worked only with unary messages. Unary messages have no arguments, so the number of programs we can express with them only is limited. The next step towards having a full-blown interpreter is to support message arguments, which will open the door to support binary and keyword messages. From the evaluator's point of view, as well as from the AST point of view, we will not distinguish between unary, binary, and keyword messages. The parser already takes care of distinguishing them and handling their precedence. Indeed, message nodes in the AST are the same for all kinds of messages, they have a selector and a collection of argument nodes.
Precedence is then modeled as relationships between the AST nodes.
Expand Down Expand Up @@ -103,7 +103,7 @@ 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
#### Previous Situation

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

Expand All @@ -128,7 +128,7 @@ pushNewMethodFrame
^ newTop
```

#### Improved version
#### Improved Version

The new version is the following one:

Expand Down Expand Up @@ -244,7 +244,7 @@ CInterpreter >> execute: anAST withReceiver: anObject andArguments: aCollection



### Handling Temporaries
### Temporary Evaluation

Temporary variables, or local variables, are variables that live within the scope of a method's **execution**.
Memory for such variables is allocated when a method is activated, and released when the method returns.
Expand Down Expand Up @@ -335,7 +335,7 @@ CInterpreter >> manageArgumentsTemps: aCollection of: anAST
```


### Implementing Temporary Variable Writes
### Temporary Variable Write Evaluation
The next aspect we have to address is temporary writing.

We test that writes to temporary variables are working too.
Expand Down
7 changes: 4 additions & 3 deletions Chapters/08-EvaluatorMessageLookup.md
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ So the top frame always contains the method it executes.
Finally, we redefine the `visitMessageNode:` method to change the class where to start looking for the method.

```
CInterpreterTest >> visitMessageNode: aMessageNode [
CInterpreter >> visitMessageNode: aMessageNode
| newReceiver method args lookupClass pragma |
newReceiver := self visitNode: aMessageNode receiver.
Expand All @@ -199,11 +199,12 @@ CInterpreterTest >> visitMessageNode: aMessageNode [
ifFalse: [ newReceiver class ].
method := self lookup: aMessageNode selector fromClass: lookupClass.
^ self executeMethod: method withReceiver: newReceiver andArguments: args
]
```

With this last change, your tests should now all pass.


### Overridden Messsages
### Overridden Messages


We have made sure that sending a message to `super` starts looking methods in the superclass of the class defining the method.
Expand Down

0 comments on commit e348fc8

Please sign in to comment.