wollok
wollok copied to clipboard
Refactor REPL context
Original Issue #764: every time you send a message in Repl console, you put the command in a stack context.
As Javi said:
Ok. This is basically how the REPL currently works.
Each line you write it reuses the same interpreter, and therefore the context. Each new line "pushes" a new stack frame with the new content, so that all previous lines are availables as parent context.
For example this program
var a = 2
var b = 3
var c = a + b
Creates a stack like this
(a)
`-- (b)
`-- (c)
And so on. If you have a 100 lines REPL session then you will have a 100 depth stack.
I'm not sure how to solve this easily :S
This can be seen in WollokInterpreter.xtend
override interpret(EObject rootObject, Boolean propagatingErrors) {
try {
log.debug("Starting interpreter")
val stackFrame = rootObject.createInitialStackElement
executionStack.push(stackFrame)
...
First I just tried to clear the stack at the end
finally {
debugger.terminated
executionStack.clear
}
}
But then this happened
Wollok interactive console (type "quit" to quit):
>>> var a = 0
>>> var b = 12 /a
>>> a
ERROR: Couldn't resolve reference to Referenciable 'a'. (line: 1)
>>> var a = 0
@tesonep any idea on how we could change the design of the REPL ? Maybe if the interpreter has some kind of "memento" set get and set from outside ?
I'm thinking that the REPL store all the REPL lines. And on each execution clear the state of the editor and assemble a new "program a { .... }" and evaluate it. But I'm afraid that could be even worst in memory consumption than just keeping the stack.
I would be great to have the program as objects (WProgram) and be able to append new elements (lines) to the body, and then set it to evaluation. Avoiding strings :(
It is not exactly a stack. Oh well, yes we do have a Stack object and the interpreter when called "performOnStack" pushes a new element to the stack, executes a closure and the pops it.
Elements on the stack are EvaluationContext (interface). They are actually connected between each other like a linked list. An evaluation Context could have a parent. If it doesn't resolve a variable then it goes up to the parent, and so on.
I think that it also follows the nature of function/method calls, right ? I'm how would it be without a stack. I guess that the EvaluationContext should be passed for all evaluating methods of the interpreter. Since the interface of the interpreter is to evaluate an EObject. And the problem here is that are EObject (and mostly in xtext) are autogenerated, therefor we cannot add them state. Which is the main usage of the stack, to isolate state (scope) with the EvaluationContext. Types of EvaluationContext:
- WollokObject: the "self". This is not a EObject. Although created from one
- MapBased: used for invoking methods holding parameter values
- *Composite: *to compose them, like when running a method you have a Composite of MapBased (local and parameters) + WollokObject (self). Also if the object was a literal, it will have a parent EvaluationContext, which could also be a Composite, if the object was defined in a method, with new locals + another self.
Also probably the stack should be in a Thread object and not in the interpreter, for example for multithreading
Most of this design is decoupled from Wollok into a generic package called "org.uqbar.project.xinterpreter". I wanted to abstract away it along with the debugger infraestructure and make it a separated open source project to contribute to XText for anyone who wants to build an interpreter, not to start from scratch. You can checkouthere: context, interpreter, stack & debugger.
How could this be without a stack ? Also notice that this stack is not the same as in "stack-oriented languages".