Add some more language link code

This commit is contained in:
2022-06-10 18:33:47 +02:00
parent f9d386c251
commit 1db1b01b2a
14 changed files with 469 additions and 16 deletions

0
carp/install.sh Normal file
View File

View 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
]

View File

@@ -0,0 +1,5 @@
Class {
#name : #CarpCommandFactory,
#superclass : #LanguageLinkCommandFactory,
#category : #'Carp-Execution'
}

View 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
]

View 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
]

View File

@@ -0,0 +1,5 @@
Class {
#name : #CarpLinkBinding,
#superclass : #LanguageLinkBinding,
#category : #'Carp-Execution'
}

View 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 ].
]

View 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
]

View File

@@ -0,0 +1,5 @@
Class {
#name : #CarpPostMortemStackFrame,
#superclass : #GtJavaScriptPostMortemStackFrame,
#category : #'Carp-Debugger'
}

View 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
]

View 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
]

View File

@@ -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"

View File

@@ -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
]

View File

@@ -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).
]