diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..9add5fa --- /dev/null +++ b/.gitattributes @@ -0,0 +1,2 @@ +*.st text linguist-language=Smalltalk + diff --git a/image-segments/.gitignore b/image-segments/.gitignore index 975a83d..e873c8c 100644 --- a/image-segments/.gitignore +++ b/image-segments/.gitignore @@ -1,2 +1,6 @@ *.json *.ems +*.so +*.dll +*.dylib + diff --git a/modules/Examples/HTTPServer/HTTPServerModule.st b/modules/Examples/HTTPServer/HTTPServerModule.st index 68de8ba..78c68bb 100644 --- a/modules/Examples/HTTPServer/HTTPServerModule.st +++ b/modules/Examples/HTTPServer/HTTPServerModule.st @@ -10,8 +10,8 @@ Class { { #category : #spec } HTTPServerModule >> imports [ ^{ - #'HTTP.CPPHTTPServer' -> #(HTTPServer). #FFI -> #(ExternalLibrary). + #'HTTP.CPPHTTPServer' -> #(HTTPServer). } ] @@ -32,7 +32,7 @@ HTTPServerModule >> main: arguments [ base := arguments at: 3 ifAbsent: ['/egg']. server := HTTPServer new. server - routeGET: base, '/hello/:name' to: #hello with: self. + routeGET: base, '/hello/{name}' to: [:req :res | self handle: req into: res with: #hello]. Kernel log: 'server configured, starting!', String cr. server start. ^0 diff --git a/modules/Examples/HTTPServer/README.md b/modules/Examples/HTTPServer/README.md new file mode 100644 index 0000000..94072d7 --- /dev/null +++ b/modules/Examples/HTTPServer/README.md @@ -0,0 +1,15 @@ +# HTTP Server example + +An example of an HTTP server based on cpp-httplib. +The CPPHTTPServer module contains an FFI interface to plain C wrappers +to cpp-httplib. Here we use those wrappers to create a blocking web +server. + +## Building + +You need to build the native cpp-httplib in HTTP/CPPHTTPServer/lib (see +its readme), then build this module and FFI + +cd image-segments +make FFI.ems FFI.Posix.ems Examples.HTTPServer.ems + diff --git a/modules/HTTP/CPPHTTPServer/HTTPRequest.st b/modules/HTTP/CPPHTTPServer/HTTPRequest.st new file mode 100644 index 0000000..74ab234 --- /dev/null +++ b/modules/HTTP/CPPHTTPServer/HTTPRequest.st @@ -0,0 +1,23 @@ +" + Copyright (c) 2024, Javier Pimás. + See (MIT) license in root directory. +" + +Class { + #name : #HTTPRequest, + #superclass : #ExternalObject, + #category : #'CPPHTTPServer' +} + +{ #category : #spec } +HTTPRequest >> paramAt: aString [ + | addr | + addr := self class module library + request: handle asParameter + paramAt: aString externalCopy asParameter. + + addr = 0 ifTrue: [^nil]. + + "should do better and look at encoding" + ^String fromMemory: addr pointedMemory +] diff --git a/modules/HTTP/CPPHTTPServer/HTTPServer.st b/modules/HTTP/CPPHTTPServer/HTTPServer.st index 2dd85c4..3e7ab7b 100644 --- a/modules/HTTP/CPPHTTPServer/HTTPServer.st +++ b/modules/HTTP/CPPHTTPServer/HTTPServer.st @@ -10,11 +10,11 @@ Class { } { #category : #spec } -HTTPServer >> handle: requestHandle with: handler into: responseHandle with: selector [ +HTTPServer >> handle: requestHandle with: handler into: responseHandle [ | request response | request := HTTPRequest new handle: requestHandle. response := HTTPResponse new handle: responseHandle. - handler handle: request into: response with: selector. + handler evaluateWith: request with: response. ^responseHandle ] @@ -29,11 +29,11 @@ HTTPServer >> library [ ] { #category : #spec } -HTTPServer >> routeGET: uri to: selector with: handler [ +HTTPServer >> routeGET: uri to: handler [ self library server: self asParameter GET: uri externalCopy asParameter - callback: [ :request :response | self handle: request with: handler into: response with: selector ] asCallback + callback: [ :request :response | self handle: request with: handler into: response ] asCallback ] { #category : #spec } diff --git a/modules/HTTP/CPPHTTPServer/HTTPServerLibrary.st b/modules/HTTP/CPPHTTPServer/HTTPServerLibrary.st index 10546b3..977b29b 100644 --- a/modules/HTTP/CPPHTTPServer/HTTPServerLibrary.st +++ b/modules/HTTP/CPPHTTPServer/HTTPServerLibrary.st @@ -29,11 +29,22 @@ HTTPServerLibrary >> newServer [ ] +{ #category : #server } +HTTPServerLibrary >> server: aServer DELETE: url callback: aCallback [ + +] + { #category : #server } HTTPServerLibrary >> server: aServer GET: url callback: aCallback [ ] +{ #category : #server } +HTTPServerLibrary >> server: aServer POST: url callback: aCallback [ + +] + + { #category : #server } HTTPServerLibrary >> startServer: aServer [ diff --git a/modules/HTTP/CPPHTTPServer/README.md b/modules/HTTP/CPPHTTPServer/README.md index aae117f..1246b21 100644 --- a/modules/HTTP/CPPHTTPServer/README.md +++ b/modules/HTTP/CPPHTTPServer/README.md @@ -23,9 +23,15 @@ egg/modules/HTTP/CPPHTTPServer/lib $ cmake -S . -B build && cmake --build build ``` -The result should be a file called libhttpserver.so (linux), httpserver.dll (windows) or -something similar for your platform. This file will go to the build dir, you'll have to -copy it to some place in your egg's FFI path (simplest way: the place from where you run egg). +The result should be a file called libhttpserver.so (linux), httpserver.dll (windows), +httpserver.dylib or something similar for your platform. +This file will go to the build dir, you'll have to copy (or better, link) it to some +place in your egg's FFI path (simplest way: the place from where you run egg). + +``` +egg/image-segments $ +ln -s ../modules/HTTP/CPPHTTPServer/lib/libhttpserver.so . +``` # Usage diff --git a/modules/HTTP/CPPHTTPServer/lib/server.cpp b/modules/HTTP/CPPHTTPServer/lib/server.cpp index cc1eb01..4851d3d 100644 --- a/modules/HTTP/CPPHTTPServer/lib/server.cpp +++ b/modules/HTTP/CPPHTTPServer/lib/server.cpp @@ -3,6 +3,12 @@ typedef void(*server_callback)(const void *, const void *); +// in our uri convention we use {var} to denote a variable, but httplib uses :var +static std::string translateUriFormat(const std::string& uri) { + static const std::regex varPattern(R"(\{([a-zA-Z_][a-zA-Z0-9_]*)\})"); + return std::regex_replace(uri, varPattern, ":$1"); +} + extern "C" { void* Server_New() @@ -14,7 +20,7 @@ void Server_Get(void *cserver, char *url, void *ccallback) { httplib::Server *server = reinterpret_cast(cserver); server_callback callback = reinterpret_cast(ccallback); - server->Get(url, [callback](const httplib::Request &req, httplib::Response &res) { + server->Get(translateUriFormat(url), [callback](const httplib::Request &req, httplib::Response &res) { callback(&req, &res); }); } @@ -33,7 +39,9 @@ char* Request_ParamAt(void *creq, char *key, char *type) { httplib::Request *req = reinterpret_cast(creq); - return (char*)req->path_params.at(key).c_str(); + auto ¶ms = req->path_params; + auto it = params.find(key); + return it != params.end() ? (char*)it->second.c_str() : nullptr; } void Response_SetContent(void *cres, char *content, char *type) diff --git a/modules/JSON/BooleanAdaptor.st b/modules/JSON/BooleanAdaptor.st new file mode 100644 index 0000000..932b074 --- /dev/null +++ b/modules/JSON/BooleanAdaptor.st @@ -0,0 +1,21 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #BooleanAdaptor, + #superclass : #DataAdaptor, + #category : #JSON +} + +{ #category : #services } +BooleanAdaptor >> dataFrom: aBoolean [ + ^aBoolean ifTrue: [1] ifFalse: [0] +] + +{ #category : #services } +BooleanAdaptor >> objectFrom: anInteger [ + ^anInteger asBoolean +] + diff --git a/modules/JSON/DataAdaptor.st b/modules/JSON/DataAdaptor.st new file mode 100644 index 0000000..6a7912b --- /dev/null +++ b/modules/JSON/DataAdaptor.st @@ -0,0 +1,28 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #DataAdaptor, + #superclass : #Object, + #category : #JSON +} + +{ #category : #services } +DataAdaptor >> dataFrom: anObject [ + ^anObject asString +] + +{ #category : #testing } +DataAdaptor >> isEntityAdaptor [ + ^false +] + +{ #category : #services } +DataAdaptor >> objectFrom: aString [ + ^(aString conform: [:ch | ch isDigit or: [#($+ $- $.) includes: ch]]) + ifTrue: [aString asNumber] + ifFalse: [aString] +] + diff --git a/modules/JSON/IdAdaptor.st b/modules/JSON/IdAdaptor.st new file mode 100644 index 0000000..12c0a08 --- /dev/null +++ b/modules/JSON/IdAdaptor.st @@ -0,0 +1,43 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #IdAdaptor, + #superclass : #DataAdaptor, + #instVars : [ + 'selector' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +IdAdaptor class >> forName [ + ^self new selector: #name +] + +{ #category : #services } +IdAdaptor >> dataFrom: anObject [ + anObject isNil ifTrue: [^nil]. + anObject isInteger ifTrue: [^anObject]. + anObject isString ifTrue: [^anObject]. + ^selector evaluateWith: anObject +] + +{ #category : #initialization } +IdAdaptor >> initialize [ + super initialize. + selector := #id +] + +{ #category : #services } +IdAdaptor >> objectFrom: anInteger [ + ^anInteger +] + +{ #category : #accessing } +IdAdaptor >> selector: aSymbol [ + selector := aSymbol +] + diff --git a/modules/JSON/JsonAnySchema.st b/modules/JSON/JsonAnySchema.st new file mode 100644 index 0000000..54d7db7 --- /dev/null +++ b/modules/JSON/JsonAnySchema.st @@ -0,0 +1,21 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonAnySchema, + #superclass : #JsonCombinedSchema, + #category : #JSON +} + +{ #category : #accessing } +JsonAnySchema class >> typeName [ + ^'anyOf' +] + +{ #category : #testing } +JsonAnySchema >> isAnySchema [ + ^true +] + diff --git a/modules/JSON/JsonArraySchema.st b/modules/JSON/JsonArraySchema.st new file mode 100644 index 0000000..3167dc8 --- /dev/null +++ b/modules/JSON/JsonArraySchema.st @@ -0,0 +1,79 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonArraySchema, + #superclass : #JsonSchema, + #instVars : [ + 'items', + 'minItems', + 'maxItems', + 'uniqueItems' + ], + #category : #JSON +} + +{ #category : #converting } +JsonArraySchema >> asJson [ + | json | + json := super asJson. + items notNil ifTrue: [json items: items asJson]. + minItems notNil ifTrue: [json at: 'minItems' put: minItems]. + maxItems notNil ifTrue: [json at: 'maxItems' put: maxItems]. + ^json +] + +{ #category : #initialization } +JsonArraySchema >> initialize [ + super initialize. + uniqueItems := false +] + +{ #category : #testing } +JsonArraySchema >> isArraySchema [ + ^true +] + +{ #category : #accessing } +JsonArraySchema >> items [ + ^items +] + +{ #category : #accessing } +JsonArraySchema >> items: aJsonSchema [ + items := aJsonSchema. + aJsonSchema parent: self +] + +{ #category : #accessing } +JsonArraySchema >> maxItems [ + ^maxItems +] + +{ #category : #accessing } +JsonArraySchema >> maxItems: aNumber [ + maxItems := aNumber +] + +{ #category : #accessing } +JsonArraySchema >> minItems [ + ^minItems +] + +{ #category : #accessing } +JsonArraySchema >> minItems: aNumber [ + minItems := aNumber +] + +{ #category : #accessing } +JsonArraySchema >> uniqueItems [ + ^uniqueItems +] + +{ #category : #accessing } +JsonArraySchema >> uniqueItems: aBoolean [ + uniqueItems := aBoolean +] + diff --git a/modules/JSON/JsonBooleanSchema.st b/modules/JSON/JsonBooleanSchema.st new file mode 100644 index 0000000..363909c --- /dev/null +++ b/modules/JSON/JsonBooleanSchema.st @@ -0,0 +1,16 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonBooleanSchema, + #superclass : #JsonPrimitiveSchema, + #category : #JSON +} + +{ #category : #testing } +JsonBooleanSchema >> isBooleanSchema [ + ^true +] + diff --git a/modules/JSON/JsonCombinedSchema.st b/modules/JSON/JsonCombinedSchema.st new file mode 100644 index 0000000..320ddfc --- /dev/null +++ b/modules/JSON/JsonCombinedSchema.st @@ -0,0 +1,30 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonCombinedSchema, + #superclass : #JsonSchema, + #instVars : [ + 'schemas' + ], + #category : #JSON +} + +{ #category : #converting } +JsonCombinedSchema >> asJson [ + ^JsonObject new at: self typeName put: schemas; yourself +] + +{ #category : #accessing } +JsonCombinedSchema >> schemas [ + ^schemas +] + +{ #category : #accessing } +JsonCombinedSchema >> schemas: aCollection [ + schemas := aCollection. + aCollection do: [:schema | schema parent: self] +] + diff --git a/modules/JSON/JsonDateFormat.st b/modules/JSON/JsonDateFormat.st new file mode 100644 index 0000000..209d723 --- /dev/null +++ b/modules/JSON/JsonDateFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonDateFormat, + #superclass : #JsonStringFormat, + #category : #JSON +} + diff --git a/modules/JSON/JsonDateTimeFormat.st b/modules/JSON/JsonDateTimeFormat.st new file mode 100644 index 0000000..27d528b --- /dev/null +++ b/modules/JSON/JsonDateTimeFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonDateTimeFormat, + #superclass : #JsonStringFormat, + #category : #JSON +} + diff --git a/modules/JSON/JsonEmailFormat.st b/modules/JSON/JsonEmailFormat.st new file mode 100644 index 0000000..0d18229 --- /dev/null +++ b/modules/JSON/JsonEmailFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonEmailFormat, + #superclass : #JsonStringFormat, + #category : #JSON +} + diff --git a/modules/JSON/JsonError.st b/modules/JSON/JsonError.st new file mode 100644 index 0000000..92aa8f0 --- /dev/null +++ b/modules/JSON/JsonError.st @@ -0,0 +1,50 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonError, + #superclass : #Error, + #instVars : [ + 'position', + 'context' + ], + #category : #JSON +} + +{ #category : #services } +JsonError class >> signal: aString at: anInteger [ + ^self new position: anInteger; description: aString; signal +] + +{ #category : #services } +JsonError class >> signal: aString at: anInteger context: context [ + ^self new + position: anInteger; + description: aString; + context: context; + signal +] + +{ #category : #accessing } +JsonError >> context: aString [ + context := aString +] + +{ #category : #accessing } +JsonError >> description [ + ^'Cannot parse the JSON stream around ' , context storeString , ' because ' + , super description +] + +{ #category : #accessing } +JsonError >> position [ + ^position +] + +{ #category : #accessing } +JsonError >> position: anInteger [ + position := anInteger +] + diff --git a/modules/JSON/JsonFormat.st b/modules/JSON/JsonFormat.st new file mode 100644 index 0000000..2bdf1a8 --- /dev/null +++ b/modules/JSON/JsonFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonFormat, + #superclass : #Object, + #category : #JSON +} + diff --git a/modules/JSON/JsonImporter.st b/modules/JSON/JsonImporter.st new file mode 100644 index 0000000..d0e48eb --- /dev/null +++ b/modules/JSON/JsonImporter.st @@ -0,0 +1,104 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonImporter, + #superclass : #Object, + #instVars : [ + 'parser', + 'metadata', + 'contents', + 'filename' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +JsonImporter class >> importFrom: aFilename [ + ^self new importFrom: aFilename +] + +{ #category : #importing } +JsonImporter >> checkContentsKey: aString value: aDictionary [ + aString = 'Contents' + ifFalse: [self error: 'the Contents key is not defined as the second one']. + contents := aDictionary +] + +{ #category : #importing } +JsonImporter >> checkKey: key value: value index: index [ + index = 1 ifTrue: [^self checkMetadataKey: key value: value]. + index = 2 ifTrue: [^self checkContentsKey: key value: value]. + index > 2 ifTrue: [^self failBecause: 'there are more keys than expected'] +] + +{ #category : #importing } +JsonImporter >> checkMetadataKey: aString value: aDictionary [ + | type version | + aString = 'Metadata' + ifFalse: [self + failBecause: 'the metadata is not defined as the first key of the file']. + type := aDictionary at: 'Type'. + (self validTypes includes: type) + ifFalse: [self failBecause: 'the defined Type is not valid']. + version := aDictionary at: 'Version'. + version <= self currentVersion ifFalse: [ + self + error: 'the version of the file is newer than the current application version']. + metadata := aDictionary +] + +{ #category : #private } +JsonImporter >> contentsOf: aFilename [ + | file | + file := aFilename asFilename asFile. + ^file exists ifTrue: [file binaryContents] +] + +{ #category : #accessing } +JsonImporter >> currentVersion [ + ^self subclassResponsibility +] + +{ #category : #exceptions } +JsonImporter >> failBecause: aString [ + | description | + description := 'Could not import ' , filename asString , ' because ' + , aString. + JsonImporterError signal: description +] + +{ #category : #importing } +JsonImporter >> importFrom: aFilename [ + | index | + self reset. + filename := aFilename. + self updateParser. + index := 1. + parser parseMapKeysAndValuesDo: [:key :value | + self checkKey: key value: value index: index. + index := index + 1]. + ^contents +] + +{ #category : #accessing } +JsonImporter >> reset [ + metadata := contents := parser := filename := nil +] + +{ #category : #updating } +JsonImporter >> updateParser [ + | data string | + data := self contentsOf: filename. + data notNil ifTrue: [ + string := String fromUTF8: data. + parser := JsonParser on: string readStream] +] + +{ #category : #accessing } +JsonImporter >> validTypes [ + ^self subclassResponsibility +] + diff --git a/modules/JSON/JsonImporterError.st b/modules/JSON/JsonImporterError.st new file mode 100644 index 0000000..005a4c6 --- /dev/null +++ b/modules/JSON/JsonImporterError.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonImporterError, + #superclass : #Error, + #category : #JSON +} + diff --git a/modules/JSON/JsonIntegerFormat.st b/modules/JSON/JsonIntegerFormat.st new file mode 100644 index 0000000..825f5bd --- /dev/null +++ b/modules/JSON/JsonIntegerFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonIntegerFormat, + #superclass : #JsonNumberFormat, + #category : #JSON +} + diff --git a/modules/JSON/JsonIntegerSchema.st b/modules/JSON/JsonIntegerSchema.st new file mode 100644 index 0000000..3e59c02 --- /dev/null +++ b/modules/JSON/JsonIntegerSchema.st @@ -0,0 +1,21 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonIntegerSchema, + #superclass : #JsonNumberSchema, + #category : #JSON +} + +{ #category : #accessing } +JsonIntegerSchema >> defaultFormat [ + ^JsonIntegerFormat new +] + +{ #category : #testing } +JsonIntegerSchema >> isIntegerSchema [ + ^true +] + diff --git a/modules/JSON/JsonMapping.st b/modules/JSON/JsonMapping.st new file mode 100644 index 0000000..54896bc --- /dev/null +++ b/modules/JSON/JsonMapping.st @@ -0,0 +1,215 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonMapping, + #superclass : #Object, + #instVars : [ + 'type', + 'class', + 'properties', + 'registry', + 'locator' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +JsonMapping class >> defaultFromSchema: aJsonSchema toClass: aClass [ + ^(self fromSchema: aJsonSchema toClass: aClass) useDefaultGetters +] + +{ #category : #'instance creation' } +JsonMapping class >> defaultFromType: anEdmType toClass: aClass [ + ^(self fromType: anEdmType toClass: aClass) useDefaultGetters +] + +{ #category : #'instance creation' } +JsonMapping class >> fromSchema: aJsonSchema [ + ^self fromSchema: aJsonSchema toClass: nil +] + +{ #category : #'instance creation' } +JsonMapping class >> fromSchema: aJsonSchema toClass: aClass [ + ^self new fromSchema: aJsonSchema; objectClass: aClass +] + +{ #category : #'instance creation' } +JsonMapping class >> fromType: anEdmType [ + ^self fromType: anEdmType toClass: nil +] + +{ #category : #'instance creation' } +JsonMapping class >> fromType: anEdmType toClass: aClass [ + ^self new fromType: anEdmType; objectClass: aClass +] + +{ #category : #services } +JsonMapping >> classFrom: aDictionary [ + | subclass | + class isNil ifTrue: [^nil]. + subclass := (class hasSubclasses + and: [class respondsTo: #classForJsonDictionary:]) + ifTrue: [class classForJsonDictionary: aDictionary]. + ^subclass ifNil: [class] +] + +{ #category : #private } +JsonMapping >> fromSchema: aJsonSchema [ + type := aJsonSchema. + properties := type properties + collect: [:property | JsonPropertyMap fromType: property mapping: self] +] + +{ #category : #private } +JsonMapping >> fromType: anEdmType [ + type := anEdmType. + properties := type allProperties + collect: [:property | JsonPropertyMap fromType: property mapping: self] +] + +{ #category : #accessing } +JsonMapping >> get: key with: selector [ + self get: key with: selector adaptor: nil +] + +{ #category : #accessing } +JsonMapping >> get: key with: selector adaptor: adaptor [ + | map | + map := properties at: key ifAbsentPut: [self newMap name: key]. + map getter: selector -> adaptor +] + +{ #category : #initialization } +JsonMapping >> initialize [ + super initialize. + properties := OrderedDictionary new +] + +{ #category : #accessing } +JsonMapping >> instanceFor: aDictionary [ + ^locator notNil ifTrue: [locator evaluateWith: aDictionary] +] + +{ #category : #services } +JsonMapping >> jsonObjectFrom: anObject [ + ^self serializer jsonObjectFrom: anObject using: self +] + +{ #category : #accessing } +JsonMapping >> keys [ + ^type isEntityType + ifTrue: [properties select: [:p | p isKey]] + ifFalse: [properties] +] + +{ #category : #accessing } +JsonMapping >> locator: evaluableObject [ + locator := evaluableObject +] + +{ #category : #private } +JsonMapping >> newMap [ + ^JsonPropertyMap new + property: EdmPropertyType new useDefaults; + mapping: self +] + +{ #category : #accessing } +JsonMapping >> objectClass [ + ^class +] + +{ #category : #accessing } +JsonMapping >> objectClass: aClass [ + class := aClass isSymbol + ifTrue: [ + Smalltalk at: aClass ifAbsent: [ + Notification signal: 'Class not found'. + JsonObject]] + ifFalse: [aClass isString + ifTrue: [Smalltalk classNamed: aClass] + ifFalse: [aClass]] +] + +{ #category : #services } +JsonMapping >> objectFrom: aDictionary [ + ^self serializer + mappings: registry; + objectFrom: aDictionary using: self +] + +{ #category : #printing } +JsonMapping >> printOn: aStream [ + aStream + nextPutAll: type typeName; + nextPutAll: ' type → '; + nextPutAll: class name +] + +{ #category : #accessing } +JsonMapping >> properties [ + ^properties +] + +{ #category : #accessing } +JsonMapping >> propertyAt: aString [ + ^properties at: aString ifAbsent: nil +] + +{ #category : #services } +JsonMapping >> read: aJsonObject into: anObject [ + self serializer + mappings: registry; + objectFrom: aJsonObject into: anObject using: self +] + +{ #category : #accessing } +JsonMapping >> registry [ + ^registry +] + +{ #category : #accessing } +JsonMapping >> registry: aJsonMappingRegistry [ + registry := aJsonMappingRegistry +] + +{ #category : #accessing } +JsonMapping >> removeProperty: aString [ + properties removeKey: aString ifAbsent: nil +] + +{ #category : #services } +JsonMapping >> serializer [ + ^JsonSerializer new condition: true; mappings: registry +] + +{ #category : #accessing } +JsonMapping >> set: key with: selector [ + self set: key with: selector adaptor: nil +] + +{ #category : #accessing } +JsonMapping >> set: key with: selector adaptor: adaptor [ + | map | + map := properties at: key ifAbsentPut: [self newMap name: key]. + map setter: selector -> adaptor +] + +{ #category : #accessing } +JsonMapping >> type [ + ^type +] + +{ #category : #accessing } +JsonMapping >> typeName [ + ^type typeName +] + +{ #category : #services } +JsonMapping >> useDefaultGetters [ + properties do: #useDefaultGetter +] + diff --git a/modules/JSON/JsonMappingRegistry.st b/modules/JSON/JsonMappingRegistry.st new file mode 100644 index 0000000..0b721de --- /dev/null +++ b/modules/JSON/JsonMappingRegistry.st @@ -0,0 +1,79 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonMappingRegistry, + #superclass : #Object, + #instVars : [ + 'mappings', + 'classes' + ], + #category : #JSON +} + +{ #category : #mappings } +JsonMappingRegistry >> addMappingsFrom: aJsonMappingRegistry [ + aJsonMappingRegistry mappingsDo: [:mapping | self register: mapping] +] + +{ #category : #initialization } +JsonMappingRegistry >> initialize [ + super initialize. + mappings := Dictionary new. + classes := Dictionary new +] + +{ #category : #mappings } +JsonMappingRegistry >> mappingFor: anObject [ + ^self mappingForClass: anObject class +] + +{ #category : #mappings } +JsonMappingRegistry >> mappingForClass: aClass [ + | class | + class := aClass. + [| mapping | + mapping := classes at: class name ifAbsent: nil. + mapping isNil ifTrue: [ + mapping := mappings detect: [:m | m objectClass == class] ifNone: nil. + mapping notNil ifTrue: [classes at: class name put: mapping]]. + mapping notNil ifTrue: [^mapping]. + class := class superclass. + class notNil] whileTrue. + ^nil +] + +{ #category : #mappings } +JsonMappingRegistry >> mappingForType: anEdmType [ + ^mappings detect: [:m | m type = anEdmType] ifNone: nil +] + +{ #category : #mappings } +JsonMappingRegistry >> mappingNamed: aString [ + ^mappings at: aString ifAbsent: nil +] + +{ #category : #mappings } +JsonMappingRegistry >> mappingNamed: aString ifAbsentPut: aBlock [ + ^mappings at: aString ifAbsent: [self register: aBlock value] +] + +{ #category : #enumerating } +JsonMappingRegistry >> mappingsDo: aBlock [ + mappings do: aBlock +] + +{ #category : #mappings } +JsonMappingRegistry >> register: aJsonMapping [ + ^self register: aJsonMapping withName: aJsonMapping typeName +] + +{ #category : #mappings } +JsonMappingRegistry >> register: aJsonMapping withName: aString [ + aJsonMapping registry: self. + classes at: aJsonMapping objectClass name put: aJsonMapping. + ^mappings at: aString put: aJsonMapping +] + diff --git a/modules/JSON/JsonModule.st b/modules/JSON/JsonModule.st new file mode 100644 index 0000000..c9b7275 --- /dev/null +++ b/modules/JSON/JsonModule.st @@ -0,0 +1,20 @@ +Class { + #name : #JsonModule, + #superclass : #Module, + #instVars : [ + '' + ], + #category : #JSON +} + +{ #category : #spec } +JsonModule >> imports [ + ^{ + #Kernel -> #(Character Error IdentitySet OrderedDictionary). + } +] + +{ #category : #spec } +JsonModule >> justLoaded [ + JsonWriter initializePools +] diff --git a/modules/JSON/JsonNotSchema.st b/modules/JSON/JsonNotSchema.st new file mode 100644 index 0000000..e4f2f86 --- /dev/null +++ b/modules/JSON/JsonNotSchema.st @@ -0,0 +1,35 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonNotSchema, + #superclass : #JsonSchema, + #instVars : [ + 'schema' + ], + #category : #JSON +} + +{ #category : #converting } +JsonNotSchema >> asJson [ + ^JsonObject new at: self typeName put: schema; yourself +] + +{ #category : #testing } +JsonNotSchema >> isNotSchema [ + ^true +] + +{ #category : #accessing } +JsonNotSchema >> schema [ + ^schema +] + +{ #category : #accessing } +JsonNotSchema >> schema: aJsonSchema [ + schema := aJsonSchema. + aJsonSchema parent: self +] + diff --git a/modules/JSON/JsonNullSchema.st b/modules/JSON/JsonNullSchema.st new file mode 100644 index 0000000..5405686 --- /dev/null +++ b/modules/JSON/JsonNullSchema.st @@ -0,0 +1,21 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonNullSchema, + #superclass : #JsonPrimitiveSchema, + #category : #JSON +} + +{ #category : #testing } +JsonNullSchema >> isNullSchema [ + ^true +] + +{ #category : #validation } +JsonNullSchema >> targetClass [ + ^UndefinedObject +] + diff --git a/modules/JSON/JsonNumberFormat.st b/modules/JSON/JsonNumberFormat.st new file mode 100644 index 0000000..065258d --- /dev/null +++ b/modules/JSON/JsonNumberFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonNumberFormat, + #superclass : #JsonFormat, + #category : #JSON +} + diff --git a/modules/JSON/JsonNumberSchema.st b/modules/JSON/JsonNumberSchema.st new file mode 100644 index 0000000..de25f2d --- /dev/null +++ b/modules/JSON/JsonNumberSchema.st @@ -0,0 +1,108 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonNumberSchema, + #superclass : #JsonPrimitiveSchema, + #instVars : [ + 'minimum', + 'exclusiveMinimum', + 'maximum', + 'exclusiveMaximum', + 'multipleOf' + ], + #category : #JSON +} + +{ #category : #converting } +JsonNumberSchema >> asJson [ + | json | + json := super asJson. + minimum notNil ifTrue: [ + json + at: 'minimum' put: minimum; + at: 'exclusiveMinimum' put: exclusiveMinimum]. + maximum notNil ifTrue: [ + json + at: 'maximum' put: maximum; + at: 'exclusiveMinimum' put: exclusiveMaximum]. + multipleOf notNil ifTrue: [json at: 'multipleOf' put: multipleOf]. + ^json +] + +{ #category : #accessing } +JsonNumberSchema >> defaultFormat [ + ^JsonNumberFormat new +] + +{ #category : #accessing } +JsonNumberSchema >> exclusiveMaximum [ + ^exclusiveMaximum ifTrue: [maximum] +] + +{ #category : #accessing } +JsonNumberSchema >> exclusiveMaximum: anObject [ + anObject isNumber + ifTrue: [ + maximum := anObject. + exclusiveMaximum := true] + ifFalse: [exclusiveMaximum := anObject] +] + +{ #category : #accessing } +JsonNumberSchema >> exclusiveMinimum [ + ^exclusiveMinimum ifTrue: [minimum] +] + +{ #category : #accessing } +JsonNumberSchema >> exclusiveMinimum: anObject [ + anObject isNumber + ifTrue: [ + minimum := anObject. + exclusiveMinimum := true] + ifFalse: [exclusiveMinimum := anObject] +] + +{ #category : #initialization } +JsonNumberSchema >> initialize [ + super initialize. + exclusiveMinimum := exclusiveMaximum := false +] + +{ #category : #testing } +JsonNumberSchema >> isNumberSchema [ + ^true +] + +{ #category : #accessing } +JsonNumberSchema >> maximum [ + ^maximum +] + +{ #category : #accessing } +JsonNumberSchema >> maximum: aNumber [ + maximum := aNumber +] + +{ #category : #accessing } +JsonNumberSchema >> minimum [ + ^minimum +] + +{ #category : #accessing } +JsonNumberSchema >> minimum: aNumber [ + minimum := aNumber +] + +{ #category : #accessing } +JsonNumberSchema >> multipleOf [ + ^multipleOf +] + +{ #category : #accessing } +JsonNumberSchema >> multipleOf: anNumber [ + multipleOf := anNumber +] + diff --git a/modules/JSON/JsonObject.st b/modules/JSON/JsonObject.st new file mode 100644 index 0000000..69f5efe --- /dev/null +++ b/modules/JSON/JsonObject.st @@ -0,0 +1,83 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonObject, + #superclass : #OrderedDictionary, + #category : #JSON +} + +{ #category : #'instance creation' } +JsonObject class >> fromString: aString [ + | json | + json := JsonParser parse: aString. + self ASSERT: json class == self. + ^json +] + +{ #category : #system } +JsonObject >> doesNotUnderstand: aMessage [ + | key | + (aMessage isBinary or: [aMessage arity > 1]) + ifTrue: [^super doesNotUnderstand: aMessage]. + key := aMessage selector asString. + aMessage arity = 0 ifTrue: [^self valueAt: key]. + self at: key allButLast put: aMessage argument +] + +{ #category : #accessing } +JsonObject >> format [ + ^self asString +] + +{ #category : #testing } +JsonObject >> isJsonObject [ + ^true +] + +{ #category : #services } +JsonObject >> literals [ + | below here | + here := OrderedSet streamContents: [:strm | + below := OrderedCollection new. + self keysAndValuesDo: [:k :v | + k isString ifTrue: [ + strm nextPut: k. + v class == self class ifTrue: [below add: v]]]]. + ^here asArray , (below gather: #literals) asArray +] + +{ #category : #printing } +JsonObject >> printOn: aStream [ + self printOn: aStream indent: 0] + +{ #category : #printing } +JsonObject >> printOn: aStream indent: anInteger [ + | string | + string := JsonWriter write: self indent: anInteger. + aStream eol = String crlf ifTrue: [string := string withCrLf]. + aStream isFileStream + ifTrue: [aStream nextBytesPut: string utf8] + ifFalse: [aStream nextPutAll: string] +] + +{ #category : #printing } +JsonObject >> printTextOn: rtf [ + self printTextOn: rtf indent: 0] + +{ #category : #printing } +JsonObject >> printTextOn: rtf indent: anInteger [ + JsonPainter write: self on: rtf indent: anInteger +] + +{ #category : #printing } +JsonObject >> printTextOn: rtf limit: aNumber [ + self printTextOn: rtf indent: 0] + +{ #category : #services } +JsonObject >> selectors [ + ^#() +] + diff --git a/modules/JSON/JsonObjectSchema.st b/modules/JSON/JsonObjectSchema.st new file mode 100644 index 0000000..23ab57f --- /dev/null +++ b/modules/JSON/JsonObjectSchema.st @@ -0,0 +1,86 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonObjectSchema, + #superclass : #JsonSchema, + #instVars : [ + 'properties', + 'required', + 'additionalProperties' + ], + #category : #JSON +} + +{ #category : #accessing } +JsonObjectSchema >> additionalProperties [ + ^additionalProperties +] + +{ #category : #accessing } +JsonObjectSchema >> additionalProperties: anObject [ + additionalProperties := anObject +] + +{ #category : #'adding / removing' } +JsonObjectSchema >> addProperty: aString schema: aJsonSchema [ + properties at: aString put: aJsonSchema. + aJsonSchema parent: self +] + +{ #category : #converting } +JsonObjectSchema >> asJson [ + | json | + json := super asJson. + json at: 'properties' put: properties. + required notEmpty ifTrue: [json at: 'required' put: required]. + additionalProperties notEmpty + ifTrue: [json at: 'additionalProperties' put: additionalProperties]. + ^json +] + +{ #category : #initialization } +JsonObjectSchema >> initialize [ + super initialize. + properties := Dictionary new. + required := OrderedCollection new. + additionalProperties := OrderedCollection new +] + +{ #category : #testing } +JsonObjectSchema >> isObjectSchema [ + ^true +] + +{ #category : #accessing } +JsonObjectSchema >> properties [ + ^properties +] + +{ #category : #accessing } +JsonObjectSchema >> propertyAt: aString [ + ^properties at: aString ifAbsent: nil +] + +{ #category : #'adding / removing' } +JsonObjectSchema >> removeProperty: aString [ + properties remove: aString ifAbsent: nil +] + +{ #category : #accessing } +JsonObjectSchema >> required [ + ^required +] + +{ #category : #accessing } +JsonObjectSchema >> required: aCollection [ + required := aCollection +] + +{ #category : #testing } +JsonObjectSchema >> requires: aString [ + ^required includes: aString +] + diff --git a/modules/JSON/JsonOneSchema.st b/modules/JSON/JsonOneSchema.st new file mode 100644 index 0000000..6d9919f --- /dev/null +++ b/modules/JSON/JsonOneSchema.st @@ -0,0 +1,21 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonOneSchema, + #superclass : #JsonCombinedSchema, + #category : #JSON +} + +{ #category : #accessing } +JsonOneSchema class >> typeName [ + ^'oneOf' +] + +{ #category : #testing } +JsonOneSchema >> isOneSchema [ + ^true +] + diff --git a/modules/JSON/JsonPainter.st b/modules/JSON/JsonPainter.st new file mode 100644 index 0000000..d3274fe --- /dev/null +++ b/modules/JSON/JsonPainter.st @@ -0,0 +1,106 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonPainter, + #superclass : #JsonWriter, + #category : #JSON +} + +{ #category : #services } +JsonPainter class >> write: anObject [ + | text writer | + text := '' asText. + writer := self write: anObject on: text. + ^text +] + +{ #category : #services } +JsonPainter class >> write: anObject indent: anInteger [ + | text | + text := '' asText. + self write: anObject on: text indent: anInteger. + ^text +] + +{ #category : #private } +JsonPainter >> codeForChar: ch [ + ^ch asInteger < 128 ifTrue: [super codeForChar: ch] ifFalse: [ch] +] + +{ #category : #colors } +JsonPainter >> keyColor [ + ^Color darkGreen +] + +{ #category : #colors } +JsonPainter >> keywordColor [ + self disableCode: [^Color red: 245 green: 100 blue: 71]. + ^Color darkBlue +] + +{ #category : #colors } +JsonPainter >> literalColor [ + ^Color darkBlue +] + +{ #category : #colors } +JsonPainter >> operatorColor [ + ^Color red: 185 green: 126 blue: 148] + +{ #category : #colors } +JsonPainter >> specialColor [ + ^Color red: 245 green: 100 blue: 71] + +{ #category : #colors } +JsonPainter >> stringColor [ + ^Color red: 84 green: 134 blue: 192] + +{ #category : #private } +JsonPainter >> writeBoolean: aBoolean [ + stream useColor: self literalColor while: [super writeBoolean: aBoolean] +] + +{ #category : #private } +JsonPainter >> writeDate: aDate [ + self writeString: aDate iso8601] + +{ #category : #private } +JsonPainter >> writeKey: aString [ + stream + useBoldWhile: [stream + useColor: self keyColor + while: [super writeKey: aString]] +] + +{ #category : #private } +JsonPainter >> writeNull [ + stream useColor: self specialColor while: [super writeNull] +] + +{ #category : #private } +JsonPainter >> writeNumber: aNumber [ + stream useColor: self literalColor while: [super writeNumber: aNumber] +] + +{ #category : #private } +JsonPainter >> writeOperator: aCharacter [ + stream + useColor: self operatorColor + while: [super writeOperator: aCharacter] +] + +{ #category : #private } +JsonPainter >> writeString: aString [ + stream useColor: self stringColor while: [super writeString: aString] +] + +{ #category : #private } +JsonPainter >> writeTimestamp: aTimestamp [ + stream + useColor: self literalColor + while: [super writeTimestamp: aTimestamp] +] + diff --git a/modules/JSON/JsonParser.st b/modules/JSON/JsonParser.st new file mode 100644 index 0000000..4111b8f --- /dev/null +++ b/modules/JSON/JsonParser.st @@ -0,0 +1,288 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonParser, + #superclass : #Object, + #instVars : [ + 'stream' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +JsonParser class >> on: aReadStream [ + ^self new on: aReadStream; yourself +] + +{ #category : #'instance creation' } +JsonParser class >> parse: aString [ + | parser string | + string := aString isByteArray + ifTrue: [UTF8 decode: aString] + ifFalse: [aString]. + parser := self on: string readStream. + ^parser next +] + +{ #category : #'instance creation' } +JsonParser class >> parse: aString for: aForeignNode [ + ^self parse: aString trimBlanks +] + +{ #category : #private } +JsonParser >> consumeWhitespace [ + [stream atEnd not and: [stream peek isBlank]] whileTrue: [stream next] +] + +{ #category : #errors } +JsonParser >> error: aString [ + ^JsonError signal: aString at: stream position context: stream context +] + +{ #category : #private } +JsonParser >> expectChar: character [ + (self matchChar: character) ifFalse: [ + self + error: 'the character ' , character asString storeString + , ' rather than ' + , stream peek asString storeString + , ' was expected'] +] + +{ #category : #private } +JsonParser >> match: string do: block [ + (string conform: [:ch | stream peekFor: ch]) ifTrue: [ + self consumeWhitespace. + block value] +] + +{ #category : #private } +JsonParser >> matchChar: character [ + ^(stream peekFor: character) ifTrue: [self consumeWhitespace]; yourself +] + +{ #category : #accessing } +JsonParser >> next [ + ^self parseValue +] + +{ #category : #accessing } +JsonParser >> on: aReadStream [ + stream := aReadStream +] + +{ #category : #parsing } +JsonParser >> parseCharacter [ + | char | + char := stream next. + char = $\ ifFalse: [^char]. + char := stream next. + ('/"\' includes: char) ifTrue: [^char]. + ('bfnrt' includes: char) ifTrue: [^self unescapeChar: char]. + char = $u ifTrue: [^self parseCharacterHex]. + self + error: 'the invalid escape character \' , char asString storeString + , ' occurred' +] + +{ #category : #parsing } +JsonParser >> parseCharacterHex [ + | value codePoint | + value := self parseCharacterHex4Value. + (value < 16rD800 or: [value > 16rDBFF]) + ifTrue: [codePoint := value] + ifFalse: [| leadSurrogate trailSurrogate | + "Characters not in the Basic Multilingual Plane are encoded as a UTF-16 surrogate pair" + "See https://tools.ietf.org/html/rfc7159#section-7" + leadSurrogate := value. + trailSurrogate := self parseTrailingSurrogateHexEscape. + codePoint := leadSurrogate - 16rD800 * 16r400 + (trailSurrogate - 16rDC00). + codePoint := 16r10000 + codePoint]. + ^Character codePoint: codePoint +] + +{ #category : #parsing } +JsonParser >> parseCharacterHex4Value [ + | value | + value := self parseCharacterHexDigit. + 3 timesRepeat: [value := (value bitShift: 4) + self parseCharacterHexDigit]. + ^value +] + +{ #category : #parsing } +JsonParser >> parseCharacterHexDigit [ + | digit char | + stream atEnd ifFalse: [ + char := stream next. + digit := char asInteger. + (digit between: "$0" 48 and: "$9" 57) ifTrue: [^digit - 48]. + (digit between: "$A" 65 and: "$F" 70) ifTrue: [^digit - 55]. + (digit between: "$a" 97 and: "$f" 102) ifTrue: [^digit - 87]]. + self + error: 'an hex-digit rather than ' , char asString storeString + , ' was expected' +] + +{ #category : #parsing } +JsonParser >> parseConstantDo: block [ + stream peek = $t ifTrue: [^self match: 'true' do: [block value: true]]. + stream peek = $f ifTrue: [^self match: 'false' do: [block value: false]]. + stream peek = $n ifTrue: [^self match: 'null' do: [block value: nil]] +] + +{ #category : #parsing } +JsonParser >> parseList [ + ^Array + streamContents: [:strm | self + parseListElementsDo: [:each | strm nextPut: each]] +] + +{ #category : #parsing } +JsonParser >> parseListDo: block [ + self expectChar: $[. + (self matchChar: $]) ifTrue: [^self]. + [stream atEnd] whileFalse: [ + block value. + (self matchChar: $]) ifTrue: [^self]. + self expectChar: $,]. + self error: 'an end of list was expected' +] + +{ #category : #parsing } +JsonParser >> parseListElementsDo: block [ + self parseListDo: [block value: self parseValue] +] + +{ #category : #parsing } +JsonParser >> parseMap [ + | map | + map := JsonObject new. + self parseMapKeysAndValuesDo: [:key :value | map at: key put: value]. + ^map +] + +{ #category : #parsing } +JsonParser >> parseMapDo: block [ + self expectChar: ${. + (self matchChar: $}) ifTrue: [^self]. + [stream atEnd] whileFalse: [ + block value. + (self matchChar: $}) ifTrue: [^self]. + self expectChar: $,]. + self error: 'an end of map was expected' +] + +{ #category : #parsing } +JsonParser >> parseMapKeysAndValuesDo: block [ + self parseMapKeysDo: [:key | block value: key value: self parseValue] +] + +{ #category : #parsing } +JsonParser >> parseMapKeysDo: block [ + self parseMapDo: [| key | + key := self parsePropertyName. + self expectChar: $:. + block value: key] +] + +{ #category : #parsing } +JsonParser >> parseNumber [ + | negated number | + negated := stream peekFor: $-. + number := self parseNumberInteger. + (stream peekFor: $.) ifTrue: [number := number + self parseNumberFraction]. + ((stream peekFor: $e) or: [stream peekFor: $E]) + ifTrue: [number := number * self parseNumberExponent]. + negated ifTrue: [number := number negated]. + self consumeWhitespace. + ^number +] + +{ #category : #parsing } +JsonParser >> parseNumberExponent [ + | number negated | + number := 0. + (negated := stream peekFor: $-) ifFalse: [stream peekFor: $+]. + [stream atEnd not and: [stream peek isDigit]] + whileTrue: [number := 10 * number + stream next digitValue]. + negated ifTrue: [number := number negated]. + ^10 raisedTo: number +] + +{ #category : #parsing } +JsonParser >> parseNumberFraction [ + | number power | + number := 0. + power := 1.0. + [stream atEnd not and: [stream peek isDigit]] whileTrue: [ + number := 10 * number + stream next digitValue. + power := power * 10.0]. + ^number / power +] + +{ #category : #parsing } +JsonParser >> parseNumberInteger [ + | number | + number := nil. + [stream atEnd not and: [stream peek isDigit]] + whileTrue: [number := 10 * (number ifNil: [0]) + stream next digitValue]. + number ifNil: [self error: 'an integer digit was expected']. + ^number +] + +{ #category : #parsing } +JsonParser >> parsePropertyName [ + | name | + name := self parseValue. + name isString ifTrue: [^name]. + self + error: 'a property name must be a string, not ' + , name asString withArticle +] + +{ #category : #parsing } +JsonParser >> parseString [ + | result | + self expectChar: $". + result := String streamContents: [:strm | + [stream atEnd or: [stream peek = $"]] + whileFalse: [strm nextPutChar: self parseCharacter]]. + self expectChar: $". + ^result +] + +{ #category : #parsing } +JsonParser >> parseTrailingSurrogateHexEscape [ + (stream next = $\ and: [stream next = $u]) + ifTrue: [^self parseCharacterHex4Value]. + self error: 'a trailing surrogate hex escape was expected' +] + +{ #category : #parsing } +JsonParser >> parseValue [ + | char | + stream atEnd ifTrue: [^self error: 'End of stream'] ifFalse: [ + char := stream peek. + char = ${ ifTrue: [^self parseMap]. + char = $[ ifTrue: [^self parseList]. + char = $" ifTrue: [^self parseString]. + (char = $- or: [char isDigit]) ifTrue: [^self parseNumber]. + self parseConstantDo: [:value | ^value]]. + ^self error: 'the character ' , char name , ' is invalid' +] + +{ #category : #parsing } +JsonParser >> unescapeChar: char [ + char = $b ifTrue: [^Character backspace]. + char = $f ifTrue: [^Character newPage]. + char = $n ifTrue: [^Character lf]. + char = $r ifTrue: [^Character cr]. + char = $t ifTrue: [^Character tab]. + self + error: 'the unknown escape character ' , char asString storeString + , ' occured' +] + diff --git a/modules/JSON/JsonPrimitiveSchema.st b/modules/JSON/JsonPrimitiveSchema.st new file mode 100644 index 0000000..e717566 --- /dev/null +++ b/modules/JSON/JsonPrimitiveSchema.st @@ -0,0 +1,39 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonPrimitiveSchema, + #superclass : #JsonSchema, + #instVars : [ + 'format' + ], + #category : #JSON +} + +{ #category : #accessing } +JsonPrimitiveSchema >> defaultFormat [ + ^self subclassResponsibility +] + +{ #category : #accessing } +JsonPrimitiveSchema >> format [ + ^format ifNil: [self defaultFormat] +] + +{ #category : #accessing } +JsonPrimitiveSchema >> format: anObject [ + format := anObject +] + +{ #category : #testing } +JsonPrimitiveSchema >> isPrimitiveSchema [ + ^true +] + +{ #category : #validation } +JsonPrimitiveSchema >> targetClass [ + ^Smalltalk at: self typeName asProperNoun asSymbol +] + diff --git a/modules/JSON/JsonPropertyMap.st b/modules/JSON/JsonPropertyMap.st new file mode 100644 index 0000000..7f6d817 --- /dev/null +++ b/modules/JSON/JsonPropertyMap.st @@ -0,0 +1,165 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonPropertyMap, + #superclass : #Object, + #instVars : [ + 'property', + 'getter', + 'setter', + 'mapping' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +JsonPropertyMap class >> fromType: anEdmPropertyType mapping: aJsonMapping [ + ^JsonPropertyMap new mapping: aJsonMapping; fromType: anEdmPropertyType +] + +{ #category : #accessing } +JsonPropertyMap >> adaptor: aDataAdaptor [ + self getterAdaptor: aDataAdaptor; setterAdaptor: aDataAdaptor +] + +{ #category : #services } +JsonPropertyMap >> defaultGetter [ + | default class identifier | + default := property name asSymbol. + class := mapping objectClass. + class isNil ifTrue: [^default]. + (class canUnderstand: default) ifTrue: [^default]. + default first isUppercase ifFalse: [^default]. + identifier := default asIdentifier. + ^(class canUnderstand: identifier) ifTrue: [identifier] ifFalse: [default] +] + +{ #category : #private } +JsonPropertyMap >> defaultSetter [ + ^(getter key , ':') asSymbol -> getter value +] + +{ #category : #private } +JsonPropertyMap >> fromType: anEdmPropertyType [ + property := anEdmPropertyType. + getter := property name asSymbol -> nil +] + +{ #category : #services } +JsonPropertyMap >> getFrom: anObject [ + | value adaptor | + value := getter key evaluateWith: anObject. + value isNil ifTrue: [^nil]. + adaptor := getter value. + adaptor isNil ifTrue: [^value]. + ^self isArrayed + ifTrue: [value collect: [:element | adaptor dataFrom: element]] + ifFalse: [adaptor dataFrom: value] +] + +{ #category : #accessing } +JsonPropertyMap >> getter: anAssociation [ + getter := anAssociation +] + +{ #category : #accessing } +JsonPropertyMap >> getterAdaptor: aDataAdaptor [ + getter notNil ifTrue: [getter value: aDataAdaptor] +] + +{ #category : #testing } +JsonPropertyMap >> isArrayed [ + ^property isArrayed +] + +{ #category : #testing } +JsonPropertyMap >> isKey [ + ^property isKey +] + +{ #category : #testing } +JsonPropertyMap >> isOptional [ + ^property isOptional +] + +{ #category : #accessing } +JsonPropertyMap >> mapping: aJsonMapping [ + mapping := aJsonMapping +] + +{ #category : #accessing } +JsonPropertyMap >> name [ + ^property name +] + +{ #category : #accessing } +JsonPropertyMap >> name: aString [ + property name: aString +] + +{ #category : #private } +JsonPropertyMap >> objectFrom: value [ + | adaptor | + adaptor := self setter value. + ^adaptor isNil ifTrue: [value] ifFalse: [adaptor objectFrom: value] +] + +{ #category : #printing } +JsonPropertyMap >> printOn: aStream [ + aStream nextPut: $#; nextPutAll: getter key printString. + getter value ifNotNil: [:adaptor | + aStream nextPut: $(. + adaptor printOn: aStream. + aStream nextPut: $)]. + aStream nextPut: $→; nextPutAll: property name +] + +{ #category : #accessing } +JsonPropertyMap >> property [ + ^property +] + +{ #category : #accessing } +JsonPropertyMap >> property: anEdmPropertyType [ + property := anEdmPropertyType +] + +{ #category : #services } +JsonPropertyMap >> set: value to: anObject [ + | adaptor adapted | + adaptor := self setter value. + adapted := adaptor isNil ifTrue: [value] ifFalse: [ + self isArrayed + ifTrue: [value collect: [:element | adaptor objectFrom: element]] + ifFalse: [adaptor objectFrom: value]]. + self setter key evaluateWith: anObject with: adapted +] + +{ #category : #accessing } +JsonPropertyMap >> setter [ + ^setter ifNil: [self defaultSetter] +] + +{ #category : #accessing } +JsonPropertyMap >> setter: anAssociation [ + setter := anAssociation +] + +{ #category : #accessing } +JsonPropertyMap >> setterAdaptor: aDataAdaptor [ + setter notNil ifTrue: [setter value: aDataAdaptor] +] + +{ #category : #accessing } +JsonPropertyMap >> type [ + ^property type +] + +{ #category : #services } +JsonPropertyMap >> useDefaultGetter [ + getter key: self defaultGetter +] + diff --git a/modules/JSON/JsonSchema.st b/modules/JSON/JsonSchema.st new file mode 100644 index 0000000..620e75f --- /dev/null +++ b/modules/JSON/JsonSchema.st @@ -0,0 +1,283 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonSchema, + #superclass : #Object, + #instVars : [ + 'title', + 'description', + 'default', + 'examples', + 'enum', + 'definitions', + 'parent' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +JsonSchema class >> anyOf: aCollection [ + ^JsonAnySchema new schemas: aCollection +] + +{ #category : #'instance creation' } +JsonSchema class >> array [ + ^JsonArraySchema new +] + +{ #category : #'instance creation' } +JsonSchema class >> arrayOf: aJsonSchema [ + ^JsonArraySchema new items: aJsonSchema +] + +{ #category : #'instance creation' } +JsonSchema class >> boolean [ + ^JsonBooleanSchema new +] + +{ #category : #'instance creation' } +JsonSchema class >> integer [ + ^JsonIntegerSchema new +] + +{ #category : #accessing } +JsonSchema class >> isAbstract [ + ^self = JsonSchema +] + +{ #category : #'instance creation' } +JsonSchema class >> multipleOf: aNumber [ + ^JsonNumberSchema new multipleOf: aNumber +] + +{ #category : #'instance creation' } +JsonSchema class >> not: aJsonSchema [ + ^JsonNotSchema new schema: aJsonSchema +] + +{ #category : #'instance creation' } +JsonSchema class >> null [ + ^JsonNullSchema new +] + +{ #category : #'instance creation' } +JsonSchema class >> number [ + ^JsonNumberSchema new +] + +{ #category : #'instance creation' } +JsonSchema class >> object [ + ^JsonObjectSchema new +] + +{ #category : #'instance creation' } +JsonSchema class >> oneOf: aCollection [ + ^JsonOneSchema new schemas: aCollection +] + +{ #category : #'instance creation' } +JsonSchema class >> ref: aString [ + ^JsonSchemaReference path: aString +] + +{ #category : #'instance creation' } +JsonSchema class >> string [ + ^JsonStringSchema new +] + +{ #category : #accessing } +JsonSchema class >> typeName [ + ^((self name trimPrefix: 'Json') trimTail: 'Schema') asLowercase +] + +{ #category : #accessing } +JsonSchema class >> typeNamed: aString [ + ^self allSubclasses + detect: [:c | c isAbstract not and: [c typeName = aString]] + ifNone: [self error: 'Type named ' , aString asString , ' not found'] +] + +{ #category : #'adding / removing' } +JsonSchema >> addDefinition: aJsonSchema named: aString [ + definitions at: aString put: aJsonSchema. + aJsonSchema parent: self +] + +{ #category : #converting } +JsonSchema >> asJson [ + | json | + json := JsonObject new. + json type: self typeName. + definitions notEmpty ifTrue: [json at: 'definitions' put: definitions]. + enum notNil ifTrue: [json at: 'enum' put: enum]. + default notNil ifTrue: [json at: 'default' put: default]. + examples notEmpty ifTrue: [json at: 'examples' put: examples]. + ^json +] + +{ #category : #accessing } +JsonSchema >> default [ + ^default +] + +{ #category : #accessing } +JsonSchema >> default: anObject [ + default := anObject +] + +{ #category : #accessing } +JsonSchema >> definitionNamed: aString [ + definitions at: aString ifPresent: [:s | ^s]. + ^parent notNil ifTrue: [parent definitionNamed: aString] +] + +{ #category : #accessing } +JsonSchema >> description [ + ^description +] + +{ #category : #accessing } +JsonSchema >> description: aString [ + description := aString +] + +{ #category : #accessing } +JsonSchema >> enum [ + ^enum +] + +{ #category : #accessing } +JsonSchema >> enum: aCollection [ + enum := aCollection +] + +{ #category : #accessing } +JsonSchema >> examples [ + ^examples +] + +{ #category : #accessing } +JsonSchema >> examples: aCollection [ + examples := aCollection +] + +{ #category : #initialization } +JsonSchema >> initialize [ + super initialize. + examples := OrderedCollection new. + definitions := Dictionary new +] + +{ #category : #testing } +JsonSchema >> isAnySchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isArraySchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isBooleanSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isIntegerSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isNotSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isNullSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isNumberSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isObjectSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isOneSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isPrimitiveSchema [ + ^false +] + +{ #category : #testing } +JsonSchema >> isSchemaReference [ + ^false +] + +{ #category : #testing } +JsonSchema >> isStringSchema [ + ^false +] + +{ #category : #accessing } +JsonSchema >> name [ + ^title +] + +{ #category : #converting } +JsonSchema >> not [ + ^JsonNotSchema new schema: self +] + +{ #category : #accessing } +JsonSchema >> parent [ + ^parent +] + +{ #category : #accessing } +JsonSchema >> parent: aJsonSchema [ + parent := aJsonSchema +] + +{ #category : #printing } +JsonSchema >> printOn: aStream [ + self asJson printOn: aStream +] + +{ #category : #printing } +JsonSchema >> printTextOn: rtf [ + self asJson printTextOn: rtf +] + +{ #category : #accessing } +JsonSchema >> title [ + ^title +] + +{ #category : #accessing } +JsonSchema >> title: aString [ + title := aString +] + +{ #category : #accessing } +JsonSchema >> typeName [ + ^self class typeName +] + +{ #category : #validation } +JsonSchema >> validate: aJsonObject [ + ^JsonObjectValidator validate: aJsonObject against: self +] + diff --git a/modules/JSON/JsonSchemaReference.st b/modules/JSON/JsonSchemaReference.st new file mode 100644 index 0000000..1c59727 --- /dev/null +++ b/modules/JSON/JsonSchemaReference.st @@ -0,0 +1,77 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonSchemaReference, + #superclass : #Object, + #instVars : [ + 'path' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +JsonSchemaReference class >> path: aString [ + ^self new path: aString +] + +{ #category : #converting } +JsonSchemaReference >> asJson [ + ^path +] + +{ #category : #testing } +JsonSchemaReference >> isLocal [ + ^path beginsWith: '#' +] + +{ #category : #testing } +JsonSchemaReference >> isSchemaReference [ + ^true +] + +{ #category : #accessing } +JsonSchemaReference >> parent: aJsonSchema [ + " + do nothing + " + ] + +{ #category : #accessing } +JsonSchemaReference >> path [ + ^path +] + +{ #category : #accessing } +JsonSchemaReference >> path: aString [ + path := aString +] + +{ #category : #printing } +JsonSchemaReference >> printOn: aStream [ + path printOn: aStream +] + +{ #category : #resolving } +JsonSchemaReference >> resolveIn: aJsonSchema [ + | segments schema i | + self ASSERT: self isLocal. + segments := $/ split: path allButFirst. + segments := segments reject: #isEmpty. + schema := aJsonSchema. + i := 1. + [i <= segments size] whileTrue: [| segment | + segment := segments at: i. + schema := segment = 'definitions' + ifTrue: [ + i = segments size ifTrue: [^nil]. + i := i + 1. + schema definitionNamed: (segments at: i)] + ifFalse: [schema propertyAt: segment]. + schema isNil ifTrue: [^nil]. + i := i + 1]. + ^schema +] + diff --git a/modules/JSON/JsonSerializer.st b/modules/JSON/JsonSerializer.st new file mode 100644 index 0000000..99bab5f --- /dev/null +++ b/modules/JSON/JsonSerializer.st @@ -0,0 +1,212 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonSerializer, + #superclass : #Object, + #instVars : [ + 'mappings', + 'level', + 'condition' + ], + #category : #JSON +} + +{ #category : #services } +JsonSerializer class >> inflate: anObject [ + ^self new inflate: anObject +] + +{ #category : #services } +JsonSerializer class >> serialize: anObject [ + ^self new serialize: anObject +] + +{ #category : #accessing } +JsonSerializer >> condition: aBlock [ + condition := aBlock +] + +{ #category : #private } +JsonSerializer >> defaultDictionaryFrom: anObject [ + | dictionary | + dictionary := Dictionary new. + dictionary at: 'Class' put: anObject class name. + anObject class allInstVarNames do: [:key | | ivar | + ivar := anObject instVarNamed: key. + dictionary at: key put: ivar]. + ^dictionary +] + +{ #category : #private } +JsonSerializer >> getObject: aJsonPropertyMap from: aJsonObject [ + | key value mapping type | + key := aJsonPropertyMap name. + value := aJsonPropertyMap isOptional + ifTrue: [aJsonObject valueAt: key] + ifFalse: [aJsonObject at: key]. + value isNil ifTrue: [^nil]. + type := aJsonPropertyMap type singleType. + type isBasicType ifTrue: [^value]. + mapping := self mappingForType: type. + ^self objectFrom: value using: mapping +] + +{ #category : #private } +JsonSerializer >> getProperty: aJsonPropertyMap from: anObject [ + | value | + value := aJsonPropertyMap getFrom: anObject. + value isNil ifTrue: [^nil]. + ^self jsonFrom: value +] + +{ #category : #services } +JsonSerializer >> inflate: aString [ + ^self inflate: aString using: nil +] + +{ #category : #services } +JsonSerializer >> inflate: aString using: aJsonMapping [ + | json | + json := [JsonParser parse: aString] on: JsonError do: []. + json isNil ifTrue: [^nil]. + ^self objectFrom: json using: aJsonMapping +] + +{ #category : #initialization } +JsonSerializer >> initialize [ + super initialize. + level := 0. + self upToLevel: 2] + +{ #category : #testing } +JsonSerializer >> isLeaf: anObject [ + anObject isUndefinedValue ifTrue: [^true]. + anObject isNumber ifTrue: [^true]. + anObject isString ifTrue: [^true]. + anObject isBoolean ifTrue: [^true]. + anObject isDate ifTrue: [^true]. + anObject isTimestamp ifTrue: [^true]. + ^false +] + +{ #category : #private } +JsonSerializer >> jsonFrom: anObject [ + (self isLeaf: anObject) ifTrue: [^anObject]. + anObject isCollection ifTrue: [ + ^anObject collect: [:element | | d | + level := level + 1. + d := self jsonFrom: element. + level := level - 1. + d]]. + ^self jsonObjectFrom: anObject +] + +{ #category : #services } +JsonSerializer >> jsonObjectFrom: anObject [ + | mapping | + anObject isJsonObject ifTrue: [^anObject]. + mapping := self mappingForObject: anObject. + mapping notNil ifTrue: [^self jsonObjectFrom: anObject using: mapping]. + (anObject respondsTo: #asJson) ifTrue: [^anObject asJson]. + ^self defaultDictionaryFrom: anObject +] + +{ #category : #services } +JsonSerializer >> jsonObjectFrom: anObject using: aJsonMapping [ + | json | + json := JsonObject new. + aJsonMapping properties do: [:p | + level := level + 1. + self writeProperty: p from: anObject into: json. + level := level - 1]. + ^json notEmpty ifTrue: [json] +] + +{ #category : #private } +JsonSerializer >> mappingForObject: anObject [ + ^mappings notNil ifTrue: [mappings mappingFor: anObject] +] + +{ #category : #private } +JsonSerializer >> mappingForType: anEdmType [ + ^mappings notNil ifTrue: [mappings mappingForType: anEdmType] +] + +{ #category : #accessing } +JsonSerializer >> mappings: aJsonMappingRegistry [ + mappings := aJsonMappingRegistry +] + +{ #category : #services } +JsonSerializer >> objectFrom: aJsonObject into: anObject using: aJsonMapping [ + aJsonMapping properties + do: [:p | self readProperty: p from: aJsonObject into: anObject]. + ^anObject +] + +{ #category : #services } +JsonSerializer >> objectFrom: anObject using: aJsonMapping [ + | class mapping object | + (self isLeaf: anObject) ifTrue: [^anObject]. + anObject isSequenceableCollection ifTrue: [ + ^anObject collect: [:element | | o | + level := level + 1. + o := self objectFrom: element using: aJsonMapping. + level := level - 1. + o]]. + self ASSERT: anObject isDictionary. + aJsonMapping isNil ifTrue: [^anObject]. + class := aJsonMapping classFrom: anObject. + class isNil ifTrue: [^anObject]. + mapping := class == aJsonMapping objectClass + ifTrue: [aJsonMapping] + ifFalse: [(mappings mappingForClass: class) ifNil: [aJsonMapping]]. + object := (mapping instanceFor: anObject) ifNil: [class new]. + level := level + 1. + self objectFrom: anObject into: object using: mapping. + level := level - 1. + ^object +] + +{ #category : #accessing } +JsonSerializer >> onlyKeys [ + self upToLevel: 1] + +{ #category : #accessing } +JsonSerializer >> onlyKeysFromLevel: anInteger [ + self upToLevel: anInteger +] + +{ #category : #private } +JsonSerializer >> readProperty: aJsonPropertyMap from: aJsonObject into: anObject [ + | object | + object := self getObject: aJsonPropertyMap from: aJsonObject. + (object isNil and: [aJsonPropertyMap isOptional]) ifTrue: [^self]. + ((condition evaluateWith: level with: object) or: [aJsonPropertyMap isKey]) + ifTrue: [aJsonPropertyMap set: object to: anObject] +] + +{ #category : #services } +JsonSerializer >> serialize: anObject [ + | json | + json := self jsonFrom: anObject. + ^JsonWriter write: json +] + +{ #category : #accessing } +JsonSerializer >> upToLevel: anInteger [ + condition := [:depth :object | depth <= anInteger] +] + +{ #category : #private } +JsonSerializer >> writeProperty: aJsonPropertyMap from: anObject into: aJsonObject [ + | value | + value := self getProperty: aJsonPropertyMap from: anObject. + (value isNil and: [aJsonPropertyMap isOptional]) ifTrue: [^self]. + ((condition evaluateWith: level with: anObject) or: [aJsonPropertyMap isKey]) + ifTrue: [aJsonObject at: aJsonPropertyMap name put: value] +] + diff --git a/modules/JSON/JsonStringFormat.st b/modules/JSON/JsonStringFormat.st new file mode 100644 index 0000000..0c75437 --- /dev/null +++ b/modules/JSON/JsonStringFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonStringFormat, + #superclass : #JsonFormat, + #category : #JSON +} + diff --git a/modules/JSON/JsonStringSchema.st b/modules/JSON/JsonStringSchema.st new file mode 100644 index 0000000..3eaf653 --- /dev/null +++ b/modules/JSON/JsonStringSchema.st @@ -0,0 +1,54 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonStringSchema, + #superclass : #JsonPrimitiveSchema, + #instVars : [ + 'minLength', + 'maxLength' + ], + #category : #JSON +} + +{ #category : #converting } +JsonStringSchema >> asJson [ + | json | + json := super asJson. + minLength notNil ifTrue: [json at: 'minLength' put: minLength]. + maxLength notNil ifTrue: [json at: 'maxLength' put: maxLength]. + ^json +] + +{ #category : #accessing } +JsonStringSchema >> defaultFormat [ + ^JsonStringFormat new +] + +{ #category : #testing } +JsonStringSchema >> isStringSchema [ + ^true +] + +{ #category : #accessing } +JsonStringSchema >> maxLength [ + ^maxLength +] + +{ #category : #accessing } +JsonStringSchema >> maxLength: anInteger [ + maxLength := anInteger +] + +{ #category : #accessing } +JsonStringSchema >> minLength [ + ^minLength +] + +{ #category : #accessing } +JsonStringSchema >> minLength: anInteger [ + minLength := anInteger +] + diff --git a/modules/JSON/JsonUriFormat.st b/modules/JSON/JsonUriFormat.st new file mode 100644 index 0000000..1fceb23 --- /dev/null +++ b/modules/JSON/JsonUriFormat.st @@ -0,0 +1,11 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonUriFormat, + #superclass : #JsonStringFormat, + #category : #JSON +} + diff --git a/modules/JSON/JsonWriter.st b/modules/JSON/JsonWriter.st new file mode 100644 index 0000000..751e859 --- /dev/null +++ b/modules/JSON/JsonWriter.st @@ -0,0 +1,331 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonWriter, + #superclass : #Object, + #instVars : [ + 'stream', + 'mappings', + 'indent', + 'visited', + 'sort' + ], + #category : #JSON +} + +{ #category : #services } +JsonWriter class >> jsonObjectFrom: anObject [ + ^self new jsonObjectFrom: anObject +] + +{ #category : #'initialization' } +JsonWriter class >> initializePools [ + self addNamespace: Character namedCharacters +] + +{ #category : #'instance creation' } +JsonWriter class >> on: aStream [ + ^self new on: aStream +] + +{ #category : #services } +JsonWriter class >> write: anObject [ + | stream writer | + stream := '' writeStream. + writer := self write: anObject on: stream. + ^String fromUTF8: writer contents +] + +{ #category : #services } +JsonWriter class >> write: anObject indent: anInteger [ + | stream writer | + stream := '' writeStream. + writer := self write: anObject on: stream indent: anInteger. + ^String fromUTF8: writer contents +] + +{ #category : #services } +JsonWriter class >> write: anObject on: aStream [ + ^self new on: aStream; write: anObject +] + +{ #category : #services } +JsonWriter class >> write: anObject on: aStream indent: anInteger [ + ^self new + on: aStream; + indent: anInteger; + write: anObject +] + +{ #category : #services } +JsonWriter class >> write: anObject registry: aJsonMappingRegistry [ + | stream writer | + stream := '' writeStream. + writer := self new. + writer + on: stream; + registry: aJsonMappingRegistry; + write: anObject. + ^String fromUTF8: writer contents +] + +{ #category : #accessing } +JsonWriter >> addMappingsFrom: aJsonMappingRegistry [ + mappings addMappingsFrom: aJsonMappingRegistry +] + +{ #category : #private } +JsonWriter >> codeForChar: ch [ + | i code | + ('\"/' includes: ch) ifTrue: [^String with: $\ with: ch]. + ch = Bs ifTrue: [^'\b']. + ch = Ff ifTrue: [^'\f']. + ch = Lf ifTrue: [^'\n']. + ch = Cr ifTrue: [^'\r']. + ch = Tab ifTrue: [^'\t']. + i := ch asInteger. + i < 0x20 ifTrue: [ + code := i printPaddedWith: $0 to: 2 base: 16. + ^'\u00' , code]. + ^i < 128 ifTrue: [ch] ifFalse: [ch utf8 asString] +] + +{ #category : #writing } +JsonWriter >> contents [ + ^stream contents +] + +{ #category : #writing } +JsonWriter >> cr [ + stream cr; tab: indent +] + +{ #category : #writing } +JsonWriter >> crtab [ + self cr; tab +] + +{ #category : #accessing } +JsonWriter >> indent: anInteger [ + indent := anInteger. + stream tab: indent +] + +{ #category : #initialization } +JsonWriter >> initialize [ + super initialize. + mappings := JsonMappingRegistry new. + indent := 0. + sort := false +] + +{ #category : #private } +JsonWriter >> isBasic: anObject [ + anObject isUndefinedValue ifTrue: [^true]. + anObject isNumber ifTrue: [^true]. + anObject isString ifTrue: [^true]. + anObject isBoolean ifTrue: [^true]. + anObject isDate ifTrue: [^true]. + anObject isTimestamp ifTrue: [^true]. + ^false +] + +{ #category : #private } +JsonWriter >> jsonObjectFrom: anObject [ + anObject isJsonObject ifTrue: [^anObject]. + ^(self mappingFor: anObject) + ifNil: [JsonSerializer new mappings: mappings; jsonObjectFrom: anObject] + ifNotNil: [:mapping | mapping jsonObjectFrom: anObject] +] + +{ #category : #private } +JsonWriter >> mappingFor: anObject [ + mappings isNil ifTrue: [^anObject class defaultMapping]. + ^mappings mappingFor: anObject +] + +{ #category : #writing } +JsonWriter >> nextWrite: aString [ + aString isCharacter ifTrue: [stream nextPut: aString] ifFalse: [ + self DENY: aString isWideString. + stream nextPutAll: aString] +] + +{ #category : #accessing } +JsonWriter >> on: aStream [ + stream := aStream. + indent := 0] + +{ #category : #accessing } +JsonWriter >> registry: aJsonMappingRegistry [ + mappings := aJsonMappingRegistry +] + +{ #category : #services } +JsonWriter >> reset [ + stream reset. + indent := 0. + visited := nil +] + +{ #category : #writing } +JsonWriter >> return [ + indent := indent - 1. + self cr +] + +{ #category : #accessing } +JsonWriter >> sortKeys: aBoolean [ + sort := aBoolean +] + +{ #category : #writing } +JsonWriter >> space [ + stream space +] + +{ #category : #accessing } +JsonWriter >> tab [ + indent := indent + 1. + stream tab +] + +{ #category : #services } +JsonWriter >> write: anObject [ + visited isNil ifTrue: [visited := IdentitySet new]. + anObject isNil ifTrue: [^self writeNull]. + anObject isNumber ifTrue: [^self writeNumber: anObject]. + anObject isString ifTrue: [^self writeString: anObject]. + anObject isBoolean ifTrue: [^self writeBoolean: anObject]. + anObject isDate ifTrue: [^self writeDate: anObject]. + anObject isTimestamp ifTrue: [^self writeTimestamp: anObject]. + anObject isFilename ifTrue: [^self writeFilename: anObject]. + anObject isExternalMemory ifTrue: [^self writeMemory: anObject]. + (visited includes: anObject) ifTrue: [^self writeVisitedObject: anObject]. + visited add: anObject. + [ + anObject isDictionary ifTrue: [^self writeDictionary: anObject]. + anObject isArray ifTrue: [^self writeArray: anObject]. + anObject isCollection ifTrue: [^self writeArray: anObject asArray]. + self writeObject: anObject] + ensure: [visited remove: anObject] +] + +{ #category : #services } +JsonWriter >> write: anObject on: aStream [ + self on: aStream; reset; write: anObject +] + +{ #category : #writing } +JsonWriter >> writeArray: anArray [ + self writeOperator: $[. + anArray notEmpty ifTrue: [ + self crtab. + anArray + do: [:object | self write: object] + separatedBy: [self writeOperator: $,; cr]. + self return]. + self writeOperator: $] +] + +{ #category : #writing } +JsonWriter >> writeBoolean: aBoolean [ + self nextWrite: aBoolean printString +] + +{ #category : #writing } +JsonWriter >> writeDate: aDate [ + self writeOperator: $". + aDate printIso8601On: stream. + self writeOperator: $"] + +{ #category : #writing } +JsonWriter >> writeDictionary: aDictionary [ + | keys | + self writeOperator: ${. + aDictionary keys size > 1 ifTrue: [self crtab]. + keys := aDictionary keys. + sort ifTrue: [keys := keys asArray sort]. + keys + do: [:key | | value | + value := aDictionary at: key. + self + writeKey: key; + writeOperator: $:; + space; + write: value] + separatedBy: [self writeOperator: $,; cr]. + aDictionary keys size > 1 ifTrue: [self return]. + self writeOperator: $}] + +{ #category : #writing } +JsonWriter >> writeFilename: aFilename [ + self writeString: aFilename asString +] + +{ #category : #writing } +JsonWriter >> writeKey: aString [ + self writeString: aString +] + +{ #category : #writing } +JsonWriter >> writeMemory: anExternalMemory [ + self writeArray: anExternalMemory bytes +] + +{ #category : #writing } +JsonWriter >> writeNull [ + self nextWrite: 'null' +] + +{ #category : #writing } +JsonWriter >> writeNumber: aNumber [ + | number | + aNumber isNaN ifTrue: [^self write: aNumber asString]. + aNumber isInfinite ifTrue: [^self write: aNumber asString]. + number := aNumber isFraction ifTrue: [aNumber asFloat] ifFalse: [aNumber]. + self nextWrite: number storeString +] + +{ #category : #writing } +JsonWriter >> writeObject: anObject [ + | json | + json := self jsonObjectFrom: anObject. + self write: json +] + +{ #category : #writing } +JsonWriter >> writeOperator: aCharacter [ + self nextWrite: aCharacter +] + +{ #category : #writing } +JsonWriter >> writeString: aString [ + self writeOperator: $". + aString do: [:ch | | code | + code := self codeForChar: ch. + self nextWrite: code]. + self writeOperator: $"] + +{ #category : #writing } +JsonWriter >> writeTimestamp: aTimestamp [ + stream nextPut: $". + aTimestamp printIso8601On: stream. + stream nextPut: $"] + +{ #category : #writing } +JsonWriter >> writeVisitedObject: anObject [ + | d | + self error: 'Recursive'. + d := Dictionary new. + d at: 'Class' put: anObject class name. + d at: 'Already Visited' put: true. + anObject class allInstVarNames do: [:key | | ivar | + ivar := anObject instVarNamed: key. + (self isBasic: ivar) ifTrue: [ivar d at: key put: ivar]]. + self writeDictionary: d +] + diff --git a/modules/JSON/Object.st b/modules/JSON/Object.st new file mode 100644 index 0000000..ab5c6ab --- /dev/null +++ b/modules/JSON/Object.st @@ -0,0 +1,17 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Extension { #name : #Object } + +{ #category : '*JSON' } +Object class >> defaultMapping [ + ^nil +] + +{ #category : '*JSON' } +Object >> isJsonObject [ + ^false +] + diff --git a/modules/JSON/StringAdaptor.st b/modules/JSON/StringAdaptor.st new file mode 100644 index 0000000..e922884 --- /dev/null +++ b/modules/JSON/StringAdaptor.st @@ -0,0 +1,19 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #StringAdaptor, + #superclass : #DataAdaptor, + #category : #JSON +} + +{ #category : #services } +StringAdaptor >> objectFrom: anObject [ + | s | + anObject isString ifFalse: [^anObject]. + s := anObject trimBlanks. + ^s isEmpty ifFalse: [s] +] + diff --git a/modules/JSON/SymbolAdaptor.st b/modules/JSON/SymbolAdaptor.st new file mode 100644 index 0000000..2d2ad1d --- /dev/null +++ b/modules/JSON/SymbolAdaptor.st @@ -0,0 +1,16 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #SymbolAdaptor, + #superclass : #DataAdaptor, + #category : #JSON +} + +{ #category : #services } +SymbolAdaptor >> objectFrom: aString [ + ^aString asSymbol +] + diff --git a/modules/JSON/Tests/JsonMappingTest.st b/modules/JSON/Tests/JsonMappingTest.st new file mode 100644 index 0000000..e72b236 --- /dev/null +++ b/modules/JSON/Tests/JsonMappingTest.st @@ -0,0 +1,55 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonMappingTest, + #superclass : #TestCase, + #category : #'JSON\Tests' +} + +{ #category : #all } +JsonMappingTest >> testTypeMapping [ + | type mapping json object | + type := EdmComplexType new. + type + name: 'point'; + addPropertyNamed: 'x' type: EdmType int; + addPropertyNamed: 'y' type: EdmType int. + mapping := JsonMapping fromType: type toClass: Point. + json := mapping jsonObjectFrom: 1 @ 2. + self + assert: json isJsonObject; + assert: (json at: 'x' ifAbsent: [self assert: false]) = 1; + assert: (json at: 'y' ifAbsent: [self assert: false]) = 2. + object := mapping objectFrom: json. + self assert: object = (1 @ 2) +] + +{ #category : #all } +JsonMappingTest >> testTypesMapping [ + | registry point mapping rectangle example dictionary object serializer | + registry := JsonMappingRegistry new. + point := EdmComplexType new. + point + name: 'point'; + addPropertyNamed: 'x' type: EdmType int; + addPropertyNamed: 'y' type: EdmType int. + mapping := JsonMapping fromType: point toClass: Point. + registry register: mapping. + rectangle := EdmComplexType new. + rectangle + name: 'rectangle'; + addPropertyNamed: 'leftTop' type: point; + addPropertyNamed: 'rightBottom' type: point. + mapping := JsonMapping fromType: rectangle toClass: Rectangle. + registry register: mapping. + example := Rectangle origin: -10 @ 5 corner: 20 @ 10. + serializer := mapping serializer condition: true. + dictionary := serializer jsonObjectFrom: example. + self assert: dictionary isDictionary. + object := mapping objectFrom: dictionary. + self assert: object = example +] + diff --git a/modules/JSON/Tests/JsonParserTest.st b/modules/JSON/Tests/JsonParserTest.st new file mode 100644 index 0000000..32582b2 --- /dev/null +++ b/modules/JSON/Tests/JsonParserTest.st @@ -0,0 +1,33 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonParserTest, + #superclass : #TestCase, + #category : #'JSON\Tests' +} + +{ #category : #private } +JsonParserTest >> testJsonNode [ + | info | + info := Compiler + compile: 'm ^{"id": 1, "name": "john"}' + in: self class. + self + assert: (info ast statements anyone expression body ast + isKindOf: JsonObject) +] + +{ #category : #private } +JsonParserTest >> testTP48846 [ + | wrong | + wrong := 'Page not found

Page not found

The requested URL was not found on this server.

Home
'. + self + should: [JsonParser parse: wrong] + raise: JsonError + satisfying: [:e | e isResumable not] + description: 'Default action' +] + diff --git a/modules/JSON/Tests/JsonSchemaTest.st b/modules/JSON/Tests/JsonSchemaTest.st new file mode 100644 index 0000000..865b8f3 --- /dev/null +++ b/modules/JSON/Tests/JsonSchemaTest.st @@ -0,0 +1,325 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonSchemaTest, + #superclass : #TestCase, + #category : #'JSON\Tests' +} + +{ #category : #validation } +JsonSchemaTest >> testAnyOfValidation [ + | string number schema | + string := JsonSchema string maxLength: 5. + number := JsonSchema number minimum: 0. + schema := JsonSchema anyOf: {string. number}. + self + assert: (schema validate: 'short') hasPassed; + assert: (schema validate: 'too long') hasValidationFailures; + assert: (schema validate: 12) hasPassed; + assert: (schema validate: -5) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testArrayItemsValidation [ + | schema | + schema := JsonSchema arrayOf: JsonSchema number. + self + assert: (schema validate: #(1 2 3 4 5)) hasPassed; + assert: (schema validate: #(1 2 '3' 4 5)) hasValidationFailures; + assert: (schema validate: #()) hasPassed; + assert: (schema validate: 'abc') hasValidationFailures; + assert: (schema validate: nil) hasValidationFailures; + assert: (schema validate: true) hasValidationFailures; + assert: (schema validate: 123) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testArrayLengthValidation [ + | schema | + schema := JsonSchema arrayOf: JsonSchema number. + schema minItems: 2; maxItems: 3. + self + assert: (schema validate: #()) hasValidationFailures; + assert: (schema validate: #(1)) hasValidationFailures; + assert: (schema validate: #(1 2)) hasPassed; + assert: (schema validate: #(1 2 3)) hasPassed; + assert: (schema validate: #(1 2 3 4)) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testArrayUniquenessValidation [ + | schema | + schema := JsonSchema array. + schema uniqueItems: true. + self + assert: (schema validate: #(1 2 3 4 5)) hasPassed; + assert: (schema validate: #(1 2 3 3 4)) hasValidationFailures; + assert: (schema validate: #()) hasPassed +] + +{ #category : #validation } +JsonSchemaTest >> testBooleanValidation [ + | schema | + schema := JsonSchema boolean. + self + assert: (schema validate: true) hasPassed; + assert: (schema validate: false) hasPassed; + assert: (schema validate: 'true') hasValidationFailures; + assert: (schema validate: 0) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testEnumValidation [ + | schema | + schema := JsonSchema string. + schema enum: #('red' 'amber' 'green'). + self + assert: (schema validate: 'red') hasPassed; + assert: (schema validate: 'blue') hasValidationFailures. + schema := JsonSchema new. + schema enum: {'red'. 'amber'. 'green'. nil. 42}. + self + assert: (schema validate: 'red') hasPassed; + assert: (schema validate: nil) hasPassed; + assert: (schema validate: 42) hasPassed; + assert: (schema validate: 0) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testIntegerValidation [ + | schema | + schema := JsonSchema integer. + self + assert: (schema validate: 42) hasPassed; + assert: (schema validate: -1) hasPassed; + assert: (schema validate: 3.1415926) hasValidationFailures; + assert: (schema validate: '42') hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testMultiple [ + | schema | + schema := JsonSchema number multipleOf: 10. + self + assert: (schema validate: 0) hasPassed; + assert: (schema validate: 10) hasPassed; + assert: (schema validate: 20) hasPassed; + assert: (schema validate: 23) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testNotValidation [ + | schema object | + schema := JsonSchema string not. + object := JsonObject with: 'key' -> 'value'. + self + assert: (schema validate: 42) hasPassed; + assert: (schema validate: object) hasPassed; + assert: (schema validate: 'I am a string') hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testNullValidation [ + | schema | + schema := JsonSchema null. + self + assert: (schema validate: nil) hasPassed; + assert: (schema validate: false) hasValidationFailures; + assert: (schema validate: 0) hasValidationFailures; + assert: (schema validate: '') hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testNumberRangeValidation [ + | schema | + schema := JsonSchema number minimum: 0; exclusiveMaximum: 100. + self + assert: (schema validate: -1) hasValidationFailures; + assert: (schema validate: 0) hasPassed; + assert: (schema validate: 10) hasPassed; + assert: (schema validate: 99) hasPassed; + assert: (schema validate: 100) hasValidationFailures; + assert: (schema validate: 101) hasValidationFailures. + schema := JsonSchema number + minimum: 0; + maximum: 100; + exclusiveMaximum: true. + self + assert: (schema validate: -1) hasValidationFailures; + assert: (schema validate: 0) hasPassed; + assert: (schema validate: 10) hasPassed; + assert: (schema validate: 99) hasPassed; + assert: (schema validate: 100) hasValidationFailures; + assert: (schema validate: 101) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testNumberValidation [ + | schema | + schema := JsonSchema number. + self + assert: (schema validate: 42) hasPassed; + assert: (schema validate: -1) hasPassed; + assert: (schema validate: 5.0) hasPassed; + assert: (schema validate: 2.99792458e8) hasPassed; + assert: (schema validate: '42') hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testObjectEnumValidation [ + | foo bar schema | + foo := JsonObject with: 'id' -> 1 with: 'name' -> 'foo'. + bar := JsonObject with: 'id' -> 2 with: 'name' -> 'bar'. + schema := JsonSchema object enum: {foo. bar}. + self + assert: (schema validate: foo) hasPassed; + assert: (schema validate: bar) hasPassed; + assert: (schema validate: JsonObject new) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testObjectValidation [ + | schema person car | + schema := JsonSchema object + addProperty: 'name' schema: JsonSchema string; + addProperty: 'age' schema: JsonSchema integer. + person := JsonObject new. + person + at: 'name' put: 'william'; + at: 'age' put: 42. + car := JsonObject new. + car at: 'brand' put: 'chevrolet'. + self + assert: (schema validate: person) hasPassed; + assert: (schema validate: JsonObject new) hasPassed; + assert: (schema validate: car) hasPassed; + assert: (schema validate: nil) hasValidationFailures; + assert: (schema validate: true) hasValidationFailures; + assert: (schema validate: 'abc') hasValidationFailures; + assert: (schema validate: 123) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testOneOfValidation [ + | multiplesOf5 multiplesOf3 schema | + multiplesOf5 := JsonSchema number multipleOf: 5. + multiplesOf3 := JsonSchema number multipleOf: 3. + schema := JsonSchema oneOf: {multiplesOf5. multiplesOf3}. + self + assert: (schema validate: 10) hasPassed; + assert: (schema validate: 9) hasPassed; + assert: (schema validate: 2) hasValidationFailures; + assert: (schema validate: 15) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testReferenceResolution [ + | address person reference | + address := JsonSchema object + addProperty: 'street_address' schema: JsonSchema string; + addProperty: 'city' schema: JsonSchema string; + addProperty: 'state' schema: JsonSchema string. + person := JsonSchema object + addProperty: 'name' schema: JsonSchema string; + addProperty: 'address' schema: address. + reference := JsonSchemaReference path: '#/address'. + self + assert: reference isLocal; + assert: (reference resolveIn: person) = address. + reference := JsonSchemaReference path: '#/address/city'. + self + assert: reference isLocal; + assert: (reference resolveIn: person) isStringSchema +] + +{ #category : #validation } +JsonSchemaTest >> testReferenceValidation [ + | schema valid invalid | + schema := JsonSchema object + addProperty: 'name' schema: JsonSchema string; + addProperty: 'nickname' schema: (JsonSchema ref: '#/name'). + valid := JsonParser + parse: + { + "name": "william", + "nickname": "will" + } + . + invalid := JsonParser + parse: + { + "name": "william", + "nickname": 33 + } + . + self + assert: (schema validate: valid) hasPassed; + assert: (schema validate: invalid) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testReferenceValidation2 [ + | reference person schema valid invalid | + reference := JsonSchema ref: '#/definitions/person'. + person := JsonSchema object + addProperty: 'name' schema: JsonSchema string; + addProperty: 'children' + schema: (JsonSchema arrayOf: reference). + schema := JsonSchema object + addDefinition: person named: 'person'; + addProperty: 'person' schema: reference. + valid := JsonParser + parse: + {"person": { + "name": "homer", + "children": [ + {"name": "bart"}, + {"name": "lisa"} + ] + }} + . + self assert: (schema validate: valid) hasPassed. + invalid := JsonParser + parse: + {"person": { + "name": "homer", + "children": [ + true, + 0, + "abc" + ] + }} + . + self assert: (schema validate: invalid) hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testStringLengthValidation [ + | schema | + schema := JsonSchema string. + schema minLength: 2; maxLength: 3. + self + assert: (schema validate: 'A') hasValidationFailures; + assert: (schema validate: 'AB') hasPassed; + assert: (schema validate: 'ABC') hasPassed; + assert: (schema validate: 'ABCD') hasValidationFailures +] + +{ #category : #validation } +JsonSchemaTest >> testStringValidation [ + | schema | + schema := JsonSchema string. + self + assert: (schema validate: 'This is a string') hasPassed; + assert: (schema validate: 'Déjà vu') hasPassed; + assert: (schema validate: '42') hasPassed; + assert: (schema validate: 42) hasValidationFailures; + assert: (schema validate: nil) hasValidationFailures; + assert: (schema validate: true) hasValidationFailures; + assert: (schema validate: false) hasValidationFailures; + assert: (schema validate: 123) hasValidationFailures +] + diff --git a/modules/JSON/Tests/JsonSerializationTest.st b/modules/JSON/Tests/JsonSerializationTest.st new file mode 100644 index 0000000..57af3a2 --- /dev/null +++ b/modules/JSON/Tests/JsonSerializationTest.st @@ -0,0 +1,239 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonSerializationTest, + #superclass : #TestCase, + #category : #'JSON\Tests' +} + +{ #category : #all } +JsonSerializationTest >> testBasicSerialization [ + | serialized inflated | + serialized := JsonSerializer serialize: #(true 1 'a'). + self assert: serialized = '[ + true, + 1, + "a" +]'. + inflated := JsonSerializer inflate: serialized. + self assert: inflated = #(true 1 'a') +] + +{ #category : #all } +JsonSerializationTest >> testInflate [ + | mappings class mapping serializer dictionary json copy | + mappings := JsonMappingRegistry new. + class := EdmEntityType new. + class + name: 'class'; + addKeyNamed: 'name' type: EdmType string; + addPropertyNamed: 'subclasses' type: (EdmArrayType of: class); + addPropertyNamed: 'format' type: EdmType integer; + addPropertyNamed: 'instanceVariables' + type: (EdmArrayType of: EdmType string). + mapping := JsonMapping fromType: class toClass: Class. + mapping + get: 'name' with: #symbol; + set: 'name' with: #name: adaptor: SymbolAdaptor new; + get: 'subclasses' with: #subclasses; + get: 'instanceVariables' with: #instVarNames; + set: 'instanceVariables' with: #instVarNames:; + get: 'format' with: #format. + mapping + locator: [:dict | (Smalltalk at: (dict at: 'name') asSymbol) objectShallowCopy]. + mappings register: mapping. + serializer := mapping serializer onlyKeysFromLevel: 2. + dictionary := serializer jsonObjectFrom: HashedCollection. + json := JsonWriter write: dictionary. + copy := serializer inflate: json using: mapping. + self + assert: copy !== HashedCollection; + assert: copy name = HashedCollection name; + assert: copy format = HashedCollection format; + assert: copy instVarNames = HashedCollection instVarNames. + copy subclasses with: HashedCollection subclasses do: [:c :d | + self + assert: c !== d; + assert: c name = d name; + assert: c format = d format; + assert: c instVarNames = d instVarNames. + c subclasses with: d subclasses do: [:e :f | self assert: e == f]] +] + +{ #category : #all } +JsonSerializationTest >> testInflate2 [ + | mappings class mapping serializer dictionary json copy | + mappings := JsonMappingRegistry new. + class := EdmEntityType new. + class + name: 'class'; + addKeyNamed: 'name' type: EdmType string; + addPropertyNamed: 'subclasses' type: (EdmArrayType of: class); + addPropertyNamed: 'format' type: EdmType integer; + addPropertyNamed: 'instanceVariables' + type: (EdmArrayType of: EdmType string). + mapping := JsonMapping fromType: class toClass: Class. + mapping + get: 'name' with: #symbol; + set: 'name' with: #name: adaptor: SymbolAdaptor new; + get: 'subclasses' with: #subclasses; + get: 'instanceVariables' with: #instVarNames; + set: 'instanceVariables' with: #instVarNames:; + get: 'format' with: #format. + mapping + locator: [:dict | (Smalltalk at: (dict at: 'name') asSymbol) objectShallowCopy]. + mappings register: mapping. + serializer := mapping serializer onlyKeysFromLevel: 3. + dictionary := serializer jsonObjectFrom: HashedCollection. + json := JsonWriter write: dictionary. + copy := serializer inflate: json using: mapping. + self + assert: copy !== HashedCollection; + assert: copy name = HashedCollection name; + assert: copy format = HashedCollection format; + assert: copy instVarNames = HashedCollection instVarNames. + copy subclasses with: HashedCollection subclasses do: [:c :d | + self + assert: c !== d; + assert: c name = d name; + assert: c format = d format; + assert: c instVarNames = d instVarNames. + c subclasses with: d subclasses do: [:e :f | self assert: e !== f]] +] + +{ #category : #all } +JsonSerializationTest >> testKeysFromLevel [ + | mappings t1 mapping t2 t3 object serializer dictionary | + mappings := JsonMappingRegistry new. + t1 := EdmEntityType new. + t1 + name: 't1'; + addKeyNamed: 'id' type: EdmType integer; + addPropertyNamed: 'name' type: EdmType string. + mapping := JsonMapping fromType: t1 toClass: Association. + mapping + get: 'id' with: #key; + get: 'name' with: #value. + mappings register: mapping. + t2 := EdmComplexType new. + t2 + name: 't2'; + addPropertyNamed: 't1' type: t1. + mapping := JsonMapping fromType: t2 toClass: Object. + mapping get: 't1' with: #t1. + mappings register: mapping. + t3 := EdmComplexType new. + t3 + name: 't3'; + addPropertyNamed: 't1' type: t1; + addPropertyNamed: 't2' type: t2. + mapping := JsonMapping fromType: t3 toClass: Window. + mapping + get: 't1' with: #t1; + get: 't2' with: #t2. + mappings register: mapping. + object := Window new. + object + answerTo: #t1 with: 1 -> 'William'; + answerTo: #t2 with: (Object new answerTo: #t1 with: 2 -> 'Juliette'). + serializer := JsonSerializer new. + serializer mappings: mappings; onlyKeysFromLevel: 2. + dictionary := serializer jsonObjectFrom: object. + self + assert: ((dictionary valueAt: 't1') keys equalsTo: #('id' 'name')); + assert: (((dictionary valueAt: 't2') valueAt: 't1') keys equalsTo: #('id')) +] + +{ #category : #all } +JsonSerializationTest >> testLevelCutting [ + | type mappings mapping object serializer dictionary | + type := EdmComplexType new. + type + name: 'type'; + addPropertyNamed: 'name' type: EdmType string; + addPropertyNamed: 'child' type: type. + mappings := JsonMappingRegistry new. + mapping := JsonMapping fromType: type toClass: Association. + mapping + get: 'name' with: #key; + get: 'child' with: #value. + mappings register: mapping. + object := 'a' -> ('b' -> ('c' -> 'd')). + serializer := mapping serializer upToLevel: 2. + dictionary := serializer jsonObjectFrom: object. + self + assert: (dictionary valueAt: 'name') = 'a'; + assert: (dictionary valueAt: 'child') notNil; + assert: ((dictionary valueAt: 'child') valueAt: 'name') = 'b'; + assert: ((dictionary valueAt: 'child') valueAt: 'child') isNil +] + +{ #category : #all } +JsonSerializationTest >> testLevelCutting2 [ + | type mappings mapping object serializer dictionary | + type := EdmEntityType new. + type + name: 'type'; + addKeyNamed: 'name' type: EdmType string; + addPropertyNamed: 'index' type: EdmType integer; + addPropertyNamed: 'child' type: type. + mappings := JsonMappingRegistry new. + mapping := JsonMapping fromType: type toClass: Association. + mapping + get: 'name' with: #key; + get: 'index' with: [:assoc | assoc value key]; + get: 'child' with: [:assoc | assoc value value]. + mappings register: mapping. + object := 'a' -> (1 -> ('b' -> (2 -> ('c' -> (3 -> 'd'))))). + serializer := mapping serializer upToLevel: 2. + dictionary := serializer jsonObjectFrom: object. + self + assert: (dictionary valueAt: 'name') = 'a'; + assert: (dictionary valueAt: 'index') = 1; + assert: ((dictionary valueAt: 'child') valueAt: 'name') = 'b'; + assert: ((dictionary valueAt: 'child') valueAt: 'index') = 2; + assert: (((dictionary valueAt: 'child') valueAt: 'child') valueAt: 'name') = 'c'; + assert: (((dictionary valueAt: 'child') valueAt: 'child') valueAt: 'index') isNil; + assert: (((dictionary valueAt: 'child') valueAt: 'child') valueAt: 'child') isNil +] + +{ #category : #all } +JsonSerializationTest >> testOnlyKeys [ + | mappings subtype mapping type object serializer dictionary | + mappings := JsonMappingRegistry new. + subtype := EdmEntityType new. + subtype + name: 'subtype'; + addKeyNamed: 'id' type: EdmType integer; + addPropertyNamed: 'name' type: EdmType string. + mapping := JsonMapping fromType: subtype toClass: Association. + mapping + get: 'id' with: #key; + get: 'name' with: #value. + mappings register: mapping. + type := EdmComplexType new. + type + name: 'type'; + addPropertyNamed: 'one' type: subtype; + addPropertyNamed: 'many' type: (EdmType arrayOf: subtype). + mapping := JsonMapping fromType: type toClass: Object. + mapping + get: 'one' with: #one; + get: 'many' with: #many. + mappings register: mapping. + object := Object new. + object + answerTo: #one with: 1 -> 'John'; + answerTo: #many + with: (Array with: 2 -> 'William' with: 3 -> 'Juliette'). + serializer := mapping serializer onlyKeys. + dictionary := serializer jsonObjectFrom: object. + self + assert: ((dictionary valueAt: 'one') keys equalsTo: #('id')); + assert: ((dictionary valueAt: 'many') + conform: [:d | d keys equalsTo: #('id')]) +] + diff --git a/modules/JSON/Tests/JsonWriterTest.st b/modules/JSON/Tests/JsonWriterTest.st new file mode 100644 index 0000000..0832856 --- /dev/null +++ b/modules/JSON/Tests/JsonWriterTest.st @@ -0,0 +1,56 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #JsonWriterTest, + #superclass : #TestCase, + #category : #'JSON\Tests' +} + +{ #category : #utf8 } +JsonWriterTest >> testEmbeddings [ + | text json read | + text := '\ b "Comment"'. + json := JsonWriter write: text. + read := JsonParser parse: json. + self assert: read = text. + text := '\ b + "Comment"'. + json := JsonWriter write: text. + read := JsonParser parse: json. + self assert: read = text +] + +{ #category : #all } +JsonWriterTest >> testSingleLine [ + | object | + object := JsonObject new at: 'property' put: 123; yourself. + self assert: (JsonWriter write: object) = '{"property": 123}' +] + +{ #category : #utf8 } +JsonWriterTest >> testTP47626 [ + | date json | + date := Date yyyymmdd: '2017-10-28'. + json := JsonWriter write: date. + self assert: json = date iso8601 doubleQuoted +] + +{ #category : #utf8 } +JsonWriterTest >> testTP56387 [ + | json | + json := JsonObject new Name: 'Trulalá'. + self assert: (json asString includesString: 'Trulalá') +] + +{ #category : #utf8 } +JsonWriterTest >> testUTF8 [ + | text json read | + text := 'a → 3 + 4'. + json := JsonWriter write: text. + read := JsonParser parse: json. + self assert: read = text +] + diff --git a/modules/JSON/TimestampAdaptor.st b/modules/JSON/TimestampAdaptor.st new file mode 100644 index 0000000..82aa867 --- /dev/null +++ b/modules/JSON/TimestampAdaptor.st @@ -0,0 +1,37 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #TimestampAdaptor, + #superclass : #DataAdaptor, + #instVars : [ + 'format' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +TimestampAdaptor class >> iso8601 [ + ^self new format: #iso8601] + +{ #category : #services } +TimestampAdaptor >> dataFrom: anObject [ + | ts | + ts := anObject isDate + ifTrue: [anObject utc] + ifFalse: [anObject asTimestamp]. + ^ts perform: format +] + +{ #category : #accessing } +TimestampAdaptor >> format: aSymbol [ + format := aSymbol +] + +{ #category : #services } +TimestampAdaptor >> objectFrom: aString [ + ^Timestamp perform: (format , ':') asSymbol with: aString +] + diff --git a/modules/JSON/TypeAdaptor.st b/modules/JSON/TypeAdaptor.st new file mode 100644 index 0000000..7d620d9 --- /dev/null +++ b/modules/JSON/TypeAdaptor.st @@ -0,0 +1,43 @@ +" + Copyright (c) 2020 Aucerna. + See (MIT) license in root directory. +" + +Class { + #name : #TypeAdaptor, + #superclass : #DataAdaptor, + #instVars : [ + 'classes' + ], + #category : #JSON +} + +{ #category : #'instance creation' } +TypeAdaptor class >> of: class [ + ^self new classes: {class}] + +{ #category : #'instance creation' } +TypeAdaptor class >> restrictedTo: classes [ + ^self new classes: classes +] + +{ #category : #accessing } +TypeAdaptor >> classes: aCollection [ + classes := aCollection asDictionaryUsing: #name +] + +{ #category : #services } +TypeAdaptor >> dataFrom: aClass [ + ^(classes includes: aClass) ifTrue: [aClass name] +] + +{ #category : #services } +TypeAdaptor >> objectClasses [ + ^classes +] + +{ #category : #services } +TypeAdaptor >> objectFrom: aString [ + ^classes at: aString ifAbsent: nil +] + diff --git a/modules/Kernel/Process.st b/modules/Kernel/Process.st index f9f7bc6..142fc70 100644 --- a/modules/Kernel/Process.st +++ b/modules/Kernel/Process.st @@ -61,6 +61,11 @@ Process >> evaluating: aMessageSend [ self notAllowed ] +{ #category : #accessing } +Process >> exceptionHandler [ + ^exceptionHandler +] + { #category : #initializing } Process >> initialize [ name := 'nameless process'. diff --git a/modules/Webside/Class.st b/modules/Webside/Class.st new file mode 100644 index 0000000..639bc8b --- /dev/null +++ b/modules/Webside/Class.st @@ -0,0 +1,31 @@ +" + Copyright (c) 2024, Javier Pimás. + See (MIT) license in root directory. +" + +Extension { #name : #Class } + +{ #category : #'*webside' } +Class >> fileOutOn: aStream [ + | symbol | + symbol := '#', self symbol printString. + aStream + nextPutAll: self superclass printString; + cr; + tab; + nextPutAll: self kindOfSubclass; + space; + nextPutAll: symbol; + cr; + tab. + self isBytes ifFalse: [ + aStream + nextPutAll: 'instanceVariableNames: '; + nextPutAll: self instanceVariableString storeString; + cr; + tab]. + aStream + nextPutAll: 'classVariableNames: '; + nextPutAll: self classVariableString storeString +] + diff --git a/modules/Webside/Metaclass.st b/modules/Webside/Metaclass.st new file mode 100644 index 0000000..7cd2148 --- /dev/null +++ b/modules/Webside/Metaclass.st @@ -0,0 +1,14 @@ +" + Copyright (c) 2024, Javier Pimás. + See (MIT) license in root directory. +" + +Extension { #name : #Metaclass } + +{ #category : #'*webside' } +Metaclass >> fileOutOn: aStream [ + aStream + nextPutAll: self name; + nextPutAll: ' instanceVariableNames: '; + nextPutAll: self instanceVariableString storeString +] diff --git a/modules/Webside/Object.st b/modules/Webside/Object.st new file mode 100644 index 0000000..a693a70 --- /dev/null +++ b/modules/Webside/Object.st @@ -0,0 +1,24 @@ +" + Copyright (c) 2024, Javier Pimás. + See (MIT) license in root directory. +" + +Extension { #name : #Object } + +{ #category : #'*webside' } +Object >> asWebsideJson [ + | printed | + printed := [self printString] + on: Error + do: ['Error while printing ' , self class name , ' instance']. + ^JsonObject new + at: 'class' put: self class name; + at: 'hasNamedSlots' put: self class instancesHavePointers; + at: 'hasIndexedSlots' put: self hasIndexedSlots; + at: 'size' + put: (self hasIndexedSlots ifTrue: [self size] ifFalse: [0]); + at: 'printString' put: printed; + yourself + +] + diff --git a/modules/Webside/Species.st b/modules/Webside/Species.st new file mode 100644 index 0000000..ad21670 --- /dev/null +++ b/modules/Webside/Species.st @@ -0,0 +1,24 @@ +" + Copyright (c) 2024, Javier Pimás. + See (MIT) license in root directory. +" + +Extension { #name : #Species } + +{ #category : #'*webside' } +Species >> asWebsideJson [ + ^super asWebsideJson + at: 'name' put: self name; + at: 'definition' put: self definitionString; + at: 'superclass' + put: (self superclass ifNotNil: [:c | c name]); + at: 'comment' put: self comment; + at: 'variable' put: self isVariable; + at: 'package' put: self module name; + yourself +] + +{ #category : #'*webside' } +Species >> definitionString [ + ^String streamContents: [:strm | self fileOutOn: strm] +] diff --git a/modules/Webside/WebsideAPI.st b/modules/Webside/WebsideAPI.st new file mode 100644 index 0000000..b0df205 --- /dev/null +++ b/modules/Webside/WebsideAPI.st @@ -0,0 +1,165 @@ +" + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +" + +Class { + #name : #WebsideAPI, + #superclass : #Object, + #instVars : [ + 'request', + 'response' + ], + #category : #Webside +} + +{#category : #routes} +WebsideAPI class >> addChangesRoutesTo: aWebApplication [ + aWebApplication + routeGET: '/changes' to: #changes; + routePOST: '/changes/update' to: #updateChanges; + routePOST: '/changes/compress' to: #compressChanges; + routePOST: '/changes' to: #addChange; + routePOST: '/changesets/download' + to: #downloadChangeset; + routePOST: '/changesets/upload' + to: #uploadChangeset; + routePOST: '/classes/{name}/methods' + to: #compileMethod; + routeDELETE: '/classes/{name}/methods/{selector}' + to: #removeMethod; + routePOST: '/classes' to: #defineClass; + routeDELETE: '/classes/{name}' to: #removeClass; + routePOST: '/packages' to: #addPackage; + routeDELETE: '/packages/{name}' to: #removePackage +] + +{#category : #routes} +WebsideAPI class >> addCodeRoutesTo: aWebApplication [ + aWebApplication + routeGET: '/packages' to: #packages; + routeGET: '/packages/{name}' to: #package; + routeGET: '/packages/{name}/classes' + to: #packageClasses; + routeGET: '/packages/{name}/methods' + to: #packageMethods; + routeGET: '/packages/{name}/unsent-methods' + to: #unsentMethods; + routeGET: '/packages/{name}/unimplemented-messages' + to: #unimplementedMessages; + routeGET: '/classes' to: #classes; + routeGET: '/classes/{name}' to: #classDefinition; + routeGET: '/classes/{name}/superclasses' + to: #superclasses; + routeGET: '/classes/{name}/subclasses' + to: #subclasses; + routeGET: '/classes/{name}/variables' + to: #variables; + routeGET: '/classes/{name}/instance-variables' + to: #instanceVariables; + routeGET: '/classes/{name}/class-variables' + to: #classVariables; + routeGET: '/classes/{name}/categories' + to: #categories; + routeGET: '/usual-categories' to: #usualCategories; + routeGET: '/classes/{name}/used-categories' + to: #usedCategories; + routeGET: '/classes/{name}/selectors' + to: #selectors; + routeGET: '/classes/{name}/methods' to: #methods; + routeGET: '/classes/{name}/methods/{selector}' + to: #method; + routeGET: '/methods' to: #methods; + routePOST: '/autocompletions' to: #autocompletions; + routeGET: '/search' to: #search; + routeGET: '/classes/{name}/methods/{selector}/history' + to: #methodHistory; + routeGET: '/classes/{name}/methods/{selector}/versions' + to: #methodVersions; + routeGET: '/classtemplate' to: #classTemplate; + routeGET: '/methodtemplate' to: #methodTemplate; + routePOST: '/selectors' to: #selectorInSource +] + +{#category : #routes} +WebsideAPI class >> addGeneralRoutesTo: aWebApplication [ + aWebApplication + routeGET: '/dialect' to: #dialect; + routeGET: '/logo' to: #logo; + routeGET: '/colors' to: #colors; + routePOST: '/save' to: #saveImage; + routeGET: '/themes' to: #themes +] + + + +{#category : #services} +WebsideAPI class >> addRoutesTo: aWebApplication [ + self + addGeneralRoutesTo: aWebApplication; + addCodeRoutesTo: aWebApplication; + addChangesRoutesTo: aWebApplication +] + +{ #category : #spec } +WebsideAPI >> classes [ + | root tree classes names depth json | + root := self queryAt: 'root'. + root := root notNil + ifTrue: [self classNamed: root] + ifFalse: [self defaultRootClass]. + root ifNil: [^self notFound]. + tree := self queryAt: 'tree'. + tree = 'true' ifTrue: [ + depth := self queryAt: 'depth' ifPresent: [:d | d asInteger]. + json := self classTreeFrom: root depth: depth. + ^Array with: json]. + classes := root withAllSubclasses. + names := self queryAt: 'names'. + names = 'true' ifTrue: [^(classes collect: [:c | c name]) sort]. + ^classes collect: [:c | c asWebsideJson] +] + +{ #category : #spec } +WebsideAPI >> classNamed: aString [ + | name metaclass class | + name := aString. + metaclass := name endsWith: ' class'. + metaclass ifTrue: [name := name trimTail: ' class']. + class := Kernel at: name asSymbol ifAbsent: [^nil]. + class isSpecies ifFalse: [^nil]. + ^metaclass ifTrue: [class class] ifFalse: [class] +] + +{ #category : #spec } +WebsideAPI >> defaultRootClass [ + ^ProtoObject +] + +{ #category : #spec } +WebsideAPI >> queryAt: aString [ + ^self queryAt: aString ifAbsent: nil +] + +{ #category : #spec } +WebsideAPI >> queryAt: aString ifAbsent: aBlock [ + ^self queryAt: aString ifPresent: nil ifAbsent: aBlock +] + +{ #category : #spec } +WebsideAPI >> queryAt: aString ifPresent: aBlock ifAbsent: anotherBlock [ + | value | + value := request paramAt: aString. + (value isNil or: [value isEmpty]) ifTrue: [^anotherBlock value]. + ^aBlock notNil ifTrue: [aBlock evaluateWith: value] ifFalse: [value]] + +{ #category : #spec } +WebsideAPI >> request: aRequest [ + request := aRequest +] + +{ #category : #spec } +WebsideAPI >> response: aResponse [ + response := aResponse +] + diff --git a/modules/Webside/WebsideApplication.st b/modules/Webside/WebsideApplication.st new file mode 100644 index 0000000..b9e9ae8 --- /dev/null +++ b/modules/Webside/WebsideApplication.st @@ -0,0 +1,83 @@ +" + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +" + +Class { + #name : #WebsideApplication, + #superclass : #Object, + #instVars : [ + 'name', + 'baseUri', + 'router', + 'api' + ], + #category : #Webside +} + +{#category : #accessing} +WebsideApplication class >> apis [ + ^{WebsideAPI} +] + +{#category : #configuration} +WebsideApplication class >> appName [ + ^'Webside' +] + +{#category : #configuration} +WebsideApplication class >> defaultBaseUri [ + ^'/egg' +] + +{#category : #configuration} +WebsideApplication class >> defaultFrontendUrl [ + ^'http://localhost:3000' +] + +{#category : #configuration} +WebsideApplication class >> defaultPort [ + ^9000 +] + +{#category : #configuration} +WebsideApplication class >> frontendUrl [ + ^self defaultFrontendUrl +] + +{#category : #configuration} +WebsideApplication class >> on: anHttpServer [ + ^self new configure: anHttpServer +] + +{#category : #initialization} +WebsideApplication >> configure: anHttpServer [ + router := anHttpServer. + baseUri := '/egg'. + WebsideAPI addRoutesTo: self +] + +{#category : #initialization} +WebsideApplication >> handleGET: request into: response with: anEvaluable [ + | api result | + api := WebsideAPI new + request: request; + response: response. + result := anEvaluable evaluateWith: api. + response setContents: result printString type: 'text/html' +] + +{#category : #initialization} +WebsideApplication >> routeDELETE: uri to: anEvaluable [ + "router routeDELETE: uri to: anEvaluable with: self" +] + +{#category : #initialization} +WebsideApplication >> routeGET: uri to: anEvaluable [ + router routeGET: baseUri, uri to: [:req :res | self handleGET: req into: res with: anEvaluable ] +] + +{#category : #initialization} +WebsideApplication >> routePOST: uri to: anEvaluable [ + "router routePOST: uri to: anEvaluable with: self" +] diff --git a/modules/Webside/WebsideModule.st b/modules/Webside/WebsideModule.st new file mode 100644 index 0000000..b528839 --- /dev/null +++ b/modules/Webside/WebsideModule.st @@ -0,0 +1,34 @@ +" + Copyright (c) 2025, Javier Pimás. + See (MIT) license in root directory. +" + +Class { + #name : #WebsideModule, + #superclass : #Module, + #category : #Webside +} + +{ #category : #spec } +WebsideModule >> imports [ + ^{ + #FFI -> #(ExternalLibrary). + #'HTTP.CPPHTTPServer' -> #(HTTPServer). + #Json -> #(JsonObject). + #Kernel -> #(Error OrderedDictionary ProtoObject ReadStream Species) + } +] + +{ #category : #initializing } +WebsideModule >> main: arguments [ + | server webside | + Kernel log: 'configuring server, initializing!', String cr. + + ExternalLibrary module initializeForCurrentPlatform. + Kernel log: 'server configured, starting!', String cr. + server := HTTPServer new. + webside := WebsideApplication on: server. + + server start +] + diff --git a/runtime/cpp/Allocator/GCGuard.h b/runtime/cpp/Allocator/GCGuard.h new file mode 100644 index 0000000..dc04a3e --- /dev/null +++ b/runtime/cpp/Allocator/GCGuard.h @@ -0,0 +1,37 @@ + +#ifndef _GCGUARD_H_ +#define _GCGUARD_H_ + +namespace Egg { + +/** + * My instances, on construction, mantain a bool var to allow/disallow the heap to be + * collected (i.e. to move objects). + * When destructed, we restore the previous state. + * + * Creating an instance does not automatically cause GC. Users should be aware of potentially + * GCing calls (basically, calls to methods that potentially allocate objects). At that point, + * all on-the-fly references to heap objects (raw pointers not known by GC) must be wrapped by + * GCedRefs or dead. + */ + +class GCGuard { + bool &_var; + bool _prev; + + public: + GCGuard(bool &var, bool newValue) : _var(var), _prev(var) { + _var = newValue; + } + + ~GCGuard() { + _var = _prev; + } + + GCGuard(const GCGuard&) = delete; + GCGuard& operator=(const GCGuard&) = delete; +}; + +} + +#endif // ~ _GCGUARD_H_ diff --git a/runtime/cpp/Allocator/GCHeap.cpp b/runtime/cpp/Allocator/GCHeap.cpp index 6a8ffe5..03caaa0 100644 --- a/runtime/cpp/Allocator/GCHeap.cpp +++ b/runtime/cpp/Allocator/GCHeap.cpp @@ -11,9 +11,12 @@ using namespace Egg; -GCHeap::GCHeap(Runtime *runtime) : _runtime(runtime), _gcNeeded(false) +GCHeap::GCHeap(Runtime *runtime) : + _runtime(runtime), + _gcNeeded(false), + _atGCSafepoint(false), // defaults to false, only particular points enable GC + _atGCUnsafepoint(false) // defaults to false, only particular points disable GC { - _atGCSafepoint = false; // defaults to false, only particular points enable GC _eden = this->addNewSpaceSized_(16*MB); _eden->_name = "Eden"; @@ -83,7 +86,7 @@ uintptr_t GCHeap::allocate_(uint32_t size) { if (size > LargeThreshold) return this->allocateLarge_(size); - if (this->isAtGCSafepoint() && !_runtime->_evaluator->isInCallback()) + if (this->isGCAllowed()) this->collectIfTime(); else requestGC(); @@ -95,7 +98,7 @@ uintptr_t GCHeap::allocate_(uint32_t size) { uintptr_t GCHeap::allocateLarge_(uint32_t size) { auto space = this->addNewSpaceSized_(size); - return space->allocateIfPossible_(size); + return space->allocateCommittingIfNeeded_(size); } @@ -179,10 +182,6 @@ HeapObject* GCHeap::allocateBytes_(uint32_t size) } */ -bool GCHeap::isAtGCSafepoint() -{ - return _atGCSafepoint; -} void GCHeap::collectIfTime() { diff --git a/runtime/cpp/Allocator/GCHeap.h b/runtime/cpp/Allocator/GCHeap.h index e15f281..414f280 100644 --- a/runtime/cpp/Allocator/GCHeap.h +++ b/runtime/cpp/Allocator/GCHeap.h @@ -4,7 +4,7 @@ #include #include -#include "Memory.h" +#include "GCGuard.h" #include "Egg.h" namespace Egg { @@ -22,6 +22,7 @@ class GCHeap { AllocationZone *_oldZone; G1GC *_fullGC; + bool _atGCUnsafepoint; // when true, moving objects is explicitly forbiden bool _atGCSafepoint; // when true, it is allowed to start GC (specially, to move objects) bool _gcNeeded; // set when fast-path allocation fails, will be done later at GC safepoints @@ -52,8 +53,14 @@ class GCHeap { uintptr_t allocateLarge_(uint32_t size); uintptr_t allocateCommitting_(uint32_t size); - bool isAtGCSafepoint(); - void beAtGCSafepoint(bool newState) { _atGCSafepoint = newState; } + bool isGCAllowed() { return _atGCSafepoint && !_atGCUnsafepoint; } + bool isAtGCSafepoint() { return _atGCSafepoint; } + bool isAtGCUnsafepoint() { return _atGCUnsafepoint; } + + GCGuard atGCSafepoint() { return GCGuard(_atGCSafepoint, true); } + GCGuard atGCUnsafepoint() { return GCGuard(_atGCUnsafepoint, true); } + + void requestGC() { _gcNeeded = true; } void finishedGC() { _gcNeeded = false; } diff --git a/runtime/cpp/Allocator/GCSafepoint.h b/runtime/cpp/Allocator/GCSafepoint.h deleted file mode 100644 index 213654c..0000000 --- a/runtime/cpp/Allocator/GCSafepoint.h +++ /dev/null @@ -1,36 +0,0 @@ - -#ifndef _GCSAFEPOINT_H_ -#define _GCSAFEPOINT_H_ - -#include "GCHeap.h" - -namespace Egg { - -/** - * My instances, on construction, allow the heap to be collected (i.e. to move objects). - * When destructed, we restore the previous state. - * - * Creating an instance does not automatically cause GC. Users should be aware of potentially - * GCing calls (basically, calls to methods that potentially allocate objects). At that point, - * all on-the-fly references to heap objects (raw pointers not known by GC) must be wrapped by - * GCedRefs or dead. - */ - -class GCSafepoint { - GCHeap* _heap; - bool _prevState; - - public: - GCSafepoint(GCHeap *heap) : _heap(heap) { - _prevState = _heap->isAtGCSafepoint(); - _heap->beAtGCSafepoint(true); - } - - ~GCSafepoint() { - _heap->beAtGCSafepoint(_prevState); - } -}; - -} - -#endif // ~ _GCSAFEPOINT_H_ diff --git a/runtime/cpp/Allocator/GarbageCollector.cpp b/runtime/cpp/Allocator/GarbageCollector.cpp index 1983462..54f16e9 100644 --- a/runtime/cpp/Allocator/GarbageCollector.cpp +++ b/runtime/cpp/Allocator/GarbageCollector.cpp @@ -217,11 +217,24 @@ void GarbageCollector::scanFirstStackChunk_(HeapObject *aProcessVMStack) { }); } +void GarbageCollector::scanPointer_(Object** pointer) +{ + this->scan_from_to_((HeapObject*)pointer, 1, 1); +} + /* only for use until we have context switches */ void GarbageCollector::scanCurrentContext() { - auto firstSP = _runtime->_evaluator->context()->stackPointer(); - auto firstBP = _runtime->_evaluator->context()->framePointer(); - auto stack = (uintptr_t**)_runtime->_evaluator->context()->stack(); + auto evaluator = _runtime->_evaluator; + auto context = evaluator->context(); + this->scanPointer_(&evaluator->_regR); + this->scanPointer_(&context->_regS); + this->scanPointer_((Object**)&context->_regE); + this->scanPointer_((Object**)&context->_regM); + + + auto firstSP = context->stackPointer(); + auto firstBP = context->framePointer(); + auto stack = (uintptr_t**)context->stack(); this->nativeFramesStartingAt_bp_do_(stack, firstSP, firstBP, [this](uintptr_t *frame, uintptr_t size) { this->scanNativeStackFrame_sized_(frame, size); diff --git a/runtime/cpp/Allocator/GarbageCollector.h b/runtime/cpp/Allocator/GarbageCollector.h index 747b7d4..d911feb 100644 --- a/runtime/cpp/Allocator/GarbageCollector.h +++ b/runtime/cpp/Allocator/GarbageCollector.h @@ -57,6 +57,7 @@ class GarbageCollector { void nativeFramesStartingAt_bp_do_(uintptr_t **stack, uintptr_t sp, uintptr_t bp, std::function block); void scanFirstStackChunk_(HeapObject * aProcessVMStack); + void scanPointer_(Object **pointer); void scanCurrentContext(); void scanStack_(HeapObject *aProcessVMStack); void scanStacks(); diff --git a/runtime/cpp/Bootstrapper.h b/runtime/cpp/Bootstrapper.h index e60fce6..44e96bf 100644 --- a/runtime/cpp/Bootstrapper.h +++ b/runtime/cpp/Bootstrapper.h @@ -142,7 +142,7 @@ class Bootstrapper { if (linker == "asSymbol") { auto symbol = _runtime->symbolTableAt_(tokens[0]); if (symbol == nullptr) { - symbol = _runtime->newString_(tokens[0]); + symbol = (Object*)_runtime->newString_(tokens[0]); _runtime->addKnownSymbol_(tokens[0], symbol); } return (Object*)symbol; diff --git a/runtime/cpp/Evaluator/EvaluationContext.cpp b/runtime/cpp/Evaluator/EvaluationContext.cpp index ec7cd53..bfbb33e 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.cpp +++ b/runtime/cpp/Evaluator/EvaluationContext.cpp @@ -66,7 +66,7 @@ std::vector EvaluationContext::methodArguments() { return arguments; } -PlatformCode* EvaluationContext::buildLaunchFrame(HeapObject *symbol, int argCount) +PlatformCode* EvaluationContext::buildLaunchFrame(Object *symbol, int argCount) { auto launcher = _runtime->newCompiledMethod(); auto bytecodes = newPlatformCode(); @@ -76,7 +76,7 @@ PlatformCode* EvaluationContext::buildLaunchFrame(HeapObject *symbol, int argCou auto literal = new SLiteral(0, (Object*)_runtime->_nilObj); std::vector dummy(argCount, literal); - auto message = new SMessage(literal, symbol, dummy, false); + auto message = new SMessage(literal, (Object*)symbol, dummy, false); auto dispatch = new SOpDispatchMessage(message); bytecodes->push_back(dispatch); @@ -222,7 +222,7 @@ void EvaluationContext::unwind() } -SBinding* EvaluationContext::staticBindingFor_(HeapObject *symbol) +SBinding* EvaluationContext::staticBindingFor_(Object *symbol) { auto b = this->staticBindingForIvar_(symbol); if (b != nullptr) @@ -233,15 +233,15 @@ SBinding* EvaluationContext::staticBindingFor_(HeapObject *symbol) return this->staticBindingForMvar_(symbol); } -SBinding* EvaluationContext::staticBindingFor_inModule_(HeapObject *symbol, HeapObject *module) { +SBinding* EvaluationContext::staticBindingFor_inModule_(Object *symbol, HeapObject *module) { auto namespace_ = this->_runtime->moduleNamespace_(module); auto assoc = this->_runtime->lookupAssociationFor_in_(symbol, namespace_); if (assoc == nullptr) - error_(("unbound variable: " + symbol->asLocalString())); + error_(("unbound variable: " + symbol->printString())); return new SAssociationBinding(assoc); } -SBinding* EvaluationContext::staticBindingForCvar_in_(HeapObject *aSymbol, HeapObject *species) { +SBinding* EvaluationContext::staticBindingForCvar_in_(Object *aSymbol, HeapObject *species) { auto nilObj = this->_runtime->_nilObj; do { auto namespaces = this->_runtime->speciesNamespaces_(species); @@ -259,12 +259,12 @@ SBinding* EvaluationContext::staticBindingForCvar_in_(HeapObject *aSymbol, HeapO return nullptr; } -SBinding* EvaluationContext::staticBindingForCvar_(HeapObject *aSymbol) { +SBinding* EvaluationContext::staticBindingForCvar_(Object *aSymbol) { auto species = this->_runtime->methodClassBinding_(this->method()); return staticBindingForCvar_in_(aSymbol, species); } -uint16_t EvaluationContext::ivarIndex_in_(HeapObject *symbol, Object *receiver) { +uint16_t EvaluationContext::ivarIndex_in_(Object *symbol, Object *receiver) { auto species = this->_runtime->speciesOf_(receiver); while (species != this->_runtime->_nilObj) @@ -274,7 +274,7 @@ uint16_t EvaluationContext::ivarIndex_in_(HeapObject *symbol, Object *receiver) if (slots != this->_runtime->_nilObj) { for (int i = 1; i <= slots->size(); i++){ auto ivar = slots->slotAt_(i)->asHeapObject(); - if (ivar->sameBytesThan(symbol)) + if (ivar->sameBytesThan(symbol->asHeapObject())) { auto superspecies = this->_runtime->speciesSuperclass_(species); auto size = (superspecies != this->_runtime->_nilObj) ? this->_runtime->speciesInstanceSize_(superspecies) : 0; @@ -289,20 +289,20 @@ uint16_t EvaluationContext::ivarIndex_in_(HeapObject *symbol, Object *receiver) } -SBinding* EvaluationContext::staticBindingForIvar_(HeapObject *aSymbol) { +SBinding* EvaluationContext::staticBindingForIvar_(Object *aSymbol) { auto ivar = this->ivarIndex_in_(aSymbol, this->_regS); return ivar > 0 ? new SInstVarBinding(ivar) : nullptr; } -SBinding* EvaluationContext::staticBindingForMvar_(HeapObject *symbol) { +SBinding* EvaluationContext::staticBindingForMvar_(Object *symbol) { auto module_ = this->_runtime->methodModule_(this->method()); return this->staticBindingFor_inModule_(symbol, module_); } -SBinding* EvaluationContext::staticBindingForNested_(HeapObject *name) { - auto binding = this->staticBindingFor_(name->slotAt_(1)->asHeapObject()); +SBinding* EvaluationContext::staticBindingForNested_(Object *name) { + auto binding = this->staticBindingFor_(name->asHeapObject()->slotAt_(1)); auto module_ = binding->valueWithin_(this); - return this->staticBindingFor_inModule_(name->slotAt_(2)->asHeapObject(), module_->asHeapObject()); + return this->staticBindingFor_inModule_(name->asHeapObject()->slotAt_(2), module_->asHeapObject()); } HeapObject * EvaluationContext::codeOfFrameAt_(uintptr_t frame) { diff --git a/runtime/cpp/Evaluator/EvaluationContext.h b/runtime/cpp/Evaluator/EvaluationContext.h index 22adbe9..d1a7c81 100644 --- a/runtime/cpp/Evaluator/EvaluationContext.h +++ b/runtime/cpp/Evaluator/EvaluationContext.h @@ -32,6 +32,8 @@ class EvaluationContext { const int FRAME_TO_FIRST_TEMP_DELTA = 5; const int FRAME_TO_FIRST_ARG_DELTA = 2; + friend class GarbageCollector; + public: const int STACK_SIZE = 64 * 1024; EvaluationContext(Runtime *runtime) : @@ -95,7 +97,7 @@ class EvaluationContext { std::vector methodArguments(); void buildFrameFor_code_environment_temps_(Object *receiver, HeapObject *compiledCode, HeapObject *environment, uint32_t temps); - PlatformCode* buildLaunchFrame(HeapObject *symbol, int argCount); + PlatformCode* buildLaunchFrame(Object *symbol, int argCount); void buildClosureFrameFor_code_environment_(Object *receiver, HeapObject *compiledCode, HeapObject *environment); void buildMethodFrameFor_code_environment_(Object *receiver, HeapObject *compiledCode, HeapObject *environment); void popLaunchFrame(HeapObject *prevRegE); @@ -244,15 +246,15 @@ class EvaluationContext { HeapObject* captureClosure_(SBlock *anSBlock); - uint16_t ivarIndex_in_(HeapObject *symbol, Object *receiver); + uint16_t ivarIndex_in_(Object *symbol, Object *receiver); - SBinding* staticBindingFor_(HeapObject *aSymbol); - SBinding* staticBindingFor_inModule_(HeapObject *symbol, HeapObject *module); - SBinding* staticBindingForCvar_(HeapObject *aSymbol); - SBinding* staticBindingForCvar_in_(HeapObject *aSymbol, HeapObject *species); - SBinding* staticBindingForIvar_(HeapObject *aSymbol); - SBinding* staticBindingForMvar_(HeapObject *symbol); - SBinding* staticBindingForNested_(HeapObject *name); + SBinding* staticBindingFor_(Object *aSymbol); + SBinding* staticBindingFor_inModule_(Object *symbol, HeapObject *module); + SBinding* staticBindingForCvar_(Object *aSymbol); + SBinding* staticBindingForCvar_in_(Object *aSymbol, HeapObject *species); + SBinding* staticBindingForIvar_(Object *aSymbol); + SBinding* staticBindingForMvar_(Object *symbol); + SBinding* staticBindingForNested_(Object *name); HeapObject* nil() { return KnownObjects::nil; } HeapObject* _true() { return KnownObjects::_true; } diff --git a/runtime/cpp/Evaluator/Evaluator.cpp b/runtime/cpp/Evaluator/Evaluator.cpp index f1ae28d..37653c2 100644 --- a/runtime/cpp/Evaluator/Evaluator.cpp +++ b/runtime/cpp/Evaluator/Evaluator.cpp @@ -1,7 +1,7 @@ #include "Evaluator.h" #include "Runtime.h" -#include "Allocator/GCSafepoint.h" +#include "Allocator/GCHeap.h" #include "SExpressionLinearizer.h" #include "SOpAssign.h" #include "SOpDispatchMessage.h" @@ -40,15 +40,14 @@ Evaluator::Evaluator(Runtime *runtime, HeapObject *falseObj, HeapObject *trueObj _runtime(runtime), _nilObj(nilObj), _trueObj(trueObj), - _falseObj(falseObj), - _inCallback(false) + _falseObj(falseObj) { + debugRuntime = _runtime; _linearizer = new SExpressionLinearizer(); _linearizer->runtime_(_runtime); _context = new EvaluationContext(runtime); this->initializeUndermessages(); this->initializePrimitives(); - debugRuntime = _runtime; } @@ -59,12 +58,12 @@ void Evaluator::_halt() void Evaluator::addPrimitive(const std::string &name, Evaluator::PrimitivePointer primitive) { - HeapObject *symbol = _runtime->existingSymbolFrom_(name); + Object *symbol = _runtime->existingSymbolFrom_(name); _primitives[symbol] = primitive; } void Evaluator::addUndermessage(const std::string &name, UndermessagePointer primitive) { - HeapObject *symbol = _runtime->existingSymbolFrom_(name); + Object *symbol = _runtime->existingSymbolFrom_(name); _undermessages[symbol] = primitive; } @@ -165,42 +164,45 @@ void Evaluator::initializePrimitives() this->addPrimitive("HostCurrentMilliseconds", &Evaluator::primitiveHostCurrentMilliseconds); this->addPrimitive("HostLog", &Evaluator::primitiveHostLog); - /*this->addPrimitive("PrepareForExecution", &Evaluator::primitivePrepareForExecution); + /* + this->addPrimitive("PrepareForExecution", &Evaluator::primitivePrepareForExecution); this->addPrimitive("ProcessVMStackInitialize", &Evaluator::primitiveProcessVMStackInitialize); this->addPrimitive("ProcessVMStackAt", &Evaluator::primitiveProcessVMStackAt); this->addPrimitive("ProcessVMStackAtPut", &Evaluator::primitiveProcessVMStackAtPut); this->addPrimitive("ProcessVMStackBpAtPut", &Evaluator::primitiveProcessVMStackBpAtPut); this->addPrimitive("ProcessVMStackPcAtPut", &Evaluator::primitiveProcessVMStackPcAtPut); - this->addPrimitive("ProcessVMStackBP", &Evaluator::primitiveProcessVMStackBP); + */ + this->addPrimitive("ProcessVMStackBP", &Evaluator::primitiveProcessBP); + /* this->addPrimitive("ProcessVMStackBufferSize", &Evaluator::primitiveProcessVMStackBufferSize); this->addPrimitive("ProcessVMStackContextSwitchTo", &Evaluator::primitiveProcessVMStackContextSwitchTo); */ _linearizer->primitives_(_primitives); } -void Evaluator::evaluatePerform_in_withArgs_(HeapObject *aSymbol, Object *receiver, Object *arguments) { +void Evaluator::evaluatePerform_in_withArgs_(Object *aSymbol, Object *receiver, Object *arguments) { HeapObject *behavior = this->_runtime->behaviorOf_(receiver); if (aSymbol->printString() == "#asBehavior") { int a = 0; } - HeapObject *method = this->_runtime->lookup_startingAt_(aSymbol, behavior); + Object *method = this->_runtime->lookup_startingAt_(aSymbol, behavior); if (!method) error_(std::string("cannot perform ") + aSymbol->printString() + " on " + receiver->printString()); auto heapargs = arguments->asHeapObject(); for (int i = 1; i <= heapargs->size(); i++) { this->_context->pushOperand_(heapargs->slotAt_(i)); } - this->invoke_with_(method, receiver); + this->invoke_with_(method->asHeapObject(), receiver); } -HeapObject* -Evaluator::lookup_startingAt_sendSite_(HeapObject *symbol, HeapObject *behavior, SAbstractMessage *message) +Object* +Evaluator::lookup_startingAt_sendSite_(Object *symbol, HeapObject *behavior, SAbstractMessage *message) { auto method = _runtime->lookup_startingAt_(symbol, behavior); if (!method) return nullptr; message->registerCacheWith_(_runtime); - message->cache_when_(method, behavior); + message->cache_when_((Object*)method, (Object*)behavior); return method; } @@ -254,7 +256,7 @@ void Egg::Evaluator::evaluateUndermessage_with_(SAbstractMessage * message, Unde _context->reserveStackSlots_(argcount); } -Object* Evaluator::send_to_with_(HeapObject *symbol, Object *receiver, std::vector &args) { +Object* Evaluator::send_to_with_(Object *symbol, Object *receiver, std::vector &args) { auto bytecodes = this->_context->buildLaunchFrame(symbol, args.size()); auto prevRegE = this->_context->environment(); this->_regR = receiver; @@ -282,7 +284,7 @@ void Egg::Evaluator::messageNotUnderstood_(SAbstractMessage *message) error_(errmsg); } -void Evaluator::doesNotKnow(HeapObject *symbol) { ASSERT(false); } +void Evaluator::doesNotKnow(const Object *symbol) { ASSERT(false); } void Evaluator::visitIdentifier(SIdentifier *identifier) { @@ -332,10 +334,10 @@ void Evaluator::visitOpDispatchMessage(SOpDispatchMessage *anSOpDispatchMessage) _runtime->superBehaviorOf_(_context->classBinding()) : _runtime->behaviorOf_(_regR); - auto method = message->methodFor_(behavior); + auto method = message->methodFor_((Object*)behavior); if (method) { - this->invoke_with_(method, _regR); + this->invoke_with_(method->asHeapObject(), _regR); return; } @@ -354,7 +356,7 @@ void Evaluator::visitOpDispatchMessage(SOpDispatchMessage *anSOpDispatchMessage) if (!method) return messageNotUnderstood_(message); - this->invoke_with_(method, _regR); + this->invoke_with_(method->asHeapObject(), _regR); } void Evaluator::visitOpDropToS(SOpDropToS *anSOpDropToS) { @@ -427,7 +429,7 @@ void Evaluator::visitOpReturn(SOpReturn *anSOpReturn) { this->popFrameAndPrepare(); - if (!_inCallback) + if (!_runtime->_heap->isAtGCUnsafepoint()) _runtime->_heap->collectIfTime(); } @@ -597,10 +599,8 @@ void Evaluator::evaluateCallback_(void *ret, HeapObject *closure, int argc, void this->_context->regPC_(_work->size()); _context->buildClosureFrameFor_code_environment_(receiver, block, closure); } - auto prev = _inCallback; - _inCallback = true; + auto guard = _runtime->_heap->atGCUnsafepoint(); this->evaluate(); - _inCallback = prev; this->_context->regPC_(prevPC); for (size_t i = 0; i < argc; ++i) { this->_context->pop(); @@ -667,7 +667,7 @@ Object* Evaluator::primitiveFloatNew() { } Object* Evaluator::primitiveFlushDispatchCaches() { - this->_runtime->flushDispatchCache_in_(this->_context->self()->asHeapObject(), this->_context->firstArgument()->asHeapObject()); + this->_runtime->flushDispatchCache_in_(this->_context->self(), this->_context->firstArgument()->asHeapObject()); return this->_context->self(); } @@ -676,7 +676,7 @@ Object* Evaluator::primitiveFlushFromCaches() { return this->_context->self(); } -Evaluator::PrimitivePointer Evaluator::primitiveFor_(HeapObject *aSymbol) { +Evaluator::PrimitivePointer Evaluator::primitiveFor_(Object *aSymbol) { return this->_primitives[aSymbol]; } @@ -728,6 +728,7 @@ Object* Evaluator::primitiveHostInitializeFFI() { return (Object*)this->_context->self(); } Object* Evaluator::primitiveHostLoadModule() { + auto guard = this->_runtime->_heap->atGCUnsafepoint(); auto name = this->_context->firstArgument()->asHeapObject()->asLocalString(); std::cout << "loading " << name << "..." << std::endl; auto module = (Object*)this->_runtime->loadModule_(this->_context->firstArgument()->asHeapObject()); @@ -736,30 +737,35 @@ Object* Evaluator::primitiveHostLoadModule() { } Object* Evaluator::primitiveNew() { - GCSafepoint safepoint(this->_runtime->_heap); + auto guard = this->_runtime->_heap->atGCSafepoint(); return (Object*)this->_runtime->newSlotsOf_(this->_context->self()->asHeapObject()); } Object* Evaluator::primitiveNewBytes() { - GCSafepoint safepoint(this->_runtime->_heap); + auto guard = this->_runtime->_heap->atGCSafepoint(); auto size = this->_context->firstArgument()->asSmallInteger()->asNative(); return (Object*)this->_runtime->newBytes_size_(this->_context->self()->asHeapObject(), size); } Object* Evaluator::primitiveNewSized() { - GCSafepoint safepoint(this->_runtime->_heap); + auto guard = this->_runtime->_heap->atGCSafepoint(); auto size = this->_context->firstArgument()->asSmallInteger()->asNative(); return (Object*)this->_runtime->newOf_sized_(this->_context->self()->asHeapObject(), size); } Object* Evaluator::primitivePerformWithArguments() { this->evaluatePerform_in_withArgs_( - this->_context->firstArgument()->asHeapObject(), + this->_context->firstArgument(), this->_context->self(), this->_context->secondArgument()); return this->_context->self(); } +Object* Evaluator::primitiveProcessBP() +{ + return (Object*)this->_runtime->newInteger_(this->_context->framePointer()); +} + Object* Evaluator::primitivePrimeFor() { return this->primitivePrimeFor_(this->_context->firstArgument()->asSmallInteger()->asNative()); } diff --git a/runtime/cpp/Evaluator/Evaluator.h b/runtime/cpp/Evaluator/Evaluator.h index 28bdccf..22964bb 100644 --- a/runtime/cpp/Evaluator/Evaluator.h +++ b/runtime/cpp/Evaluator/Evaluator.h @@ -39,16 +39,15 @@ class Evaluator : public SExpressionVisitor { Object *_regR; std::vector *_work; - - bool _inCallback; + friend class GarbageCollector; public: using PrimitivePointer = Object* (Evaluator::*)(); using UndermessagePointer = Object* (Evaluator::*)(Object *, std::vector &args); private: - std::map _primitives; - std::map _undermessages; + std::map _primitives; + std::map _undermessages; public: Evaluator(Runtime *runtime, HeapObject *falseObj, HeapObject *trueObj, HeapObject *nilObj); @@ -123,7 +122,7 @@ class Evaluator : public SExpressionVisitor { return _regR; } - void evaluatePerform_in_withArgs_(HeapObject *aSymbol, Object *receiver, Object *arguments); + void evaluatePerform_in_withArgs_(Object *aSymbol, Object *receiver, Object *arguments); SmallInteger* evaluatePrimitiveHash_(HeapObject *receiver); void evaluateCallback_(void *ret, HeapObject *self, int argc, void *args[]); @@ -134,17 +133,15 @@ class Evaluator : public SExpressionVisitor { return this->_falseObj; } - bool isInCallback() { return _inCallback; } - - HeapObject* lookup_startingAt_sendSite_(HeapObject* symbol, HeapObject *behavior, SAbstractMessage *message); + Object* lookup_startingAt_sendSite_(Object* symbol, HeapObject *behavior, SAbstractMessage *message); Object* invoke_with_(HeapObject* method, Object *receiver); HeapObject* prepareForExecution_(HeapObject *method); - Object* send_to_with_(HeapObject *symbol, Object *receiver, std::vector &args); + Object* send_to_with_(Object *symbol, Object *receiver, std::vector &args); void messageNotUnderstood_(SAbstractMessage *message); - void doesNotKnow(HeapObject *symbol); + void doesNotKnow(const Object *symbol); void visitIdentifier(SIdentifier* identifier) override; void visitLiteral(SLiteral* sLiteral) override; void visitBlock(SBlock* sBlock) override; @@ -178,7 +175,7 @@ class Evaluator : public SExpressionVisitor { void addPrimitive(const std::string &name, PrimitivePointer primitive); void addUndermessage(const std::string &name, UndermessagePointer primitive); - PrimitivePointer primitiveFor_(HeapObject *symbol); + PrimitivePointer primitiveFor_(Object *symbol); Object* newDoubleObject(double aDouble); Object* newIntObject(auto anInteger); @@ -217,6 +214,7 @@ class Evaluator : public SExpressionVisitor { Object* primitiveNewObjectHeap(); Object* primitiveNewSized(); Object* primitivePerformWithArguments(); + Object* primitiveProcessBP(); Object* primitivePrimeFor(); Object* primitivePrimeFor_(auto anInteger); Object* primitiveSMIBitAnd(); diff --git a/runtime/cpp/Evaluator/Runtime.cpp b/runtime/cpp/Evaluator/Runtime.cpp index 4c8f1e4..f134799 100644 --- a/runtime/cpp/Evaluator/Runtime.cpp +++ b/runtime/cpp/Evaluator/Runtime.cpp @@ -204,7 +204,7 @@ std::string Runtime::printGlobalCache() { void Runtime::checkCache() { for (const auto& entry : _globalCache) { auto symbol = entry.first.first->get(); - auto methodSelector = debugRuntime->methodSelector_(entry.second->get()); + auto methodSelector = debugRuntime->methodSelector_(entry.second->get()->asHeapObject()); ASSERT( symbol == methodSelector); if (symbol != methodSelector) { int a = 0; @@ -212,16 +212,16 @@ void Runtime::checkCache() { } } -HeapObject* Runtime::lookup_startingAt_(HeapObject *symbol, HeapObject *behavior) +Object* Runtime::lookup_startingAt_(Object *symbol, HeapObject *behavior) { checkCache(); - if (symbol->printString() == "#sizeInBytes") { - int a = 0; - } - auto iter = _globalCache.find(global_cache_key(symbol,behavior)); + //if (symbol->printString() == "#sizeInBytes") { + // int a = 0; + //} + auto iter = _globalCache.find(global_cache_key(symbol,(Object*)behavior)); if (iter != _globalCache.end()) { - if (iter->second->get()->slotAt_(5)->printString() != symbol->printString()) + if (iter->second->get()->asHeapObject()->slotAt_(5)->printString() != symbol->printString()) int b = 1; return iter->second->get(); } @@ -229,14 +229,14 @@ HeapObject* Runtime::lookup_startingAt_(HeapObject *symbol, HeapObject *behavior auto method = this->doLookup_startingAt_(symbol, behavior); if (!method) error_(this->behaviorClass_(behavior)->printString() + " does not understand " + symbol->printString()); - auto key = gced_global_cache_key(new GCedRef(symbol),new GCedRef(behavior)); - auto value = new GCedRef(method); + auto key = gced_global_cache_key(new GCedRef(symbol),new GCedRef((Object*)behavior)); + auto value = new GCedRef((Object*)method); _globalCache.insert({key, value}); checkCache(); return method; } -HeapObject* Runtime::doLookup_startingAt_(HeapObject *symbol, HeapObject *startBehavior) +Object* Runtime::doLookup_startingAt_(Object *symbol, HeapObject *startBehavior) { auto behavior = startBehavior; do { @@ -249,18 +249,18 @@ HeapObject* Runtime::doLookup_startingAt_(HeapObject *symbol, HeapObject *startB return nullptr; } -HeapObject* Runtime::methodFor_in_(HeapObject *symbol, HeapObject *behavior) +Object* Runtime::methodFor_in_(Object *symbol, HeapObject *behavior) { auto md = this->behaviorMethodDictionary_(behavior); auto table = this->dictionaryTable_(md); for (int index = 2; index < table->size(); index += 2) { - if (table->slotAt_(index) == (Object*)symbol) - return table->slotAt_(index + 1)->asHeapObject(); + if (table->slotAt_(index) == symbol) + return table->slotAt_(index + 1); } return nullptr; } -HeapObject* Runtime::existingSymbolFrom_(const std::string &selector) { +Object* Runtime::existingSymbolFrom_(const std::string &selector) { auto result = this->symbolTableAt_(selector); if (result == nullptr) { std::string str = std::string("symbol #") + selector + " not found in image"; @@ -268,34 +268,34 @@ HeapObject* Runtime::existingSymbolFrom_(const std::string &selector) { } return result; } -HeapObject* Runtime::symbolTableAt_(const std::string &selector) +Object* Runtime::symbolTableAt_(const std::string &selector) { auto it = this->_knownSymbols.find(selector); if (it != this->_knownSymbols.end()) - return it->second; + return it->second->get(); if (selector == "linker:token:") { int a = 0; } HeapObject *table = this->_symbolTable->slotAt_(2)->asHeapObject(); for (int i = 2; i < table->size(); i++){ - auto symbol = table->slotAt_(i)->asHeapObject(); - if (symbol != this->_nilObj){ + auto symbol = table->slotAt_(i); + if (symbol != (Object*)this->_nilObj){ //std::cout << "symbol" << symbol->printString() << " at: 0x" << i << std::endl; - if (symbol->sameBytesThan(selector)) - return symbol; + if (symbol->asHeapObject()->sameBytesThan(selector)) + return symbol; } } return nullptr; } -HeapObject* Runtime::lookupAssociationFor_in_(HeapObject *symbol, HeapObject *dictionary) { +HeapObject* Runtime::lookupAssociationFor_in_(Object *symbol, HeapObject *dictionary) { auto table = this->dictionaryTable_(dictionary); for (int index = 2; index <= table->size(); index++) { auto assoc = table->slotAt_(index)->asHeapObject(); if (assoc != this->_nilObj) { - auto key = assoc->slotAt_(1)->asHeapObject(); + auto key = assoc->slotAt_(1); if (key == symbol) return assoc; } @@ -303,7 +303,7 @@ HeapObject* Runtime::lookupAssociationFor_in_(HeapObject *symbol, HeapObject *di return nullptr; } -void Runtime::flushDispatchCache_(HeapObject *aSymbol) { +void Runtime::flushDispatchCache_(Object *aSymbol) { auto iter = _inlineCaches.find(aSymbol); if (iter != _inlineCaches.end()) { @@ -325,7 +325,7 @@ void Runtime::flushDispatchCache_(HeapObject *aSymbol) { } } -void Runtime::flushDispatchCache_in_(HeapObject *aSymbol, HeapObject *klass) { +void Runtime::flushDispatchCache_in_(Object *aSymbol, HeapObject *klass) { HeapObject *behavior = this->speciesInstanceBehavior_(klass); @@ -338,7 +338,7 @@ void Runtime::flushDispatchCache_in_(HeapObject *aSymbol, HeapObject *klass) { } } - global_cache_key pair = std::make_pair(aSymbol, behavior); + global_cache_key pair = std::make_pair(aSymbol, (Object*)behavior); auto globalIter = _globalCache.find(pair); if (globalIter != _globalCache.end()) _globalCache.erase(globalIter); @@ -363,7 +363,7 @@ void Runtime::registerGCedRef_(GCedRef *gcedRef) { _gcedRefs[gcedRef->index()] = gcedRef; } -GCedRef * Runtime::createGCedRef_(HeapObject *object) { +GCedRef * Runtime::createGCedRef_(Object *object) { auto index = this->assignGCedRefIndex(); GCedRef *result = new GCedRef(object, index); diff --git a/runtime/cpp/Evaluator/Runtime.h b/runtime/cpp/Evaluator/Runtime.h index 682410c..9843620 100644 --- a/runtime/cpp/Evaluator/Runtime.h +++ b/runtime/cpp/Evaluator/Runtime.h @@ -32,13 +32,13 @@ class Runtime { Evaluator *_evaluator; GCHeap *_heap; - std::map _knownSymbols; + std::map _knownSymbols; //typedef std::vector inline_cache; std::map *, GCedRef::Comparator > _inlineCaches; typedef std::pair gced_global_cache_key; - typedef std::pair global_cache_key; + typedef std::pair global_cache_key; std::map _globalCache; uint16_t _lastHash; @@ -60,17 +60,17 @@ class Runtime { Object* sendLocal_to_with_(const std::string &selector, Object *receiver, Object *arg1); Object* sendLocal_to_with_with_(const std::string &selector, Object *receiver, Object *arg1, Object* arg2); - HeapObject* lookup_startingAt_(HeapObject *symbol, HeapObject *behavior); - HeapObject* doLookup_startingAt_(HeapObject *symbol, HeapObject *behavior); - HeapObject* methodFor_in_(HeapObject *symbol, HeapObject *behavior); + Object* lookup_startingAt_(Object *symbol, HeapObject *behavior); + Object* doLookup_startingAt_(Object *symbol, HeapObject *behavior); + Object* methodFor_in_(Object *symbol, HeapObject *behavior); - HeapObject* existingSymbolFrom_(const std::string &selector); - HeapObject* symbolTableAt_(const std::string &selector); + Object* existingSymbolFrom_(const std::string &selector); + Object* symbolTableAt_(const std::string &selector); - HeapObject* lookupAssociationFor_in_(HeapObject *symbol, HeapObject *dictionary); + HeapObject* lookupAssociationFor_in_(Object *symbol, HeapObject *dictionary); - void flushDispatchCache_(HeapObject *aSymbol); - void flushDispatchCache_in_(HeapObject *aSymbol, HeapObject *klass); + void flushDispatchCache_(Object *aSymbol); + void flushDispatchCache_in_(Object *aSymbol, HeapObject *klass); HeapObject* newDouble_(double value) { auto result = newBytes_size_(_floatClass, sizeof(double)); @@ -100,8 +100,8 @@ class Runtime { HeapObject* newExecutableCodeFor_with_(HeapObject *compiledCode, PlatformCode *platformCode); HeapObject* newString_(const std::string &str); HeapObject* addSymbol_(const std::string &str); - void addKnownSymbol_(const std::string &str, HeapObject *symbol) { - _knownSymbols[str] = symbol; + void addKnownSymbol_(const std::string &str, Object *symbol) { + _knownSymbols[str] = new GCedRef(symbol); } HeapObject* loadModule_(HeapObject *name); void addSegmentSpace_(ImageSegment *segment); @@ -118,7 +118,7 @@ class Runtime { return this->_lastHash; } - void registerCache_for_(SAbstractMessage *message, HeapObject *symbol) { + void registerCache_for_(SAbstractMessage *message, Object *symbol) { auto it = _inlineCaches.find(symbol); std::vector *messages; if (it == _inlineCaches.end()) @@ -352,8 +352,8 @@ class Runtime { } } - HeapObject* methodSelector_(HeapObject *method) { - return method->slot(Offsets::MethodSelector)->asHeapObject(); + Object* methodSelector_(HeapObject *method) { + return method->slot(Offsets::MethodSelector); } bool methodIsFFI_(HeapObject *method) { @@ -531,7 +531,7 @@ class Runtime { uintptr_t assignGCedRefIndex(); void registerGCedRef_(GCedRef *gcedRef); - GCedRef* createGCedRef_(HeapObject * object); + GCedRef* createGCedRef_(Object * object); void releaseGCedRef_(uintptr_t index); void gcedRefsDo_(const std::function &aBlock); diff --git a/runtime/cpp/Evaluator/SAbstractMessage.h b/runtime/cpp/Evaluator/SAbstractMessage.h index c48d1fa..ca1a749 100644 --- a/runtime/cpp/Evaluator/SAbstractMessage.h +++ b/runtime/cpp/Evaluator/SAbstractMessage.h @@ -27,7 +27,7 @@ class SAbstractMessage : public SExpression { }; public: - SAbstractMessage(HeapObject *selector, const std::vector& arguments) : + SAbstractMessage(Object *selector, const std::vector& arguments) : _selector(selector), _arguments(arguments) { } @@ -43,7 +43,7 @@ class SAbstractMessage : public SExpression { _arguments = aCollection; } - void cache_when_(HeapObject* anSCompiledMethod, HeapObject* type) { + void cache_when_(Object* anSCompiledMethod, Object* type) { _cache.push_back(new GCedRef(type)); _cache.push_back(new GCedRef(anSCompiledMethod)); } @@ -66,7 +66,7 @@ class SAbstractMessage : public SExpression { _cache.clear(); } - HeapObject* methodFor_(HeapObject *behavior) const { + Object* methodFor_(Object *behavior) const { for (size_t i = 0; i < _cache.size(); i += 2) { GCedRef *cached = _cache[i]; if (cached->get() == behavior) { @@ -85,7 +85,7 @@ class SAbstractMessage : public SExpression { } } - HeapObject* selector() { + Object* selector() { return _selector.get(); } diff --git a/runtime/cpp/Evaluator/SAssociationBinding.h b/runtime/cpp/Evaluator/SAssociationBinding.h index f2d8f8f..2cc3042 100644 --- a/runtime/cpp/Evaluator/SAssociationBinding.h +++ b/runtime/cpp/Evaluator/SAssociationBinding.h @@ -10,20 +10,20 @@ class HeapObject; class SAssociationBinding : public SBinding { int _index; - HeapObject *_association; + GCedRef _association; public: - SAssociationBinding(HeapObject *assoc) : _association(assoc) {} + SAssociationBinding(HeapObject *assoc) : _association((Object*)assoc) {} void assign_within_(Object *value, EvaluationContext *anEvaluationContext) override { - anEvaluationContext->storeAssociation_value_(this->_association, value); + anEvaluationContext->storeAssociation_value_(this->association(), value); } HeapObject* association() { - return this->_association; + return this->_association.get()->asHeapObject(); } void association_(HeapObject *anAssociation) { - this->_association = anAssociation; + this->_association.set_((Object*)anAssociation); } int index() { @@ -43,7 +43,7 @@ class SAssociationBinding : public SBinding { } Object* valueWithin_(EvaluationContext* anEvaluationContext) override { - return anEvaluationContext->loadAssociationValue_(this->_association); + return anEvaluationContext->loadAssociationValue_(this->association()); } }; diff --git a/runtime/cpp/Evaluator/SBinding.h b/runtime/cpp/Evaluator/SBinding.h index f6cec6d..bcc061a 100644 --- a/runtime/cpp/Evaluator/SBinding.h +++ b/runtime/cpp/Evaluator/SBinding.h @@ -64,7 +64,7 @@ class SBinding { return false; } - virtual HeapObject* name() const { + virtual const Object* name() const { subclassResponsibility(); return nullptr; } diff --git a/runtime/cpp/Evaluator/SBlock.h b/runtime/cpp/Evaluator/SBlock.h index 3266c15..90f0c4e 100644 --- a/runtime/cpp/Evaluator/SBlock.h +++ b/runtime/cpp/Evaluator/SBlock.h @@ -56,7 +56,7 @@ class SBlock : public SScript { } bool isInlined() const { - return _compiledCode == nullptr; + return _compiledCode.get() == (Object*)KnownObjects::nil; } int offsetOfCurrentEnvironment() const { diff --git a/runtime/cpp/Evaluator/SCascadeMessage.h b/runtime/cpp/Evaluator/SCascadeMessage.h index 7fd5c8f..24bfc2b 100644 --- a/runtime/cpp/Evaluator/SCascadeMessage.h +++ b/runtime/cpp/Evaluator/SCascadeMessage.h @@ -12,7 +12,7 @@ class SCascadeMessage : public SAbstractMessage { SCascade* _cascade; public: - SCascadeMessage(HeapObject *selector, const std::vector& arguments, SCascade *cascade) : + SCascadeMessage(Object *selector, const std::vector& arguments, SCascade *cascade) : SAbstractMessage(selector, arguments), _cascade(cascade) { } diff --git a/runtime/cpp/Evaluator/SDynamicBinding.h b/runtime/cpp/Evaluator/SDynamicBinding.h index 8e1c513..50abbfb 100644 --- a/runtime/cpp/Evaluator/SDynamicBinding.h +++ b/runtime/cpp/Evaluator/SDynamicBinding.h @@ -8,7 +8,7 @@ namespace Egg { class SDynamicBinding : public SBinding { public: - SDynamicBinding(HeapObject *name) : _name(name), _cache(nullptr) {} + SDynamicBinding(Object *name) : _name(name), _cache(nullptr) {} void assign_within_(Object* value, EvaluationContext* anEvaluationContext) override { if (_cache == nullptr) { @@ -22,15 +22,15 @@ class SDynamicBinding : public SBinding { } virtual void lookupWithin_(EvaluationContext* anEvaluationContext) { - _cache = anEvaluationContext->staticBindingFor_(this->_name); + _cache = anEvaluationContext->staticBindingFor_(this->_name.get()); } - HeapObject* name() const override { - return this->_name; + const Object* name() const override { + return this->_name.get(); } - SDynamicBinding* name_(HeapObject *aSymbol) { - _name = aSymbol; + SDynamicBinding* name_(Object *aSymbol) { + _name.set_(aSymbol); return this; } @@ -42,7 +42,7 @@ class SDynamicBinding : public SBinding { } protected: - HeapObject *_name; + GCedRef _name; SBinding *_cache; }; diff --git a/runtime/cpp/Evaluator/SExpressionLinearizer.cpp b/runtime/cpp/Evaluator/SExpressionLinearizer.cpp index a0c6351..89c94f3 100644 --- a/runtime/cpp/Evaluator/SExpressionLinearizer.cpp +++ b/runtime/cpp/Evaluator/SExpressionLinearizer.cpp @@ -451,7 +451,7 @@ void SExpressionLinearizer::visitBlock(SBlock *anSBlock) { if (!anSBlock->isInlined()) { - auto code = _runtime->newExecutableCodeFor_with_(anSBlock->_compiledCode, this->_operations); + auto code = _runtime->newExecutableCodeFor_with_(anSBlock->compiledCode(), this->_operations); _runtime->blockExecutableCode_put_(anSBlock->compiledCode(), code); } @@ -482,7 +482,7 @@ void SExpressionLinearizer::visitIdentifier(SIdentifier *anSIdentifier) { } void SExpressionLinearizer::visitInlinedMessage(SMessage *anSMessage) { - HeapObject *selector = anSMessage->selector(); + Object *selector = anSMessage->selector(); if (selector == this->_ifTrue) return this->inline_if_(anSMessage, true); if (selector == this->_ifFalse) return this->inline_if_(anSMessage, false); @@ -504,8 +504,8 @@ void SExpressionLinearizer::visitInlinedMessage(SMessage *anSMessage) { if (selector == this->_orNot) return this->inlineOrNot_(anSMessage); // check if selector is or:or:or:... or and:and:and:... - if (selector->asLocalString().starts_with("or:")) return this->inlineOr_(anSMessage); - if (selector->asLocalString().starts_with("and:")) return this->inlineAnd_(anSMessage); + if (selector->asHeapObject()->asLocalString().starts_with("or:")) return this->inlineOr_(anSMessage); + if (selector->asHeapObject()->asLocalString().starts_with("and:")) return this->inlineAnd_(anSMessage); ASSERT(false); } @@ -543,8 +543,17 @@ void SExpressionLinearizer::visitMethod(SMethod *anSMethod, HeapObject *method) this->reset(); auto primitive = anSMethod->pragma(); if (primitive != nullptr) { - auto name = (_runtime->methodIsFFI_(method)) ? _runtime->existingSymbolFrom_("FFICall") : anSMethod->primitive(); - PrimitivePointer primitive = this->_primitives[name]; + auto name = (_runtime->methodIsFFI_(method)) ? _runtime->existingSymbolFrom_("FFICall") : (Object*)anSMethod->primitive(); + + PrimitivePointer primitive; + auto it = this->_primitives.find(name); + if (it == this->_primitives.end()) { + error_("primitive " + name->printString() + " not found"); + } + else { + primitive = it->second; + } + this->primitive_(primitive); this->returnOp(); } diff --git a/runtime/cpp/Evaluator/SExpressionLinearizer.h b/runtime/cpp/Evaluator/SExpressionLinearizer.h index 7fd4b09..d70578c 100644 --- a/runtime/cpp/Evaluator/SExpressionLinearizer.h +++ b/runtime/cpp/Evaluator/SExpressionLinearizer.h @@ -13,7 +13,7 @@ namespace Egg { class Evaluator; class SExpressionLinearizer : public SExpressionVisitor { - HeapObject *_greaterThan, *_plus, *_not, *_equalsEquals, *_ifTrue, *_ifFalse, *_ifTrueIfFalse, *_ifFalseIfTrue, *_ifNil, *_ifNotNil, *_ifNilIfNotNil, *_ifNotNilIfNil, *_whileTrue, *_whileFalse, *_whileTrue_, *_whileFalse_, *_toDo, *_toByDo, *_repeat, *_timesRepeat, *_andNot, *_orNot; + Object *_greaterThan, *_plus, *_not, *_equalsEquals, *_ifTrue, *_ifFalse, *_ifTrueIfFalse, *_ifFalseIfTrue, *_ifNil, *_ifNotNil, *_ifNilIfNotNil, *_ifNotNilIfNil, *_whileTrue, *_whileFalse, *_whileTrue_, *_whileFalse_, *_toDo, *_toByDo, *_repeat, *_timesRepeat, *_andNot, *_orNot; SLiteral *_one; bool _inBlock; bool _dropsArguments; @@ -21,7 +21,7 @@ class SExpressionLinearizer : public SExpressionVisitor { PlatformCode *_operations; using PrimitivePointer = Object* (Evaluator::*)(); - std::map _primitives; + std::map _primitives; Runtime *_runtime; public: @@ -96,7 +96,7 @@ class SExpressionLinearizer : public SExpressionVisitor { void loadRwithSelf(); void popR(); void primitive_(PrimitivePointer aClosure); - void primitives_(std::map &primitives) { _primitives = primitives; } + void primitives_(std::map &primitives) { _primitives = primitives; } void pushR(); void reset(); void returnOp(); diff --git a/runtime/cpp/Evaluator/SFalseBinding.h b/runtime/cpp/Evaluator/SFalseBinding.h index 1a1146a..7dcfb53 100644 --- a/runtime/cpp/Evaluator/SFalseBinding.h +++ b/runtime/cpp/Evaluator/SFalseBinding.h @@ -2,16 +2,12 @@ #define _SFALSEBINDING_H_ #include "SLiteralBinding.h" -#include "KnownObjects.h" #include "EvaluationContext.h" namespace Egg { class SFalseBinding : public SLiteralBinding { public: - HeapObject* value() const override { - return KnownObjects::_false; - } Object* valueWithin_(EvaluationContext* anEvaluationContext) override { return (Object*)anEvaluationContext->_false(); diff --git a/runtime/cpp/Evaluator/SLiteral.h b/runtime/cpp/Evaluator/SLiteral.h index 2caa248..876b63f 100644 --- a/runtime/cpp/Evaluator/SLiteral.h +++ b/runtime/cpp/Evaluator/SLiteral.h @@ -8,7 +8,7 @@ namespace Egg { class SLiteral : public SLiteralVar { - Object *_value; + GCedRef _value; public: SLiteral(int64_t index, Object *value) : SLiteralVar(index), _value(value) {} @@ -20,12 +20,12 @@ class SLiteral : public SLiteralVar { return true; } - Object* value() const { - return _value; + Object* value() { + return _value.get(); } void value_(Object* anObject) { - _value = anObject; + _value.set_(anObject); } }; diff --git a/runtime/cpp/Evaluator/SLiteralBinding.h b/runtime/cpp/Evaluator/SLiteralBinding.h index c577162..198cfcd 100644 --- a/runtime/cpp/Evaluator/SLiteralBinding.h +++ b/runtime/cpp/Evaluator/SLiteralBinding.h @@ -8,7 +8,6 @@ namespace Egg { class SLiteralBinding : public SBinding { public: - virtual HeapObject* value() const = 0; bool isLiteral() const override { return true; diff --git a/runtime/cpp/Evaluator/SMessage.h b/runtime/cpp/Evaluator/SMessage.h index 3fedf69..9012cba 100644 --- a/runtime/cpp/Evaluator/SMessage.h +++ b/runtime/cpp/Evaluator/SMessage.h @@ -13,7 +13,7 @@ class SMessage : public SAbstractMessage { bool _inlined; public: - SMessage(SExpression *receiver, HeapObject *selector, const std::vector& arguments, bool inlined) : + SMessage(SExpression *receiver, Object *selector, const std::vector& arguments, bool inlined) : SAbstractMessage(selector, arguments), _receiver(receiver), _inlined(inlined) { } diff --git a/runtime/cpp/Evaluator/SMethod.cpp b/runtime/cpp/Evaluator/SMethod.cpp index 5baa205..8e774ef 100644 --- a/runtime/cpp/Evaluator/SMethod.cpp +++ b/runtime/cpp/Evaluator/SMethod.cpp @@ -4,6 +4,6 @@ using namespace Egg; -HeapObject* SMethod::primitive() const { +const Object* SMethod::primitive() const { return _pragma ? _pragma->name() : nullptr; } diff --git a/runtime/cpp/Evaluator/SMethod.h b/runtime/cpp/Evaluator/SMethod.h index b060e6e..dd7e046 100644 --- a/runtime/cpp/Evaluator/SMethod.h +++ b/runtime/cpp/Evaluator/SMethod.h @@ -38,7 +38,7 @@ class SMethod : public SScript { _pragma = anSPragma; } - HeapObject* primitive() const; + const Object* primitive() const; }; diff --git a/runtime/cpp/Evaluator/SNestedDynamicBinding.h b/runtime/cpp/Evaluator/SNestedDynamicBinding.h index 82a5c44..1d9b4b7 100644 --- a/runtime/cpp/Evaluator/SNestedDynamicBinding.h +++ b/runtime/cpp/Evaluator/SNestedDynamicBinding.h @@ -8,10 +8,10 @@ namespace Egg { class SNestedDynamicBinding : public SDynamicBinding { public: - SNestedDynamicBinding(HeapObject *name) : SDynamicBinding(name) {} + SNestedDynamicBinding(Object *name) : SDynamicBinding(name) {} void lookupWithin_ (EvaluationContext* anEvaluationContext) override { - _cache = anEvaluationContext->staticBindingForNested_(this->_name); + _cache = anEvaluationContext->staticBindingForNested_(this->_name.get()); } }; diff --git a/runtime/cpp/Evaluator/SNilBinding.h b/runtime/cpp/Evaluator/SNilBinding.h index b5da69f..5f0d679 100644 --- a/runtime/cpp/Evaluator/SNilBinding.h +++ b/runtime/cpp/Evaluator/SNilBinding.h @@ -2,16 +2,12 @@ #define _SNILBINDING_H_ #include "SLiteralBinding.h" -#include "KnownObjects.h" #include "EvaluationContext.h" namespace Egg { class SNilBinding : public SLiteralBinding { public: - HeapObject* value() const override { - return KnownObjects::nil; - } Object* valueWithin_(EvaluationContext* anEvaluationContext) override { return (Object*)anEvaluationContext->nil(); diff --git a/runtime/cpp/Evaluator/SPragma.h b/runtime/cpp/Evaluator/SPragma.h index 8af0f66..4bd55bc 100644 --- a/runtime/cpp/Evaluator/SPragma.h +++ b/runtime/cpp/Evaluator/SPragma.h @@ -7,22 +7,20 @@ namespace Egg { class SPragma : public SExpression { public: - HeapObject * _name; + GCedRef _name; - SPragma(HeapObject *name) { - _name = name; - } + SPragma(Object *name) : _name(name) {} void acceptVisitor_(SExpressionVisitor* visitor) override { visitor->visitPragma(this); } - HeapObject* name() const { - return _name; + const Object* name() const { + return _name.get(); } - void name(HeapObject *name) { - _name = name; + void name(Object *name) { + _name.set_(name); } diff --git a/runtime/cpp/Evaluator/SScript.h b/runtime/cpp/Evaluator/SScript.h index 5604e9b..2456c7f 100644 --- a/runtime/cpp/Evaluator/SScript.h +++ b/runtime/cpp/Evaluator/SScript.h @@ -10,18 +10,17 @@ namespace Egg { class SScript : public SExpression { public: std::vector _statements; - HeapObject* _compiledCode; + GCedRef _compiledCode; - SScript() { - _compiledCode = nullptr; + SScript() : _compiledCode((Object*)KnownObjects::nil) { } HeapObject* compiledCode() { - return _compiledCode; + return _compiledCode.get()->asHeapObject(); } void compiledCode_(HeapObject* anObject) { - _compiledCode = anObject; + _compiledCode.set_((Object*)anObject); } std::vector& statements() { diff --git a/runtime/cpp/Evaluator/STrueBinding.h b/runtime/cpp/Evaluator/STrueBinding.h index 77e64bd..6cd0e6e 100644 --- a/runtime/cpp/Evaluator/STrueBinding.h +++ b/runtime/cpp/Evaluator/STrueBinding.h @@ -2,16 +2,11 @@ #define _STRUEBINDING_H_ #include "SLiteralBinding.h" -#include "KnownObjects.h" #include "EvaluationContext.h" namespace Egg { class STrueBinding : public SLiteralBinding { -public: - HeapObject* value() const override { - return KnownObjects::_true; - } Object* valueWithin_(EvaluationContext* anEvaluationContext) override { return (Object*)anEvaluationContext->_true(); diff --git a/runtime/cpp/Evaluator/TreecodeDecoder.h b/runtime/cpp/Evaluator/TreecodeDecoder.h index 31c6ca1..c32fab2 100644 --- a/runtime/cpp/Evaluator/TreecodeDecoder.h +++ b/runtime/cpp/Evaluator/TreecodeDecoder.h @@ -274,14 +274,14 @@ class TreecodeDecoder { return this->_runtime->newInteger_(value); } - HeapObject* nextSymbol() { + Object* nextSymbol() { auto index = this->nextInteger(); - return this->literalAt_(index)->asHeapObject(); + return this->literalAt_(index); } - HeapObject* nextSymbolOrNil() { + Object* nextSymbolOrNil() { auto index = this->nextInteger(); - return index != 0 ? this->literalAt_(index)->asHeapObject() : KnownObjects::nil; + return index != 0 ? this->literalAt_(index) : (Object*)KnownObjects::nil; } uint64_t nextUnsignedInteger() { diff --git a/runtime/cpp/GCedRef.cpp b/runtime/cpp/GCedRef.cpp index d622c5c..8daab9f 100644 --- a/runtime/cpp/GCedRef.cpp +++ b/runtime/cpp/GCedRef.cpp @@ -7,11 +7,11 @@ using namespace Egg; class Runtime; -GCedRef::GCedRef(HeapObject* object, uintptr_t index) +GCedRef::GCedRef(Object* object, uintptr_t index) : _object(object), _index(index) {} -GCedRef::GCedRef(HeapObject* object) +GCedRef::GCedRef(Object* object) : _object(object), _index(debugRuntime->assignGCedRefIndex()) { debugRuntime->registerGCedRef_(this); @@ -20,19 +20,36 @@ GCedRef::GCedRef(HeapObject* object) // : _runtime(other._runtime), _object(other.get()), index(_runtime->assignRef(other.get())) //{} +/* +GCedRef::GCedRef(GCedRef&& other) : _object(other._object), _index(other._index) { + debugRuntime->registerGCedRef_(this); + other._object = (Object*)KnownObjects::nil; +} + +GCedRef& GCedRef::operator=(GCedRef&& other) { + if (this != &other) { + _object = other._object; + _index = other._index; + other._object = nullptr; + } + return *this; +} +*/ + GCedRef::~GCedRef() { - debugRuntime->releaseGCedRef_(_index); + //if (_object != KnownObjects::nil) + debugRuntime->releaseGCedRef_(_index); } uintptr_t GCedRef::Comparator::hash(const GCedRef *obj) const { return debugRuntime->hashFor_((Object*)obj->get()); } -uintptr_t GCedRef::Comparator::hash(const HeapObject *obj) const { +uintptr_t GCedRef::Comparator::hash(const Object *obj) const { return debugRuntime->hashFor_((Object*)obj); } -HeapObject* GCedRef::get() +Object* GCedRef::get() { return _object; } diff --git a/runtime/cpp/GCedRef.h b/runtime/cpp/GCedRef.h index 3e0c027..aef4e11 100644 --- a/runtime/cpp/GCedRef.h +++ b/runtime/cpp/GCedRef.h @@ -12,35 +12,40 @@ class GCedRef { public: /* Create a new NULL reference. */ - GCedRef(HeapObject *object, uintptr_t index); - GCedRef(HeapObject *object); + GCedRef(Object *object, uintptr_t index); + GCedRef(Object *object); /* Create a new reference from another reference */ GCedRef(GCedRef &other); + // we allow moving ownership of one ref to another + //GCedRef(GCedRef &&other); + //GCedRef& operator=(GCedRef&& other); + ~GCedRef(); - HeapObject *get(); - const HeapObject* get() const { return _object; }; + Object *get(); + const Object* get() const { return _object; }; + void set_(Object *object) { _object = object; }; uintptr_t index(); - HeapObject **getRaw() { return &_object; } + Object **getRaw() { return &_object; } // Comparator for GCedRef* that allows comparisons with HeapObject* struct Comparator { using is_transparent = void; uintptr_t hash(const GCedRef *obj) const; - uintptr_t hash(const HeapObject *obj) const; + uintptr_t hash(const Object *obj) const; bool operator()(const GCedRef* lhs, const GCedRef* rhs) const { return hash(lhs) <= hash(rhs); } - bool operator()(const GCedRef* lhs, const HeapObject* rhs) const { + bool operator()(const GCedRef* lhs, const Object* rhs) const { return hash(lhs) <= hash(rhs); } - bool operator()(const HeapObject* lhs, const GCedRef* rhs) const { + bool operator()(const Object* lhs, const GCedRef* rhs) const { return hash(lhs) <= hash(rhs); } @@ -52,7 +57,7 @@ class GCedRef } // Compare two std::pair - bool operator()(const std::pair& lhs, const std::pair& rhs) const { + bool operator()(const std::pair& lhs, const std::pair& rhs) const { auto lhs1 = hash(lhs.first), lhs2 = hash(lhs.second); auto rhs1 = hash(rhs.first), rhs2 = hash(rhs.second); return std::tie(lhs1, lhs2) <= std::tie(rhs1, rhs2); @@ -60,13 +65,13 @@ class GCedRef // Compare pairs of HeapObject with pairs of GCedRef bool operator()(const std::pair& lhs, - const std::pair& rhs) const { + const std::pair& rhs) const { auto lhs1 = hash(lhs.first), lhs2 = hash(lhs.second); auto rhs1 = hash(rhs.first), rhs2 = hash(rhs.second); return std::tie(lhs1, lhs2) <= std::tie(rhs1, rhs2); } - bool operator()(const std::pair& lhs, + bool operator()(const std::pair& lhs, const std::pair& rhs) const { auto lhs1 = hash(lhs.first), lhs2 = hash(lhs.second); auto rhs1 = hash(rhs.first), rhs2 = hash(rhs.second); @@ -78,7 +83,7 @@ class GCedRef GCedRef(const GCedRef &other) = delete; // not allowed, to prevent aliasing GCedRef& operator=(const GCedRef &other) = delete; - HeapObject *_object; + Object *_object; uintptr_t _index; //static Runtime *_runtime; }; diff --git a/runtime/cpp/Launcher.cpp b/runtime/cpp/Launcher.cpp index f22d645..bd89336 100644 --- a/runtime/cpp/Launcher.cpp +++ b/runtime/cpp/Launcher.cpp @@ -87,7 +87,7 @@ void runBareTests(Runtime *runtime, HeapObject *kernel, std::vector &ar for (auto method : methods) { // auto result = runtime->_evaluator->invoke_with_(method, (Object*)runtime->_nilObj); //runtime->_evaluator->evaluate(); - auto selector = runtime->methodSelector_(method)->asLocalString(); + auto selector = runtime->methodSelector_(method)->printString(); // if (selector == "test161CreateDictionary") { auto result = runtime->sendLocal_to_(selector, (Object*)module); diff --git a/runtime/cpp/Posix/Memory.cpp b/runtime/cpp/Posix/Memory.cpp index 00191d1..9521e7d 100644 --- a/runtime/cpp/Posix/Memory.cpp +++ b/runtime/cpp/Posix/Memory.cpp @@ -126,7 +126,11 @@ void Egg::CommitMemory(uintptr_t base, uintptr_t size) void Egg::DecommitMemory(uintptr_t base, uintptr_t size) { - if (madvise((void*)base, size, MADV_DONTNEED) != 0) { + // just to guarantee memory is wiped out + std::memset((char*)base, 0, size); + + // if (madvise((void*)base, size, MADV_DONTNEED) != 0) { + if (mprotect((void*)base, size, PROT_NONE) != 0) { error("Failed to decommit memory."); } }