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' } { #category : #'start-stop' }
CarpApplication class >> start [ CarpApplication class >> start [
^ self startWith: LanguageLinkSettings jsDefaultSettings. ^ self startWith: LanguageLinkSettings carpDefaultSettings.
] ]
{ #category : #accessing } { #category : #accessing }
@@ -18,10 +18,17 @@ CarpApplication >> baseApplication [
^ CarpApplication ^ 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 } { #category : #accessing }
CarpApplication >> initializeHandlers [ CarpApplication >> initializeHandlers [
loggingHandler := LanguageLinkLoggingHandler application: self. loggingHandler := LanguageLinkLoggingHandler application: self.
communicationHandler := LanguageLinkCommunicationHandler application: self. communicationHandler := LanguageLinkCommunicationHandler application: self.
processHandler := LanguageLinkServerHandler 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 | | carpSource trimmedSource ast varNames lastStatement application commandFactory |
trimmedSource := SmaCCString on: sourceString trimRight. trimmedSource := SmaCCString on: sourceString trimRight.
ast := JSParser parse: trimmedSource. ast := CarpParser parse: trimmedSource.
"The variables to be returned are names that are in pharoBindings" "The variables to be returned are names that are in pharoBindings"
varNames := pharoBindings bindingNames asSet. varNames := pharoBindings bindingNames asSet.
"Assign the final statement to snippetResult" "Assign the final statement to snippetResult"
lastStatement := ast items last. lastStatement := ast expressions last.
trimmedSource trimmedSource
insert: '(defdynamic snippetResult ' insert: '(defdynamic snippetResult '
at: lastStatement startPosition. at: lastStatement startPosition.
@@ -108,6 +108,19 @@ GtCarpCoderModel >> pharoBindings: anObject [
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 } { #category : #accessing }
GtCarpCoderModel >> sourceFrom: trimmedSourceString returnedVarNames: varNames [ GtCarpCoderModel >> sourceFrom: trimmedSourceString returnedVarNames: varNames [
"Answer the modified source to return the declared variables" "Answer the modified source to return the declared variables"

View File

@@ -9,12 +9,12 @@ LanguageLinkSettings class >> carpDefaultSettings [
serverSocketAddress: (LanguageLinkSocketAddress serverSocketAddress: (LanguageLinkSocketAddress
ipOrName: 'localhost' port: (9900 + 99 atRandom)); ipOrName: 'localhost' port: (9900 + 99 atRandom));
messageBrokerStrategy: LanguageLinkHttpMessageBroker; messageBrokerStrategy: LanguageLinkHttpMessageBroker;
serverProcessClass: JSLinkPharoNodejsProcess; serverProcessClass: CarpProcess;
platform: JSLinkPharoPlatform new; platform: CarpPlatform new;
commandFactoryClass: JSLinkCommandFactory; commandFactoryClass: CarpCommandFactory;
commandClass: LanguageLinkCommand; commandClass: LanguageLinkCommand;
serializerClass: LanguageLinkSerializer; serializerClass: LanguageLinkSerializer;
deserializerClass: JSLinkDeserializer; deserializerClass: CarpDeserializer;
parserClass: CarpParser; parserClass: CarpParser;
yourself yourself
] ]

View File

@@ -14,9 +14,9 @@ LeCarpApplicationStrategy class >> strategyName [
LeCarpApplicationStrategy >> applicationServer [ LeCarpApplicationStrategy >> applicationServer [
content database isDatabase ifFalse: [ ^ nil ]. content database isDatabase ifFalse: [ ^ nil ].
JSLinkApplication uniqueInstance ifNil: CarpApplication uniqueInstance ifNil:
[ JSLinkApplication uniqueInstance: (self newJavaScriptApplicationFor: content database) ]. [ CarpApplication uniqueInstance: (self newCarpApplicationFor: content database) ].
^ JSLinkApplication uniqueInstance ^ CarpApplication uniqueInstance
] ]
{ #category : #accessing } { #category : #accessing }
@@ -24,16 +24,16 @@ LeCarpApplicationStrategy >> applicationSettings [
"Answer the settings that will be used by the server. "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." This musn't actually start the server as that should be deferred until a snippet is evaluated for the first time."
^ JSLinkApplication isRunning ifTrue: ^ CarpApplication isRunning ifTrue:
[ JSLinkApplication uniqueInstance settings ] [ CarpApplication uniqueInstance settings ]
ifFalse: ifFalse:
[ self updatedSettings: JSLinkApplication defaultSettings ] [ self updatedSettings: CarpApplication defaultSettings ]
] ]
{ #category : #accessing } { #category : #accessing }
LeCarpApplicationStrategy >> newJavaScriptApplicationFor: aLeDatabase [ LeCarpApplicationStrategy >> newCarpApplicationFor: aLeDatabase [
^ JSLinkApplication new initializeWith: ^ CarpApplication new initializeWith:
(self updatedSettings: LanguageLinkSettings carpDefaultSettings). (self updatedSettings: LanguageLinkSettings carpDefaultSettings).
] ]