Add some more language link code
This commit is contained in:
0
carp/install.sh
Normal file
0
carp/install.sh
Normal file
@@ -10,7 +10,7 @@ Class {
|
||||
{ #category : #'start-stop' }
|
||||
CarpApplication class >> start [
|
||||
|
||||
^ self startWith: LanguageLinkSettings jsDefaultSettings.
|
||||
^ self startWith: LanguageLinkSettings carpDefaultSettings.
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
@@ -18,10 +18,17 @@ CarpApplication >> baseApplication [
|
||||
^ CarpApplication
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpApplication >> debuggerClientFor: anException [
|
||||
"Answer the debugger client to be used by the Gt Post Mortem debugger"
|
||||
|
||||
^ CarpPostMortemDebugger new exception: anException
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpApplication >> initializeHandlers [
|
||||
loggingHandler := LanguageLinkLoggingHandler application: self.
|
||||
communicationHandler := LanguageLinkCommunicationHandler application: self.
|
||||
processHandler := LanguageLinkServerHandler application: self.
|
||||
"executionHandler := CarpExecutionHandler application: self"
|
||||
executionHandler := CarpExecutionHandler application: self
|
||||
]
|
||||
|
5
src/Carp/CarpCommandFactory.class.st
Normal file
5
src/Carp/CarpCommandFactory.class.st
Normal file
@@ -0,0 +1,5 @@
|
||||
Class {
|
||||
#name : #CarpCommandFactory,
|
||||
#superclass : #LanguageLinkCommandFactory,
|
||||
#category : #'Carp-Execution'
|
||||
}
|
22
src/Carp/CarpDeserializer.class.st
Normal file
22
src/Carp/CarpDeserializer.class.st
Normal file
@@ -0,0 +1,22 @@
|
||||
Class {
|
||||
#name : #CarpDeserializer,
|
||||
#superclass : #LanguageLinkDeserializer,
|
||||
#category : #'Carp-Serialization'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpDeserializer >> buildProxyFor: rawObject [
|
||||
| proxy |
|
||||
proxy := CarpProxyObject
|
||||
carpType: (rawObject at: #carptype)
|
||||
var: (rawObject at: #carpvar) asJSGI
|
||||
application: self application.
|
||||
self executionHandler registerObject: proxy.
|
||||
^ proxy
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpDeserializer >> deserialize: anObject [
|
||||
^ self new
|
||||
deserialize: anObject
|
||||
]
|
20
src/Carp/CarpExecutionHandler.class.st
Normal file
20
src/Carp/CarpExecutionHandler.class.st
Normal file
@@ -0,0 +1,20 @@
|
||||
Class {
|
||||
#name : #CarpExecutionHandler,
|
||||
#superclass : #LanguageLinkExecutionHandler,
|
||||
#category : #'Carp-LanguageLink'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpExecutionHandler >> initializeHandler [
|
||||
|
||||
commandQueue := LanguageLinkCommandRegistry executionHandler: self.
|
||||
mapperFactory := LanguageLinkMapperFactory forExecutionHandler: self.
|
||||
promiseRegistry := LanguageLinkPromiseRegistry new.
|
||||
weakRegistry := self settings platform weakRegistry.
|
||||
objectRegistry := LanguageLinkObjectRegistry new.
|
||||
|
||||
self communicationHandler
|
||||
addHandler: [ :msg | self notifyHandler: msg ] forMessageClass: LanguageLinkUpdatePromiseMessage;
|
||||
addHandler: [ :msg | self notifyErrorHandler: msg ] forMessageClass: LanguageLinkErrorMessage;
|
||||
addHandler: [ :msg | self notifyCallbackHandler: msg ] forMessageClass: LanguageLinkCallbackMessage
|
||||
]
|
5
src/Carp/CarpLinkBinding.class.st
Normal file
5
src/Carp/CarpLinkBinding.class.st
Normal file
@@ -0,0 +1,5 @@
|
||||
Class {
|
||||
#name : #CarpLinkBinding,
|
||||
#superclass : #LanguageLinkBinding,
|
||||
#category : #'Carp-Execution'
|
||||
}
|
117
src/Carp/CarpPlatform.class.st
Normal file
117
src/Carp/CarpPlatform.class.st
Normal file
@@ -0,0 +1,117 @@
|
||||
Class {
|
||||
#name : #CarpPlatform,
|
||||
#superclass : #LanguageLinkPlatform,
|
||||
#classVars : [
|
||||
'RuntimeSourceDirectory'
|
||||
],
|
||||
#category : #'Carp-Processes'
|
||||
}
|
||||
|
||||
{ #category : #hooks }
|
||||
CarpPlatform class >> weakRegistryClass [
|
||||
^ LanguageLinkPharoWeakRegistry
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPlatform >> copyApplicationTo: appFolder application: application [
|
||||
"Copy the Carp runtime environment to the specified folder"
|
||||
| srcDir cpCommand proc error |
|
||||
|
||||
srcDir := self runtimeSourceDirectoryFor: application.
|
||||
srcDir resolve = appFolder resolve ifTrue: [ ^ self ].
|
||||
|
||||
"Copy the runtime directory"
|
||||
cpCommand := String streamContents: [ :stream |
|
||||
stream
|
||||
<< 'cp -a "';
|
||||
<< srcDir fullName;
|
||||
<< '" "';
|
||||
<< appFolder fullName;
|
||||
<< '"' ].
|
||||
proc := GtSubprocessWithInMemoryOutput new
|
||||
shellCommand: cpCommand;
|
||||
runAndWait.
|
||||
proc isSuccess ifFalse:
|
||||
[ error := LanguageLinkProcessError new
|
||||
messageText: 'Unable to install Carp runtime';
|
||||
application: application;
|
||||
process: proc.
|
||||
error signal ].
|
||||
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPlatform >> ensureApplicationDirectory: application [
|
||||
"If the runtimeFolder doesn't exist, attempt to symlink to the respository directory"
|
||||
| appFolder |
|
||||
|
||||
application settings workingDirectory exists ifFalse:
|
||||
[ appFolder := application settings workingDirectory.
|
||||
appFolder exists ifTrue: [ ^ self ].
|
||||
self copyApplicationTo: appFolder application: application ].
|
||||
self setExecutableFor: application.
|
||||
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPlatform >> ensureEnvironmentForApp: anApplication [
|
||||
|
||||
self ensureApplicationDirectory: anApplication.
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPlatform >> folderForApplication [
|
||||
"Answer the directory where JSLink runs from"
|
||||
|
||||
^ FileLocator imageDirectory / 'carp'
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPlatform >> newRandomName [
|
||||
^ 'carp' , UUID new asString36
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPlatform >> runtimeSourceDirectoryFor: aCarpApplication [
|
||||
"Answer the source directory containing the runtime files.
|
||||
This is the first of:
|
||||
1. RuntimeSourceDirectory (if specified)
|
||||
2. The git repository copy.
|
||||
3. An existing copy in the image directory"
|
||||
| fileReference |
|
||||
|
||||
(RuntimeSourceDirectory isNotNil and: [ RuntimeSourceDirectory exists ]) ifTrue:
|
||||
[ ^ RuntimeSourceDirectory ].
|
||||
IceRepository registry
|
||||
detect: [ :each | each includesPackageNamed: aCarpApplication class package name ]
|
||||
ifFound: [ :repository |
|
||||
fileReference := repository location / 'carp'.
|
||||
fileReference exists ifTrue: [ ^ fileReference ] ]
|
||||
ifNone: [ ].
|
||||
fileReference := self folderForApplication.
|
||||
fileReference exists ifTrue: [ ^ fileReference ].
|
||||
self error: 'Unable to locate Carp runtime source'.
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPlatform >> setExecutableFor: application [
|
||||
"Copy the Carp runtime environment to the specified folder"
|
||||
| chmodCommand proc error appFolderString |
|
||||
|
||||
appFolderString := application settings workingDirectory resolve fullName copyReplaceAll: '"' with: '\"'.
|
||||
chmodCommand := String streamContents: [ :stream |
|
||||
stream
|
||||
<< 'chmod +x "';
|
||||
<< appFolderString;
|
||||
<< '"/*.sh' ].
|
||||
proc := GtSubprocessWithInMemoryOutput new
|
||||
shellCommand: chmodCommand;
|
||||
runAndWait.
|
||||
proc isSuccess ifFalse:
|
||||
[ error := LanguageLinkProcessError new
|
||||
messageText: 'Unable to set Carp runtime executable bits';
|
||||
application: application;
|
||||
process: proc.
|
||||
error signal ].
|
||||
|
||||
]
|
83
src/Carp/CarpPostMortemDebugger.class.st
Normal file
83
src/Carp/CarpPostMortemDebugger.class.st
Normal file
@@ -0,0 +1,83 @@
|
||||
Class {
|
||||
#name : #CarpPostMortemDebugger,
|
||||
#superclass : #Object,
|
||||
#instVars : [
|
||||
'exception',
|
||||
'stackFrames',
|
||||
'frameRegex'
|
||||
],
|
||||
#category : #'Carp-Debugger'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> exception [
|
||||
^ exception
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> exception: anException [
|
||||
exception := anException
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> initialize [
|
||||
|
||||
super initialize.
|
||||
frameRegex := '\s+at.+\(([^:]+)\:(\d+)\:(\d+)\)' asRegexIgnoringCase.
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> sourceStyler [
|
||||
"Answer the styler used by the source code editor for this exception"
|
||||
|
||||
^ CarpParser gtStyler
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> stackFrameFromLine: aString ordinal: ordinal [
|
||||
"Answer a frame if the supplied string contains a valid file and line number, or nil"
|
||||
<return: #GtPythonPostMortemStackFrame or: nil>
|
||||
| file line column |
|
||||
|
||||
^ (frameRegex search: aString) ifTrue:
|
||||
[ file := frameRegex subexpression: 2.
|
||||
line := frameRegex subexpression: 3.
|
||||
column := frameRegex subexpression: 4.
|
||||
CarpPostMortemStackFrame new
|
||||
ordinal: ordinal;
|
||||
displayString: aString;
|
||||
exception: exception;
|
||||
file: file asFileReference;
|
||||
line: line asNumber;
|
||||
column: column asNumber ]
|
||||
ifFalse:
|
||||
[ nil ]
|
||||
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> stackFrames [
|
||||
"Answer a ordered collection of stack frames.
|
||||
This is called many times by the debugger, so cache"
|
||||
| ordinal |
|
||||
|
||||
^ stackFrames ifNil:
|
||||
[ ordinal := 1.
|
||||
stackFrames := OrderedCollection new.
|
||||
exception trace lines do: [ :line |
|
||||
(self stackFrameFromLine: line ordinal: ordinal) ifNotNil: [ :frame |
|
||||
stackFrames add: frame.
|
||||
ordinal := ordinal + 1 ] ].
|
||||
stackFrames ].
|
||||
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> stderr [
|
||||
^ exception application stderr
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpPostMortemDebugger >> stdout [
|
||||
^ exception application stdout
|
||||
]
|
5
src/Carp/CarpPostMortemStackFrame.class.st
Normal file
5
src/Carp/CarpPostMortemStackFrame.class.st
Normal file
@@ -0,0 +1,5 @@
|
||||
Class {
|
||||
#name : #CarpPostMortemStackFrame,
|
||||
#superclass : #GtJavaScriptPostMortemStackFrame,
|
||||
#category : #'Carp-Debugger'
|
||||
}
|
142
src/Carp/CarpProcess.class.st
Normal file
142
src/Carp/CarpProcess.class.st
Normal file
@@ -0,0 +1,142 @@
|
||||
Class {
|
||||
#name : #CarpProcess,
|
||||
#superclass : #LanguageLinkAbstractProcess,
|
||||
#instVars : [
|
||||
'process',
|
||||
'environmentVariables'
|
||||
],
|
||||
#classVars : [
|
||||
'CarpPath'
|
||||
],
|
||||
#category : #'Carp-Processes'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess class >> resolveCarpPath [
|
||||
| proc |
|
||||
|
||||
proc := GtSubprocessWithInMemoryOutput new
|
||||
command: 'which';
|
||||
arguments: { 'carp' }.
|
||||
CarpPlatform subProcessEnvironmentDictionary keysAndValuesDo: [ :key :value |
|
||||
proc environmentAt: key put: value ].
|
||||
proc runAndWait.
|
||||
(#(0 1) includes: proc exitCode) ifFalse:
|
||||
[ self error: 'Unable to request carp location' ].
|
||||
^ proc stdout trim asFileReference
|
||||
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess class >> resolveNodejsPath [
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess class >> serverPath [
|
||||
^ CarpPath
|
||||
ifNil: [ CarpPath := self resolveCarpPath ]
|
||||
ifNotNil: [ CarpPath ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> exitCode [
|
||||
|
||||
^ process
|
||||
ifNil: [ nil ]
|
||||
ifNotNil: [ process exitCode ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> hasProcess [
|
||||
"Answer a boolean indicating whether the receiver has a process object"
|
||||
|
||||
^ process isNotNil
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> initialize [
|
||||
super initialize.
|
||||
environmentVariables := Dictionary new.
|
||||
self setDefaultEnvironmentVariables
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> isRunning [
|
||||
^ process
|
||||
ifNil: [ false ]
|
||||
ifNotNil: [ process isRunning ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> newProcess| newProcess [ |
|
||||
newProcess := GtSubprocessWithInMemoryOutput new
|
||||
command: self serverPath fullName;
|
||||
arguments: self processArguments;
|
||||
workingDirectory: self workingDirectory resolve fullName;
|
||||
terminateOnShutdown;
|
||||
yourself.
|
||||
environmentVariables associationsDo: [ :assoc |
|
||||
newProcess environmentAt: assoc key put: assoc value ].
|
||||
^ newProcess
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> processArguments [
|
||||
| args |
|
||||
|
||||
args := OrderedCollection new.
|
||||
self settings serverDebugMode ifTrue:
|
||||
[ args add: '--inspect' ].
|
||||
args
|
||||
add: (self workingDirectory / 'src/languagelink.carp') resolve fullName;
|
||||
add: self settings serverSocketAddress port asString;
|
||||
add: self settings clientSocketAddress port asString.
|
||||
^ args
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> serverPath [
|
||||
| fileReference |
|
||||
|
||||
fileReference := self settings serverExecutable.
|
||||
fileReference ifNil: [ fileReference := self class serverPath ].
|
||||
^ fileReference.
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> setDefaultEnvironmentVariables [
|
||||
|
||||
environmentVariables := CarpPlatform subProcessEnvironmentDictionary.
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> start [
|
||||
process := self newProcess.
|
||||
process run.
|
||||
self settings serverDebugMode ifTrue:
|
||||
[ self startServerDebugger ].
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> stderr [
|
||||
"Answer the process stderr contents"
|
||||
|
||||
^ process stderr
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> stdout [
|
||||
"Answer the process stdout contents"
|
||||
|
||||
^ process stdout
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProcess >> stop [
|
||||
process ifNil: [ ^ self ].
|
||||
[ process queryExitStatus ifNil: [ process terminate ] ]
|
||||
on: Error
|
||||
do: [ "Do nothing.":e | ].
|
||||
process closeAndCleanStreams.
|
||||
process := nil
|
||||
]
|
34
src/Carp/CarpProxyObject.class.st
Normal file
34
src/Carp/CarpProxyObject.class.st
Normal file
@@ -0,0 +1,34 @@
|
||||
Class {
|
||||
#name : #CarpProxyObject,
|
||||
#superclass : #Object,
|
||||
#instVars : [
|
||||
'carpVariable',
|
||||
'carpType',
|
||||
'application'
|
||||
],
|
||||
#category : #'Carp-Serialization'
|
||||
}
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProxyObject class >> carpType: aType var: aVar application: application [
|
||||
^ self new
|
||||
carpVariable: aVar;
|
||||
carpType: aType;
|
||||
application: application;
|
||||
yourself
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProxyObject >> application: anObject [
|
||||
application := anObject
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProxyObject >> carpType: aType [
|
||||
carpType := aType
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
CarpProxyObject >> carpVariable: aVar [
|
||||
carpVariable := aVar
|
||||
]
|
@@ -28,12 +28,12 @@ GtCarpCoderModel >> bindAndExecute: sourceString [
|
||||
| carpSource trimmedSource ast varNames lastStatement application commandFactory |
|
||||
|
||||
trimmedSource := SmaCCString on: sourceString trimRight.
|
||||
ast := JSParser parse: trimmedSource.
|
||||
ast := CarpParser parse: trimmedSource.
|
||||
"The variables to be returned are names that are in pharoBindings"
|
||||
varNames := pharoBindings bindingNames asSet.
|
||||
|
||||
"Assign the final statement to snippetResult"
|
||||
lastStatement := ast items last.
|
||||
lastStatement := ast expressions last.
|
||||
trimmedSource
|
||||
insert: '(defdynamic snippetResult '
|
||||
at: lastStatement startPosition.
|
||||
@@ -108,6 +108,19 @@ GtCarpCoderModel >> pharoBindings: anObject [
|
||||
pharoBindings := anObject
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
GtCarpCoderModel >> primitiveEvaluate: aSourceString inContext: aGtSourceCoderEvaluationContext onFailDo: anEvaluationFailBlock [
|
||||
| result |
|
||||
|
||||
result := self bindAndExecute: aSourceString.
|
||||
result associationsDo: [ :binding |
|
||||
(pharoBindings bindingOf: binding key asSymbol) value: binding value ].
|
||||
|
||||
^ result
|
||||
at: 'snippetResult'
|
||||
ifAbsent: anEvaluationFailBlock
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
GtCarpCoderModel >> sourceFrom: trimmedSourceString returnedVarNames: varNames [
|
||||
"Answer the modified source to return the declared variables"
|
||||
|
@@ -9,12 +9,12 @@ LanguageLinkSettings class >> carpDefaultSettings [
|
||||
serverSocketAddress: (LanguageLinkSocketAddress
|
||||
ipOrName: 'localhost' port: (9900 + 99 atRandom));
|
||||
messageBrokerStrategy: LanguageLinkHttpMessageBroker;
|
||||
serverProcessClass: JSLinkPharoNodejsProcess;
|
||||
platform: JSLinkPharoPlatform new;
|
||||
commandFactoryClass: JSLinkCommandFactory;
|
||||
serverProcessClass: CarpProcess;
|
||||
platform: CarpPlatform new;
|
||||
commandFactoryClass: CarpCommandFactory;
|
||||
commandClass: LanguageLinkCommand;
|
||||
serializerClass: LanguageLinkSerializer;
|
||||
deserializerClass: JSLinkDeserializer;
|
||||
deserializerClass: CarpDeserializer;
|
||||
parserClass: CarpParser;
|
||||
yourself
|
||||
]
|
||||
|
@@ -14,9 +14,9 @@ LeCarpApplicationStrategy class >> strategyName [
|
||||
LeCarpApplicationStrategy >> applicationServer [
|
||||
|
||||
content database isDatabase ifFalse: [ ^ nil ].
|
||||
JSLinkApplication uniqueInstance ifNil:
|
||||
[ JSLinkApplication uniqueInstance: (self newJavaScriptApplicationFor: content database) ].
|
||||
^ JSLinkApplication uniqueInstance
|
||||
CarpApplication uniqueInstance ifNil:
|
||||
[ CarpApplication uniqueInstance: (self newCarpApplicationFor: content database) ].
|
||||
^ CarpApplication uniqueInstance
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
@@ -24,16 +24,16 @@ LeCarpApplicationStrategy >> applicationSettings [
|
||||
"Answer the settings that will be used by the server.
|
||||
This musn't actually start the server as that should be deferred until a snippet is evaluated for the first time."
|
||||
|
||||
^ JSLinkApplication isRunning ifTrue:
|
||||
[ JSLinkApplication uniqueInstance settings ]
|
||||
^ CarpApplication isRunning ifTrue:
|
||||
[ CarpApplication uniqueInstance settings ]
|
||||
ifFalse:
|
||||
[ self updatedSettings: JSLinkApplication defaultSettings ]
|
||||
[ self updatedSettings: CarpApplication defaultSettings ]
|
||||
]
|
||||
|
||||
{ #category : #accessing }
|
||||
LeCarpApplicationStrategy >> newJavaScriptApplicationFor: aLeDatabase [
|
||||
LeCarpApplicationStrategy >> newCarpApplicationFor: aLeDatabase [
|
||||
|
||||
^ JSLinkApplication new initializeWith:
|
||||
^ CarpApplication new initializeWith:
|
||||
(self updatedSettings: LanguageLinkSettings carpDefaultSettings).
|
||||
|
||||
]
|
||||
|
Reference in New Issue
Block a user