From 206c7b0f4a3bb496a3070294ada382a1d1804f7c Mon Sep 17 00:00:00 2001 From: puffaboo Date: Sun, 10 Jul 2022 00:48:38 +0100 Subject: [PATCH] initial commit with some unfinished json-ld impl --- go.mod | 35 +++++ go.sum | 100 ++++++++++++ pkg/asld/asld.go | 305 +++++++++++++++++++++++++++++++++++++ pkg/asld/unmarshal_test.go | 39 +++++ pkg/asld/walker.go | 161 ++++++++++++++++++++ pkg/asld/walker_test.go | 190 +++++++++++++++++++++++ pkg/epk/epk.go | 34 +++++ 7 files changed, 864 insertions(+) create mode 100644 go.mod create mode 100644 go.sum create mode 100644 pkg/asld/asld.go create mode 100644 pkg/asld/unmarshal_test.go create mode 100644 pkg/asld/walker.go create mode 100644 pkg/asld/walker_test.go create mode 100644 pkg/epk/epk.go diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..f9fe18a --- /dev/null +++ b/go.mod @@ -0,0 +1,35 @@ +module git.sectorinf.com/emilis/asflab + +go 1.18 + +require ( + github.com/gin-gonic/gin v1.8.1 + github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d + github.com/piprate/json-gold v0.4.1 + github.com/stretchr/testify v1.7.2 +) + +require ( + github.com/davecgh/go-spew v1.1.1 // indirect + github.com/gin-contrib/sse v0.1.0 // indirect + github.com/go-playground/locales v0.14.0 // indirect + github.com/go-playground/universal-translator v0.18.0 // indirect + github.com/go-playground/validator/v10 v10.11.0 // indirect + github.com/goccy/go-json v0.9.8 // indirect + github.com/json-iterator/go v1.1.12 // indirect + github.com/leodido/go-urn v1.2.1 // indirect + github.com/mattn/go-isatty v0.0.14 // indirect + github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect + github.com/modern-go/reflect2 v1.0.2 // indirect + github.com/pelletier/go-toml/v2 v2.0.2 // indirect + github.com/pmezard/go-difflib v1.0.0 // indirect + github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 // indirect + github.com/ugorji/go/codec v1.2.7 // indirect + golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d // indirect + golang.org/x/net v0.0.0-20220630215102-69896b714898 // indirect + golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e // indirect + golang.org/x/text v0.3.7 // indirect + google.golang.org/protobuf v1.28.0 // indirect + gopkg.in/yaml.v2 v2.4.0 // indirect + gopkg.in/yaml.v3 v3.0.1 // indirect +) diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..717d7af --- /dev/null +++ b/go.sum @@ -0,0 +1,100 @@ +github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= +github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/gin-contrib/sse v0.1.0 h1:Y/yl/+YNO8GZSjAhjMsSuLt29uWRFHdHYUb5lYOV9qE= +github.com/gin-contrib/sse v0.1.0/go.mod h1:RHrZQHXnP2xjPF+u1gW/2HnVO7nvIa9PG3Gm+fLHvGI= +github.com/gin-gonic/gin v1.8.1 h1:4+fr/el88TOO3ewCmQr8cx/CtZ/umlIRIs5M4NTNjf8= +github.com/gin-gonic/gin v1.8.1/go.mod h1:ji8BvRH1azfM+SYow9zQ6SZMvR8qOMZHmsCuWR9tTTk= +github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d h1:Z/oRXMlZHjvjIqDma1FrIGL3iE5YL7MUI0bwYEZ6qbA= +github.com/go-ap/jsonld v0.0.0-20220615144122-1d862b15410d/go.mod h1:jyveZeGw5LaADntW+UEsMjl3IlIwk+DxlYNsbofQkGA= +github.com/go-playground/assert/v2 v2.0.1 h1:MsBgLAaY856+nPRTKrp3/OZK38U/wa0CcBYNjji3q3A= +github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= +github.com/go-playground/locales v0.14.0 h1:u50s323jtVGugKlcYeyzC0etD1HifMjqmJqb8WugfUU= +github.com/go-playground/locales v0.14.0/go.mod h1:sawfccIbzZTqEDETgFXqTho0QybSa7l++s0DH+LDiLs= +github.com/go-playground/universal-translator v0.18.0 h1:82dyy6p4OuJq4/CByFNOn/jYrnRPArHwAcmLoJZxyho= +github.com/go-playground/universal-translator v0.18.0/go.mod h1:UvRDBj+xPUEGrFYl+lu/H90nyDXpg0fqeB/AQUGNTVA= +github.com/go-playground/validator/v10 v10.11.0 h1:0W+xRM511GY47Yy3bZUbJVitCNg2BOGlCyvTqsp/xIw= +github.com/go-playground/validator/v10 v10.11.0/go.mod h1:i+3WkQ1FvaUjjxh1kSvIA4dMGDBiPU55YFDl0WbKdWU= +github.com/goccy/go-json v0.9.8 h1:DxXB6MLd6yyel7CLph8EwNIonUtVZd3Ue5iRcL4DQCE= +github.com/goccy/go-json v0.9.8/go.mod h1:6MelG93GURQebXPDq3khkgXZkazVtN9CRI+MGFi0w8I= +github.com/golang/protobuf v1.5.0/go.mod h1:FsONVRAS9T7sI+LIUmWTfcYkHO4aIWwzhcaSAoJOfIk= +github.com/google/go-cmp v0.5.5 h1:Khx7svrCpmxxtHBq5j2mp/xVjsi8hQMfNLvJFAlrGgU= +github.com/google/go-cmp v0.5.5/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= +github.com/json-iterator/go v1.1.12 h1:PV8peI4a0ysnczrg+LtxykD8LfKY9ML6u2jnxaEnrnM= +github.com/json-iterator/go v1.1.12/go.mod h1:e30LSqwooZae/UwlEbR2852Gd8hjQvJoHmT4TnhNGBo= +github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= +github.com/kr/pretty v0.2.1/go.mod h1:ipq/a2n7PKx3OHsz4KJII5eveXtPO4qwEXGdVfWzfnI= +github.com/kr/pretty v0.3.0 h1:WgNl7dwNpEZ6jJ9k1snq4pZsg7DOEN8hP9Xw0Tsjwk0= +github.com/kr/pretty v0.3.0/go.mod h1:640gp4NfQd8pI5XOwp5fnNeVWj67G7CFk/SaSQn7NBk= +github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= +github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= +github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE= +github.com/leodido/go-urn v1.2.1 h1:BqpAaACuzVSgi/VLzGZIobT2z4v53pjosyNd9Yv6n/w= +github.com/leodido/go-urn v1.2.1/go.mod h1:zt4jvISO2HfUBqxjfIshjdMTYS56ZS/qv49ictyFfxY= +github.com/mattn/go-isatty v0.0.14 h1:yVuAays6BHfxijgZPzw+3Zlu5yQgKGP2/hcQbHb7S9Y= +github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94= +github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd h1:TRLaZ9cD/w8PVh93nsPXa1VrQ6jlwL5oN8l14QlcNfg= +github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= +github.com/modern-go/reflect2 v1.0.2 h1:xBagoLtFs94CBntxluKeaWgTMpvLxC4ur3nMaC9Gz0M= +github.com/modern-go/reflect2 v1.0.2/go.mod h1:yWuevngMOJpCy52FWWMvUC8ws7m/LJsjYzDa0/r8luk= +github.com/pelletier/go-toml/v2 v2.0.2 h1:+jQXlF3scKIcSEKkdHzXhCTDLPFi5r1wnK6yPS+49Gw= +github.com/pelletier/go-toml/v2 v2.0.2/go.mod h1:MovirKjgVRESsAvNZlAjtFwV867yGuwRkXbG66OzopI= +github.com/piprate/json-gold v0.4.1 h1:JYbYN36n6YcAYipKy3ttv3X2HDQPeqWqmwta35NPj04= +github.com/piprate/json-gold v0.4.1/go.mod h1:OK1z7UgtBZk06n2cDE2OSq1kffmjFFp5/2yhLLCz9UM= +github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= +github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35 h1:J9b7z+QKAmPf4YLrFg6oQUotqHQeUNWwkvo7jZp1GLU= +github.com/pquerna/cachecontrol v0.0.0-20180517163645-1555304b9b35/go.mod h1:prYjPmNq4d1NPVmpShWobRqXY3q7Vp+80DqgxxUrUIA= +github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= +github.com/rogpeppe/go-internal v1.8.0 h1:FCbCCtXNOY3UtUuHUYaghJg4y7Fd14rXifAYUAtL9R8= +github.com/rogpeppe/go-internal v1.8.0/go.mod h1:WmiCO8CzOY8rg0OYDC4/i/2WRWAB6poM+XZ2dLUbcbE= +github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= +github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= +github.com/stretchr/testify v1.7.2 h1:4jaiDzPyXQvSd7D0EjG45355tLlV3VOECpq10pLC+8s= +github.com/stretchr/testify v1.7.2/go.mod h1:R6va5+xMeoiuVRoj+gSkQ7d3FALtqAAGI1FQKckRals= +github.com/ugorji/go v1.2.7/go.mod h1:nF9osbDWLy6bDVv/Rtoh6QgnvNDpmCalQV5urGCCS6M= +github.com/ugorji/go/codec v1.2.7 h1:YPXUKf7fYbp/y8xloBqZOw2qaVggbfwMlI8WM3wZUJ0= +github.com/ugorji/go/codec v1.2.7/go.mod h1:WGN1fab3R1fzQlVQTkfxVtIBhWDRqOviHU95kRgeqEY= +golang.org/x/crypto v0.0.0-20211215153901-e495a2d5b3d3/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d h1:sK3txAijHtOK88l68nt020reeT1ZdKLIYetKl95FzVY= +golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20220630215102-69896b714898 h1:K7wO6V1IrczY9QOQ2WkVpw4JQSwCd52UsxVEirZUfiw= +golang.org/x/net v0.0.0-20220630215102-69896b714898/go.mod h1:XRhObCWvk6IyKnWLug+ECip1KBveYUHfp+8e9klMJ9c= +golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20210806184541-e5e7981a1069/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e h1:CsOuNlbOuf0mzxJIefr6Q4uAUetRUwZE4qt7VfzP+xo= +golang.org/x/sys v0.0.0-20220704084225-05e143d24a9e/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= +golang.org/x/text v0.3.7 h1:olpwvP2KacW1ZWvsR7uQhoyTYvKAupfQrRGBFM352Gk= +golang.org/x/text v0.3.7/go.mod h1:u+2+/6zg+i71rQMx5EYifcz6MCKuco9NR6JIITiCfzQ= +golang.org/x/tools v0.0.0-20180917221912-90fa682c2a6e/go.mod h1:n7NCudcB/nEzxVGmLbDWY5pfWTLqBcC2KZ6jyYvM4mQ= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543 h1:E7g+9GITq07hpfrRu66IVDexMakfv52eLZ2CXBWiKr4= +golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= +google.golang.org/protobuf v1.26.0-rc.1/go.mod h1:jlhhOSvTdKEhbULTjvd4ARK9grFBp09yW+WbY/TyQbw= +google.golang.org/protobuf v1.28.0 h1:w43yiav+6bVFTBQFZX0r7ipe9JQ1QsbMgHwbBziscLw= +google.golang.org/protobuf v1.28.0/go.mod h1:HV8QOd/L58Z+nl8r43ehVNZIU/HEI6OcFqwMG9pJV4I= +gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c h1:Hei/4ADfdWqJk1ZMxUNpqntNwaWcugrBjAiHlqqRiVk= +gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EVd6muEfDQjcINNoR0C8j2r3qZ4Q= +gopkg.in/errgo.v2 v2.1.0/go.mod h1:hNsd1EY+bozCKY1Ytp96fpM3vjJbqLJn88ws8XvfDNI= +gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI= +gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY= +gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ= +gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.0-20210107192922-496545a6307b/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= +gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= +gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= diff --git a/pkg/asld/asld.go b/pkg/asld/asld.go new file mode 100644 index 0000000..429268d --- /dev/null +++ b/pkg/asld/asld.go @@ -0,0 +1,305 @@ +// Package asld handles JSON-LD for asflab +// +// This will not go well +package asld + +import ( + "bytes" + "errors" + "fmt" + "reflect" + "strings" +) + +const ( + tagName = "asld" +) + +var ( + ErrNoMatching = errors.New("could not find matching") + ErrSyntaxError = errors.New("syntax error") + ErrEntryWithoutValue = errors.New("entry without value") +) + +// assigned by init +var ( + byByte map[byte]Symbol + symbolsRaw []byte + iriPrefixes [][]byte + openSymbols []Symbol + openRaw []byte +) + +func init() { + byByte = map[byte]Symbol{} + for _, symbol := range symbols { + byByte[symbol.self] = symbol + } + symbolsRaw = make([]byte, len(symbols)) + for index, symbol := range symbols { + symbolsRaw[index] = symbol.self + } + iriPrefixesStrings := []string{ + // Currently only doing https + "https://", + } + iriPrefixes = make([][]byte, len(iriPrefixesStrings)) + for index, prefix := range iriPrefixesStrings { + iriPrefixes[index] = []byte(prefix) + } + openSymbolEnums := []int{ + symbolOpenParen, symbolOpenArray, symbolString, + } + openSymbols = make([]Symbol, len(openSymbolEnums)) + for index, enum := range openSymbolEnums { + openSymbols[index] = symbols[enum] + } + openRaw = _map(openSymbols, func(s Symbol) byte { + return s.self + }) +} + +type Symbol struct { + self byte + closer byte +} + +const ( + symbolOpenParen = iota + symbolClosedParen + symbolOpenArray + symbolClosedArray + symbolString + symbolColon +) + +const ( + statusOK = iota + statusError + statusWalkerNotAffected +) + +type status int +type walkerStatus struct { + walker + status +} + +var ( + symbols = []Symbol{ + symbolOpenParen: { + self: '{', + closer: '}', + }, + symbolClosedParen: { + self: '}', + }, + symbolOpenArray: { + self: '[', + closer: ']', + }, + symbolClosedArray: { + self: ']', + }, + symbolString: { + self: '"', + closer: '"', + }, + symbolColon: { + self: ':', + }, + } +) + +func in[T comparable](this []T, has T) bool { + for _, elem := range this { + if elem == has { + return true + } + } + return false +} + +func _map[T any, V any](v []T, f func(T) V) []V { + output := make([]V, len(v)) + for index, elem := range v { + output[index] = f(elem) + } + return output +} + +func isIRI(v []byte) bool { + for _, prefix := range iriPrefixes { + if bytes.Equal(v, prefix) { + return true + } + } + return false +} + +func arrayMembers(w walker) ([]walker, error) { + var ( + members = []walker{} + final bool + ) + + elements, ok := w.SliceInner() + if !ok { + return nil, fmt.Errorf("%s %w", string(w.content[w.position]), ErrNoMatching) + } + + for !final { + elem, err := elements.CommaOrEnd() + if err != nil { + return nil, fmt.Errorf("comma or end %s: %w", string(elem.content), err) + } + final = elem.status == statusWalkerNotAffected + value := elem.walker.Until().Reset() + if !final { + // Incremenet elem.walker.position here so + // that it skips over the comma that we're + // on right now. as there's not really anything + // valid to stop on in arrays aside from them + // so we can't just call ToNext() later. + elem.walker.position++ + } else { + value = elem.walker + } + elements = elem.walker.Sub() + members = append(members, value) + } + + return members, nil +} + +func mapMembers(w walker) (map[string]walker, error) { + var ( + members = map[string]walker{} + lastLine bool + ) + elements, ok := w.SliceInner() + if !ok { + return nil, fmt.Errorf("%s %w", string(w.content[w.position]), ErrNoMatching) + } + + for !lastLine { + lineInfo, err := elements.CommaOrEnd() + if err != nil { + return nil, fmt.Errorf("comma or end: %s: %w", string(elements.content), err) + } + lastLine = lineInfo.status == statusWalkerNotAffected + line := lineInfo.walker + if !lastLine { + line = line.Until() + } + + name, ok := line.Reset().ToOrStay('"') + if !ok { + continue + } + nameString, ok := name.SliceInner() + if !ok { + // TODO: maybe these should have global position + return nil, fmt.Errorf("%s %w", string(name.Current()), ErrNoMatching) + } + + // We know this is OK because the above SliceInner called it + name, _ = name.To('"') + + wNext, ok := name.Next() + if !ok && wNext.Current() != ':' && isIRI(nameString.content) { + panic("IRI expansion not implemented") + } else if !ok || wNext.Current() != ':' { + return nil, fmt.Errorf("%s: %w", string(nameString.content), ErrEntryWithoutValue) + } + + value, ok := wNext.Next() + if !ok { + if value.position < value.len-1 { + value.position++ + value = value.Sub() + } else { + return nil, fmt.Errorf("non-IRI %s: %w", string(nameString.content), ErrEntryWithoutValue) + } + } + elements = lineInfo.walker.Sub() + members[string(nameString.content)] = value + } + + return members, nil +} + +func unmarshalMap(out reflect.Value, w walker) error { + members, err := mapMembers(w) + if err != nil { + return fmt.Errorf("getting members: %w", err) + } + + outType := out.Type() + // Deconstruct the struct fields + for index := 0; index < out.NumField(); index++ { + field := out.Field(index) + fType := outType.Field(index) + + tagInfo := fType.Tag.Get(tagName) + // TODO: support expandible/collapsible/whatever I name it + // and omitempty probably + tagParts := strings.Split(tagInfo, ",") + name := tagParts[0] + if tagInfo == "" || name == "" { + name = fType.Name + } + // mimic encoding/json behavior + if name == "-" && len(tagParts) == 1 { + continue + } + + wField, exists := members[name] + if !exists { + continue + } + setValue(fType, field, wField) + } + + return nil +} + +func setValue(fType reflect.StructField, field reflect.Value, w walker) error { + switch field.Kind() { + case reflect.String: + if w.content[0] != '"' { + return fmt.Errorf("%s is not a string", string(w.content)) + } + field.SetString(w.String()) + default: + panic("not implemented") + } + + return nil +} + +func unmarshal(out reflect.Value, w walker) error { + switch out.Kind() { + case reflect.Struct: + return unmarshalMap(out, w) + case reflect.Array, reflect.Slice: + // do array stuff here + default: + panic(out.Kind().String() + " not yet supported") + } + + return nil +} + +func (w walker) Reset() walker { + w.position = 0 + return w +} + +func Unmarshal[T any](data []byte) (T, error) { + tPtr := new(T) + tValue := reflect.Indirect(reflect.ValueOf(tPtr)) + w := newWalker(data) + + return *tPtr, unmarshal(tValue, w) +} diff --git a/pkg/asld/unmarshal_test.go b/pkg/asld/unmarshal_test.go new file mode 100644 index 0000000..21c8b17 --- /dev/null +++ b/pkg/asld/unmarshal_test.go @@ -0,0 +1,39 @@ +package asld_test + +import ( + "encoding/json" + "testing" + + "git.sectorinf.com/emilis/asflab/pkg/asld" + "git.sectorinf.com/emilis/asflab/pkg/epk" + "github.com/stretchr/testify/require" +) + +type testObj struct { + String string `asld:"string" json:"string"` +} + +func TestUnmarshal(t *testing.T) { + tests := map[string]struct { + obj testObj + errCheck epk.ErrorTest + }{ + "string children": { + obj: testObj{ + "hello", + }, + }, + } + + for name, test := range tests { + test := test + t.Run(name, func(tt *testing.T) { + that := require.New(tt) + objJSON, err := json.Marshal(test.obj) + that.NoError(err) + result, err := asld.Unmarshal[testObj](objJSON) + that.NoError(err) + that.Equal(test.obj, result) + }) + } +} diff --git a/pkg/asld/walker.go b/pkg/asld/walker.go new file mode 100644 index 0000000..0aefbc9 --- /dev/null +++ b/pkg/asld/walker.go @@ -0,0 +1,161 @@ +package asld + +import "fmt" + +// Walker.... texas ranger. +// Except he's a cringe conservative. +// +// This is also cringe but not for cringe reasons. + +func (w walker) Debug_PRINT() { + for index, b := range w.content { + out := string(b) + if w.position == index { + out = "<[>" + out + "<]>" + } + print(out) + } + print("\n") +} + +func (w walker) SliceInner() (walker, bool) { + // the !ok scenario here is only if the code is bad + s, ok := byByte[w.Current()] + // Debug + if !ok { + panic(w) + } + var height uint + for pos := w.position + 1; pos < w.len; pos++ { + curr := w.content[pos] + if curr == s.self && s.self != s.closer { + height++ + continue + } + if curr == s.closer { + if height == 0 { + return newWalker(w.content[w.position+1 : pos]), ok + } + height-- + } + } + return w, false +} + +// Sub returns a subwalker from the current position +func (w walker) Sub() walker { + w.content = w.content[w.position:] + w.len = len(w.content) + w.position = 0 + return w +} + +type walker struct { + content []byte + len int + position int +} + +func newWalker(data []byte) walker { + return walker{ + content: data, + len: len(data), + } +} + +// Until returns a subwalker from position 0 to the current position +func (w walker) Until() walker { + w.content = w.content[:w.position] + w.len = len(w.content) + return w +} + +// ToOrStay will stay at where the walker is if b is the +// same as the current walker position. +// +// Otherwise, calls To +func (w walker) ToOrStay(b byte) (walker, bool) { + if w.Current() == b { + return w, true + } + return w.To(b) +} + +func (w walker) To(b byte) (walker, bool) { + for pos := w.position + 1; pos < w.len; pos++ { + if w.content[pos] == b { + w.position = pos + return w, true + } + } + return w, false +} + +func (w walker) Next() (walker, bool) { + w, ok := w.ToNext() + return w.Sub(), ok +} + +func (w walker) ToNext() (walker, bool) { + for pos := w.position + 1; pos < w.len; pos++ { + if in(symbolsRaw, w.content[pos]) { + w.position = pos + return w, true + } + } + return w, false +} + +// CommaOrEnd walks to the next comma, or, if none +// is present in the current walker scope, returns +// itself with status statusWalkerNotAffected. +func (w walker) CommaOrEnd() (walkerStatus, error) { + if w.Current() == ',' { + w.position++ + } + for pos := w.position; pos < w.len; pos++ { + if in(openRaw, w.content[pos]) { + sb, ok := byByte[w.content[pos]] + if !ok { + panic("ok someone fucked up somewhere") + } + w, ok = w.To(sb.closer) + if !ok { + return walkerStatus{ + status: statusError, + walker: w, + }, fmt.Errorf("%w %s", ErrNoMatching, string(sb.closer)) + } + pos = w.position + continue + } + if w.content[pos] == ',' { + w.position = pos + return walkerStatus{ + status: statusOK, + walker: w, + }, nil + } + } + + return walkerStatus{ + status: statusWalkerNotAffected, + walker: w, + }, nil +} + +func (w walker) Current() byte { + return w.content[w.position] +} + +func (w walker) CurrentSymbol() (Symbol, bool) { + b, ok := byByte[w.Current()] + return b, ok +} + +func (w walker) String() string { + if w.Current() == '"' { + w, _ = w.SliceInner() + } + return string(w.content) +} diff --git a/pkg/asld/walker_test.go b/pkg/asld/walker_test.go new file mode 100644 index 0000000..36cceb9 --- /dev/null +++ b/pkg/asld/walker_test.go @@ -0,0 +1,190 @@ +package asld + +import ( + "encoding/json" + "math" + "testing" + + "git.sectorinf.com/emilis/asflab/pkg/epk" + "github.com/stretchr/testify/require" +) + +func TestWalkerSliceInner(t *testing.T) { + tests := map[string]struct { + data string + position int + expect bool + expected string + }{ + "paren simple": { + data: `{"hello":"world"}`, + expect: true, + expected: `"hello":"world"`, + }, + "paren child nested": { + data: `{"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world"}}}}`, + expect: true, + expected: `"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world", "sub":{"hello":"world"}}}`, + }, + "get within string": { + data: `{"hello":"world"}`, + position: 1, + expect: true, + expected: `hello`, + }, + "inverted array": { + data: `][`, + expect: false, + expected: `][`, + }, + } + + for name, test := range tests { + test := test + // if name != "get within string" { + // continue + // } + t.Run(name, func(tt *testing.T) { + that := require.New(tt) + w := newWalker([]byte(test.data)) + w.position = test.position + sub, ok := w.SliceInner() + that.Equal(test.expect, ok) + that.Equal(test.expected, string(sub.content)) + }) + } +} + +func TestNexts(t *testing.T) { + tests := map[string]struct { + contents string + expPos int + }{ + "doesn't stay at the same location if is on a next": { + contents: `"hello"`, + expPos: 6, + }, + } + + for name, test := range tests { + test := test + t.Run(name, func(tt *testing.T) { + that := require.New(t) + w := newWalker([]byte(test.contents)) + w, ok := w.To('"') + that.True(ok) + w, ok = w.ToNext() + that.True(ok) + that.Equal(test.expPos, w.position, "positions are not the same") + }) + } +} + +func TestMapMembers(t *testing.T) { + tests := map[string]struct { + object map[string]any + errCheck epk.ErrorTest + }{ + "all types": { + object: map[string]any{ + "hello": "world", + "object_with_children": map[string]any{ + "hello": "world", + }, + "floating point": 123.456, + "integer": -50, + "uint": uint(math.MaxUint), + "array": []string{"hello", "world"}, + }, + }, + } + + for name, test := range tests { + test := test + t.Run(name, func(tt *testing.T) { + that := require.New(tt) + objJson, err := json.Marshal(test.object) + that.NoError(err) + w := newWalker(objJson) + members, err := mapMembers(w) + if test.errCheck == nil { + test.errCheck = epk.NoError + } + test.errCheck(that, err) + for key, val := range test.object { + valJson, err := json.Marshal(val) + that.NoError(err) + walker, exists := members[key] + that.True(exists) + that.Equal(valJson, walker.content) + } + }) + } +} + +func jsonElemList[T any](that *require.Assertions, a any) [][]byte { + that.NotNil(a) + v, ok := a.([]T) + that.True(ok) + fields := make([][]byte, len(v)) + for index, field := range v { + jsonField, err := json.Marshal(field) + that.NoError(err) + fields[index] = jsonField + } + return fields +} + +type hello struct { + Hello string +} + +func TestArrayMembers(t *testing.T) { + tests := map[string]struct { + object any + errCheck epk.ErrorTest + toJsonElementList func(*require.Assertions, any) [][]byte + }{ + "strings": { + object: []string{"a", "b", "c", "d"}, + toJsonElementList: jsonElemList[string], + }, + "ints": { + object: []int{1, 2, 3, 4, 5, 6}, + toJsonElementList: jsonElemList[int], + }, + "floats": { + object: []float64{123.456, 789.0123, 456.789}, + toJsonElementList: jsonElemList[float64], + }, + "strings with commas": { + object: []string{"this, is what I do", "what, don't like it?"}, + toJsonElementList: jsonElemList[string], + }, + "objects": { + object: []hello{{"world"}, {"mom, dad"}}, + toJsonElementList: jsonElemList[hello], + }, + } + + for name, test := range tests { + test := test + t.Run(name, func(tt *testing.T) { + that := require.New(tt) + objJson, err := json.Marshal(test.object) + that.NoError(err) + w := newWalker(objJson) + members, err := arrayMembers(w) + if test.errCheck == nil { + test.errCheck = epk.NoError + } + test.errCheck(that, err) + expected := test.toJsonElementList(that, test.object) + that.Len(members, len(expected)) + for index, elem := range expected { + equivMember := members[index] + that.Equal(elem, equivMember.content) + } + }) + } +} diff --git a/pkg/epk/epk.go b/pkg/epk/epk.go new file mode 100644 index 0000000..3f55c58 --- /dev/null +++ b/pkg/epk/epk.go @@ -0,0 +1,34 @@ +package epk + +import "github.com/stretchr/testify/require" + +type ErrorTest func(*require.Assertions, error) + +var ( + NoError ErrorTest = func(a *require.Assertions, err error) { + a.NoError(err) + } + Error ErrorTest = func(a *require.Assertions, err error) { + a.Error(err) + } +) + +func ErrorIs(target error, contains ...string) ErrorTest { + return func(a *require.Assertions, err error) { + a.Error(err) + a.ErrorIs(err, target) + for _, elem := range contains { + a.Contains(err.Error(), elem) + } + } +} + +func NotErrorIs(target error, contains ...string) ErrorTest { + return func(a *require.Assertions, err error) { + a.Error(err) + a.NotErrorIs(err, target) + for _, elem := range contains { + a.Contains(err.Error(), elem) + } + } +}