Smalltalk current createPackage: 'Compiler-Interpreter' properties: #{}!
NodeVisitor subclass: #AIContext
	instanceVariableNames: 'outerContext pc locals selector'
	package: 'Compiler-Interpreter'!

!AIContext methodsFor: 'accessing'!

initializeFromMethodContext: aMethodContext
	self pc: aMethodContext pc.
    self receiver: aMethodContext receiver.
    self selector: aMethodContext selector.
    aMethodContext outerContext ifNotNil: [
		self outerContext: (self class fromMethodContext: aMethodContext outerContext) ].
    aMethodContext locals keysAndValuesDo: [ :key :value |
    	self locals at: key put: value ]
!

localAt: aString
	^ self locals at: aString ifAbsent: [ nil ]
!

localAt: aString put: anObject
	self locals at: aString put: anObject
!

locals
	^ locals ifNil: [ locals := Dictionary new ]
!

outerContext
	^ outerContext
!

outerContext: anAIContext
	outerContext := anAIContext
!

pc
	^ pc ifNil: [ pc := 0 ]
!

pc: anInteger
	pc := anInteger
!

receiver
	^ self localAt: 'self'
!

receiver: anObject
	self localAt: 'self' put: anObject
! !

NodeVisitor subclass: #ASTInterpreter
	instanceVariableNames: 'currentNode context shouldReturn currentValue'
	package: 'Compiler-Interpreter'!

!ASTInterpreter methodsFor: 'accessing'!

context
	^ context ifNil: [ context := AIContext new ]
!

context: anAIContext
	context := anAIContext
!

currentValue
	^ currentValue
! !

!ASTInterpreter methodsFor: 'initialization'!

initialize
	super initialize.
    shouldReturn := false
! !

!ASTInterpreter methodsFor: 'interpreting'!

assign: aNode to: anObject
	^ aNode binding isInstanceVar 
    	ifTrue: [ self context receiver instVarAt: aNode value put: anObject ]
      	ifFalse: [ self context localAt: aNode value put: anObject ]
!

continue: anObject
	currentValue := anObject
!

eval: aString
	"Evaluate aString as JS source inside an JS function. 
    aString is not sandboxed."
    
    | source function |
    
    source := String streamContents: [ :str |
    	str nextPutAll: '(function('.
        self context locals keys 
        	do: [ :each | str nextPutAll: each ]
          	separatedBy: [ str nextPutAll: ',' ].
        str 
        	nextPutAll: '){ return (function() {';
        	nextPutAll: aString;
            nextPutAll: '})() })' ].
            
	function := Compiler new eval: source.
    
	^ function valueWithPossibleArguments: self context locals values
!

interpret: aNode
	shouldReturn := false.
    self interpret: aNode continue: [ :value |
    	currentValue := value ]
!

interpret: aNode continue: aBlock

	shouldReturn ifTrue: [ ^ self ].

	aNode isNode 
    	ifTrue: [ self visit: aNode ]
        ifFalse: [ currentValue := aNode ].
	aBlock value: self currentValue
!

interpretAll: aCollection continue: aBlock
	self 
    	interpretAll: aCollection 
        continue: aBlock 
        result: OrderedCollection new
!

interpretAll: nodes continue: aBlock result: aCollection
	nodes isEmpty 
    	ifTrue: [ aBlock value: aCollection ]
    	ifFalse: [
    		self interpret: nodes first continue: [:value |
    			self 
                	interpretAll: nodes allButFirst 
                    continue: aBlock
  					result: aCollection, { value } ] ]
!

messageFromSendNode: aSendNode do: aBlock
	self interpretAll: aSendNode arguments continue: [ :args |
    	aBlock value: (Message new
    		selector: aSendNode selector;
        	arguments: args;
        	yourself) ]
! !

!ASTInterpreter methodsFor: 'visiting'!

visitAssignmentNode: aNode
	self interpret: aNode right continue: [ :value |
    	self continue: (self assign: aNode left to: value) ]
!

visitBlockNode: aNode
	"TODO: Context should be set"
    self continue: [ self interpret: aNode nodes first; currentValue ]
!

visitCascadeNode: aNode
	"TODO: Handle super sends"
	
    self interpret: aNode receiver continue: [ :receiver |
		"Only interpret the receiver once"
        aNode nodes do: [ :each | each receiver: receiver ].
  
    	self 
        	interpretAll: aNode nodes allButLast
    		continue: [
              	self 
                	interpret: aNode nodes last
                	continue: [ :val | self continue: val ] ] ]
!

visitClassReferenceNode: aNode
	self continue: (Smalltalk current at: aNode value)
!

visitDynamicArrayNode: aNode

	self interpretAll: aNode nodes continue: [ :array |
    	self continue: array ]
!

visitDynamicDictionaryNode: aNode
	
    self interpretAll: aNode nodes continue: [ :array | | hashedCollection |
    	hashedCollection := HashedCollection new.
        array do: [ :each | hashedCollection add: each ].
        self continue: hashedCollection ]
!

visitJSStatementNode: aNode
	shouldReturn := true.
	self continue: (self eval: aNode source)
!

visitReturnNode: aNode
    self interpret: aNode nodes first continue: [ :value |
    	shouldReturn := true.
		self continue: value ]
!

visitSendNode: aNode
	"TODO: Handle super sends"
    
    self interpret: aNode receiver continue: [ :receiver |
    	self messageFromSendNode: aNode do: [ :message |
        	self context pc: self context pc + 1.
        	self continue: (message sendTo: receiver) ] ]
!

visitSequenceNode: aNode
	self interpretAll: aNode nodes continue: [ :array |
    	self continue: array last ]
!

visitValueNode: aNode
	self continue: aNode value
!

visitVariableNode: aNode
    self continue: (aNode binding isInstanceVar
		ifTrue: [ self context receiver instVarAt: aNode value ]
		ifFalse: [ self context localAt: aNode value ])
! !

ASTInterpreter subclass: #ASTDebugger
	instanceVariableNames: 'continuation'
	package: 'Compiler-Interpreter'!

!ASTDebugger methodsFor: 'initialization'!

initialize
	super initialize.
    continuation := [  ]
! !

!ASTDebugger methodsFor: 'interpreting'!

interpret: aNode continue: aBlock
	continuation := [ super interpret: aNode continue: aBlock ]
! !

!ASTDebugger methodsFor: 'stepping'!

stepOver
	continuation value
! !

