├── .github └── workflows │ ├── build.yml │ ├── lint.yml │ └── test.yml ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.MD ├── collections ├── boundaries.go ├── boundaries_test.go ├── copy.go ├── copy_test.go ├── flat.go ├── flat_test.go ├── hierarchic.go ├── hierarchic_test.go ├── reference.go ├── reference_test.go ├── size_align_pad.go └── size_align_pad_test.go ├── examples ├── cache_rounding_cpu_l1 │ └── transaction │ │ ├── transaction.go │ │ └── transaction_test.go ├── false_sharing_cpu_l1 │ └── transaction │ │ ├── transaction.go │ │ └── transaction_test.go ├── memory_pack │ └── transaction │ │ ├── transaction.go │ │ └── transaction_test.go └── transaction │ ├── transaction.go │ └── transaction_test.go ├── extensions └── vscode │ ├── .prettierrc.json │ ├── .vscode │ ├── launch.json │ └── tasks.json │ ├── .vscodeignore │ ├── LICENSE │ ├── README.MD │ ├── gopher.png │ ├── package-lock.json │ ├── package.json │ ├── src │ └── extension.ts │ ├── tsconfig.json │ ├── tslint.json │ └── vscode.gif ├── fmtio ├── ast.go ├── ast_test.go ├── astutil │ ├── apply.go │ ├── apply_test.go │ ├── package.go │ ├── package_test.go │ ├── walk.go │ └── walk_test.go ├── bytes.go ├── bytes_test.go ├── diff.go ├── diff_test.go ├── gofmt.go ├── gofmt_test.go ├── goprinter.go ├── goprinter_test.go ├── io.go ├── io_test.go ├── writer.go └── writer_test.go ├── go.mod ├── go.sum ├── gopher.kra ├── gopher.png ├── gopium ├── adt.go ├── ast.go ├── fmt.go ├── maven.go ├── opium.go ├── parser.go ├── persister.go ├── printer.go ├── runner.go ├── strategy.go ├── struct.go ├── walker.go └── writer.go ├── main.go ├── runners ├── cli.go ├── cli_test.go ├── visitor.go └── visitor_test.go ├── strategies ├── builder.go ├── builder_test.go ├── cache.go ├── cache_test.go ├── filter.go ├── filter_test.go ├── fshare.go ├── fshare_test.go ├── group.go ├── group_test.go ├── ignore.go ├── ignore_test.go ├── nlex.go ├── nlex_test.go ├── note.go ├── note_test.go ├── pack.go ├── pack_test.go ├── pad.go ├── pad_test.go ├── pipe.go ├── pipe_test.go ├── sep.go ├── sep_test.go ├── tag.go ├── tag_test.go ├── tlex.go ├── tlex_test.go ├── unpack.go └── unpack_test.go ├── tests ├── data │ ├── embedded │ │ └── file.go │ ├── empty │ │ └── file.go │ ├── flat │ │ └── file.go │ ├── locator.go │ ├── multi │ │ ├── file-1.go │ │ ├── file-2.go │ │ └── file-3.go │ ├── nested │ │ └── file.go │ ├── note │ │ ├── file-1.go │ │ ├── file-2.go │ │ └── file-3.go │ ├── parser.go │ ├── purify.go │ ├── single │ │ └── file.go │ └── writer.go ├── mocks │ ├── ast.go │ ├── context.go │ ├── fmt.go │ ├── maven.go │ ├── parser.go │ ├── persister.go │ ├── printer.go │ ├── runner.go │ ├── rwc.go │ ├── strategy.go │ ├── walker.go │ └── writer.go ├── os.go └── path.go ├── typepkg ├── locator.go ├── locator_test.go ├── maven.go ├── maven_test.go ├── parser.go ├── parser_test.go └── sizes.go └── walkers ├── builder.go ├── builder_test.go ├── maven.go ├── maven_test.go ├── visit.go ├── visit_test.go ├── wast.go ├── wast_test.go ├── wdiff.go ├── wdiff_test.go ├── wout.go └── wout_test.go /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: build 2 | on: [push, pull_request] 3 | jobs: 4 | build: 5 | strategy: 6 | matrix: 7 | go-version: [1.22.x] 8 | platform: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.platform }} 10 | steps: 11 | - name: setup 12 | uses: actions/setup-go@v5 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: checkout 16 | uses: actions/checkout@v4 17 | - name: build 18 | run: go build ./... 19 | -------------------------------------------------------------------------------- /.github/workflows/lint.yml: -------------------------------------------------------------------------------- 1 | name: lint 2 | on: [push, pull_request] 3 | jobs: 4 | lint: 5 | strategy: 6 | matrix: 7 | go-version: [1.22.x] 8 | platform: [ubuntu-latest] 9 | runs-on: ${{ matrix.platform }} 10 | steps: 11 | - name: setup 12 | uses: actions/setup-go@v5 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - uses: actions/checkout@v4 16 | - name: golangci-lint 17 | uses: golangci/golangci-lint-action@v4 18 | with: 19 | version: v1.57 20 | args: -E misspell 21 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: test 2 | on: [push, pull_request] 3 | jobs: 4 | test: 5 | strategy: 6 | matrix: 7 | go-version: [1.22.x] 8 | platform: [ubuntu-latest, macos-latest, windows-latest] 9 | runs-on: ${{ matrix.platform }} 10 | steps: 11 | - name: setup 12 | uses: actions/setup-go@v5 13 | with: 14 | go-version: ${{ matrix.go-version }} 15 | - name: checkout 16 | uses: actions/checkout@v4 17 | - name: test 18 | uses: nick-invision/retry@v3 19 | with: 20 | max_attempts: 3 21 | timeout_minutes: 10 22 | command: go test -v -race -count=1 -coverprofile test.cover ./... 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | extensions/vscode/out 2 | extensions/vscode/*.vsix 3 | # don't vendor vscode ext node modules 4 | # as it uses npm package-lock way already 5 | extensions/vscode/node_modules -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "cmd/vscode/vscode-go"] 2 | path = extensions/vscode/src/vscode-go 3 | url = https://github.com/1pkg/vscode-go.git 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Kostiantyn Masliuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /collections/boundaries.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import "go/token" 4 | 5 | // Boundary defines sorted pos pair type 6 | type Boundary struct { 7 | First token.Pos `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 8 | Last token.Pos `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 9 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 10 | 11 | // Less checks if pos strictly less then boundary 12 | func (b Boundary) Less(p token.Pos) bool { 13 | return b.Last < p 14 | } 15 | 16 | // Greater checks if pos strictly greater then boundary 17 | func (b Boundary) Greater(p token.Pos) bool { 18 | return b.First > p 19 | } 20 | 21 | // Inside checks if pos inside boundary 22 | func (b Boundary) Inside(p token.Pos) bool { 23 | return !b.Less(p) && !b.Greater(p) 24 | } 25 | 26 | // Boundaries defines ordered set of boundary 27 | type Boundaries []Boundary 28 | 29 | // Inside checks if pos inside boundaries 30 | // by using binary search to check boundaries 31 | func (bs Boundaries) Inside(p token.Pos) bool { 32 | // use binary search to check boundaries 33 | l, r := 0, len(bs)-1 34 | for l <= r { 35 | // calculate the index 36 | i := (l + r) / 2 37 | b := bs[i] 38 | // if pos is inside 39 | // we found the answer 40 | if b.Inside(p) { 41 | return true 42 | } 43 | // if pos is inside 44 | // left half search there 45 | if b.Greater(p) { 46 | r = i - 1 47 | continue 48 | } 49 | // if comment is inside 50 | // right half search there 51 | if b.Less(p) { 52 | l = i + 1 53 | continue 54 | } 55 | } 56 | // we found the answer 57 | return false 58 | } 59 | -------------------------------------------------------------------------------- /collections/boundaries_test.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "go/token" 5 | "reflect" 6 | "testing" 7 | ) 8 | 9 | func TestBoundaries(t *testing.T) { 10 | // prepare 11 | table := map[string]struct { 12 | b Boundaries 13 | p token.Pos 14 | in bool 15 | }{ 16 | "nil boundaries shouldn't containt the pos": { 17 | b: nil, 18 | p: token.Pos(1), 19 | }, 20 | "empty boundaries shouldn't containt the pos": { 21 | b: Boundaries{}, 22 | p: token.Pos(1), 23 | }, 24 | "bigger boundaries shouldn't containt the pos": { 25 | b: Boundaries{ 26 | Boundary{20, 40}, 27 | Boundary{50, 80}, 28 | Boundary{90, 100}, 29 | }, 30 | p: token.Pos(10), 31 | }, 32 | "lowwer boundaries shouldn't containt the pos": { 33 | b: Boundaries{ 34 | Boundary{20, 40}, 35 | Boundary{50, 80}, 36 | Boundary{90, 100}, 37 | }, 38 | p: token.Pos(1000), 39 | }, 40 | "non overlapped boundaries shouldn't containt the pos": { 41 | b: Boundaries{ 42 | Boundary{1, 15}, 43 | Boundary{20, 40}, 44 | Boundary{50, 80}, 45 | Boundary{90, 100}, 46 | Boundary{120, 200}, 47 | Boundary{250, 512}, 48 | Boundary{600, 999}, 49 | }, 50 | p: token.Pos(88), 51 | }, 52 | "overlapped boundaries should containt the pos": { 53 | b: Boundaries{ 54 | Boundary{1, 15}, 55 | Boundary{20, 40}, 56 | Boundary{50, 80}, 57 | Boundary{90, 100}, 58 | Boundary{120, 200}, 59 | Boundary{250, 512}, 60 | Boundary{600, 999}, 61 | }, 62 | p: token.Pos(510), 63 | in: true, 64 | }, 65 | } 66 | for name, tcase := range table { 67 | t.Run(name, func(t *testing.T) { 68 | // exec 69 | in := tcase.b.Inside(tcase.p) 70 | // check 71 | if !reflect.DeepEqual(in, tcase.in) { 72 | t.Errorf("actual %v doesn't equal to %v", in, tcase.in) 73 | } 74 | }) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /collections/copy.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import "github.com/1pkg/gopium/gopium" 4 | 5 | // CopyField defines helper that 6 | // deep copies provided field 7 | func CopyField(f gopium.Field) gopium.Field { 8 | nf := f 9 | // check that field doc exists 10 | if f.Doc != nil { 11 | nf.Doc = make([]string, len(f.Doc), cap(f.Doc)) 12 | copy(nf.Doc, f.Doc) 13 | } 14 | // check that field comment exists 15 | if f.Comment != nil { 16 | nf.Comment = make([]string, len(f.Comment), cap(f.Comment)) 17 | copy(nf.Comment, f.Comment) 18 | } 19 | return nf 20 | } 21 | 22 | // CopyStruct defines helper that 23 | // deep copies provided struct 24 | func CopyStruct(s gopium.Struct) gopium.Struct { 25 | ns := s 26 | // check that struct doc exists 27 | if s.Doc != nil { 28 | ns.Doc = make([]string, len(s.Doc), cap(s.Doc)) 29 | copy(ns.Doc, s.Doc) 30 | } 31 | // check that struct comment exists 32 | if s.Comment != nil { 33 | ns.Comment = make([]string, len(s.Comment), cap(s.Comment)) 34 | copy(ns.Comment, s.Comment) 35 | } 36 | // check that struct fields exists 37 | if s.Fields != nil { 38 | ns.Fields = make([]gopium.Field, len(s.Fields), cap(s.Fields)) 39 | copy(ns.Fields, s.Fields) 40 | } 41 | // go through struct fields and 42 | // deep copy them one by one 43 | for i, f := range ns.Fields { 44 | ns.Fields[i] = CopyField(f) 45 | } 46 | return ns 47 | } 48 | -------------------------------------------------------------------------------- /collections/copy_test.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | func TestCopyField(t *testing.T) { 11 | // prepare 12 | table := map[string]struct { 13 | o gopium.Field 14 | r gopium.Field 15 | }{ 16 | "empty field should be copied to empty field": { 17 | o: gopium.Field{}, 18 | r: gopium.Field{}, 19 | }, 20 | "non empty field should be copied to same field": { 21 | o: gopium.Field{ 22 | Name: "test", 23 | Type: "type", 24 | Exported: true, 25 | }, 26 | r: gopium.Field{ 27 | Name: "test", 28 | Type: "type", 29 | Exported: true, 30 | }, 31 | }, 32 | "non empty field with notes should be copied to same field": { 33 | o: gopium.Field{ 34 | Name: "test", 35 | Type: "type", 36 | Exported: true, 37 | Doc: []string{"test-doc"}, 38 | Comment: []string{"test-com-1", "test-com-2"}, 39 | }, 40 | r: gopium.Field{ 41 | Name: "test", 42 | Type: "type", 43 | Exported: true, 44 | Doc: []string{"test-doc"}, 45 | Comment: []string{"test-com-1", "test-com-2"}, 46 | }, 47 | }, 48 | } 49 | for name, tcase := range table { 50 | t.Run(name, func(t *testing.T) { 51 | // exec 52 | r := CopyField(tcase.o) 53 | // check 54 | if !reflect.DeepEqual(r, tcase.r) { 55 | t.Errorf("actual %v doesn't equal to %v", r, tcase.r) 56 | } 57 | }) 58 | } 59 | } 60 | 61 | func TestCopyStruct(t *testing.T) { 62 | // prepare 63 | table := map[string]struct { 64 | o gopium.Struct 65 | r gopium.Struct 66 | }{ 67 | "empty struct should be copied to empty struct": { 68 | o: gopium.Struct{}, 69 | r: gopium.Struct{}, 70 | }, 71 | "non empty struct should be copied to same struct": { 72 | o: gopium.Struct{ 73 | Name: "test", 74 | }, 75 | r: gopium.Struct{ 76 | Name: "test", 77 | }, 78 | }, 79 | "non empty struct with notes should be copied to same struct": { 80 | o: gopium.Struct{ 81 | Name: "test", 82 | Doc: []string{"test-doc"}, 83 | Comment: []string{"test-com-1", "test-com-2"}, 84 | }, 85 | r: gopium.Struct{ 86 | Name: "test", 87 | Doc: []string{"test-doc"}, 88 | Comment: []string{"test-com-1", "test-com-2"}, 89 | }, 90 | }, 91 | "non empty struct with notes and fields should be copied to same struct": { 92 | o: gopium.Struct{ 93 | Name: "test", 94 | Doc: []string{"test-doc"}, 95 | Comment: []string{"test-com-1", "test-com-2"}, 96 | Fields: []gopium.Field{ 97 | { 98 | Name: "test", 99 | Type: "type", 100 | Exported: true, 101 | }, 102 | { 103 | Name: "test", 104 | Type: "type", 105 | Embedded: true, 106 | Doc: []string{"test-doc"}, 107 | Comment: []string{"test-com-1", "test-com-2"}, 108 | }, 109 | }, 110 | }, 111 | r: gopium.Struct{ 112 | Name: "test", 113 | Doc: []string{"test-doc"}, 114 | Comment: []string{"test-com-1", "test-com-2"}, 115 | Fields: []gopium.Field{ 116 | { 117 | Name: "test", 118 | Type: "type", 119 | Exported: true, 120 | }, 121 | { 122 | Name: "test", 123 | Type: "type", 124 | Embedded: true, 125 | Doc: []string{"test-doc"}, 126 | Comment: []string{"test-com-1", "test-com-2"}, 127 | }, 128 | }, 129 | }, 130 | }, 131 | } 132 | for name, tcase := range table { 133 | t.Run(name, func(t *testing.T) { 134 | // exec 135 | r := CopyStruct(tcase.o) 136 | // check 137 | if !reflect.DeepEqual(r, tcase.r) { 138 | t.Errorf("actual %v doesn't equal to %v", r, tcase.r) 139 | } 140 | }) 141 | } 142 | } 143 | -------------------------------------------------------------------------------- /collections/flat.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "fmt" 5 | "sort" 6 | "strings" 7 | 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // Flat defines strucs flat collection 12 | // which is categorized only by id 13 | type Flat map[string]gopium.Struct 14 | 15 | // Sorted converts flat collection 16 | // to sorted slice of structs 17 | // note: it's possible due to next: 18 | // generated ids are ordered inside same loc 19 | func (f Flat) Sorted() []gopium.Struct { 20 | // preapare ids and sorted slice 21 | ids := make([]string, 0, len(f)) 22 | sorted := make([]gopium.Struct, 0, len(f)) 23 | // collect all ids 24 | for id := range f { 25 | ids = append(ids, id) 26 | } 27 | // sort all ids in asc order 28 | sort.SliceStable(ids, func(i, j int) bool { 29 | // only first part of "%d-%s" id is ordered 30 | // so we need to parse and compare it 31 | var numi, numj int 32 | var sumi, sumj string 33 | // scanf works only with space 34 | // separated values so we need 35 | // to apply this format first 36 | // 37 | // in case of any pattern error 38 | // just apply natural sort 39 | // otherwise sort it by id 40 | _, erri := fmt.Sscanf(strings.Replace(ids[i], ":", " ", 1), "%s %d", &sumi, &numi) 41 | _, errj := fmt.Sscanf(strings.Replace(ids[j], ":", " ", 1), "%s %d", &sumj, &numj) 42 | switch { 43 | case erri != nil && errj != nil: 44 | return ids[i] < ids[j] 45 | case erri != nil: 46 | return false 47 | case errj != nil: 48 | return true 49 | default: 50 | return numi < numj 51 | } 52 | }) 53 | // collect all structs in asc order 54 | for _, id := range ids { 55 | sorted = append(sorted, f[id]) 56 | } 57 | return sorted 58 | } 59 | -------------------------------------------------------------------------------- /collections/flat_test.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | func TestFlatSorted(t *testing.T) { 11 | // prepare 12 | table := map[string]struct { 13 | f Flat 14 | r []gopium.Struct 15 | }{ 16 | "nil flat collection should return empty sorted": { 17 | f: nil, 18 | r: []gopium.Struct{}, 19 | }, 20 | "empty flat collection should return empty sorted": { 21 | f: Flat{}, 22 | r: []gopium.Struct{}, 23 | }, 24 | "single item flat collection should return single item sorted": { 25 | f: Flat{"test:1": gopium.Struct{Name: "test1"}}, 26 | r: []gopium.Struct{{Name: "test1"}}, 27 | }, 28 | "multiple presorted items flat collection should return multiple items sorted": { 29 | f: Flat{ 30 | "test:1": gopium.Struct{Name: "test1"}, 31 | "test:2": gopium.Struct{Name: "test2"}, 32 | "test:3": gopium.Struct{Name: "test3"}, 33 | }, 34 | r: []gopium.Struct{ 35 | {Name: "test1"}, 36 | {Name: "test2"}, 37 | {Name: "test3"}, 38 | }, 39 | }, 40 | "multiple reverted items flat collection should return multiple items sorted": { 41 | f: Flat{ 42 | "test:3": gopium.Struct{Name: "test3"}, 43 | "test:2": gopium.Struct{Name: "test2"}, 44 | "test:1": gopium.Struct{Name: "test1"}, 45 | }, 46 | r: []gopium.Struct{ 47 | {Name: "test1"}, 48 | {Name: "test2"}, 49 | {Name: "test3"}, 50 | }, 51 | }, 52 | "multiple mixed items flat collection should return multiple items sorted": { 53 | f: Flat{ 54 | "test:99": gopium.Struct{Name: "test99"}, 55 | "test:5": gopium.Struct{Name: "test5"}, 56 | "test:1000": gopium.Struct{Name: "test1000"}, 57 | "test:3": gopium.Struct{Name: "test3"}, 58 | "test:1": gopium.Struct{Name: "test1"}, 59 | "test:2": gopium.Struct{Name: "test2"}, 60 | "test:4": gopium.Struct{Name: "test4"}, 61 | "test:0": gopium.Struct{Name: "test0"}, 62 | }, 63 | r: []gopium.Struct{ 64 | {Name: "test0"}, 65 | {Name: "test1"}, 66 | {Name: "test2"}, 67 | {Name: "test3"}, 68 | {Name: "test4"}, 69 | {Name: "test5"}, 70 | {Name: "test99"}, 71 | {Name: "test1000"}, 72 | }, 73 | }, 74 | "multiple non pattern ids items flat collection should return items sorted naturally": { 75 | f: Flat{ 76 | "test:a": gopium.Struct{Name: "testa"}, 77 | "test:b": gopium.Struct{Name: "testb"}, 78 | "test:c": gopium.Struct{Name: "testc"}, 79 | }, 80 | r: []gopium.Struct{ 81 | {Name: "testa"}, 82 | {Name: "testb"}, 83 | {Name: "testc"}, 84 | }, 85 | }, 86 | "multiple mixed non pattern ids items flat collection should return items sorted naturally": { 87 | f: Flat{ 88 | "test:3": gopium.Struct{Name: "test3"}, 89 | "test:2": gopium.Struct{Name: "test2"}, 90 | "test:1": gopium.Struct{Name: "test1"}, 91 | "test:a": gopium.Struct{Name: "testa"}, 92 | "test:b": gopium.Struct{Name: "testb"}, 93 | "test:c": gopium.Struct{Name: "testc"}, 94 | }, 95 | r: []gopium.Struct{ 96 | {Name: "test1"}, 97 | {Name: "test2"}, 98 | {Name: "test3"}, 99 | {Name: "testa"}, 100 | {Name: "testb"}, 101 | {Name: "testc"}, 102 | }, 103 | }, 104 | "complex multiple mixed non pattern ids items flat collection should return items sorted naturally": { 105 | f: Flat{ 106 | "test:z": gopium.Struct{Name: "testz"}, 107 | "test:3": gopium.Struct{Name: "test3"}, 108 | "test:2": gopium.Struct{Name: "test2"}, 109 | "test:1": gopium.Struct{Name: "test1"}, 110 | "test:a": gopium.Struct{Name: "testa"}, 111 | "test:b": gopium.Struct{Name: "testb"}, 112 | "test:c": gopium.Struct{Name: "testc"}, 113 | "test:99": gopium.Struct{Name: "test99"}, 114 | "test:5": gopium.Struct{Name: "test5"}, 115 | "test:1000": gopium.Struct{Name: "test1000"}, 116 | "test:4": gopium.Struct{Name: "test4"}, 117 | "test:0": gopium.Struct{Name: "test0"}, 118 | "test:xy": gopium.Struct{Name: "testxy"}, 119 | }, 120 | r: []gopium.Struct{ 121 | {Name: "test0"}, 122 | {Name: "test1"}, 123 | {Name: "test2"}, 124 | {Name: "test3"}, 125 | {Name: "test4"}, 126 | {Name: "test5"}, 127 | {Name: "test99"}, 128 | {Name: "test1000"}, 129 | {Name: "testa"}, 130 | {Name: "testb"}, 131 | {Name: "testc"}, 132 | {Name: "testxy"}, 133 | {Name: "testz"}, 134 | }, 135 | }, 136 | } 137 | for name, tcase := range table { 138 | t.Run(name, func(t *testing.T) { 139 | // exec 140 | r := tcase.f.Sorted() 141 | // check 142 | if !reflect.DeepEqual(r, tcase.r) { 143 | t.Errorf("actual %v doesn't equal to expected %v", r, tcase.r) 144 | } 145 | }) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /collections/hierarchic.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "fmt" 5 | "path/filepath" 6 | "sort" 7 | "strings" 8 | 9 | "github.com/1pkg/gopium/gopium" 10 | ) 11 | 12 | // Hierarchic defines strucs hierarchic collection 13 | // which is categorized by pair of loc and id 14 | type Hierarchic struct { 15 | cats map[string]Flat `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 16 | rcat string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 17 | _ [8]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 18 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 19 | 20 | // NewHierarchic creates new hierarchic 21 | // collection with root category 22 | func NewHierarchic(rcat string) Hierarchic { 23 | return Hierarchic{ 24 | rcat: rcat, 25 | cats: make(map[string]Flat), 26 | } 27 | } 28 | 29 | // Push adds struct to hierarchic collection 30 | func (h Hierarchic) Push(key string, cat string, sts ...gopium.Struct) { 31 | // remove root cat from the cat 32 | cat = strings.Replace(cat, h.rcat, "", 1) 33 | // if cat hasn't been created yet 34 | flat, ok := h.cats[cat] 35 | if !ok { 36 | flat = make(Flat) 37 | } 38 | // push not structs to flat collection 39 | switch l := len(sts); { 40 | case l == 1: 41 | flat[key] = sts[0] 42 | case l > 1: 43 | // if we have list of struct 44 | // make unique keys 45 | for i, st := range sts { 46 | flat[fmt.Sprintf("%s-%d", key, i)] = st 47 | } 48 | } 49 | // update hierarchic structs collection 50 | h.cats[cat] = flat 51 | } 52 | 53 | // Cat returns hierarchic categoty 54 | // flat collection if any exists 55 | func (h Hierarchic) Cat(cat string) (map[string]gopium.Struct, bool) { 56 | // remove root cat from the cat 57 | cat = strings.Replace(cat, h.rcat, "", 1) 58 | flat, ok := h.cats[cat] 59 | return flat, ok 60 | } 61 | 62 | // Catflat returns hierarchic categoty 63 | // flat collection if any exists 64 | func (h Hierarchic) Catflat(cat string) (Flat, bool) { 65 | // cat flat is just alias to cat 66 | f, ok := h.Cat(cat) 67 | return Flat(f), ok 68 | } 69 | 70 | // Full converts hierarchic collection to flat collection 71 | func (h Hierarchic) Full() map[string]gopium.Struct { 72 | // collect all structs by key 73 | flat := make(Flat) 74 | for _, lsts := range h.cats { 75 | for key, st := range lsts { 76 | flat[key] = st 77 | } 78 | } 79 | return flat 80 | } 81 | 82 | // Flat converts hierarchic collection to flat collection 83 | func (h Hierarchic) Flat() Flat { 84 | // flat is just alias to full 85 | return Flat(h.Full()) 86 | } 87 | 88 | // Rcat finds root category that 89 | // all other category contain for collection 90 | func (h Hierarchic) Rcat() string { 91 | // make cats order predictable 92 | cats := make([]string, 0, len(h.cats)) 93 | for cat := range h.cats { 94 | cats = append(cats, cat) 95 | } 96 | sort.Strings(cats) 97 | // go through cats 98 | var rcat string 99 | for _, cat := range cats { 100 | cat = filepath.Dir(cat) 101 | switch { 102 | case cat == ".": 103 | // just skip empty dir 104 | case rcat == "": 105 | // if root cat hasn't been 106 | // initialized yet set it 107 | rcat = cat 108 | case strings.Contains(rcat, cat): 109 | // if root cat contains cat 110 | // update root cat 111 | rcat = cat 112 | case strings.Contains(cat, rcat): 113 | // just skip this case 114 | default: 115 | // otherwise there are no 116 | // specific root cat found 117 | return "" 118 | } 119 | } 120 | return rcat 121 | } 122 | 123 | // Len calculates total len of hierarchic collection 124 | func (h Hierarchic) Len() int { 125 | var l int 126 | for _, cat := range h.cats { 127 | l += len(cat) 128 | } 129 | return l 130 | } 131 | -------------------------------------------------------------------------------- /collections/reference.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import "sync" 4 | 5 | // Reference defines backreference helper 6 | // that helps to set, and get wait for 7 | // key value pairs 8 | type Reference struct { 9 | vals map[string]interface{} `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 10 | signals map[string]chan struct{} `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 11 | mutex sync.Mutex `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 12 | _ [8]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 14 | 15 | // NewReference creates reference instance 16 | // accordingly to passed nil flag 17 | func NewReference(actual bool) *Reference { 18 | // in case we want to use 19 | // nil reference instance 20 | if !actual { 21 | return nil 22 | } 23 | // othewise return actual reference instance 24 | return &Reference{ 25 | vals: make(map[string]interface{}), 26 | signals: make(map[string]chan struct{}), 27 | } 28 | } 29 | 30 | // Get retrieves value for given key from the reference, 31 | // in case value hasn't been set yet 32 | // it waits until value is set 33 | func (r *Reference) Get(key string) interface{} { 34 | // in case of nil reference 35 | // just skip it and 36 | // return def val 37 | if r == nil { 38 | return struct{}{} 39 | } 40 | // grab signal with locking 41 | r.mutex.Lock() 42 | sig, ok := r.signals[key] 43 | r.mutex.Unlock() 44 | // in case there is no slot 45 | // has been reserved 46 | // return def val 47 | if !ok { 48 | return struct{}{} 49 | } 50 | // othewise wait for signal 51 | <-sig 52 | // lock the reference againg 53 | defer r.mutex.Unlock() 54 | r.mutex.Lock() 55 | // grab the reference value 56 | if val, ok := r.vals[key]; ok { 57 | return val 58 | } 59 | // in case no value has been set 60 | // return def val 61 | return struct{}{} 62 | } 63 | 64 | // Set update value for given key, 65 | // if slot for that value has been preallocated 66 | func (r *Reference) Set(key string, val interface{}) { 67 | // in case of nil reference 68 | // just skip it 69 | if r == nil { 70 | return 71 | } 72 | // lock the reference 73 | defer r.mutex.Unlock() 74 | r.mutex.Lock() 75 | // if slot hasn't been allocated yet 76 | // then just skip set at all 77 | // otherwise set value for the key 78 | // and prodcast on the signal 79 | if ch, ok := r.signals[key]; ok { 80 | r.vals[key] = val 81 | // check that channel 82 | // hasn't been closed yet 83 | // and then close it 84 | select { 85 | case <-ch: 86 | default: 87 | close(ch) 88 | } 89 | } 90 | } 91 | 92 | // Alloc preallocates slot in the 93 | // reference for the given key 94 | func (r *Reference) Alloc(key string) { 95 | // in case of nil reference 96 | // just skip it 97 | if r == nil { 98 | return 99 | } 100 | // lock the reference 101 | defer r.mutex.Unlock() 102 | r.mutex.Lock() 103 | // if signal hasn't been set yet 104 | // then allocate a signal for the key 105 | if _, ok := r.signals[key]; !ok { 106 | r.signals[key] = make(chan struct{}) 107 | } 108 | } 109 | 110 | // Prune releases all value waiters 111 | func (r *Reference) Prune() { 112 | // in case of nil reference 113 | // just skip it 114 | if r == nil { 115 | return 116 | } 117 | // lock the reference 118 | defer r.mutex.Unlock() 119 | r.mutex.Lock() 120 | // go through all reference signals 121 | for _, ch := range r.signals { 122 | // check that channel 123 | // hasn't been closed yet 124 | // and then close it 125 | select { 126 | case <-ch: 127 | default: 128 | close(ch) 129 | } 130 | } 131 | } 132 | -------------------------------------------------------------------------------- /collections/reference_test.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "reflect" 5 | "sync" 6 | "testing" 7 | "time" 8 | ) 9 | 10 | func TestNewReference(t *testing.T) { 11 | // prepare 12 | table := map[string]struct { 13 | b bool 14 | ref *Reference 15 | }{ 16 | "nil new reference should return nil ref": { 17 | b: false, 18 | ref: nil, 19 | }, 20 | "not nil new reference should return actual ref": { 21 | b: true, 22 | ref: &Reference{ 23 | vals: make(map[string]interface{}), 24 | signals: make(map[string]chan struct{}), 25 | }, 26 | }, 27 | } 28 | for name, tcase := range table { 29 | t.Run(name, func(t *testing.T) { 30 | // exec 31 | ref := NewReference(tcase.b) 32 | // check 33 | if !reflect.DeepEqual(ref, tcase.ref) { 34 | t.Errorf("actual %v doesn't equal to %v", ref, tcase.ref) 35 | } 36 | }) 37 | } 38 | } 39 | 40 | func TestNilReference(t *testing.T) { 41 | // prepare 42 | var r *Reference 43 | r.Set("test-1", 10) 44 | r.Prune() 45 | r.Set("test-2", 10) 46 | r.Alloc("test-3") 47 | r.Set("test-3", 10) 48 | table := map[string]struct { 49 | key string 50 | val interface{} 51 | }{ 52 | "invalid key should return empty result": { 53 | key: "key", 54 | val: struct{}{}, 55 | }, 56 | "test-1 key should return empty result": { 57 | key: "test-1", 58 | val: struct{}{}, 59 | }, 60 | "test-2 key should return empty result": { 61 | key: "test-2", 62 | val: struct{}{}, 63 | }, 64 | "test-3 key should return empty result": { 65 | key: "test-3", 66 | val: struct{}{}, 67 | }, 68 | } 69 | for name, tcase := range table { 70 | t.Run(name, func(t *testing.T) { 71 | // exec 72 | val := r.Get(tcase.key) 73 | // check 74 | if !reflect.DeepEqual(val, tcase.val) { 75 | t.Errorf("actual %v doesn't equal to %v", val, tcase.val) 76 | } 77 | }) 78 | } 79 | } 80 | 81 | func TestActualReference(t *testing.T) { 82 | // prepare 83 | var wg sync.WaitGroup 84 | r := NewReference(true) 85 | r.Set("test-1", 10) 86 | go func() { 87 | r.Set("test-2", 10) 88 | }() 89 | r.Alloc("test-3") 90 | go func() { 91 | time.Sleep(time.Millisecond) 92 | r.Set("test-3", 10) 93 | go func() { 94 | time.Sleep(time.Millisecond) 95 | r.Set("test-4", 5) 96 | time.Sleep(time.Millisecond) 97 | r.Prune() 98 | }() 99 | }() 100 | r.Alloc("test-4") 101 | r.Alloc("test-5") 102 | time.Sleep(3 * time.Millisecond) 103 | table := map[string]struct { 104 | key string 105 | val interface{} 106 | }{ 107 | "invalid key should return empty result": { 108 | key: "key", 109 | val: struct{}{}, 110 | }, 111 | "test-1 key should return empty result": { 112 | key: "test-1", 113 | val: struct{}{}, 114 | }, 115 | "test-2 key should return empty result": { 116 | key: "test-2", 117 | val: struct{}{}, 118 | }, 119 | "test-3 key should return expected result": { 120 | key: "test-3", 121 | val: 10, 122 | }, 123 | "test-4 key should return expected result": { 124 | key: "test-4", 125 | val: 5, 126 | }, 127 | "test-5 key should return empty result": { 128 | key: "test-5", 129 | val: struct{}{}, 130 | }, 131 | } 132 | for name, tcase := range table { 133 | // run all parser tests 134 | // in separate goroutine 135 | name := name 136 | tcase := tcase 137 | wg.Add(1) 138 | go func() { 139 | defer wg.Done() 140 | t.Run(name, func(t *testing.T) { 141 | // exec 142 | val := r.Get(tcase.key) 143 | // check 144 | if !reflect.DeepEqual(val, tcase.val) { 145 | t.Errorf("actual %v doesn't equal to %v", val, tcase.val) 146 | } 147 | }) 148 | }() 149 | } 150 | // wait util tests finish 151 | wg.Wait() 152 | } 153 | -------------------------------------------------------------------------------- /collections/size_align_pad.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "fmt" 5 | "math" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // OnPadFields defines pad fields callback that 11 | // accepts pad and optional list of following structure fields 12 | type OnPadFields func(int64, ...gopium.Field) 13 | 14 | // WalkStruct iterates over structure fields with optional 15 | // system align and calls on pad fields callback for all pads and fields 16 | func WalkStruct(st gopium.Struct, sysalign int64, onpad OnPadFields) { 17 | // preset defaults 18 | var stalign, falign, offset, pad int64 = 1, sysalign, 0, 0 19 | // calculate total struct size and align 20 | for _, f := range st.Fields { 21 | // if provided field align 22 | // was invalid use field align instead 23 | if sysalign == 0 { 24 | falign = f.Align 25 | } 26 | // update struct align size 27 | // if field align size is bigger 28 | if falign > stalign { 29 | stalign = falign 30 | } 31 | // check that field align is valid 32 | if falign > 0 { 33 | // calculate align with padding 34 | alpad := Align(offset, falign) 35 | // then calculate padding 36 | pad = alpad - offset 37 | // increment structure offset 38 | offset = alpad + f.Size 39 | } 40 | // call onpad func 41 | onpad(pad, CopyField(f)) 42 | // reset current pad 43 | pad = 0 44 | } 45 | // check if struct align size is valid 46 | // and append final padding to structure 47 | if stalign > 0 { 48 | // calculate align with padding 49 | alpad := Align(offset, stalign) 50 | // then calculate padding 51 | pad = alpad - offset 52 | // call onpad func 53 | onpad(pad) 54 | } 55 | } 56 | 57 | // SizeAlignPtr calculates sturct aligned size, size and ptr size 58 | // by using walk struct helper 59 | func SizeAlignPtr(st gopium.Struct) (int64, int64, int64) { 60 | // preset defaults 61 | var alsize, align, ptrsize int64 = 0, 1, 0 62 | WalkStruct(st, 0, func(pad int64, fields ...gopium.Field) { 63 | // add pad to aligned size 64 | alsize += pad 65 | // go through fields 66 | for _, f := range fields { 67 | // in case filed has pointer data 68 | // move pts size to last aligned size 69 | // + this field ptr data size 70 | if f.Ptr > 0 { 71 | ptrsize = alsize + f.Ptr 72 | } 73 | // add field size aligned sizes 74 | alsize += f.Size 75 | // update struct align size 76 | // if field align size is bigger 77 | if f.Align > align { 78 | align = f.Align 79 | } 80 | } 81 | }) 82 | return alsize, align, ptrsize 83 | } 84 | 85 | // PadField defines helper that 86 | // creates pad field with specified size 87 | func PadField(pad int64) gopium.Field { 88 | pad = int64(math.Max(0, float64(pad))) 89 | return gopium.Field{ 90 | Name: "_", 91 | Type: fmt.Sprintf("[%d]byte", pad), 92 | Size: pad, 93 | Align: 1, 94 | } 95 | } 96 | 97 | // Align returns the smallest y >= x such that y % a == 0. 98 | // note: copied from `go/types/sizes.go` 99 | func Align(x int64, a int64) int64 { 100 | y := x + a - 1 101 | return y - y%a 102 | } 103 | -------------------------------------------------------------------------------- /collections/size_align_pad_test.go: -------------------------------------------------------------------------------- 1 | package collections 2 | 3 | import ( 4 | "reflect" 5 | "testing" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | func TestSizeAlignPtr(t *testing.T) { 11 | // prepare 12 | table := map[string]struct { 13 | st gopium.Struct 14 | size int64 15 | align int64 16 | ptr int64 17 | }{ 18 | "empty struct should return expected size, align and ptr": { 19 | size: 0, 20 | align: 1, 21 | ptr: 0, 22 | }, 23 | "non empty struct should return expected size, align and ptr": { 24 | st: gopium.Struct{ 25 | Name: "test", 26 | Comment: []string{"test"}, 27 | Fields: []gopium.Field{ 28 | { 29 | Name: "test1", 30 | Type: "int", 31 | Size: 8, 32 | Align: 4, 33 | Ptr: 8, 34 | }, 35 | { 36 | Name: "test2", 37 | Type: "string", 38 | Comment: []string{"test"}, 39 | }, 40 | { 41 | Name: "test2", 42 | Type: "float64", 43 | Size: 8, 44 | Align: 8, 45 | }, 46 | }, 47 | }, 48 | size: 16, 49 | align: 8, 50 | ptr: 8, 51 | }, 52 | "struct with pads should return expected size, align and ptr": { 53 | st: gopium.Struct{ 54 | Name: "test", 55 | Comment: []string{"test"}, 56 | Fields: []gopium.Field{ 57 | { 58 | Name: "test1", 59 | Size: 3, 60 | Align: 1, 61 | Ptr: 3, 62 | }, 63 | { 64 | Name: "test2", 65 | Type: "float64", 66 | Size: 8, 67 | Align: 6, 68 | Ptr: 8, 69 | }, 70 | { 71 | Name: "test3", 72 | Size: 3, 73 | Align: 1, 74 | Ptr: 3, 75 | }, 76 | }, 77 | }, 78 | size: 18, 79 | align: 6, 80 | ptr: 17, 81 | }, 82 | } 83 | for name, tcase := range table { 84 | t.Run(name, func(t *testing.T) { 85 | // exec 86 | size, align, ptr := SizeAlignPtr(tcase.st) 87 | // check 88 | if !reflect.DeepEqual(size, tcase.size) { 89 | t.Errorf("actual %v doesn't equal to %v", size, tcase.size) 90 | } 91 | if !reflect.DeepEqual(align, tcase.align) { 92 | t.Errorf("actual %v doesn't equal to %v", size, tcase.size) 93 | } 94 | if !reflect.DeepEqual(ptr, tcase.ptr) { 95 | t.Errorf("actual %v doesn't equal to %v", ptr, tcase.ptr) 96 | } 97 | }) 98 | } 99 | } 100 | 101 | func TestPadField(t *testing.T) { 102 | // prepare 103 | table := map[string]struct { 104 | pad int64 105 | f gopium.Field 106 | }{ 107 | "empty pad should return empty field pad": { 108 | f: gopium.Field{ 109 | Name: "_", 110 | Type: "[0]byte", 111 | Size: 0, 112 | Align: 1, 113 | }, 114 | }, 115 | "positive pad should return valid field pad": { 116 | pad: 10, 117 | f: gopium.Field{ 118 | Name: "_", 119 | Type: "[10]byte", 120 | Size: 10, 121 | Align: 1, 122 | }, 123 | }, 124 | "negative pad should return empty field": { 125 | pad: -10, 126 | f: gopium.Field{ 127 | Name: "_", 128 | Type: "[0]byte", 129 | Size: 0, 130 | Align: 1, 131 | }, 132 | }, 133 | } 134 | for name, tcase := range table { 135 | t.Run(name, func(t *testing.T) { 136 | // exec 137 | f := PadField(tcase.pad) 138 | // check 139 | if !reflect.DeepEqual(f, tcase.f) { 140 | t.Errorf("actual %v doesn't equal to %v", f, tcase.f) 141 | } 142 | }) 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /examples/cache_rounding_cpu_l1/transaction/transaction.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "sort" 7 | "sync" 8 | ) 9 | 10 | // transaction defines business transaction 11 | type transaction struct { 12 | void bool `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | _ [7]byte `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 14 | amount float64 `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 15 | serial uint64 `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 16 | skip bool `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 17 | _ [7]byte `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 18 | discount float64 `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 19 | _ [24]byte `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 20 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 21 | 22 | // aggregate defines compressed set of transactions 23 | type aggregate struct { 24 | total float64 `gopium:"filter_pads,explicit_paddings_system_alignment,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 25 | } // struct size: 8 bytes; struct align: 8 bytes; struct aligned size: 8 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 26 | 27 | // generate creates n pseudo random transactions 28 | func generate(number uint) []transaction { 29 | // generate n pseudo random transactions 30 | transactions := make([]transaction, 0, number) 31 | for i := 0; i < int(number); i++ { 32 | transactions = append(transactions, transaction{ 33 | void: i%10 == 0, 34 | amount: math.Abs(rand.Float64()), 35 | serial: uint64(i) + 1, 36 | skip: i%25 == 0, 37 | discount: rand.Float64(), 38 | }) 39 | } 40 | // and shuffle them 41 | for i := range transactions { 42 | j := rand.Intn(i + 1) 43 | transactions[i], transactions[j] = transactions[j], transactions[i] 44 | } 45 | return transactions 46 | } 47 | 48 | // normalize preprocess list of transactions before compressing 49 | func normalize(transactions []transaction) []transaction { 50 | // filter and normalize transactions 51 | normalized := make([]transaction, 0, len(transactions)) 52 | for _, trx := range transactions { 53 | if trx.skip || trx.serial == 0 { 54 | continue 55 | } 56 | if trx.void { 57 | trx.amount = -trx.amount 58 | } 59 | trx.discount = math.Abs(trx.discount) 60 | normalized = append(normalized, trx) 61 | } 62 | // sort transactions by serial 63 | sort.Slice(normalized, func(i int, j int) bool { 64 | return normalized[i].serial < normalized[j].serial 65 | }) 66 | return normalized 67 | } 68 | 69 | // compress builds single aggregate from provided normalized transactions list 70 | func compress(transactions []transaction) aggregate { 71 | var amount, discont aggregate 72 | var wg sync.WaitGroup 73 | wg.Add(2) 74 | // run amount calculation in separate goroutine 75 | go func() { 76 | for _, tr := range transactions { 77 | amount.total += tr.amount 78 | } 79 | wg.Done() 80 | }() 81 | // run discounts calculation in separate goroutine 82 | go func() { 83 | for _, tr := range transactions { 84 | discont.total += tr.discount 85 | } 86 | wg.Done() 87 | }() 88 | wg.Wait() 89 | // apply discount logic to final aggregate 90 | if discont.total > amount.total/2 { 91 | discont.total = amount.total / 2 92 | } 93 | result := amount 94 | result.total -= discont.total 95 | return result 96 | } 97 | -------------------------------------------------------------------------------- /examples/cache_rounding_cpu_l1/transaction/transaction_test.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import "testing" 4 | 5 | func BenchmarkGenerate(b *testing.B) { 6 | // run benchmark for generation 7 | for n := 0; n < b.N; n++ { 8 | generate(1000000) 9 | } 10 | } 11 | 12 | func BenchmarkNormalize(b *testing.B) { 13 | // generate one million of transactions 14 | // and reset benchmark timer 15 | transactions := generate(1000000) 16 | b.ResetTimer() 17 | // run benchmark for normalization 18 | for n := 0; n < b.N; n++ { 19 | normalize(transactions) 20 | } 21 | } 22 | 23 | func BenchmarkCompress(b *testing.B) { 24 | // generate one million of normalized transactions 25 | // and reset benchmark timer 26 | transactions := normalize(generate(1000000)) 27 | b.ResetTimer() 28 | // run benchmark for compressing 29 | for n := 0; n < b.N; n++ { 30 | compress(transactions) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/false_sharing_cpu_l1/transaction/transaction.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "sort" 7 | "sync" 8 | ) 9 | 10 | // transaction defines business transaction 11 | type transaction struct { 12 | void bool 13 | amount float64 14 | serial uint64 15 | skip bool 16 | discount float64 17 | } // struct size: 26 bytes; struct align: 8 bytes; struct aligned size: 40 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 18 | 19 | // aggregate defines compressed set of transactions 20 | type aggregate struct { 21 | total float64 `gopium:"filter_pads,false_sharing_cpu_l1,struct_annotate_comment,add_tag_group_force"` 22 | _ [56]byte `gopium:"filter_pads,false_sharing_cpu_l1,struct_annotate_comment,add_tag_group_force"` 23 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 24 | 25 | // generate creates n pseudo random transactions 26 | func generate(number uint) []transaction { 27 | // generate n pseudo random transactions 28 | transactions := make([]transaction, 0, number) 29 | for i := 0; i < int(number); i++ { 30 | transactions = append(transactions, transaction{ 31 | void: i%10 == 0, 32 | amount: math.Abs(rand.Float64()), 33 | serial: uint64(i) + 1, 34 | skip: i%25 == 0, 35 | discount: rand.Float64(), 36 | }) 37 | } 38 | // and shuffle them 39 | for i := range transactions { 40 | j := rand.Intn(i + 1) 41 | transactions[i], transactions[j] = transactions[j], transactions[i] 42 | } 43 | return transactions 44 | } 45 | 46 | // normalize preprocess list of transactions before compressing 47 | func normalize(transactions []transaction) []transaction { 48 | // filter and normalize transactions 49 | normalized := make([]transaction, 0, len(transactions)) 50 | for _, trx := range transactions { 51 | if trx.skip || trx.serial == 0 { 52 | continue 53 | } 54 | if trx.void { 55 | trx.amount = -trx.amount 56 | } 57 | trx.discount = math.Abs(trx.discount) 58 | normalized = append(normalized, trx) 59 | } 60 | // sort transactions by serial 61 | sort.Slice(normalized, func(i int, j int) bool { 62 | return normalized[i].serial < normalized[j].serial 63 | }) 64 | return normalized 65 | } 66 | 67 | // compress builds single aggregate from provided normalized transactions list 68 | func compress(transactions []transaction) aggregate { 69 | var amount, discont aggregate 70 | var wg sync.WaitGroup 71 | wg.Add(2) 72 | // run amount calculation in separate goroutine 73 | go func() { 74 | for _, tr := range transactions { 75 | amount.total += tr.amount 76 | } 77 | wg.Done() 78 | }() 79 | // run discounts calculation in separate goroutine 80 | go func() { 81 | for _, tr := range transactions { 82 | discont.total += tr.discount 83 | } 84 | wg.Done() 85 | }() 86 | wg.Wait() 87 | // apply discount logic to final aggregate 88 | if discont.total > amount.total/2 { 89 | discont.total = amount.total / 2 90 | } 91 | result := amount 92 | result.total -= discont.total 93 | return result 94 | } 95 | -------------------------------------------------------------------------------- /examples/false_sharing_cpu_l1/transaction/transaction_test.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import "testing" 4 | 5 | func BenchmarkGenerate(b *testing.B) { 6 | // run benchmark for generation 7 | for n := 0; n < b.N; n++ { 8 | generate(1000000) 9 | } 10 | } 11 | 12 | func BenchmarkNormalize(b *testing.B) { 13 | // generate one million of transactions 14 | // and reset benchmark timer 15 | transactions := generate(1000000) 16 | b.ResetTimer() 17 | // run benchmark for normalization 18 | for n := 0; n < b.N; n++ { 19 | normalize(transactions) 20 | } 21 | } 22 | 23 | func BenchmarkCompress(b *testing.B) { 24 | // generate one million of normalized transactions 25 | // and reset benchmark timer 26 | transactions := normalize(generate(1000000)) 27 | b.ResetTimer() 28 | // run benchmark for compressing 29 | for n := 0; n < b.N; n++ { 30 | compress(transactions) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/memory_pack/transaction/transaction.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "sort" 7 | "sync" 8 | ) 9 | 10 | // transaction defines business transaction 11 | type transaction struct { 12 | amount float64 `gopium:"memory_pack,struct_annotate_comment,add_tag_group_force"` 13 | serial uint64 `gopium:"memory_pack,struct_annotate_comment,add_tag_group_force"` 14 | discount float64 `gopium:"memory_pack,struct_annotate_comment,add_tag_group_force"` 15 | void bool `gopium:"memory_pack,struct_annotate_comment,add_tag_group_force"` 16 | skip bool `gopium:"memory_pack,struct_annotate_comment,add_tag_group_force"` 17 | } // struct size: 26 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 18 | 19 | // aggregate defines compressed set of transactions 20 | type aggregate struct { 21 | total float64 22 | } // struct size: 8 bytes; struct align: 8 bytes; struct aligned size: 8 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 23 | 24 | // generate creates n pseudo random transactions 25 | func generate(number uint) []transaction { 26 | // generate n pseudo random transactions 27 | transactions := make([]transaction, 0, number) 28 | for i := 0; i < int(number); i++ { 29 | transactions = append(transactions, transaction{ 30 | void: i%10 == 0, 31 | amount: math.Abs(rand.Float64()), 32 | serial: uint64(i) + 1, 33 | skip: i%25 == 0, 34 | discount: rand.Float64(), 35 | }) 36 | } 37 | // and shuffle them 38 | for i := range transactions { 39 | j := rand.Intn(i + 1) 40 | transactions[i], transactions[j] = transactions[j], transactions[i] 41 | } 42 | return transactions 43 | } 44 | 45 | // normalize preprocess list of transactions before compressing 46 | func normalize(transactions []transaction) []transaction { 47 | // filter and normalize transactions 48 | normalized := make([]transaction, 0, len(transactions)) 49 | for _, trx := range transactions { 50 | if trx.skip || trx.serial == 0 { 51 | continue 52 | } 53 | if trx.void { 54 | trx.amount = -trx.amount 55 | } 56 | trx.discount = math.Abs(trx.discount) 57 | normalized = append(normalized, trx) 58 | } 59 | // sort transactions by serial 60 | sort.Slice(normalized, func(i int, j int) bool { 61 | return normalized[i].serial < normalized[j].serial 62 | }) 63 | return normalized 64 | } 65 | 66 | // compress builds single aggregate from provided normalized transactions list 67 | func compress(transactions []transaction) aggregate { 68 | var amount, discont aggregate 69 | var wg sync.WaitGroup 70 | wg.Add(2) 71 | // run amount calculation in separate goroutine 72 | go func() { 73 | for _, tr := range transactions { 74 | amount.total += tr.amount 75 | } 76 | wg.Done() 77 | }() 78 | // run discounts calculation in separate goroutine 79 | go func() { 80 | for _, tr := range transactions { 81 | discont.total += tr.discount 82 | } 83 | wg.Done() 84 | }() 85 | wg.Wait() 86 | // apply discount logic to final aggregate 87 | if discont.total > amount.total/2 { 88 | discont.total = amount.total / 2 89 | } 90 | result := amount 91 | result.total -= discont.total 92 | return result 93 | } 94 | -------------------------------------------------------------------------------- /examples/memory_pack/transaction/transaction_test.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import "testing" 4 | 5 | func BenchmarkGenerate(b *testing.B) { 6 | // run benchmark for generation 7 | for n := 0; n < b.N; n++ { 8 | generate(1000000) 9 | } 10 | } 11 | 12 | func BenchmarkNormalize(b *testing.B) { 13 | // generate one million of transactions 14 | // and reset benchmark timer 15 | transactions := generate(1000000) 16 | b.ResetTimer() 17 | // run benchmark for normalization 18 | for n := 0; n < b.N; n++ { 19 | normalize(transactions) 20 | } 21 | } 22 | 23 | func BenchmarkCompress(b *testing.B) { 24 | // generate one million of normalized transactions 25 | // and reset benchmark timer 26 | transactions := normalize(generate(1000000)) 27 | b.ResetTimer() 28 | // run benchmark for compressing 29 | for n := 0; n < b.N; n++ { 30 | compress(transactions) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /examples/transaction/transaction.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import ( 4 | "math" 5 | "math/rand" 6 | "sort" 7 | "sync" 8 | ) 9 | 10 | // transaction defines business transaction 11 | type transaction struct { 12 | void bool 13 | amount float64 14 | serial uint64 15 | skip bool 16 | discount float64 17 | } // struct size: 26 bytes; struct align: 8 bytes; struct aligned size: 40 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 18 | 19 | // aggregate defines compressed set of transactions 20 | type aggregate struct { 21 | total float64 22 | } // struct size: 8 bytes; struct align: 8 bytes; struct aligned size: 8 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 23 | 24 | // generate creates n pseudo random transactions 25 | func generate(number uint) []transaction { 26 | // generate n pseudo random transactions 27 | transactions := make([]transaction, 0, number) 28 | for i := 0; i < int(number); i++ { 29 | transactions = append(transactions, transaction{ 30 | void: i%10 == 0, 31 | amount: math.Abs(rand.Float64()), 32 | serial: uint64(i) + 1, 33 | skip: i%25 == 0, 34 | discount: rand.Float64(), 35 | }) 36 | } 37 | // and shuffle them 38 | for i := range transactions { 39 | j := rand.Intn(i + 1) 40 | transactions[i], transactions[j] = transactions[j], transactions[i] 41 | } 42 | return transactions 43 | } 44 | 45 | // normalize preprocess list of transactions before compressing 46 | func normalize(transactions []transaction) []transaction { 47 | // filter and normalize transactions 48 | normalized := make([]transaction, 0, len(transactions)) 49 | for _, trx := range transactions { 50 | if trx.skip || trx.serial == 0 { 51 | continue 52 | } 53 | if trx.void { 54 | trx.amount = -trx.amount 55 | } 56 | trx.discount = math.Abs(trx.discount) 57 | normalized = append(normalized, trx) 58 | } 59 | // sort transactions by serial 60 | sort.Slice(normalized, func(i int, j int) bool { 61 | return normalized[i].serial < normalized[j].serial 62 | }) 63 | return normalized 64 | } 65 | 66 | // compress builds single aggregate from provided normalized transactions list 67 | func compress(transactions []transaction) aggregate { 68 | var amount, discont aggregate 69 | var wg sync.WaitGroup 70 | wg.Add(2) 71 | // run amount calculation in separate goroutine 72 | go func() { 73 | for _, tr := range transactions { 74 | amount.total += tr.amount 75 | } 76 | wg.Done() 77 | }() 78 | // run discounts calculation in separate goroutine 79 | go func() { 80 | for _, tr := range transactions { 81 | discont.total += tr.discount 82 | } 83 | wg.Done() 84 | }() 85 | wg.Wait() 86 | // apply discount logic to final aggregate 87 | if discont.total > amount.total/2 { 88 | discont.total = amount.total / 2 89 | } 90 | result := amount 91 | result.total -= discont.total 92 | return result 93 | } 94 | -------------------------------------------------------------------------------- /examples/transaction/transaction_test.go: -------------------------------------------------------------------------------- 1 | package transaction 2 | 3 | import "testing" 4 | 5 | func BenchmarkGenerate(b *testing.B) { 6 | // run benchmark for generation 7 | for n := 0; n < b.N; n++ { 8 | generate(1000000) 9 | } 10 | } 11 | 12 | func BenchmarkNormalize(b *testing.B) { 13 | // generate one million of transactions 14 | // and reset benchmark timer 15 | transactions := generate(1000000) 16 | b.ResetTimer() 17 | // run benchmark for normalization 18 | for n := 0; n < b.N; n++ { 19 | normalize(transactions) 20 | } 21 | } 22 | 23 | func BenchmarkCompress(b *testing.B) { 24 | // generate one million of normalized transactions 25 | // and reset benchmark timer 26 | transactions := normalize(generate(1000000)) 27 | b.ResetTimer() 28 | // run benchmark for compressing 29 | for n := 0; n < b.N; n++ { 30 | compress(transactions) 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /extensions/vscode/.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 120, 3 | "singleQuote": true, 4 | "arrowParens": "always", 5 | "quoteProps": "consistent", 6 | "trailingComma": "all", 7 | "semi": false, 8 | "useTabs": true 9 | } 10 | -------------------------------------------------------------------------------- /extensions/vscode/.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "0.2.0", 3 | "configurations": [ 4 | { 5 | "name": "Launch Extension", 6 | "type": "extensionHost", 7 | "request": "launch", 8 | "runtimeExecutable": "${execPath}", 9 | "args": ["--extensionDevelopmentPath=${workspaceFolder}"], 10 | "stopOnEntry": false, 11 | "outFiles": ["${workspaceFolder}/out/**/*.js"] 12 | } 13 | ] 14 | } 15 | -------------------------------------------------------------------------------- /extensions/vscode/.vscode/tasks.json: -------------------------------------------------------------------------------- 1 | { 2 | "version": "2.0.0", 3 | "command": "npm", 4 | "type": "shell", 5 | "presentation": { 6 | "reveal": "never" 7 | }, 8 | "args": ["run", "compile"], 9 | "isBackground": true, 10 | "problemMatcher": "$tsc-watch", 11 | "tasks": [ 12 | { 13 | "type": "typescript", 14 | "tsconfig": "tsconfig.json", 15 | "problemMatcher": ["$tsc"] 16 | }, 17 | { 18 | "type": "npm", 19 | "script": "watch", 20 | "problemMatcher": "$tsc-watch", 21 | "isBackground": true, 22 | "presentation": { 23 | "reveal": "never" 24 | }, 25 | "group": { 26 | "kind": "build", 27 | "isDefault": true 28 | } 29 | } 30 | ] 31 | } 32 | -------------------------------------------------------------------------------- /extensions/vscode/.vscodeignore: -------------------------------------------------------------------------------- 1 | .vscode/** 2 | src/**/* 3 | tsconfig.json 4 | tslint.json 5 | .prettierrc.json 6 | **/*.map 7 | **/*.ts 8 | node_modules/fs-extra 9 | -------------------------------------------------------------------------------- /extensions/vscode/LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Kostiantyn Masliuk 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /extensions/vscode/README.MD: -------------------------------------------------------------------------------- 1 | # Gopium 🌺 VSCode Extension 2 | 3 | Gopium vscode extension for [Gopium](https://github.com/1pkg/gopium) provides better experience for usage and simplify interactions with Gopium CLI. 4 | ![](vscode.gif) 5 | 6 | ## Requirements and Installation 7 | 8 | To install gopium vscode extension go to [vscode marketplace](https://marketplace.visualstudio.com/items?itemName=1pkg.gopium). 9 | 10 | In order to work, extension requires latest version of [Gopium CLI](https://github.com/1pkg/gopium) and [Goutline](https://github.com/1pkg/goutline) tools. 11 | Extension automatically tries to install required tools if they have been not detected in your system. 12 | 13 | To install and update gopium cli use: 14 | 15 | ```bash 16 | go install github.com/1pkg/gopium@latest 17 | ``` 18 | 19 | To install and update goutline use: 20 | 21 | ```bash 22 | go install github.com/1pkg/goutline@latest 23 | ``` 24 | 25 | ## Transformations Customization 26 | 27 | You can easily adjust list of Gopium transformations to your needs by updating `gopium.actions` settings in vscode, see also Gopium available [transformations](https://github.com/1pkg/gopium#strategies-and-transformations) and [walkers](https://github.com/1pkg/gopium#walkers-and-formatters). 28 | By default extension uses next transformations presets: 29 | 30 | `gopium pack | gopium cache | gopium pack cache | gopium false sharing | gopium pack verbose | gopium cache verbose` 31 | 32 | which are defined by next settings in vscode: 33 | 34 | ```json 35 | { 36 | "gopium.actions": [ 37 | { 38 | "name": "pack", 39 | "walker": "ast_go", 40 | "strategies": ["memory_pack"] 41 | }, 42 | { 43 | "name": "cache", 44 | "walker": "ast_go", 45 | "strategies": ["cache_rounding_cpu_l1_discrete"] 46 | }, 47 | { 48 | "name": "pack cache", 49 | "walker": "ast_go", 50 | "strategies": ["filter_pads", "memory_pack", "explicit_paddings_type_natural", "cache_rounding_cpu_l1_discrete"] 51 | }, 52 | { 53 | "name": "false sharing", 54 | "walker": "ast_go", 55 | "strategies": ["filter_pads", "false_sharing_cpu_l1"] 56 | }, 57 | { 58 | "name": "pack verbose", 59 | "walker": "ast_go", 60 | "strategies": [ 61 | "filter_pads", 62 | "memory_pack", 63 | "explicit_paddings_type_natural", 64 | "fields_annotate_comment", 65 | "struct_annotate_comment", 66 | "add_tag_group_soft" 67 | ] 68 | }, 69 | { 70 | "name": "cache verbose", 71 | "walker": "ast_go", 72 | "strategies": [ 73 | "filter_pads", 74 | "explicit_paddings_type_natural", 75 | "cache_rounding_cpu_l1_discrete", 76 | "fields_annotate_comment", 77 | "struct_annotate_comment", 78 | "add_tag_group_soft" 79 | ] 80 | } 81 | ] 82 | } 83 | ``` 84 | 85 | ## Origin and Decisions 86 | 87 | Extension is based on and heavily using [vscode-go](https://github.com/microsoft/vscode-go) extension functionality and keeps it as [submodule](https://github.com/1pkg/vscode-go). Such decision to vendor extension was made in favor to simplify origin Gopium VSCode Extension development by reusing common components like: goutline codelens, tools installation, etc. 88 | 89 | Nevertheless it's impossible to just seamlessly reuse another extension to build your own, so a lot of functionality has been patched and changed, most noticeable: vscode-go telemetry is disabled, vscode-go extension entrypoits are removed, [go-outline](https://github.com/ramya-rao-a/go-outline) is replaced with [go-outline](https://github.com/1pkg/goutline), etc. 90 | 91 | To publish the extension use [guide](https://code.visualstudio.com/api/working-with-extensions/publishing-extension). 92 | 93 | ## Licence 94 | 95 | Gopium is licensed under the MIT License. 96 | See [LICENSE](LICENSE) for the full license text. 97 | -------------------------------------------------------------------------------- /extensions/vscode/gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1pkg/gopium/26cf8136c3e4de73ff1dac2435fea8ea18ce9bcd/extensions/vscode/gopher.png -------------------------------------------------------------------------------- /extensions/vscode/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "target": "es6", 5 | "outDir": "out", 6 | "lib": ["es6"], 7 | "noImplicitAny": true, 8 | "noImplicitThis": true, 9 | "alwaysStrict": true, 10 | "strictBindCallApply": true, 11 | "strictFunctionTypes": true 12 | }, 13 | "exclude": ["node_modules", "src/vscode-go/test"] 14 | } 15 | -------------------------------------------------------------------------------- /extensions/vscode/tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "adjacent-overload-signatures": true, 4 | "align": { 5 | "options": ["parameters", "statements"] 6 | }, 7 | "arrow-parens": true, 8 | "arrow-return-shorthand": true, 9 | "ban-types": { 10 | "options": [ 11 | ["Object", "Avoid using the `Object` type. Did you mean `object`?"], 12 | ["Function", "Avoid using the `Function` type. Prefer a specific function type, like `() => void`."], 13 | ["Boolean", "Avoid using the `Boolean` type. Did you mean `boolean`?"], 14 | ["Number", "Avoid using the `Number` type. Did you mean `number`?"], 15 | ["String", "Avoid using the `String` type. Did you mean `string`?"], 16 | ["Symbol", "Avoid using the `Symbol` type. Did you mean `symbol`?"] 17 | ] 18 | }, 19 | "class-name": true, 20 | "comment-format": [true, "check-space"], 21 | "curly": true, 22 | "eofline": true, 23 | "forin": true, 24 | "import-spacing": true, 25 | "indent": [true, "tabs"], 26 | "interface-over-type-literal": true, 27 | "jsdoc-format": true, 28 | "label-position": true, 29 | "max-classes-per-file": { 30 | "options": 2 31 | }, 32 | "max-line-length": { "options": 120 }, 33 | "member-access": true, 34 | "member-ordering": { 35 | "options": { 36 | "order": "statics-first" 37 | } 38 | }, 39 | "new-parens": true, 40 | "no-arg": true, 41 | "no-conditional-assignment": true, 42 | "no-consecutive-blank-lines": true, 43 | "no-construct": true, 44 | "no-debugger": true, 45 | "no-duplicate-super": true, 46 | "no-duplicate-variable": true, 47 | "no-empty": true, 48 | "no-empty-interface": true, 49 | "no-eval": true, 50 | "no-internal-module": true, 51 | "no-invalid-this": false, 52 | "no-misused-new": true, 53 | "no-namespace": true, 54 | "no-reference": true, 55 | "no-reference-import": true, 56 | "no-shadowed-variable": true, 57 | "no-string-throw": true, 58 | "no-trailing-whitespace": true, 59 | "no-unnecessary-initializer": true, 60 | "no-unsafe-finally": true, 61 | "no-unused-expression": true, 62 | "no-var-keyword": true, 63 | "no-var-requires": true, 64 | "object-literal-key-quotes": { "options": "consistent-as-needed" }, 65 | "object-literal-shorthand": true, 66 | "one-line": [true, "check-catch", "check-else", "check-finally", "check-open-brace", "check-whitespace"], 67 | "one-variable-per-declaration": { "options": ["ignore-for-loop"] }, 68 | "only-arrow-functions": { 69 | "options": ["allow-declarations", "allow-named-functions"] 70 | }, 71 | "ordered-imports": { 72 | "options": { 73 | "import-sources-order": "case-insensitive", 74 | "module-source-path": "full", 75 | "named-imports-order": "case-insensitive" 76 | } 77 | }, 78 | "prefer-const": true, 79 | "prefer-for-of": true, 80 | "quotemark": [true, "single"], 81 | "radix": true, 82 | "semicolon": true, 83 | "space-before-function-paren": { 84 | "options": { 85 | "anonymous": "always", 86 | "asyncArrow": "always", 87 | "constructor": "never", 88 | "method": "never", 89 | "named": "never" 90 | } 91 | }, 92 | "trailing-comma": { 93 | "options": { 94 | "esSpecCompliant": true 95 | } 96 | }, 97 | "triple-equals": [true, "allow-null-check"], 98 | "typedef-whitespace": [ 99 | true, 100 | { 101 | "call-signature": "nospace", 102 | "index-signature": "nospace", 103 | "parameter": "nospace", 104 | "property-declaration": "nospace", 105 | "variable-declaration": "nospace" 106 | } 107 | ], 108 | "unified-signatures": true, 109 | "use-isnan": true, 110 | "variable-name": [true, "ban-keywords", "check-format", "allow-pascal-case"], 111 | "whitespace": [true, "check-branch", "check-decl", "check-operator", "check-separator", "check-type"] 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /extensions/vscode/vscode.gif: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1pkg/gopium/26cf8136c3e4de73ff1dac2435fea8ea18ce9bcd/extensions/vscode/vscode.gif -------------------------------------------------------------------------------- /fmtio/astutil/package.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | 9 | "golang.org/x/sync/errgroup" 10 | ) 11 | 12 | // Package ast asyn pesists package implementation 13 | // which persists one ast file by one ast file 14 | // to fmtio writer by using printer 15 | type Package struct{} // struct size: 0 bytes; struct align: 1 bytes; struct aligned size: 0 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 16 | 17 | // Persist package implementation 18 | func (Package) Persist( 19 | ctx context.Context, 20 | p gopium.Printer, 21 | w gopium.Writer, 22 | loc gopium.Locator, 23 | node ast.Node, 24 | ) error { 25 | // create sync error group 26 | // with cancelation context 27 | group, gctx := errgroup.WithContext(ctx) 28 | // go through all files in package 29 | // and update them to concurently 30 | for name, file := range node.(*ast.Package).Files { 31 | // manage context actions 32 | // in case of cancelation 33 | // stop execution 34 | select { 35 | case <-gctx.Done(): 36 | break 37 | default: 38 | } 39 | // capture name and file copies 40 | name := name 41 | file := file 42 | // run error group write call 43 | group.Go(func() error { 44 | // generate relevant writer 45 | writer, err := w.Generate(name) 46 | // in case any error happened 47 | // just return error back 48 | if err != nil { 49 | return err 50 | } 51 | // grab the latest file fset 52 | fset, _ := loc.Fset(name, nil) 53 | // write updated ast file to related os file 54 | // use original file set to keep format 55 | // in case any error happened 56 | // just return error back 57 | if err := p.Print(gctx, writer, fset, file); err != nil { 58 | return err 59 | } 60 | // flush writer result 61 | // in case any error happened 62 | // just return error back 63 | if err := writer.Close(); err != nil { 64 | return err 65 | } 66 | return gctx.Err() 67 | }) 68 | } 69 | // wait until all writers 70 | // resolve their jobs and 71 | return group.Wait() 72 | } 73 | -------------------------------------------------------------------------------- /fmtio/astutil/walk_test.go: -------------------------------------------------------------------------------- 1 | package astutil 2 | -------------------------------------------------------------------------------- /fmtio/bytes.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "bytes" 5 | "encoding/csv" 6 | "encoding/json" 7 | "encoding/xml" 8 | "fmt" 9 | "io" 10 | "strconv" 11 | "strings" 12 | 13 | "github.com/1pkg/gopium/gopium" 14 | ) 15 | 16 | // Jsonb defines bytes implementation 17 | // which uses json marshal with indent 18 | // to serialize flat collection to byte slice 19 | func Jsonb(sts []gopium.Struct) ([]byte, error) { 20 | // just use json marshal with indent 21 | return json.MarshalIndent(sts, "", "\t") 22 | } 23 | 24 | // Xmlb defines bytes implementation 25 | // which uses xml marshal with indent 26 | // to serialize flat collection to byte slice 27 | func Xmlb(sts []gopium.Struct) ([]byte, error) { 28 | // just use xml marshal with indent 29 | return xml.MarshalIndent(sts, "", "\t") 30 | } 31 | 32 | // Csvb defines bytes implementation 33 | // which serializes flat collection 34 | // to formatted csv byte slice 35 | func Csvb(rw io.ReadWriter) gopium.Bytes { 36 | return func(sts []gopium.Struct) ([]byte, error) { 37 | // prepare csv writer 38 | w := csv.NewWriter(rw) 39 | // write header 40 | // no error should be 41 | // checked as it uses 42 | // buffered writer 43 | _ = w.Write([]string{ 44 | "Struct Name", 45 | "Struct Doc", 46 | "Struct Comment", 47 | "Field Name", 48 | "Field Type", 49 | "Field Size", 50 | "Field Align", 51 | "Field Ptr", 52 | "Field Tag", 53 | "Field Exported", 54 | "Field Embedded", 55 | "Field Doc", 56 | "Field Comment", 57 | }) 58 | for _, st := range sts { 59 | // go through all fields 60 | // and write then one by one 61 | for _, f := range st.Fields { 62 | // no error should be 63 | // checked as it uses 64 | // buffered writer 65 | _ = w.Write([]string{ 66 | st.Name, 67 | strings.Join(st.Doc, " "), 68 | strings.Join(st.Comment, " "), 69 | f.Name, 70 | f.Type, 71 | strconv.Itoa(int(f.Size)), 72 | strconv.Itoa(int(f.Align)), 73 | strconv.Itoa(int(f.Ptr)), 74 | f.Tag, 75 | strconv.FormatBool(f.Exported), 76 | strconv.FormatBool(f.Embedded), 77 | strings.Join(f.Doc, " "), 78 | strings.Join(f.Comment, " "), 79 | }) 80 | } 81 | // flush to buf 82 | w.Flush() 83 | // check flush error 84 | if err := w.Error(); err != nil { 85 | return nil, err 86 | } 87 | } 88 | // and return buf result 89 | return io.ReadAll(rw) 90 | } 91 | } 92 | 93 | // Mdtb defines bytes implementation 94 | // which serializes flat collection 95 | // to formatted markdown table byte slice 96 | func Mdtb(sts []gopium.Struct) ([]byte, error) { 97 | // prepare buffer and collections 98 | var buf bytes.Buffer 99 | if len(sts) > 0 { 100 | // write header 101 | // no error should be 102 | // checked as it uses 103 | // buffered writer 104 | _, _ = buf.WriteString("| Struct Name | Struct Doc | Struct Comment | Field Name | Field Type | Field Size | Field Align | Field Ptr | Field Tag | Field Exported | Field Embedded | Field Doc | Field Comment |\n") 105 | _, _ = buf.WriteString("| :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: | :---: |\n") 106 | for _, st := range sts { 107 | // go through all fields 108 | // and write then one by one 109 | for _, f := range st.Fields { 110 | // no error should be 111 | // checked as it uses 112 | // buffered writer 113 | _, _ = buf.WriteString( 114 | fmt.Sprintf("| %s | %s | %s | %s | %s | %d | %d | %d | %s | %s | %s | %s | %s |\n", 115 | st.Name, 116 | strings.Join(st.Doc, " "), 117 | strings.Join(st.Comment, " "), 118 | f.Name, 119 | f.Type, 120 | f.Size, 121 | f.Align, 122 | f.Ptr, 123 | f.Tag, 124 | strconv.FormatBool(f.Exported), 125 | strconv.FormatBool(f.Embedded), 126 | strings.Join(f.Doc, " "), 127 | strings.Join(f.Comment, " "), 128 | ), 129 | ) 130 | } 131 | } 132 | } 133 | return buf.Bytes(), nil 134 | } 135 | -------------------------------------------------------------------------------- /fmtio/gofmt.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | "go/format" 7 | "go/token" 8 | "io" 9 | ) 10 | 11 | // Gofmt implements printer by 12 | // using canonical ast go fmt printer 13 | type Gofmt struct{} // struct size: 0 bytes; struct align: 1 bytes; struct aligned size: 0 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 14 | 15 | // Print gofmt implementation 16 | func (p Gofmt) Print(ctx context.Context, w io.Writer, fset *token.FileSet, node ast.Node) error { 17 | // manage context actions 18 | // in case of cancelation 19 | // stop execution 20 | select { 21 | case <-ctx.Done(): 22 | return ctx.Err() 23 | default: 24 | } 25 | // use gofmt node 26 | return format.Node(w, fset, node) 27 | } 28 | -------------------------------------------------------------------------------- /fmtio/gofmt_test.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "go/ast" 8 | "go/token" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func TestGofmt(t *testing.T) { 15 | // prepare 16 | node := &ast.StructType{ 17 | Fields: &ast.FieldList{ 18 | List: []*ast.Field{ 19 | { 20 | Names: []*ast.Ident{ 21 | { 22 | Name: "test-removed", 23 | }, 24 | { 25 | Name: "_", 26 | }, 27 | }, 28 | Type: &ast.Ident{ 29 | Name: "string", 30 | }, 31 | Tag: &ast.BasicLit{ 32 | Kind: token.STRING, 33 | Value: "test", 34 | }, 35 | }, 36 | { 37 | Names: []*ast.Ident{ 38 | { 39 | Name: "test-1", 40 | }, 41 | { 42 | Name: "test-2", 43 | }, 44 | }, 45 | Type: &ast.Ident{ 46 | Name: "int64", 47 | }, 48 | Doc: &ast.CommentGroup{ 49 | List: []*ast.Comment{ 50 | { 51 | Text: "// random", 52 | }, 53 | }, 54 | }, 55 | }, 56 | { 57 | Names: []*ast.Ident{ 58 | { 59 | Name: "_", 60 | }, 61 | }, 62 | Type: &ast.Ident{ 63 | Name: "float32", 64 | }, 65 | Tag: &ast.BasicLit{ 66 | Kind: token.STRING, 67 | Value: "tag", 68 | }, 69 | }, 70 | }, 71 | }, 72 | } 73 | cctx, cancel := context.WithCancel(context.Background()) 74 | cancel() 75 | table := map[string]struct { 76 | ctx context.Context 77 | r []byte 78 | err error 79 | }{ 80 | "single struct pkg should print the struct": { 81 | ctx: context.Background(), 82 | r: []byte(` 83 | struct { 84 | test-removed, _ string test// random 85 | test-1, test-2 int64 86 | _ float32 tag 87 | } 88 | `), 89 | }, 90 | "single struct pkg should print nothing on canceled context": { 91 | ctx: cctx, 92 | r: []byte{}, 93 | err: context.Canceled, 94 | }, 95 | } 96 | for name, tcase := range table { 97 | t.Run(name, func(t *testing.T) { 98 | // exec 99 | var buf bytes.Buffer 100 | err := Gofmt{}.Print(tcase.ctx, &buf, token.NewFileSet(), node) 101 | // check 102 | if fmt.Sprintf("%v", err) != fmt.Sprintf("%v", tcase.err) { 103 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 104 | } 105 | // format actual and expected identically 106 | actual := strings.Trim(buf.String(), "\n") 107 | expected := strings.Trim(string(tcase.r), "\n") 108 | if !reflect.DeepEqual(actual, expected) { 109 | t.Errorf("name %v actual %v doesn't equal to expected %v", name, actual, expected) 110 | } 111 | }) 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /fmtio/goprinter.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | "go/printer" 7 | "go/token" 8 | "io" 9 | ) 10 | 11 | // Goprinter implements printer by 12 | // using ast go printer printer 13 | type Goprinter struct { 14 | cfg *printer.Config `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 15 | } // struct size: 8 bytes; struct align: 8 bytes; struct aligned size: 8 bytes; struct ptr scan size: 8 bytes; - 🌺 gopium @1pkg 16 | 17 | // NewGoprinter creates instances of goprinter with configs 18 | func NewGoprinter(indent int, tabwidth int, usespace bool) Goprinter { 19 | // prepare printer with params 20 | cfg := &printer.Config{Indent: indent, Tabwidth: tabwidth} 21 | if usespace { 22 | cfg.Mode = printer.UseSpaces 23 | } 24 | return Goprinter{cfg: cfg} 25 | } 26 | 27 | // Print goprinter implementation 28 | func (p Goprinter) Print(ctx context.Context, w io.Writer, fset *token.FileSet, node ast.Node) error { 29 | // manage context actions 30 | // in case of cancelation 31 | // stop execution 32 | select { 33 | case <-ctx.Done(): 34 | return ctx.Err() 35 | default: 36 | } 37 | // use printer fprint 38 | return p.cfg.Fprint(w, fset, node) 39 | } 40 | -------------------------------------------------------------------------------- /fmtio/goprinter_test.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "bytes" 5 | "context" 6 | "fmt" 7 | "go/ast" 8 | "go/token" 9 | "reflect" 10 | "strings" 11 | "testing" 12 | ) 13 | 14 | func TestGoprinter(t *testing.T) { 15 | // prepare 16 | node := &ast.StructType{ 17 | Fields: &ast.FieldList{ 18 | List: []*ast.Field{ 19 | { 20 | Names: []*ast.Ident{ 21 | { 22 | Name: "test-removed", 23 | }, 24 | { 25 | Name: "_", 26 | }, 27 | }, 28 | Type: &ast.Ident{ 29 | Name: "string", 30 | }, 31 | Tag: &ast.BasicLit{ 32 | Kind: token.STRING, 33 | Value: "test", 34 | }, 35 | }, 36 | { 37 | Names: []*ast.Ident{ 38 | { 39 | Name: "test-1", 40 | }, 41 | { 42 | Name: "test-2", 43 | }, 44 | }, 45 | Type: &ast.Ident{ 46 | Name: "int64", 47 | }, 48 | Doc: &ast.CommentGroup{ 49 | List: []*ast.Comment{ 50 | { 51 | Text: "// random", 52 | }, 53 | }, 54 | }, 55 | }, 56 | { 57 | Names: []*ast.Ident{ 58 | { 59 | Name: "_", 60 | }, 61 | }, 62 | Type: &ast.Ident{ 63 | Name: "float32", 64 | }, 65 | Tag: &ast.BasicLit{ 66 | Kind: token.STRING, 67 | Value: "tag", 68 | }, 69 | }, 70 | }, 71 | }, 72 | } 73 | cctx, cancel := context.WithCancel(context.Background()) 74 | cancel() 75 | table := map[string]struct { 76 | indent int 77 | tabwidth int 78 | usespace bool 79 | ctx context.Context 80 | r []byte 81 | err error 82 | }{ 83 | "single struct pkg should print the struct": { 84 | indent: 0, 85 | tabwidth: 4, 86 | usespace: false, 87 | ctx: context.Background(), 88 | r: []byte(` 89 | struct { 90 | test-removed, _ string test// random 91 | test-1, test-2 int64 92 | _ float32 tag 93 | } 94 | `), 95 | }, 96 | "single struct pkg should print the struct with indent": { 97 | indent: 1, 98 | tabwidth: 8, 99 | usespace: true, 100 | ctx: context.Background(), 101 | r: []byte(` 102 | struct { 103 | test-removed, _ string test// random 104 | test-1, test-2 int64 105 | _ float32 tag 106 | } 107 | `), 108 | }, 109 | "single struct pkg should print nothing on canceled context": { 110 | indent: 0, 111 | tabwidth: 4, 112 | usespace: false, 113 | ctx: cctx, 114 | r: []byte{}, 115 | err: context.Canceled, 116 | }, 117 | } 118 | for name, tcase := range table { 119 | t.Run(name, func(t *testing.T) { 120 | // exec 121 | var buf bytes.Buffer 122 | p := NewGoprinter(tcase.indent, tcase.tabwidth, tcase.usespace) 123 | err := p.Print(tcase.ctx, &buf, token.NewFileSet(), node) 124 | // check 125 | if fmt.Sprintf("%v", err) != fmt.Sprintf("%v", tcase.err) { 126 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 127 | } 128 | // format actual and expected identically 129 | actual := strings.Trim(buf.String(), "\n") 130 | expected := strings.Trim(string(tcase.r), "\n") 131 | if !reflect.DeepEqual(actual, expected) { 132 | t.Errorf("name %v actual %v doesn't equal to expected %v", name, actual, expected) 133 | } 134 | }) 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /fmtio/io.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "bytes" 5 | "io" 6 | "os" 7 | ) 8 | 9 | // list of supported extensions 10 | const ( 11 | GOPIUM = "gopium" 12 | GO = "go" 13 | JSON = "json" 14 | XML = "xml" 15 | CSV = "csv" 16 | MD = "md" 17 | HTML = "html" 18 | ) 19 | 20 | // stdout defines tiny wrapper for 21 | // os stdout stream that couldn't be closed 22 | type stdout struct{} // struct size: 0 bytes; struct align: 1 bytes; struct aligned size: 0 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 23 | 24 | // Write just reuses os stdout stream write 25 | func (stdout) Write(p []byte) (n int, err error) { 26 | return os.Stdout.Write(p) 27 | } 28 | 29 | // Close just does nothing 30 | func (stdout) Close() error { 31 | return nil 32 | } 33 | 34 | // Buffer defines buffer creator helper 35 | func Buffer() io.ReadWriter { 36 | return &bytes.Buffer{} 37 | } 38 | -------------------------------------------------------------------------------- /fmtio/io_test.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | -------------------------------------------------------------------------------- /fmtio/writer.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "fmt" 5 | "io" 6 | "os" 7 | "path/filepath" 8 | "strings" 9 | 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | // Stdout defines writer implementation 14 | // which only returns os stdout all the time 15 | type Stdout struct{} // struct size: 0 bytes; struct align: 1 bytes; struct aligned size: 0 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 16 | 17 | // Generate stdout implementation 18 | func (Stdout) Generate(string) (io.WriteCloser, error) { 19 | return stdout{}, nil 20 | } 21 | 22 | // File defines writer implementation 23 | // which creates underlying single file 24 | // with provided name and ext on provided loc 25 | type File struct { 26 | Name string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 27 | Ext string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 28 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 24 bytes; - 🌺 gopium @1pkg 29 | 30 | // Generate file implementation 31 | func (f File) Generate(loc string) (io.WriteCloser, error) { 32 | path := filepath.Dir(loc) 33 | return os.Create(filepath.Join(path, fmt.Sprintf("%s.%s", f.Name, f.Ext))) 34 | } 35 | 36 | // Files defines writer implementation 37 | // which creates underlying files list 38 | // with provided ext on provided loc 39 | type Files struct { 40 | Ext string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 41 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; struct ptr scan size: 8 bytes; - 🌺 gopium @1pkg 42 | 43 | // Generate files implementation 44 | func (f Files) Generate(loc string) (io.WriteCloser, error) { 45 | path := strings.Replace(loc, filepath.Ext(loc), fmt.Sprintf(".%s", f.Ext), 1) 46 | return os.Create(path) 47 | } 48 | 49 | // Origin defines category writer implementation 50 | // which simply uses underlying writter 51 | type Origin struct { 52 | Writter gopium.Writer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 53 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 54 | 55 | // Category origin implementation 56 | func (o Origin) Category(cat string) error { 57 | return nil 58 | } 59 | 60 | // Generate origin implementation 61 | func (o Origin) Generate(loc string) (io.WriteCloser, error) { 62 | return o.Writter.Generate(loc) 63 | } 64 | 65 | // Suffix defines category writer implementation 66 | // which replaces category for writer 67 | // with provided suffixed category 68 | type Suffix struct { 69 | Writter gopium.Writer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 70 | Suffix string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 71 | oldcat string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 72 | newcat string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 73 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 56 bytes; - 🌺 gopium @1pkg 74 | 75 | // Category suffix implementation 76 | func (s *Suffix) Category(cat string) error { 77 | // add suffix to category 78 | scat := fmt.Sprintf("%s_%s", cat, s.Suffix) 79 | s.oldcat = cat 80 | s.newcat = scat 81 | // create dir for new suffixed cat 82 | return os.MkdirAll(scat, os.ModePerm) 83 | } 84 | 85 | // Generate suffix implementation 86 | func (s Suffix) Generate(loc string) (io.WriteCloser, error) { 87 | loc = strings.Replace(loc, s.oldcat, s.newcat, 1) 88 | return s.Writter.Generate(loc) 89 | } 90 | -------------------------------------------------------------------------------- /fmtio/writer_test.go: -------------------------------------------------------------------------------- 1 | package fmtio 2 | 3 | import ( 4 | "fmt" 5 | "os" 6 | "path/filepath" 7 | "reflect" 8 | "testing" 9 | 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | func TestWriter(t *testing.T) { 14 | // prepare 15 | pdir, err := filepath.Abs("..") 16 | if !reflect.DeepEqual(err, nil) { 17 | t.Fatalf("actual %v doesn't equal to %v", err, nil) 18 | } 19 | pfile, err := filepath.Abs(filepath.Join("..", "opium.go")) 20 | if !reflect.DeepEqual(err, nil) { 21 | t.Fatalf("actual %v doesn't equal to %v", err, nil) 22 | } 23 | table := map[string]struct { 24 | w gopium.Writer 25 | loc string 26 | path string 27 | err error 28 | werr error 29 | cerr error 30 | }{ 31 | "stdout should return expected stdout writer": { 32 | w: Stdout{}, 33 | loc: pdir, 34 | path: "", 35 | }, 36 | "file json should return expected json writer": { 37 | w: File{Name: "test", Ext: "json"}, 38 | loc: pfile, 39 | path: filepath.Join(pdir, "test.json"), 40 | }, 41 | "file xml should return expected xml writer": { 42 | w: File{Name: "test", Ext: "xml"}, 43 | loc: pfile, 44 | path: filepath.Join(pdir, "test.xml"), 45 | }, 46 | "file csv should return expected csv writer": { 47 | w: File{Name: "test", Ext: "csv"}, 48 | loc: pfile, 49 | path: filepath.Join(pdir, "test.csv"), 50 | }, 51 | "files json should return expected json writer": { 52 | w: Files{Ext: "json"}, 53 | loc: pfile, 54 | path: filepath.Join(pdir, "opium.json"), 55 | }, 56 | "files xml should return expected xml writer": { 57 | w: Files{Ext: "xml"}, 58 | loc: pfile, 59 | path: filepath.Join(pdir, "opium.xml"), 60 | }, 61 | "files csv should return expected csv writer": { 62 | w: Files{Ext: "csv"}, 63 | loc: pfile, 64 | path: filepath.Join(pdir, "opium.csv"), 65 | }, 66 | } 67 | for name, tcase := range table { 68 | t.Run(name, func(t *testing.T) { 69 | // exec 70 | wc, err := tcase.w.Generate(tcase.loc) 71 | n, werr := wc.Write([]byte(``)) 72 | cerr := wc.Close() 73 | // check 74 | if fmt.Sprintf("%v", err) != fmt.Sprintf("%v", tcase.err) { 75 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 76 | } 77 | if err == nil && reflect.DeepEqual(wc, nil) { 78 | t.Errorf("actual %v doesn't equal to expected not %v", wc, nil) 79 | } 80 | // check that such file exists 81 | if tcase.path != "" { 82 | defer os.Remove(tcase.path) 83 | if _, err := os.Stat(tcase.path); !reflect.DeepEqual(err, nil) { 84 | t.Errorf("actual %v doesn't equal to expected %v", err, nil) 85 | } 86 | } 87 | if fmt.Sprintf("%v", werr) != fmt.Sprintf("%v", tcase.werr) { 88 | t.Errorf("actual %v doesn't equal to expected %v", werr, tcase.werr) 89 | } 90 | if !reflect.DeepEqual(n, 0) { 91 | t.Errorf("actual %v doesn't equal to expected %v", n, 0) 92 | } 93 | if fmt.Sprintf("%v", cerr) != fmt.Sprintf("%v", tcase.cerr) { 94 | t.Errorf("actual %v doesn't equal to expected %v", werr, tcase.werr) 95 | } 96 | }) 97 | } 98 | } 99 | 100 | func TestCategoryWriter(t *testing.T) { 101 | // prepare 102 | pdir, err := filepath.Abs("..") 103 | if !reflect.DeepEqual(err, nil) { 104 | t.Fatalf("actual %v doesn't equal to %v", err, nil) 105 | } 106 | pfile, err := filepath.Abs(filepath.Join("..", "opium.go")) 107 | if !reflect.DeepEqual(err, nil) { 108 | t.Fatalf("actual %v doesn't equal to %v", err, nil) 109 | } 110 | table := map[string]struct { 111 | w gopium.CategoryWriter 112 | cat string 113 | loc string 114 | path string 115 | err error 116 | }{ 117 | "file json with replace cat writer should return expected json writer": { 118 | w: Origin{Writter: File{Name: "test", Ext: "json"}}, 119 | cat: pdir, 120 | loc: pfile, 121 | path: filepath.Join(pdir, "test.json"), 122 | }, 123 | "file json with copy cat writer should return expected json writer": { 124 | w: &Suffix{Writter: File{Name: "test", Ext: "json"}, Suffix: "test"}, 125 | cat: pdir, 126 | loc: pfile, 127 | path: filepath.Join(fmt.Sprintf("%s_%s", pdir, "test"), "test.json"), 128 | }, 129 | } 130 | for name, tcase := range table { 131 | t.Run(name, func(t *testing.T) { 132 | // exec 133 | err := tcase.w.Category(tcase.cat) 134 | wc, gerr := tcase.w.Generate(tcase.loc) 135 | n, werr := wc.Write([]byte(``)) 136 | cerr := wc.Close() 137 | // check 138 | if fmt.Sprintf("%v", err) != fmt.Sprintf("%v", tcase.err) { 139 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 140 | } 141 | if err == nil && reflect.DeepEqual(wc, nil) { 142 | t.Errorf("actual %v doesn't equal to expected not %v", wc, nil) 143 | } 144 | // check that such file exists 145 | if tcase.path != "" { 146 | defer func() { 147 | os.Remove(tcase.path) 148 | os.Remove(filepath.Dir(tcase.path)) 149 | }() 150 | if _, err := os.Stat(tcase.path); !reflect.DeepEqual(err, nil) { 151 | t.Errorf("actual %v doesn't equal to expected %v", err, nil) 152 | } 153 | } 154 | if !reflect.DeepEqual(gerr, nil) { 155 | t.Errorf("actual %v doesn't equal to expected %v", gerr, nil) 156 | } 157 | if !reflect.DeepEqual(werr, nil) { 158 | t.Errorf("actual %v doesn't equal to expected %v", werr, nil) 159 | } 160 | if !reflect.DeepEqual(n, 0) { 161 | t.Errorf("actual %v doesn't equal to expected %v", n, 0) 162 | } 163 | if !reflect.DeepEqual(cerr, nil) { 164 | t.Errorf("actual %v doesn't equal to expected %v", werr, nil) 165 | } 166 | }) 167 | } 168 | } 169 | -------------------------------------------------------------------------------- /go.mod: -------------------------------------------------------------------------------- 1 | module github.com/1pkg/gopium 2 | 3 | go 1.22 4 | 5 | require ( 6 | github.com/spf13/cobra v1.1.1 7 | golang.org/x/sync v0.7.0 8 | golang.org/x/tools v0.20.0 9 | ) 10 | 11 | require ( 12 | github.com/inconshreveable/mousetrap v1.0.0 // indirect 13 | github.com/spf13/pflag v1.0.5 // indirect 14 | golang.org/x/mod v0.17.0 // indirect 15 | ) 16 | -------------------------------------------------------------------------------- /gopher.kra: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1pkg/gopium/26cf8136c3e4de73ff1dac2435fea8ea18ce9bcd/gopher.kra -------------------------------------------------------------------------------- /gopher.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/1pkg/gopium/26cf8136c3e4de73ff1dac2435fea8ea18ce9bcd/gopher.png -------------------------------------------------------------------------------- /gopium/adt.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | // Categorized defines abstraction for 4 | // categorized structures collection 5 | type Categorized interface { 6 | Full() map[string]Struct 7 | Cat(string) (map[string]Struct, bool) 8 | } 9 | -------------------------------------------------------------------------------- /gopium/ast.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | ) 7 | 8 | // Walk defines ast walker function abstraction 9 | // that walks through type spec ast nodes with provided 10 | // comparator function and applies some custom action 11 | type Walk func(context.Context, ast.Node, Visitor, Comparator) (ast.Node, error) 12 | 13 | // Visitor defines walker action abstraction 14 | // that applies custom action on ast type spec node 15 | type Visitor interface { 16 | Visit(*ast.TypeSpec, Struct) error 17 | } 18 | 19 | // Comparator defines walker comparator abstraction 20 | // that checks if ast type spec node needs to be visitted 21 | // and returns relevant gopium struct and existing flag 22 | type Comparator interface { 23 | Check(*ast.TypeSpec) (Struct, bool) 24 | } 25 | -------------------------------------------------------------------------------- /gopium/fmt.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | ) 7 | 8 | // Bytes defines abstraction for formatting 9 | // gopium flat collection to byte slice 10 | type Bytes func([]Struct) ([]byte, error) 11 | 12 | // Ast defines abstraction for 13 | // formatting original ast type spec 14 | // accordingly to gopium struct 15 | type Ast func(*ast.TypeSpec, Struct) error 16 | 17 | // Diff defines abstraction for formatting 18 | // gopium collections difference to byte slice 19 | type Diff func(Categorized, Categorized) ([]byte, error) 20 | 21 | // Apply defines abstraction for 22 | // formatting original ast package by 23 | // applying custom action accordingly to 24 | // provided categorized collection 25 | type Apply func(context.Context, *ast.Package, Locator, Categorized) (*ast.Package, error) 26 | -------------------------------------------------------------------------------- /gopium/maven.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import "go/types" 4 | 5 | // Curator defines system level info curator abstraction 6 | // to expose system word, aligment and cache levels sizes 7 | type Curator interface { 8 | SysWord() int64 9 | SysAlign() int64 10 | SysCache(level uint) int64 11 | } 12 | 13 | // Exposer defines type info exposer abstraction 14 | // to expose name, size and aligment for provided data type 15 | type Exposer interface { 16 | Name(types.Type) string 17 | Size(types.Type) int64 18 | Align(types.Type) int64 19 | Ptr(types.Type) int64 20 | } 21 | 22 | // Maven defines abstraction that 23 | // aggregates curator and exposer abstractions 24 | type Maven interface { 25 | Curator 26 | Exposer 27 | } 28 | -------------------------------------------------------------------------------- /gopium/opium.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | // list of global registered gopium constants 4 | const ( 5 | NAME = "gopium" 6 | VERSION = "1.8.0" 7 | PKG = "https://github.com/1pkg/gopium" 8 | STAMP = "🌺 gopium @1pkg" 9 | ) 10 | -------------------------------------------------------------------------------- /gopium/parser.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | "go/token" 7 | "go/types" 8 | ) 9 | 10 | // Locator defines abstraction that helps to 11 | // encapsulate pkgs file set related operations 12 | type Locator interface { 13 | ID(token.Pos) string 14 | Loc(token.Pos) string 15 | Locator(string) (Locator, bool) 16 | Fset(string, *token.FileSet) (*token.FileSet, bool) 17 | Root() *token.FileSet 18 | } 19 | 20 | // TypeParser defines abstraction for 21 | // types packages parsing processor 22 | type TypeParser interface { 23 | ParseTypes(context.Context, ...byte) (*types.Package, Locator, error) 24 | } 25 | 26 | // AstParser defines abstraction for 27 | // ast packages parsing processor 28 | type AstParser interface { 29 | ParseAst(context.Context, ...byte) (*ast.Package, Locator, error) 30 | } 31 | 32 | // Parser defines abstraction that 33 | // aggregates ast and type parsers abstractions 34 | type Parser interface { 35 | TypeParser 36 | AstParser 37 | } 38 | -------------------------------------------------------------------------------- /gopium/persister.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | ) 7 | 8 | // Persister defines abstraction for 9 | // ast node pesister with provided printer 10 | // to provided writer by provided locator 11 | type Persister interface { 12 | Persist(context.Context, Printer, Writer, Locator, ast.Node) error 13 | } 14 | -------------------------------------------------------------------------------- /gopium/printer.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | "go/token" 7 | "io" 8 | ) 9 | 10 | // Printer defines abstraction for 11 | // ast node printing function to io writer 12 | type Printer interface { 13 | Print(context.Context, io.Writer, *token.FileSet, ast.Node) error 14 | } 15 | -------------------------------------------------------------------------------- /gopium/runner.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import "context" 4 | 5 | // Runner defines abstraction for gopium runner 6 | type Runner interface { 7 | Run(context.Context) error 8 | } 9 | -------------------------------------------------------------------------------- /gopium/strategy.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import "context" 4 | 5 | // Strategy defines custom action abstraction 6 | // that applies some action payload on struct 7 | // and returns resulted struct object or error 8 | type Strategy interface { 9 | Apply(context.Context, Struct) (Struct, error) 10 | } 11 | 12 | // StrategyName defines registered strategy name abstraction 13 | // used by StrategyBuilder to build registered strategies 14 | type StrategyName string 15 | 16 | // StrategyBuilder defines strategy builder abstraction 17 | // that helps to create single strategy by strategies names 18 | type StrategyBuilder interface { 19 | Build(...StrategyName) (Strategy, error) 20 | } 21 | -------------------------------------------------------------------------------- /gopium/struct.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | // Field defines single structure field 4 | // data transfer object abstraction 5 | type Field struct { 6 | Name string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 7 | Type string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 8 | Size int64 `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 9 | Align int64 `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 10 | Ptr int64 `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 11 | Tag string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 12 | Exported bool `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 13 | Embedded bool `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 14 | Doc []string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 15 | Comment []string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 16 | } // struct size: 122 bytes; struct align: 8 bytes; struct aligned size: 128 bytes; struct ptr scan size: 106 bytes; - 🌺 gopium @1pkg 17 | 18 | // Struct defines single structure 19 | // data transfer object abstraction 20 | type Struct struct { 21 | Name string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 22 | Doc []string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 23 | Comment []string `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 24 | Fields []Field `gopium:"filter_pads,struct_annotate_comment,add_tag_group_force"` 25 | } // struct size: 88 bytes; struct align: 8 bytes; struct aligned size: 88 bytes; struct ptr scan size: 72 bytes; - 🌺 gopium @1pkg 26 | -------------------------------------------------------------------------------- /gopium/walker.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | ) 7 | 8 | // Walker defines hierarchical walker abstraction 9 | // that applies some strategy to code tree structures 10 | // and modifies them or creates other related side effects 11 | type Walker interface { 12 | Visit(context.Context, *regexp.Regexp, Strategy) error 13 | } 14 | 15 | // WalkerName defines registered walker name abstraction 16 | // used by walker builder to build registered walkers 17 | type WalkerName string 18 | 19 | // WalkerBuilder defines walker builder abstraction 20 | // that helps to create single walker by walker name 21 | type WalkerBuilder interface { 22 | Build(WalkerName) (Walker, error) 23 | } 24 | -------------------------------------------------------------------------------- /gopium/writer.go: -------------------------------------------------------------------------------- 1 | package gopium 2 | 3 | import "io" 4 | 5 | // Writer defines abstraction for 6 | // io witers generation 7 | type Writer interface { 8 | Generate(string) (io.WriteCloser, error) 9 | } 10 | 11 | // CategoryWriter defines abstraction for 12 | // io witers generation with flexible category 13 | type CategoryWriter interface { 14 | Category(string) error 15 | Writer 16 | } 17 | -------------------------------------------------------------------------------- /runners/cli.go: -------------------------------------------------------------------------------- 1 | package runners 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go/build" 7 | "go/parser" 8 | "path/filepath" 9 | "regexp" 10 | "strings" 11 | "time" 12 | 13 | "github.com/1pkg/gopium/fmtio" 14 | "github.com/1pkg/gopium/gopium" 15 | "github.com/1pkg/gopium/strategies" 16 | "github.com/1pkg/gopium/typepkg" 17 | "github.com/1pkg/gopium/walkers" 18 | 19 | "golang.org/x/tools/go/packages" 20 | ) 21 | 22 | // Cli defines cli runner implementation 23 | // that is able to run full gopium cli application 24 | type Cli struct { 25 | wb gopium.WalkerBuilder `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 26 | sb gopium.StrategyBuilder `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 27 | v visitor `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 28 | wname gopium.WalkerName `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 29 | snames []gopium.StrategyName `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 30 | _ [40]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 31 | } // struct size: 128 bytes; struct align: 8 bytes; struct aligned size: 128 bytes; struct ptr scan size: 72 bytes; - 🌺 gopium @1pkg 32 | 33 | // NewCli helps to spawn new cli application runner 34 | // from list of received parameters or returns error 35 | func NewCli( 36 | // target platform vars 37 | compiler, 38 | arch string, 39 | cpucaches []int, 40 | // package parser vars 41 | pkg, 42 | path string, 43 | benvs, 44 | bflags []string, 45 | // gopium walker vars 46 | walker, 47 | regex string, 48 | deep, 49 | backref bool, 50 | stgs []string, 51 | // gopium printer vars 52 | indent, 53 | tabwidth int, 54 | usespace, 55 | usegofmt bool, 56 | // gopium global vars 57 | timeout int, 58 | ) (*Cli, error) { 59 | // cast caches to int64 60 | caches := make([]int64, 0, len(cpucaches)) 61 | for _, cache := range cpucaches { 62 | caches = append(caches, int64(cache)) 63 | } 64 | // set up maven 65 | m, err := typepkg.NewMavenGoTypes(compiler, arch, caches...) 66 | if err != nil { 67 | return nil, fmt.Errorf("can't set up maven %v", err) 68 | } 69 | // replace package template 70 | path = strings.Replace(path, "{{package}}", pkg, 1) 71 | // set root to gopath only if 72 | // not absolute path has been provided 73 | var root string 74 | if !filepath.IsAbs(path) { 75 | root = build.Default.GOPATH 76 | } 77 | // set up parser 78 | xp := &typepkg.ParserXToolPackagesAst{ 79 | Pattern: pkg, 80 | Root: root, 81 | Path: path, 82 | //nolint 83 | ModeTypes: packages.LoadAllSyntax, 84 | ModeAst: parser.ParseComments | parser.AllErrors, 85 | BuildEnv: benvs, 86 | BuildFlags: bflags, 87 | } 88 | // set up printer 89 | var p gopium.Printer 90 | if usegofmt { 91 | p = fmtio.Gofmt{} 92 | } else { 93 | p = fmtio.NewGoprinter(indent, tabwidth, usespace) 94 | } 95 | // compile regexp 96 | cregex, err := regexp.Compile(regex) 97 | if err != nil { 98 | return nil, fmt.Errorf("can't compile such regexp %v", err) 99 | } 100 | // cast timeout to second duration 101 | stimeout := time.Duration(timeout) * time.Second 102 | // set up visitor 103 | v := visitor{ 104 | regex: cregex, 105 | timeout: stimeout, 106 | } 107 | // set walker and strategy builders 108 | wb := walkers.Builder{ 109 | Parser: xp, 110 | Exposer: m, 111 | Printer: p, 112 | Deep: deep, 113 | Bref: backref, 114 | } 115 | sb := strategies.Builder{Curator: m} 116 | // cast strategies strings to strategy names 117 | snames := make([]gopium.StrategyName, 0, len(stgs)) 118 | for _, strategy := range stgs { 119 | snames = append(snames, gopium.StrategyName(strategy)) 120 | } 121 | // cast walker string to walker name 122 | wname := gopium.WalkerName(walker) 123 | // combine cli runner 124 | return &Cli{ 125 | v: v, 126 | wb: wb, 127 | sb: sb, 128 | wname: wname, 129 | snames: snames, 130 | }, nil 131 | } 132 | 133 | // Run cli implementation 134 | func (cli *Cli) Run(ctx context.Context) error { 135 | // build strategy 136 | stg, err := cli.v.strategy(cli.sb, cli.snames) 137 | if err != nil { 138 | return err 139 | } 140 | // build walker 141 | w, err := cli.v.walker(cli.wb, cli.wname) 142 | if err != nil { 143 | return err 144 | } 145 | // run visitor visiting 146 | return cli.v.visit(ctx, w, stg) 147 | } 148 | -------------------------------------------------------------------------------- /runners/visitor.go: -------------------------------------------------------------------------------- 1 | package runners 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "regexp" 7 | "time" 8 | 9 | "github.com/1pkg/gopium/gopium" 10 | ) 11 | 12 | // visitor defines helper 13 | // that coordinates runner stages 14 | // to finally make visiting 15 | // - strategy building 16 | // - walker building 17 | // - visiting 18 | type visitor struct { 19 | regex *regexp.Regexp `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 20 | timeout time.Duration `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 21 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; struct ptr scan size: 8 bytes; - 🌺 gopium @1pkg 22 | 23 | // strategy builds strategy instance 24 | // by using builder and strategies names 25 | func (visitor) strategy(b gopium.StrategyBuilder, snames []gopium.StrategyName) (gopium.Strategy, error) { 26 | // build strategy 27 | stg, err := b.Build(snames...) 28 | if err != nil { 29 | return nil, fmt.Errorf("can't build such strategy %v %v", snames, err) 30 | } 31 | return stg, nil 32 | } 33 | 34 | // walker builds walker instance 35 | // by using builder and walker name 36 | func (visitor) walker(b gopium.WalkerBuilder, wname gopium.WalkerName) (gopium.Walker, error) { 37 | // build walker 38 | walker, err := b.Build(wname) 39 | if err != nil { 40 | return nil, fmt.Errorf("can't build such walker %q %v", wname, err) 41 | } 42 | return walker, nil 43 | } 44 | 45 | // visit coordinates walker visiting 46 | func (v visitor) visit(ctx context.Context, w gopium.Walker, stg gopium.Strategy) error { 47 | // set up timeout context 48 | if v.timeout > 0 { 49 | nctx, cancel := context.WithTimeout(ctx, v.timeout) 50 | defer cancel() 51 | ctx = nctx 52 | } 53 | // exec visit on walker with strategy 54 | if err := w.Visit(ctx, v.regex, stg); err != nil { 55 | return fmt.Errorf("visiting error happened %v", err) 56 | } 57 | return nil 58 | } 59 | -------------------------------------------------------------------------------- /runners/visitor_test.go: -------------------------------------------------------------------------------- 1 | package runners 2 | -------------------------------------------------------------------------------- /strategies/cache.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1pkg/gopium/collections" 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // list of cache presets 11 | var ( 12 | cachel1d = cache{line: 1, div: true} 13 | cachel2d = cache{line: 2, div: true} 14 | cachel3d = cache{line: 3, div: true} 15 | cachebd = cache{div: true} 16 | cachel1f = cache{line: 1, div: false} 17 | cachel2f = cache{line: 2, div: false} 18 | cachel3f = cache{line: 3, div: false} 19 | cachebf = cache{div: false} 20 | ) 21 | 22 | // cache defines strategy implementation 23 | // that fits structure into cpu cache line 24 | // by adding bottom rounding cpu cache padding 25 | type cache struct { 26 | curator gopium.Curator `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 27 | line uint `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 28 | bytes uint `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 29 | div bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 30 | _ [31]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 31 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 32 | 33 | // Bytes erich cache strategy with custom bytes 34 | func (stg cache) Bytes(bytes uint) cache { 35 | stg.bytes = bytes 36 | return stg 37 | } 38 | 39 | // Curator erich cache strategy with curator instance 40 | func (stg cache) Curator(curator gopium.Curator) cache { 41 | stg.curator = curator 42 | return stg 43 | } 44 | 45 | // Apply cache implementation 46 | func (stg cache) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 47 | // copy original structure to result 48 | r := collections.CopyStruct(o) 49 | // calculate aligned size of structure 50 | var alsize int64 51 | collections.WalkStruct(r, 0, func(pad int64, fields ...gopium.Field) { 52 | // add pad to aligned size 53 | // only if it's not last pad 54 | if len(fields) > 0 { 55 | alsize += pad 56 | } 57 | // go through all fields 58 | for _, f := range fields { 59 | // add field size aligned sizes 60 | alsize += f.Size 61 | } 62 | }) 63 | // check if cache line size or bytes are valid 64 | if cachel := stg.curator.SysCache(stg.line); cachel > 0 || stg.bytes > 0 { 65 | if stg.line == 0 { 66 | cachel = int64(stg.bytes) 67 | } 68 | // if fractional cache line is allowed 69 | if stg.div { 70 | // find smallest size of fraction for cache line 71 | if alsize > 0 && cachel > alsize { 72 | for cachel >= alsize && cachel > 1 { 73 | cachel /= 2 74 | } 75 | cachel *= 2 76 | } 77 | } 78 | // get number of padding bytes 79 | // to fill cpu cache line 80 | // if padding is valid append it 81 | if pad := alsize % cachel; pad > 0 { 82 | pad = cachel - pad 83 | r.Fields = append(r.Fields, collections.PadField(pad)) 84 | } 85 | } 86 | return r, ctx.Err() 87 | } 88 | -------------------------------------------------------------------------------- /strategies/filter.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // list of filter presets 12 | var ( 13 | // list of filter presets 14 | fpad = filter{ 15 | nregex: regexp.MustCompile(`^_$`), 16 | } 17 | ) 18 | 19 | // filter defines strategy implementation 20 | // that filters out all structure fields 21 | // that matches provided criteria 22 | type filter struct { 23 | nregex *regexp.Regexp `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 24 | tregex *regexp.Regexp `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 25 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 26 | 27 | // Apply filter implementation 28 | func (stg filter) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 29 | // copy original structure to result 30 | r := collections.CopyStruct(o) 31 | // prepare filtered fields slice 32 | if flen := len(r.Fields); flen > 0 { 33 | fields := make([]gopium.Field, 0, flen) 34 | // then go though all original fields 35 | for _, f := range r.Fields { 36 | // check if field name matches regex 37 | if stg.nregex != nil && stg.nregex.MatchString(f.Name) { 38 | continue 39 | } 40 | // check if field type matches regex 41 | if stg.tregex != nil && stg.tregex.MatchString(f.Type) { 42 | continue 43 | } 44 | // if it doesn't append it to fields 45 | fields = append(fields, f) 46 | } 47 | // update result fields 48 | r.Fields = fields 49 | } 50 | return r, ctx.Err() 51 | } 52 | -------------------------------------------------------------------------------- /strategies/filter_test.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | "regexp" 8 | "testing" 9 | 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | func TestFilter(t *testing.T) { 14 | // prepare 15 | cctx, cancel := context.WithCancel(context.Background()) 16 | cancel() 17 | table := map[string]struct { 18 | filter filter 19 | ctx context.Context 20 | o gopium.Struct 21 | r gopium.Struct 22 | err error 23 | }{ 24 | "empty struct should be applied to empty struct with empty filter": { 25 | filter: filter{}, 26 | ctx: context.Background(), 27 | }, 28 | "non empty struct should be applied to itself with empty filter": { 29 | filter: filter{}, 30 | ctx: context.Background(), 31 | o: gopium.Struct{ 32 | Name: "test", 33 | Fields: []gopium.Field{ 34 | { 35 | Name: "test", 36 | Type: "test", 37 | }, 38 | }, 39 | }, 40 | r: gopium.Struct{ 41 | Name: "test", 42 | Fields: []gopium.Field{ 43 | { 44 | Name: "test", 45 | Type: "test", 46 | }, 47 | }, 48 | }, 49 | }, 50 | "non empty struct should be applied to itself on canceled context with empty filter": { 51 | filter: filter{}, 52 | ctx: cctx, 53 | o: gopium.Struct{ 54 | Name: "test", 55 | Fields: []gopium.Field{ 56 | { 57 | Name: "test", 58 | Type: "test", 59 | }, 60 | }, 61 | }, 62 | r: gopium.Struct{ 63 | Name: "test", 64 | Fields: []gopium.Field{ 65 | { 66 | Name: "test", 67 | Type: "test", 68 | }, 69 | }, 70 | }, 71 | err: context.Canceled, 72 | }, 73 | "non empty struct should be applied accordingly to filter name": { 74 | filter: filter{nregex: regexp.MustCompile(`^test-2$`)}, 75 | ctx: context.Background(), 76 | o: gopium.Struct{ 77 | Name: "test", 78 | Fields: []gopium.Field{ 79 | { 80 | Name: "test-1", 81 | Type: "test-1", 82 | Embedded: true, 83 | Exported: true, 84 | }, 85 | { 86 | Name: "test-2", 87 | Type: "test-2", 88 | Exported: true, 89 | }, 90 | { 91 | Name: "test-3", 92 | Type: "test-3", 93 | Embedded: true, 94 | }, 95 | }, 96 | }, 97 | r: gopium.Struct{ 98 | Name: "test", 99 | Fields: []gopium.Field{ 100 | { 101 | Name: "test-1", 102 | Type: "test-1", 103 | Embedded: true, 104 | Exported: true, 105 | }, 106 | { 107 | Name: "test-3", 108 | Type: "test-3", 109 | Embedded: true, 110 | }, 111 | }, 112 | }, 113 | }, 114 | "non empty struct should be applied accordingly to filter type": { 115 | filter: filter{tregex: regexp.MustCompile(`^test-2$`)}, 116 | ctx: context.Background(), 117 | o: gopium.Struct{ 118 | Name: "test", 119 | Fields: []gopium.Field{ 120 | { 121 | Name: "test-1", 122 | Type: "test-1", 123 | Embedded: true, 124 | Exported: true, 125 | }, 126 | { 127 | Name: "test-2", 128 | Type: "test-2", 129 | Exported: true, 130 | }, 131 | { 132 | Name: "test-3", 133 | Type: "test-3", 134 | Embedded: true, 135 | }, 136 | }, 137 | }, 138 | r: gopium.Struct{ 139 | Name: "test", 140 | Fields: []gopium.Field{ 141 | { 142 | Name: "test-1", 143 | Type: "test-1", 144 | Embedded: true, 145 | Exported: true, 146 | }, 147 | { 148 | Name: "test-3", 149 | Type: "test-3", 150 | Embedded: true, 151 | }, 152 | }, 153 | }, 154 | }, 155 | } 156 | for name, tcase := range table { 157 | t.Run(name, func(t *testing.T) { 158 | // exec 159 | r, err := tcase.filter.Apply(tcase.ctx, tcase.o) 160 | // check 161 | if !reflect.DeepEqual(r, tcase.r) { 162 | t.Errorf("actual %v doesn't equal to expected %v", r, tcase.r) 163 | } 164 | if !errors.Is(err, tcase.err) { 165 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 166 | } 167 | }) 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /strategies/fshare.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1pkg/gopium/collections" 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // list of fshare presets 11 | var ( 12 | fsharel1 = fshare{line: 1} 13 | fsharel2 = fshare{line: 2} 14 | fsharel3 = fshare{line: 3} 15 | fshareb = fshare{} 16 | ) 17 | 18 | // fshare defines strategy implementation 19 | // that guards structure from false sharing 20 | // by adding extra cpu cache line paddings 21 | // for each structure field 22 | type fshare struct { 23 | curator gopium.Curator `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 24 | line uint `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 25 | bytes uint `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 26 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 27 | 28 | // Bytes erich fshare strategy with custom bytes 29 | func (stg fshare) Bytes(bytes uint) fshare { 30 | stg.bytes = bytes 31 | return stg 32 | } 33 | 34 | // Curator erich fshare strategy with curator instance 35 | func (stg fshare) Curator(curator gopium.Curator) fshare { 36 | stg.curator = curator 37 | return stg 38 | } 39 | 40 | // Apply fshare implementation 41 | func (stg fshare) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 42 | // copy original structure to result 43 | r := collections.CopyStruct(o) 44 | // check that struct has fields 45 | // and cache line size or bytes are valid 46 | if flen, cachel := len(r.Fields), stg.curator.SysCache(stg.line); flen > 0 && (cachel > 0 || stg.bytes > 0) { 47 | if stg.line == 0 { 48 | cachel = int64(stg.bytes) 49 | } 50 | // setup resulted fields slice 51 | fields := make([]gopium.Field, 0, flen) 52 | // go through all fields 53 | for _, f := range r.Fields { 54 | fields = append(fields, f) 55 | // if padding size is valid 56 | if pad := f.Size % cachel; pad > 0 { 57 | pad = cachel - pad 58 | fields = append(fields, collections.PadField(pad)) 59 | } 60 | } 61 | // update resulted fields 62 | r.Fields = fields 63 | } 64 | return r, ctx.Err() 65 | } 66 | -------------------------------------------------------------------------------- /strategies/ignore.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1pkg/gopium/gopium" 7 | ) 8 | 9 | // list of ignore presets 10 | var ( 11 | ignr = ignore{} 12 | ) 13 | 14 | // ignore defines nil strategy implementation 15 | // that does nothing by returning original structure 16 | type ignore struct{} // struct size: 0 bytes; struct align: 1 bytes; struct aligned size: 0 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 17 | 18 | // Apply ignore implementation 19 | func (stg ignore) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 20 | return o, ctx.Err() 21 | } 22 | -------------------------------------------------------------------------------- /strategies/ignore_test.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/1pkg/gopium/gopium" 10 | ) 11 | 12 | func TestNope(t *testing.T) { 13 | // prepare 14 | cctx, cancel := context.WithCancel(context.Background()) 15 | cancel() 16 | table := map[string]struct { 17 | ctx context.Context 18 | o gopium.Struct 19 | r gopium.Struct 20 | err error 21 | }{ 22 | "empty struct should be applied to itself": { 23 | ctx: context.Background(), 24 | }, 25 | "non empty struct should be applied to itself": { 26 | ctx: context.Background(), 27 | o: gopium.Struct{ 28 | Name: "test", 29 | Fields: []gopium.Field{ 30 | { 31 | Name: "test", 32 | }, 33 | }, 34 | }, 35 | r: gopium.Struct{ 36 | Name: "test", 37 | Fields: []gopium.Field{ 38 | { 39 | Name: "test", 40 | }, 41 | }, 42 | }, 43 | }, 44 | "non empty struct should be applied to itself on canceled context": { 45 | ctx: cctx, 46 | o: gopium.Struct{ 47 | Name: "test", 48 | Fields: []gopium.Field{ 49 | { 50 | Name: "test", 51 | }, 52 | }, 53 | }, 54 | r: gopium.Struct{ 55 | Name: "test", 56 | Fields: []gopium.Field{ 57 | { 58 | Name: "test", 59 | }, 60 | }, 61 | }, 62 | err: context.Canceled, 63 | }, 64 | "complex struct should be applied to itself": { 65 | ctx: context.Background(), 66 | o: gopium.Struct{ 67 | Name: "test", 68 | Doc: []string{"test"}, 69 | Fields: []gopium.Field{ 70 | { 71 | Name: "test1", 72 | Type: "int", 73 | Size: 8, 74 | }, 75 | { 76 | Name: "test2", 77 | Type: "string", 78 | Doc: []string{"test"}, 79 | }, 80 | { 81 | Name: "test2", 82 | Type: "float64", 83 | }, 84 | }, 85 | }, 86 | r: gopium.Struct{ 87 | Name: "test", 88 | Doc: []string{"test"}, 89 | Fields: []gopium.Field{ 90 | { 91 | Name: "test1", 92 | Type: "int", 93 | Size: 8, 94 | }, 95 | { 96 | Name: "test2", 97 | Type: "string", 98 | Doc: []string{"test"}, 99 | }, 100 | { 101 | Name: "test2", 102 | Type: "float64", 103 | }, 104 | }, 105 | }, 106 | }, 107 | } 108 | for name, tcase := range table { 109 | t.Run(name, func(t *testing.T) { 110 | // exec 111 | r, err := ignr.Apply(tcase.ctx, tcase.o) 112 | // check 113 | if !reflect.DeepEqual(r, tcase.r) { 114 | t.Errorf("actual %v doesn't equal to expected %v", r, tcase.r) 115 | } 116 | if !errors.Is(err, tcase.err) { 117 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 118 | } 119 | }) 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /strategies/nlex.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "sort" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // list of nlex presets 12 | var ( 13 | nlexasc = nlex{asc: true} 14 | nlexdesc = nlex{asc: false} 15 | ) 16 | 17 | // nlex defines strategy implementation 18 | // that sorts fields accordingly to their names 19 | // in ascending or descending order 20 | type nlex struct { 21 | asc bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 22 | _ [1]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 23 | } // struct size: 2 bytes; struct align: 1 bytes; struct aligned size: 2 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 24 | 25 | // Apply nlex implementation 26 | func (stg nlex) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 27 | // copy original structure to result 28 | r := collections.CopyStruct(o) 29 | // then execute lexicographical sorting 30 | sort.SliceStable(r.Fields, func(i, j int) bool { 31 | // sort depends on type of ordering 32 | if stg.asc { 33 | return r.Fields[i].Name < r.Fields[j].Name 34 | } 35 | return r.Fields[i].Name > r.Fields[j].Name 36 | }) 37 | return r, ctx.Err() 38 | } 39 | -------------------------------------------------------------------------------- /strategies/note.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // list of note presets 12 | var ( 13 | fnotedoc = note{doc: true, field: true} 14 | fnotecom = note{doc: false, field: true} 15 | stnotedoc = note{doc: true, field: false} 16 | stnotecom = note{doc: false, field: false} 17 | ) 18 | 19 | // note defines strategy implementation 20 | // that adds size doc or comment annotation 21 | // for each structure field 22 | // and aggregated size annotation for structure 23 | type note struct { 24 | doc bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 25 | field bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 26 | } // struct size: 2 bytes; struct align: 1 bytes; struct aligned size: 2 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 27 | 28 | // Apply note implementation 29 | func (stg note) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 30 | // copy original structure to result 31 | r := collections.CopyStruct(o) 32 | // preset defaults 33 | var size, alsize, align, aptr, prevptr int64 = 0, 0, 1, 0, 0 34 | // prepare fields slice 35 | if flen := len(r.Fields); flen > 0 { 36 | // note each field with size comment 37 | rfields := make([]gopium.Field, 0, flen) 38 | collections.WalkStruct(r, 0, func(pad int64, fields ...gopium.Field) { 39 | // add pad to aligned size 40 | alsize += pad 41 | for _, f := range fields { 42 | // note only in field mode 43 | if stg.field { 44 | // create note comment 45 | note := fmt.Sprintf( 46 | "// field size: %d bytes; field align: %d bytes; field ptr: %d bytes; - %s", 47 | f.Size, 48 | f.Align, 49 | f.Ptr, 50 | gopium.STAMP, 51 | ) 52 | if stg.doc { 53 | f.Doc = append(f.Doc, note) 54 | } else { 55 | f.Comment = append(f.Comment, note) 56 | } 57 | } 58 | // add field size to both sizes 59 | size += f.Size 60 | alsize += f.Size 61 | // for pointer data track prev fields 62 | // if current field has pointer data 63 | // then add prev fields size + ptr size 64 | // otherwise track prev fields further 65 | if f.Ptr > 0 { 66 | aptr += prevptr + f.Ptr 67 | prevptr = f.Size - f.Ptr 68 | } else { 69 | prevptr += f.Size 70 | } 71 | // update struct align size 72 | // if field align size is bigger 73 | if f.Align > align { 74 | align = f.Align 75 | } 76 | // append field to result 77 | rfields = append(rfields, f) 78 | } 79 | }) 80 | // update result fields 81 | r.Fields = rfields 82 | } 83 | // note structure with size comment 84 | // note only in non field mode 85 | if !stg.field { 86 | // create note comment 87 | note := fmt.Sprintf( 88 | "// struct size: %d bytes; struct align: %d bytes; struct aligned size: %d bytes; struct ptr scan size: %d bytes; - %s", 89 | size, 90 | align, 91 | alsize, 92 | aptr, 93 | gopium.STAMP, 94 | ) 95 | if stg.doc { 96 | r.Doc = append(r.Doc, note) 97 | } else { 98 | r.Comment = append(r.Comment, note) 99 | } 100 | } 101 | return r, ctx.Err() 102 | } 103 | -------------------------------------------------------------------------------- /strategies/pack.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "sort" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // list of pack presets 12 | var ( 13 | pck = pack{} 14 | ) 15 | 16 | // pack defines strategy implementation 17 | // that rearranges structure fields 18 | // to obtain optimal memory utilization 19 | // by sorting fields accordingly 20 | // to their aligns and sizes in some order 21 | type pack struct{} // struct size: 0 bytes; struct align: 1 bytes; struct aligned size: 0 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 22 | 23 | // Apply pack implementation 24 | func (stg pack) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 25 | // copy original structure to result 26 | r := collections.CopyStruct(o) 27 | // execute memory sorting 28 | // https://cs.opensource.google/go/x/tools/+/refs/tags/v0.1.7:go/analysis/passes/fieldalignment/fieldalignment.go;l=145;bpv=0;bpt=1 29 | sort.SliceStable(r.Fields, func(i, j int) bool { 30 | // place zero sized objects before non-zero sized objects 31 | zeroi, zeroj := r.Fields[i].Size == 0, r.Fields[j].Size == 0 32 | if zeroi != zeroj { 33 | return zeroi 34 | } 35 | // then compare aligns of two fields 36 | // bigger aligmnet means upper position 37 | if r.Fields[i].Align != r.Fields[j].Align { 38 | return r.Fields[i].Align > r.Fields[j].Align 39 | } 40 | // place pointerful objects before pointer-free objects 41 | noptri, noptrj := r.Fields[i].Ptr == 0, r.Fields[j].Ptr == 0 42 | if noptri != noptrj { 43 | return noptrj 44 | } 45 | 46 | if !noptri { 47 | // if both have pointers 48 | // then place objects with less trailing 49 | // non-pointer bytes earlier; 50 | // that is, place the field with the most trailing 51 | // non-pointer bytes at the end of the pointerful section 52 | traili, trailj := r.Fields[i].Size-r.Fields[i].Ptr, r.Fields[j].Size-r.Fields[j].Ptr 53 | if traili != trailj { 54 | return traili < trailj 55 | } 56 | } 57 | 58 | // then compare sizes of two fields 59 | // bigger size means upper position 60 | return r.Fields[i].Size > r.Fields[j].Size 61 | }) 62 | return r, ctx.Err() 63 | } 64 | -------------------------------------------------------------------------------- /strategies/pack_test.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/1pkg/gopium/gopium" 10 | ) 11 | 12 | func TestPack(t *testing.T) { 13 | // prepare 14 | cctx, cancel := context.WithCancel(context.Background()) 15 | cancel() 16 | table := map[string]struct { 17 | ctx context.Context 18 | o gopium.Struct 19 | r gopium.Struct 20 | err error 21 | }{ 22 | "empty struct should be applied to empty struct": { 23 | ctx: context.Background(), 24 | }, 25 | "non empty struct should be applied to itself": { 26 | ctx: context.Background(), 27 | o: gopium.Struct{ 28 | Name: "test", 29 | Fields: []gopium.Field{ 30 | { 31 | Name: "test", 32 | Type: "test", 33 | }, 34 | }, 35 | }, 36 | r: gopium.Struct{ 37 | Name: "test", 38 | Fields: []gopium.Field{ 39 | { 40 | Name: "test", 41 | Type: "test", 42 | }, 43 | }, 44 | }, 45 | }, 46 | "non empty struct should be applied to itself on canceled context": { 47 | ctx: cctx, 48 | o: gopium.Struct{ 49 | Name: "test", 50 | Fields: []gopium.Field{ 51 | { 52 | Name: "test", 53 | Type: "test", 54 | }, 55 | }, 56 | }, 57 | r: gopium.Struct{ 58 | Name: "test", 59 | Fields: []gopium.Field{ 60 | { 61 | Name: "test", 62 | Type: "test", 63 | }, 64 | }, 65 | }, 66 | err: context.Canceled, 67 | }, 68 | "pack struct with ptr should be applied to sorted struct": { 69 | ctx: context.Background(), 70 | o: gopium.Struct{ 71 | Name: "test", 72 | Fields: []gopium.Field{ 73 | { 74 | Name: "test", 75 | Type: "test-1", 76 | Size: 8, 77 | Align: 8, 78 | Ptr: 8, 79 | }, 80 | { 81 | Name: "test", 82 | Type: "test-2", 83 | Size: 16, 84 | Align: 16, 85 | Ptr: 4, 86 | }, 87 | { 88 | Name: "test", 89 | Type: "test-3", 90 | Size: 24, 91 | Align: 16, 92 | Ptr: 8, 93 | }, 94 | { 95 | Name: "test", 96 | Type: "test-4", 97 | Size: 4, 98 | Align: 8, 99 | }, 100 | { 101 | Name: "test", 102 | Type: "test-5", 103 | Size: 20, 104 | Align: 20, 105 | Ptr: 1, 106 | }, 107 | { 108 | Name: "test", 109 | Type: "test-6", 110 | Size: 8, 111 | Align: 8, 112 | Ptr: 8, 113 | }, 114 | { 115 | Name: "test", 116 | Type: "test-7", 117 | Size: 0, 118 | Align: 0, 119 | Ptr: 0, 120 | }, 121 | }, 122 | }, 123 | r: gopium.Struct{ 124 | Name: "test", 125 | Fields: []gopium.Field{ 126 | { 127 | Name: "test", 128 | Type: "test-7", 129 | Size: 0, 130 | Align: 0, 131 | Ptr: 0, 132 | }, 133 | { 134 | Name: "test", 135 | Type: "test-5", 136 | Size: 20, 137 | Align: 20, 138 | Ptr: 1, 139 | }, 140 | { 141 | Name: "test", 142 | Type: "test-2", 143 | Size: 16, 144 | Align: 16, 145 | Ptr: 4, 146 | }, 147 | { 148 | Name: "test", 149 | Type: "test-3", 150 | Size: 24, 151 | Align: 16, 152 | Ptr: 8, 153 | }, 154 | { 155 | Name: "test", 156 | Type: "test-1", 157 | Size: 8, 158 | Align: 8, 159 | Ptr: 8, 160 | }, 161 | { 162 | Name: "test", 163 | Type: "test-6", 164 | Size: 8, 165 | Align: 8, 166 | Ptr: 8, 167 | }, 168 | { 169 | Name: "test", 170 | Type: "test-4", 171 | Size: 4, 172 | Align: 8, 173 | }, 174 | }, 175 | }, 176 | }, 177 | } 178 | for name, tcase := range table { 179 | t.Run(name, func(t *testing.T) { 180 | // exec 181 | r, err := pck.Apply(tcase.ctx, tcase.o) 182 | // check 183 | if !reflect.DeepEqual(r, tcase.r) { 184 | t.Errorf("actual %v doesn't equal to expected %v", r, tcase.r) 185 | } 186 | if !errors.Is(err, tcase.err) { 187 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 188 | } 189 | }) 190 | } 191 | } 192 | -------------------------------------------------------------------------------- /strategies/pad.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1pkg/gopium/collections" 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // list of pad presets 11 | var ( 12 | padsys = pad{sys: true} 13 | padtnat = pad{sys: false} 14 | ) 15 | 16 | // pad defines strategy implementation 17 | // that explicitly aligns each structure field 18 | // to system or type alignment padding 19 | // by adding missing paddings for each field 20 | type pad struct { 21 | curator gopium.Curator `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 22 | sys bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 23 | _ [15]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 24 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 25 | 26 | // Curator erich pad strategy with curator instance 27 | func (stg pad) Curator(curator gopium.Curator) pad { 28 | stg.curator = curator 29 | return stg 30 | } 31 | 32 | // Apply pad implementation 33 | func (stg pad) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 34 | // copy original structure to result 35 | r := collections.CopyStruct(o) 36 | // prepare fields slice 37 | if flen := len(r.Fields); flen > 0 { 38 | // set sys align based on sys flag 39 | sysaling := stg.curator.SysAlign() 40 | if !stg.sys { 41 | sysaling = 0 42 | } 43 | // collect all struct fields with pads 44 | rfields := make([]gopium.Field, 0, flen) 45 | collections.WalkStruct(r, sysaling, func(pad int64, fields ...gopium.Field) { 46 | // if pad is vallid append it to fields 47 | if pad > 0 { 48 | rfields = append(rfields, collections.PadField(pad)) 49 | } 50 | // append field to fields 51 | rfields = append(rfields, fields...) 52 | }) 53 | // update resulted fields 54 | r.Fields = rfields 55 | } 56 | return r, ctx.Err() 57 | } 58 | -------------------------------------------------------------------------------- /strategies/pipe.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1pkg/gopium/collections" 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // pipe defines strategy implementation 11 | // that pipes together set of strategies 12 | // by applying them one after another 13 | type pipe []gopium.Strategy 14 | 15 | // Apply pipe implementation 16 | func (stgs pipe) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 17 | // copy original structure to result 18 | r := collections.CopyStruct(o) 19 | // go through all inner strategies 20 | // and apply them one by one 21 | for _, stg := range stgs { 22 | // manage context actions 23 | // in case of cancelation 24 | // stop execution 25 | select { 26 | case <-ctx.Done(): 27 | return o, ctx.Err() 28 | default: 29 | } 30 | tmp, err := stg.Apply(ctx, r) 31 | // in case of any error 32 | // return immediately 33 | if err != nil { 34 | return r, err 35 | } 36 | // copy result back to 37 | // result structure 38 | r = tmp 39 | } 40 | return r, ctx.Err() 41 | } 42 | -------------------------------------------------------------------------------- /strategies/pipe_test.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "errors" 6 | "reflect" 7 | "testing" 8 | 9 | "github.com/1pkg/gopium/gopium" 10 | "github.com/1pkg/gopium/tests/mocks" 11 | ) 12 | 13 | func TestPipe(t *testing.T) { 14 | // prepare 15 | cctx, cancel := context.WithCancel(context.Background()) 16 | cancel() 17 | terr := errors.New("test error") 18 | table := map[string]struct { 19 | pipe pipe 20 | ctx context.Context 21 | o gopium.Struct 22 | r gopium.Struct 23 | err error 24 | }{ 25 | "empty struct should be applied to empty struct with empty pipe": { 26 | ctx: context.Background(), 27 | }, 28 | "non empty struct should be applied to itself with empty pipe": { 29 | ctx: context.Background(), 30 | o: gopium.Struct{ 31 | Name: "test", 32 | Fields: []gopium.Field{ 33 | { 34 | Name: "test", 35 | Type: "test", 36 | }, 37 | }, 38 | }, 39 | r: gopium.Struct{ 40 | Name: "test", 41 | Fields: []gopium.Field{ 42 | { 43 | Name: "test", 44 | Type: "test", 45 | }, 46 | }, 47 | }, 48 | }, 49 | "non empty struct should be applied to itself with empty pipe on canceled context": { 50 | ctx: cctx, 51 | o: gopium.Struct{ 52 | Name: "test", 53 | Fields: []gopium.Field{ 54 | { 55 | Name: "test", 56 | Type: "test", 57 | }, 58 | }, 59 | }, 60 | r: gopium.Struct{ 61 | Name: "test", 62 | Fields: []gopium.Field{ 63 | { 64 | Name: "test", 65 | Type: "test", 66 | }, 67 | }, 68 | }, 69 | err: context.Canceled, 70 | }, 71 | "non empty struct should be applied accordingly to pipe": { 72 | pipe: pipe([]gopium.Strategy{fnotecom, fnotedoc}), 73 | ctx: context.Background(), 74 | o: gopium.Struct{ 75 | Name: "test", 76 | Fields: []gopium.Field{ 77 | { 78 | Name: "test", 79 | Type: "test", 80 | }, 81 | }, 82 | }, 83 | r: gopium.Struct{ 84 | Name: "test", 85 | Fields: []gopium.Field{ 86 | { 87 | Name: "test", 88 | Type: "test", 89 | Doc: []string{"// field size: 0 bytes; field align: 0 bytes; field ptr: 0 bytes; - 🌺 gopium @1pkg"}, 90 | Comment: []string{"// field size: 0 bytes; field align: 0 bytes; field ptr: 0 bytes; - 🌺 gopium @1pkg"}, 91 | }, 92 | }, 93 | }, 94 | }, 95 | "non empty struct should be applied to itself on canceled context": { 96 | pipe: pipe([]gopium.Strategy{fnotecom, fnotedoc}), 97 | ctx: cctx, 98 | o: gopium.Struct{ 99 | Name: "test", 100 | Fields: []gopium.Field{ 101 | { 102 | Name: "test", 103 | Type: "test", 104 | }, 105 | }, 106 | }, 107 | r: gopium.Struct{ 108 | Name: "test", 109 | Fields: []gopium.Field{ 110 | { 111 | Name: "test", 112 | Type: "test", 113 | }, 114 | }, 115 | }, 116 | err: context.Canceled, 117 | }, 118 | "non empty struct should be applied to expected result on pipe error": { 119 | pipe: pipe([]gopium.Strategy{ 120 | fnotecom, 121 | &mocks.Strategy{Err: terr}, 122 | fnotedoc, 123 | }), 124 | ctx: context.Background(), 125 | o: gopium.Struct{ 126 | Name: "test", 127 | Fields: []gopium.Field{ 128 | { 129 | Name: "test", 130 | Type: "test", 131 | }, 132 | }, 133 | }, 134 | r: gopium.Struct{ 135 | Name: "test", 136 | Fields: []gopium.Field{ 137 | { 138 | Name: "test", 139 | Type: "test", 140 | Comment: []string{"// field size: 0 bytes; field align: 0 bytes; field ptr: 0 bytes; - 🌺 gopium @1pkg"}, 141 | }, 142 | }, 143 | }, 144 | err: terr, 145 | }, 146 | } 147 | for name, tcase := range table { 148 | t.Run(name, func(t *testing.T) { 149 | // exec 150 | r, err := tcase.pipe.Apply(tcase.ctx, tcase.o) 151 | // check 152 | if !reflect.DeepEqual(r, tcase.r) { 153 | t.Errorf("actual %v doesn't equal to expected %v", r, tcase.r) 154 | } 155 | if !errors.Is(err, tcase.err) { 156 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 157 | } 158 | }) 159 | } 160 | } 161 | -------------------------------------------------------------------------------- /strategies/sep.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1pkg/gopium/collections" 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // list of fshare presets 11 | var ( 12 | sepsyst = sep{sys: true, top: true} 13 | sepl1t = sep{line: 1, top: true} 14 | sepl2t = sep{line: 2, top: true} 15 | sepl3t = sep{line: 3, top: true} 16 | sepbt = sep{top: true} 17 | sepsysb = sep{sys: true, top: false} 18 | sepl1b = sep{line: 1, top: false} 19 | sepl2b = sep{line: 2, top: false} 20 | sepl3b = sep{line: 3, top: false} 21 | sepbb = sep{top: false} 22 | ) 23 | 24 | // sep defines strategy implementation 25 | // that separates structure with 26 | // extra system or cpu cache alignment padding 27 | // by adding the padding at the top 28 | // or the padding at the bottom 29 | type sep struct { 30 | curator gopium.Curator `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 31 | line uint `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 32 | bytes uint `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 33 | sys bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 34 | top bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 35 | _ [30]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 36 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 37 | 38 | // Bytes erich sep strategy with custom bytes 39 | func (stg sep) Bytes(bytes uint) sep { 40 | stg.bytes = bytes 41 | return stg 42 | } 43 | 44 | // Curator erich sep strategy with curator instance 45 | func (stg sep) Curator(curator gopium.Curator) sep { 46 | stg.curator = curator 47 | return stg 48 | } 49 | 50 | // Apply sep implementation 51 | func (stg sep) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 52 | // copy original structure to result 53 | r := collections.CopyStruct(o) 54 | // get separator size 55 | sep := stg.curator.SysAlign() 56 | // set separator based on sys flag 57 | if !stg.sys { 58 | sep = stg.curator.SysCache(stg.line) 59 | } 60 | // if struct has feilds and separator size or bytes are valid 61 | if flen := len(r.Fields); flen > 0 && (sep > 0 || stg.bytes > 0) { 62 | if !stg.sys && stg.line == 0 { 63 | sep = int64(stg.bytes) 64 | } 65 | // add field before or after 66 | // structure fields list 67 | if stg.top { 68 | r.Fields = append([]gopium.Field{collections.PadField(sep)}, r.Fields...) 69 | } else { 70 | r.Fields = append(r.Fields, collections.PadField(sep)) 71 | } 72 | } 73 | return r, ctx.Err() 74 | } 75 | -------------------------------------------------------------------------------- /strategies/tag.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "reflect" 7 | "strings" 8 | 9 | "github.com/1pkg/gopium/collections" 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | // list of tag presets 14 | var ( 15 | tags = tag{force: false, discrete: false} 16 | tagf = tag{force: true, discrete: false} 17 | tagsd = tag{force: false, discrete: true} 18 | tagfd = tag{force: true, discrete: true} 19 | ) 20 | 21 | // tag defines strategy implementation 22 | // that adds or updates fields tags annotation 23 | // that could be processed by group strategy 24 | type tag struct { 25 | tag string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 26 | force bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 27 | discrete bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 28 | _ [14]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 29 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 8 bytes; - 🌺 gopium @1pkg 30 | 31 | // Names erich tag strategy with strategy names tag 32 | func (stg tag) Names(names ...gopium.StrategyName) tag { 33 | // convert strategies names to strings 34 | strs := make([]string, 0, len(names)) 35 | for _, name := range names { 36 | strs = append(strs, string(name)) 37 | } 38 | // concat them by comma 39 | stg.tag = strings.Join(strs, ",") 40 | return stg 41 | } 42 | 43 | // Apply tag implementation 44 | func (stg tag) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 45 | // copy original structure to result 46 | r := collections.CopyStruct(o) 47 | // iterate through all fields 48 | for i := range r.Fields { 49 | f := &r.Fields[i] 50 | // grab the field tag 51 | tag, ok := reflect.StructTag(f.Tag).Lookup(gopium.NAME) 52 | // build group tag 53 | gtag := stg.tag 54 | // if we want to build discrete groups 55 | if stg.discrete { 56 | // use default group tag 57 | // and append index of field to it 58 | group := fmt.Sprintf("%s-%d", gopium.NAME, i+1) 59 | gtag = fmt.Sprintf("group:%s;%s", group, stg.tag) 60 | } 61 | // in case gopium tag already exists 62 | // and force is set - replace tag 63 | // in case gopium tag already exists 64 | // and force isn't set - do nothing 65 | // in case tag is not empty and 66 | // gopium tag doesn't exist - append tag 67 | // in case tag is empty - set tag 68 | fulltag := fmt.Sprintf(`%s:"%s"`, gopium.NAME, gtag) 69 | switch { 70 | case ok && stg.force: 71 | f.Tag = strings.Replace(f.Tag, tag, gtag, 1) 72 | case ok: 73 | break 74 | case f.Tag != "": 75 | f.Tag += " " + fulltag 76 | default: 77 | f.Tag = fulltag 78 | } 79 | } 80 | return r, ctx.Err() 81 | } 82 | -------------------------------------------------------------------------------- /strategies/tlex.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "sort" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // list of tlex presets 12 | var ( 13 | tlexasc = tlex{asc: true} 14 | tlexdesc = tlex{asc: false} 15 | ) 16 | 17 | // tlex defines strategy implementation 18 | // that sorts fields accordingly to their types 19 | // in ascending or descending order 20 | type tlex struct { 21 | asc bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 22 | _ [1]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 23 | } // struct size: 2 bytes; struct align: 1 bytes; struct aligned size: 2 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 24 | 25 | // Apply tlex implementation 26 | func (stg tlex) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 27 | // copy original structure to result 28 | r := collections.CopyStruct(o) 29 | // then execute lexicographical sorting 30 | sort.SliceStable(r.Fields, func(i, j int) bool { 31 | // sort depends on type of ordering 32 | if stg.asc { 33 | return r.Fields[i].Type < r.Fields[j].Type 34 | } 35 | return r.Fields[i].Type > r.Fields[j].Type 36 | }) 37 | return r, ctx.Err() 38 | } 39 | -------------------------------------------------------------------------------- /strategies/unpack.go: -------------------------------------------------------------------------------- 1 | package strategies 2 | 3 | import ( 4 | "context" 5 | "math" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // list of unpack presets 12 | var ( 13 | unpck = unpack{} 14 | ) 15 | 16 | // unpack defines strategy implementation 17 | // that rearranges structure fields 18 | // to obtain inflated memory utilization 19 | // by sorting fields accordingly 20 | // to their aligns and sizes in some order 21 | type unpack struct{} // struct size: 0 bytes; struct align: 1 bytes; struct aligned size: 0 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 22 | 23 | // Apply unpack implementation 24 | func (stg unpack) Apply(ctx context.Context, o gopium.Struct) (gopium.Struct, error) { 25 | // copy original structure to result 26 | r := collections.CopyStruct(o) 27 | // execute pack strategy 28 | r, err := pck.Apply(ctx, r) 29 | if err != nil { 30 | return o, err 31 | } 32 | // check that struct has fields 33 | if flen := len(r.Fields); flen > 0 { 34 | // slice fields by half ceil 35 | mid := int(math.Ceil(float64(flen) / 2.0)) 36 | left, right := r.Fields[:mid], r.Fields[mid:] 37 | r.Fields = make([]gopium.Field, 0, flen) 38 | // combine fields in chess order 39 | for li, ri := 0, len(right)-1; li < mid; li, ri = li+1, ri-1 { 40 | if ri >= 0 { 41 | r.Fields = append(r.Fields, right[ri]) 42 | } 43 | r.Fields = append(r.Fields, left[li]) 44 | } 45 | } 46 | return r, ctx.Err() 47 | } 48 | -------------------------------------------------------------------------------- /tests/data/embedded/file.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package embedded 4 | 5 | import "time" 6 | 7 | type MetaLabaratory struct { 8 | } 9 | 10 | type Person struct { 11 | Birtday time.Time `json:"birthday" db:"birthday"` 12 | Weight float64 `json:"weight" db:"weight"` 13 | Height float64 `json:"height" db:"height"` 14 | } 15 | 16 | type PatientObject struct { 17 | MetaLabaratory 18 | Person 19 | ID string `json:"id" db:"id"` 20 | Enrolled bool `json:"enrolled" db:"enrolled"` 21 | Gender string `json:"gender" db:"gender"` 22 | PhoneNumber *string `json:"phone_number" db:"phone_number"` 23 | Email *string `json:"email" db:"email"` 24 | AddressTitle *string `json:"address_title" db:"address_title"` 25 | } 26 | -------------------------------------------------------------------------------- /tests/data/empty/file.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package empty 4 | -------------------------------------------------------------------------------- /tests/data/flat/file.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package flat 4 | 5 | import ( 6 | "errors" 7 | "strings" 8 | ) 9 | 10 | type A struct { 11 | a int64 12 | } 13 | 14 | var a1 string = strings.Join([]string{"a", "b", "c"}, "|") 15 | 16 | type b struct { 17 | A 18 | b float64 19 | } 20 | 21 | type C struct { 22 | c []string 23 | A struct { 24 | b b 25 | z A 26 | } 27 | } 28 | 29 | type c1 C 30 | 31 | // table := []struct{A string}{{A: "test"}} 32 | type D struct { 33 | t [13]byte 34 | b bool 35 | _ int64 36 | } 37 | 38 | // ggg := func (interface{}){} 39 | type AW func() error 40 | 41 | type AZ struct { 42 | a bool 43 | D D 44 | z bool 45 | } 46 | 47 | var Err error = errors.New("test") 48 | -------------------------------------------------------------------------------- /tests/data/locator.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "fmt" 5 | "go/token" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // locator defines tests data locator implementation 11 | // which reuses underlying locator 12 | // but simplifies and purifies ID generation 13 | type locator struct { 14 | loc gopium.Locator `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 15 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 16 | 17 | // ID locator implementation 18 | func (l locator) ID(p token.Pos) string { 19 | // check if such file exists 20 | if f := l.loc.Root().File(p); f != nil { 21 | // purify the loc then 22 | // generate ordered id 23 | return fmt.Sprintf("%s:%d", purify(f.Name()), f.Line(p)) 24 | } 25 | return "" 26 | } 27 | 28 | // Loc locator implementation 29 | func (l locator) Loc(p token.Pos) string { 30 | return l.loc.Loc(p) 31 | } 32 | 33 | // Locator locator implementation 34 | func (l locator) Locator(loc string) (gopium.Locator, bool) { 35 | return l.loc.Locator(loc) 36 | } 37 | 38 | // Fset locator implementation 39 | func (l locator) Fset(loc string, fset *token.FileSet) (*token.FileSet, bool) { 40 | return l.loc.Fset(loc, fset) 41 | } 42 | 43 | // Root locator implementation 44 | func (l locator) Root() *token.FileSet { 45 | return l.loc.Root() 46 | } 47 | -------------------------------------------------------------------------------- /tests/data/multi/file-1.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package multi 4 | 5 | import ( 6 | "strings" 7 | ) 8 | 9 | type A struct { 10 | a int64 11 | } 12 | 13 | var a1 string = strings.Join([]string{"a", "b", "c"}, "|") 14 | 15 | type b struct { 16 | A 17 | b float64 18 | } 19 | 20 | type C struct { 21 | c []string 22 | A struct { 23 | b b 24 | z A 25 | } 26 | } 27 | 28 | func scope() { 29 | type TestAZ struct { 30 | a bool 31 | D A 32 | z bool 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /tests/data/multi/file-2.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package multi 4 | 5 | import "errors" 6 | 7 | func scope1() error { 8 | type B struct { 9 | b 10 | } 11 | type b1 b 12 | type b2 struct { 13 | A 14 | b float64 15 | } 16 | return errors.New("test data") 17 | } 18 | -------------------------------------------------------------------------------- /tests/data/multi/file-3.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package multi 4 | 5 | type c1 C 6 | 7 | // table := []struct{A string}{{A: "test"}} 8 | type D struct { 9 | t [13]byte 10 | b bool 11 | _ int64 12 | } 13 | 14 | /* ggg := func (interface{}){} */ 15 | type AW func() error 16 | 17 | type AZ struct { 18 | a bool 19 | D D 20 | z bool 21 | } 22 | 23 | type ze interface { 24 | AW() AW 25 | } 26 | 27 | type Zeze struct { 28 | ze 29 | D 30 | AZ 31 | AWA D 32 | } 33 | 34 | // test comment 35 | type ( 36 | d1 int64 37 | d2 float64 38 | d3 string 39 | ) 40 | -------------------------------------------------------------------------------- /tests/data/nested/file.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package nested 4 | 5 | import "errors" 6 | 7 | type A struct { 8 | a int64 9 | } 10 | 11 | type b struct { 12 | A 13 | b float64 14 | } 15 | 16 | type C struct { 17 | c []string 18 | A struct { 19 | b b 20 | z A 21 | } 22 | } 23 | 24 | func scope1() error { 25 | type B struct { 26 | b 27 | } 28 | type b1 struct { 29 | A 30 | b float64 31 | } 32 | return errors.New("test data") 33 | } 34 | 35 | func scope2() struct{A complex64; B int64; C float64} { 36 | // name shadowing 37 | type A struct { 38 | a int32 39 | } 40 | type a1 struct { 41 | i interface{} 42 | } 43 | 44 | scope3 := func(v int) { 45 | // name shadowing 46 | type a1 struct { 47 | i struct{} 48 | } 49 | } 50 | 51 | scope4 := func(v int) { 52 | // name shadowing 53 | var a1 A 54 | var b1 b 55 | var c1 C 56 | } 57 | 58 | return struct{A complex64; B int64; C float64} {} 59 | } 60 | 61 | var scope5 = func() {} 62 | 63 | type Z struct { 64 | a bool 65 | C C 66 | z bool 67 | } -------------------------------------------------------------------------------- /tests/data/note/file-1.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package note 4 | 5 | // Note doc 6 | type Note struct { 7 | // AAAA 8 | A string // zzz 9 | // 🌺 gopium @1pkg 10 | B string /* 1pkg - 🌺 gopium @1pkg */ 11 | // C string 12 | C string 13 | } // some comment 14 | 15 | // last comment 16 | -------------------------------------------------------------------------------- /tests/data/note/file-2.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package note 4 | 5 | /* 1pkg - 🌺 gopium @1pkg */ 6 | type DocCom struct { 7 | f complex128 8 | // 🌺 gopium @1pkg 9 | } // doc com 10 | -------------------------------------------------------------------------------- /tests/data/note/file-3.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package note 4 | 5 | import "github.com/1pkg/gopium/gopium" 6 | 7 | // random function 8 | func rnd() int { 9 | return -1 10 | } 11 | 12 | type tf rnd 13 | 14 | type tt gopium.Struct 15 | -------------------------------------------------------------------------------- /tests/data/parser.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "context" 5 | "fmt" 6 | "go/ast" 7 | "go/parser" 8 | "go/types" 9 | "path/filepath" 10 | "sync" 11 | 12 | "github.com/1pkg/gopium/gopium" 13 | "github.com/1pkg/gopium/tests" 14 | "github.com/1pkg/gopium/typepkg" 15 | 16 | "golang.org/x/tools/go/packages" 17 | ) 18 | 19 | // init types cache map and sync 20 | var ( 21 | tcache map[string]typesloc = make(map[string]typesloc, 6) 22 | tmutex sync.Mutex 23 | ) 24 | 25 | // typesloc data transfer object 26 | // that contains types package and loc 27 | type typesloc struct { 28 | loc gopium.Locator `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 29 | pkg *types.Package `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 30 | _ [8]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 31 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; - 🌺 gopium @1pkg 32 | 33 | // Parser defines tests data parser implementation 34 | // that adds internal caching for results 35 | type Parser struct { 36 | p gopium.Parser `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 37 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 38 | 39 | // NewParser creates parser for single tests data 40 | func NewParser(pkg string) gopium.Parser { 41 | p := &typepkg.ParserXToolPackagesAst{ 42 | Pattern: fmt.Sprintf("github.com/1pkg/gopium/tests/data/%s", pkg), 43 | Path: filepath.Join(tests.Gopium, "tests", "data", pkg), 44 | //nolint 45 | ModeTypes: packages.LoadAllSyntax, 46 | ModeAst: parser.ParseComments | parser.AllErrors, 47 | BuildFlags: []string{"-tags=tests_data"}, 48 | } 49 | return Parser{p: p} 50 | } 51 | 52 | // ParseTypes parser implementation 53 | func (p Parser) ParseTypes(ctx context.Context, src ...byte) (*types.Package, gopium.Locator, error) { 54 | // check that known parser should be cached 55 | if xparser, ok := p.p.(*typepkg.ParserXToolPackagesAst); ok { 56 | // access cache syncroniusly 57 | defer tmutex.Unlock() 58 | tmutex.Lock() 59 | // build full dir cache key 60 | dir := filepath.Join(xparser.Root, xparser.Path) 61 | // check if key exists in cache 62 | if tp, ok := tcache[dir]; ok { 63 | return tp.pkg, tp.loc, nil 64 | } 65 | // if not then do actual parsing 66 | // and wrap locator with data locator 67 | pkg, loc, err := p.p.ParseTypes(ctx, src...) 68 | // store result to cache if no error occurred 69 | if err == nil { 70 | tcache[dir] = typesloc{pkg: pkg, loc: locator{loc: loc}} 71 | } 72 | return pkg, locator{loc: loc}, err 73 | } 74 | // otherwise use real parser 75 | // also wrap locator with data locator 76 | pkg, loc, err := p.p.ParseTypes(ctx, src...) 77 | return pkg, locator{loc: loc}, err 78 | } 79 | 80 | // ParseAst cache parser implementation 81 | func (p Parser) ParseAst(ctx context.Context, src ...byte) (*ast.Package, gopium.Locator, error) { 82 | // it's cheap to parse ast each time 83 | // also wrap locator with data locator 84 | pkg, loc, err := p.p.ParseAst(ctx, src...) 85 | return pkg, locator{loc: loc}, err 86 | } 87 | -------------------------------------------------------------------------------- /tests/data/purify.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "os" 5 | "strings" 6 | 7 | "github.com/1pkg/gopium/tests" 8 | ) 9 | 10 | // purify helps to transform 11 | // absolute path to relative local one 12 | func purify(loc string) string { 13 | // remove abs part from loc 14 | // replace os path separators 15 | // with underscores and trim them 16 | loc = strings.Replace(loc, tests.Gopium, "", 1) 17 | loc = strings.ReplaceAll(loc, string(os.PathSeparator), "_") 18 | return strings.Trim(loc, "_") 19 | } 20 | -------------------------------------------------------------------------------- /tests/data/single/file.go: -------------------------------------------------------------------------------- 1 | //go:build tests_data 2 | 3 | package single 4 | 5 | type Single struct { 6 | A string 7 | B string 8 | C string 9 | } 10 | -------------------------------------------------------------------------------- /tests/data/writer.go: -------------------------------------------------------------------------------- 1 | package data 2 | 3 | import ( 4 | "io" 5 | 6 | "github.com/1pkg/gopium/gopium" 7 | ) 8 | 9 | // Writer defines tests data writter implementation 10 | // which reuses underlying locator 11 | // but purifies location generation 12 | type Writer struct { 13 | Writer gopium.CategoryWriter `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 14 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 15 | 16 | // Generate writer implementation 17 | func (w Writer) Generate(loc string) (io.WriteCloser, error) { 18 | // purify the loc then 19 | // just reuse underlying writer 20 | return w.Writer.Generate(purify(loc)) 21 | } 22 | 23 | // Category writer implementation 24 | func (w Writer) Category(cat string) error { 25 | return w.Writer.Category(cat) 26 | } 27 | -------------------------------------------------------------------------------- /tests/mocks/ast.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // Walk defines mock ast walker implementation 11 | type Walk struct { 12 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 14 | 15 | // Walk mock implementation 16 | func (w Walk) Walk(context.Context, ast.Node, gopium.Visitor, gopium.Comparator) (ast.Node, error) { 17 | return nil, w.Err 18 | } 19 | -------------------------------------------------------------------------------- /tests/mocks/context.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "time" 6 | ) 7 | 8 | // Context defines mock context implementation 9 | type Context struct { 10 | After int `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 11 | current int `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 12 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 13 | 14 | // Deadline mock implementation 15 | func (ctx Context) Deadline() (deadline time.Time, ok bool) { 16 | return context.Background().Deadline() 17 | } 18 | 19 | // Done mock implementation 20 | func (ctx *Context) Done() <-chan struct{} { 21 | ctx.current++ 22 | ch := make(chan struct{}) 23 | // after n-th call to done 24 | // close the chan 25 | if ctx.current >= ctx.After { 26 | close(ch) 27 | } 28 | return ch 29 | } 30 | 31 | // Err mock implementation 32 | func (ctx Context) Err() error { 33 | // after n-th call to done 34 | // return ctx error 35 | if ctx.current >= ctx.After { 36 | return context.Canceled 37 | } 38 | return nil 39 | } 40 | 41 | // Value mock implementation 42 | func (ctx Context) Value(key interface{}) interface{} { 43 | return context.Background().Value(key) 44 | } 45 | -------------------------------------------------------------------------------- /tests/mocks/fmt.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "encoding/json" 6 | "go/ast" 7 | 8 | "github.com/1pkg/gopium/collections" 9 | "github.com/1pkg/gopium/gopium" 10 | ) 11 | 12 | // Bytes defines mock fmtio bytes implementation 13 | type Bytes struct { 14 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 15 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 16 | 17 | // Bytes mock implementation 18 | func (fmt Bytes) Bytes(sts []gopium.Struct) ([]byte, error) { 19 | // in case we have error 20 | // return it back 21 | if fmt.Err != nil { 22 | return nil, fmt.Err 23 | } 24 | // otherwise use json bytes impl 25 | return json.MarshalIndent(sts, "", "\t") 26 | } 27 | 28 | // Ast defines mock ast type spec implementation 29 | type Ast struct { 30 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 31 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 32 | 33 | // Ast mock implementation 34 | func (fmt Ast) Ast(*ast.TypeSpec, gopium.Struct) error { 35 | return fmt.Err 36 | } 37 | 38 | // Diff defines mock diff implementation 39 | type Diff struct { 40 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 41 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 42 | 43 | // Diff mock implementation 44 | func (fmt Diff) Diff(o gopium.Categorized, r gopium.Categorized) ([]byte, error) { 45 | // in case we have error 46 | // return it back 47 | if fmt.Err != nil { 48 | return nil, fmt.Err 49 | } 50 | // otherwise use json bytes impl 51 | fo, fr := collections.Flat(o.Full()), collections.Flat(r.Full()) 52 | data := [][]gopium.Struct{fo.Sorted(), fr.Sorted()} 53 | return json.MarshalIndent(data, "", "\t") 54 | } 55 | 56 | // Apply defines mock apply implementation 57 | type Apply struct { 58 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 59 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 60 | 61 | // Apply mock implementation 62 | func (a Apply) Apply(context.Context, *ast.Package, gopium.Locator, gopium.Categorized) (*ast.Package, error) { 63 | return nil, a.Err 64 | } 65 | -------------------------------------------------------------------------------- /tests/mocks/maven.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "go/types" 4 | 5 | // Type defines mock type 6 | // data transfer object 7 | type Type struct { 8 | Name string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 9 | Size int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 10 | Align int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 11 | Ptr int64 12 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; - 🌺 gopium @1pkg 13 | 14 | // Maven defines mock maven implementation 15 | type Maven struct { 16 | SCache []int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 17 | Types map[string]Type `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 18 | SWord int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 19 | SAlign int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 20 | _ [16]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 21 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; - 🌺 gopium @1pkg 22 | 23 | // SysWord mock implementation 24 | func (m Maven) SysWord() int64 { 25 | return m.SWord 26 | } 27 | 28 | // SysAlign mock implementation 29 | func (m Maven) SysAlign() int64 { 30 | return m.SAlign 31 | } 32 | 33 | // SysCache mock implementation 34 | func (m Maven) SysCache(level uint) int64 { 35 | // decrement level to match index 36 | l := int(level) - 1 37 | // check if we have it in vals 38 | if l >= 0 && l < len(m.SCache) { 39 | return m.SCache[l] 40 | } 41 | // otherwise return default val 42 | return 0 43 | } 44 | 45 | // Name mock implementation 46 | func (m Maven) Name(t types.Type) string { 47 | // check if we have it in vals 48 | if t, ok := m.Types[t.String()]; ok { 49 | return t.Name 50 | } 51 | // otherwise return default val 52 | return "" 53 | } 54 | 55 | // Size mock implementation 56 | func (m Maven) Size(t types.Type) int64 { 57 | // check if we have it in vals 58 | if t, ok := m.Types[t.String()]; ok { 59 | return t.Size 60 | } 61 | // otherwise return default val 62 | return 0 63 | } 64 | 65 | // Align mock implementation 66 | func (m Maven) Align(t types.Type) int64 { 67 | // check if we have it in vals 68 | if t, ok := m.Types[t.String()]; ok { 69 | return t.Align 70 | } 71 | // otherwise return default val 72 | return 0 73 | } 74 | 75 | // Ptr mock implementation 76 | func (m Maven) Ptr(t types.Type) int64 { 77 | // check if we have it in vals 78 | if t, ok := m.Types[t.String()]; ok { 79 | return t.Ptr 80 | } 81 | // otherwise return default val 82 | return 0 83 | } 84 | -------------------------------------------------------------------------------- /tests/mocks/parser.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | "go/token" 7 | "go/types" 8 | 9 | "github.com/1pkg/gopium/gopium" 10 | ) 11 | 12 | // Pos defines mock pos 13 | // data transfer object 14 | type Pos struct { 15 | ID string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 16 | Loc string `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 17 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; - 🌺 gopium @1pkg 18 | 19 | // Locator defines mock locator implementation 20 | type Locator struct { 21 | Poses map[token.Pos]Pos `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 22 | } // struct size: 8 bytes; struct align: 8 bytes; struct aligned size: 8 bytes; - 🌺 gopium @1pkg 23 | 24 | // ID mock implementation 25 | func (l Locator) ID(pos token.Pos) string { 26 | // check if we have it in vals 27 | if t, ok := l.Poses[pos]; ok { 28 | return t.ID 29 | } 30 | // otherwise return default val 31 | return "" 32 | } 33 | 34 | // Loc mock implementation 35 | func (l Locator) Loc(pos token.Pos) string { 36 | // check if we have it in vals 37 | if t, ok := l.Poses[pos]; ok { 38 | return t.Loc 39 | } 40 | // otherwise return default val 41 | return "" 42 | } 43 | 44 | // Locator mock implementation 45 | func (l Locator) Locator(string) (gopium.Locator, bool) { 46 | return l, true 47 | } 48 | 49 | // Fset mock implementation 50 | func (l Locator) Fset(string, *token.FileSet) (*token.FileSet, bool) { 51 | return token.NewFileSet(), true 52 | } 53 | 54 | // Root mock implementation 55 | func (l Locator) Root() *token.FileSet { 56 | return token.NewFileSet() 57 | } 58 | 59 | // Parser defines mock parser implementation 60 | type Parser struct { 61 | Parser gopium.Parser `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 62 | Typeserr error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 63 | Asterr error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 64 | _ [16]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 65 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; - 🌺 gopium @1pkg 66 | 67 | // ParseTypes mock implementation 68 | func (p Parser) ParseTypes(ctx context.Context, src ...byte) (*types.Package, gopium.Locator, error) { 69 | // if parser provided use it 70 | if p.Parser != nil { 71 | pkg, loc, _ := p.Parser.ParseTypes(ctx, src...) 72 | return pkg, loc, p.Typeserr 73 | } 74 | return types.NewPackage("", ""), Locator{}, p.Typeserr 75 | } 76 | 77 | // ParseAst mock implementation 78 | func (p Parser) ParseAst(ctx context.Context, src ...byte) (*ast.Package, gopium.Locator, error) { 79 | // if parser provided use it 80 | if p.Parser != nil { 81 | pkg, loc, _ := p.Parser.ParseAst(ctx, src...) 82 | return pkg, loc, p.Asterr 83 | } 84 | return &ast.Package{}, Locator{}, p.Asterr 85 | } 86 | -------------------------------------------------------------------------------- /tests/mocks/persister.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | 7 | "github.com/1pkg/gopium/gopium" 8 | ) 9 | 10 | // Persister defines mock pesister implementation 11 | type Persister struct { 12 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 14 | 15 | // Persist mock implementation 16 | func (p Persister) Persist(context.Context, gopium.Printer, gopium.Writer, gopium.Locator, ast.Node) error { 17 | return p.Err 18 | } 19 | -------------------------------------------------------------------------------- /tests/mocks/printer.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "go/ast" 6 | "go/token" 7 | "io" 8 | ) 9 | 10 | // Printer defines mock ast printer implementation 11 | type Printer struct { 12 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 14 | 15 | // Print mock implementation 16 | func (p Printer) Print(context.Context, io.Writer, *token.FileSet, ast.Node) error { 17 | return p.Err 18 | } 19 | -------------------------------------------------------------------------------- /tests/mocks/runner.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "context" 4 | 5 | // Runner defines mock runner implementation 6 | type Runner struct { 7 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 8 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; - 🌺 gopium @1pkg 9 | 10 | // Run mock implementation 11 | func (r Runner) Run(context.Context) error { 12 | return r.Err 13 | } 14 | -------------------------------------------------------------------------------- /tests/mocks/rwc.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import "bytes" 4 | 5 | // RWC defines mock io reader writer closer implementation 6 | type RWC struct { 7 | buf bytes.Buffer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 8 | Rerr error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 9 | Werr error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 10 | Cerr error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 11 | _ [40]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 12 | } // struct size: 121 bytes; struct align: 8 bytes; struct aligned size: 128 bytes; - 🌺 gopium @1pkg 13 | 14 | // Read mock implementation 15 | func (rwc *RWC) Read(p []byte) (int, error) { 16 | // in case we have error 17 | // return it back 18 | if rwc.Rerr != nil { 19 | return 0, rwc.Rerr 20 | } 21 | // otherwise use buf impl 22 | return rwc.buf.Read(p) 23 | } 24 | 25 | // Write mock implementation 26 | func (rwc *RWC) Write(p []byte) (n int, err error) { 27 | // in case we have error 28 | // return it back 29 | if rwc.Werr != nil { 30 | return 0, rwc.Werr 31 | } 32 | // otherwise use buf impl 33 | return rwc.buf.Write(p) 34 | } 35 | 36 | // Close mock implementation 37 | func (rwc *RWC) Close() error { 38 | return rwc.Cerr 39 | } 40 | -------------------------------------------------------------------------------- /tests/mocks/strategy.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | 6 | "github.com/1pkg/gopium/gopium" 7 | ) 8 | 9 | // Strategy defines mock strategy implementation 10 | type Strategy struct { 11 | R gopium.Struct `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 12 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | _ [24]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 14 | } // struct size: 128 bytes; struct align: 8 bytes; struct aligned size: 128 bytes; - 🌺 gopium @1pkg 15 | 16 | // Apply mock implementation 17 | func (stg *Strategy) Apply(context.Context, gopium.Struct) (gopium.Struct, error) { 18 | return stg.R, stg.Err 19 | } 20 | 21 | // StrategyBuilder defines mock strategy builder implementation 22 | type StrategyBuilder struct { 23 | Strategy gopium.Strategy `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 24 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 25 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; - 🌺 gopium @1pkg 26 | 27 | // Build mock implementation 28 | func (b StrategyBuilder) Build(...gopium.StrategyName) (gopium.Strategy, error) { 29 | return b.Strategy, b.Err 30 | } 31 | -------------------------------------------------------------------------------- /tests/mocks/walker.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | "time" 7 | 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // Walker defines mock walker implementation 12 | type Walker struct { 13 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 14 | Wait time.Duration `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 15 | _ [8]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 16 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; - 🌺 gopium @1pkg 17 | 18 | // Visit mock implementation 19 | func (w Walker) Visit(ctx context.Context, regex *regexp.Regexp, stg gopium.Strategy) error { 20 | // check error at start 21 | if w.Err != nil { 22 | return w.Err 23 | } 24 | // sleep for duration if any 25 | if w.Wait > 0 { 26 | time.Sleep(w.Wait) 27 | } 28 | // return context error otherwise 29 | return ctx.Err() 30 | } 31 | 32 | // WalkerBuilder defines mock walker builder implementation 33 | type WalkerBuilder struct { 34 | Walker gopium.Walker `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 35 | Err error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 36 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; - 🌺 gopium @1pkg 37 | 38 | // Build mock implementation 39 | func (b WalkerBuilder) Build(gopium.WalkerName) (gopium.Walker, error) { 40 | return b.Walker, b.Err 41 | } 42 | -------------------------------------------------------------------------------- /tests/mocks/writer.go: -------------------------------------------------------------------------------- 1 | package mocks 2 | 3 | import ( 4 | "io" 5 | "sync" 6 | ) 7 | 8 | // Writer defines mock category writer implementation 9 | type Writer struct { 10 | Gerr error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 11 | Cerr error `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 12 | RWCs map[string]*RWC `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | mutex sync.Mutex `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 14 | _ [16]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 15 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; - 🌺 gopium @1pkg 16 | 17 | // Generate mock implementation 18 | func (w *Writer) Generate(loc string) (io.WriteCloser, error) { 19 | // lock rwcs access 20 | // and init them if they 21 | // haven't inited before 22 | defer w.mutex.Unlock() 23 | w.mutex.Lock() 24 | if w.RWCs == nil { 25 | w.RWCs = make(map[string]*RWC) 26 | } 27 | // if loc is inside existed rwcs 28 | // just return found rwc back 29 | if rwc, ok := w.RWCs[loc]; ok { 30 | return rwc, w.Gerr 31 | } 32 | // otherwise create new rwc 33 | // store and return it back 34 | rwc := &RWC{} 35 | w.RWCs[loc] = rwc 36 | return rwc, w.Gerr 37 | } 38 | 39 | // Category mock implementation 40 | func (w *Writer) Category(cat string) error { 41 | return w.Cerr 42 | } 43 | -------------------------------------------------------------------------------- /tests/os.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import "runtime" 4 | 5 | // OnOS acts as ternary operator for os check 6 | // if provided os equals to runtime os 7 | // it returns true value otherwise false value 8 | func OnOS(os string, tval interface{}, fval interface{}) interface{} { 9 | if runtime.GOOS == os { 10 | return tval 11 | } 12 | return fval 13 | } 14 | -------------------------------------------------------------------------------- /tests/path.go: -------------------------------------------------------------------------------- 1 | package tests 2 | 3 | import ( 4 | "path/filepath" 5 | 6 | "github.com/1pkg/gopium/gopium" 7 | ) 8 | 9 | // Gopium defines root gopium path 10 | var Gopium string 11 | 12 | // sets gopium data path 13 | func init() { 14 | // grabs running root path 15 | p, err := filepath.Abs(".") 16 | if err != nil { 17 | panic(err) 18 | } 19 | // until we rich project root 20 | for filepath.Base(p) != gopium.NAME { 21 | p = filepath.Dir(p) 22 | } 23 | Gopium = p 24 | } 25 | -------------------------------------------------------------------------------- /typepkg/locator.go: -------------------------------------------------------------------------------- 1 | package typepkg 2 | 3 | import ( 4 | "crypto/sha256" 5 | "encoding/hex" 6 | "fmt" 7 | "go/token" 8 | "sync" 9 | 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | // Locator defines abstraction that helps 14 | // encapsulate pkgs token.FileSets and provides 15 | // some operations on top of it 16 | type Locator struct { 17 | root *token.FileSet `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 18 | extra map[string]*token.FileSet `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 19 | mutex sync.Mutex `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 20 | _ [8]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 21 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 22 | 23 | // NewLocator creates new locator instance 24 | // from provided file set 25 | func NewLocator(fset *token.FileSet) *Locator { 26 | // init root with defaul fset 27 | // if nil fset has been provided 28 | if fset == nil { 29 | fset = token.NewFileSet() 30 | } 31 | return &Locator{ 32 | root: fset, 33 | extra: make(map[string]*token.FileSet), 34 | } 35 | } 36 | 37 | // ID calculates sha256 hash hex string 38 | // for specified token.Pos in token.FileSet 39 | // note: generated ids are ordered inside same loc 40 | func (l *Locator) ID(p token.Pos) string { 41 | // check if such file exists 42 | if f := l.root.File(p); f != nil { 43 | // generate hash sum 44 | h := sha256.Sum256([]byte(f.Name())) 45 | sum := hex.EncodeToString(h[:]) 46 | // generate ordered id 47 | return fmt.Sprintf("%s:%d", sum, f.Line(p)) 48 | } 49 | return "" 50 | } 51 | 52 | // Loc returns full filepath 53 | // for specified token.Pos in token.FileSet 54 | func (l *Locator) Loc(p token.Pos) string { 55 | // check if such file exists 56 | if f := l.root.File(p); f != nil { 57 | return f.Name() 58 | } 59 | return "" 60 | } 61 | 62 | // Locator returns child locator if any 63 | func (l *Locator) Locator(loc string) (gopium.Locator, bool) { 64 | fset, ok := l.Fset(loc, nil) 65 | return NewLocator(fset), ok 66 | } 67 | 68 | // Fset multifunc method that 69 | // either set new fset for location 70 | // or returns child fset if any 71 | func (l *Locator) Fset(loc string, fset *token.FileSet) (*token.FileSet, bool) { 72 | // lock concurrent map access 73 | defer l.mutex.Unlock() 74 | l.mutex.Lock() 75 | // if fset isn't nil 76 | if fset == nil { 77 | // write it to exta 78 | if fset, ok := l.extra[loc]; ok { 79 | return fset, true 80 | } 81 | return l.root, false 82 | } 83 | // otherwise read if from exta 84 | l.extra[loc] = fset 85 | return fset, true 86 | } 87 | 88 | // Root just returns root token.FileSet back 89 | func (l *Locator) Root() *token.FileSet { 90 | return l.root 91 | } 92 | -------------------------------------------------------------------------------- /typepkg/maven.go: -------------------------------------------------------------------------------- 1 | package typepkg 2 | 3 | import ( 4 | "fmt" 5 | "go/types" 6 | ) 7 | 8 | // MavenGoTypes defines maven default "go/types" implementation 9 | // that uses types.Sizes Sizeof in order to get type info 10 | type MavenGoTypes struct { 11 | sizes stdsizes `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 12 | caches map[uint]int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 13 | _ [8]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 14 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 24 bytes; - 🌺 gopium @1pkg 15 | 16 | // NewMavenGoTypes creates instance of MavenGoTypes 17 | // and requires compiler and arch for types.Sizes initialization 18 | func NewMavenGoTypes(compiler, arch string, caches ...int64) (MavenGoTypes, error) { 19 | // go through all passed caches 20 | // and fill them to cache map 21 | cm := make(map[uint]int64, len(caches)) 22 | for i, cache := range caches { 23 | cm[uint(i+1)] = cache 24 | } 25 | // try to get size for compiler and arch 26 | if sizes := types.SizesFor(compiler, arch); sizes != nil { 27 | return MavenGoTypes{ 28 | sizes: stdsizes{sizes}, 29 | caches: cm, 30 | }, nil 31 | } 32 | return MavenGoTypes{}, fmt.Errorf("unsuported compiler %q arch %q combination", compiler, arch) 33 | } 34 | 35 | // SysWord MavenGoTypes implementation 36 | func (m MavenGoTypes) SysWord() int64 { 37 | return m.sizes.WordSize() 38 | } 39 | 40 | // SysAlign MavenGoTypes implementation 41 | func (m MavenGoTypes) SysAlign() int64 { 42 | return m.sizes.MaxAlign() 43 | } 44 | 45 | // SysCache MavenGoTypes implementation 46 | func (m MavenGoTypes) SysCache(level uint) int64 { 47 | // if we have specified cache size 48 | if size, ok := m.caches[level]; ok { 49 | return size 50 | } 51 | // otherwise just return 52 | // typical cpu cache size 53 | return 64 54 | } 55 | 56 | // Name MavenGoTypes implementation 57 | func (m MavenGoTypes) Name(t types.Type) string { 58 | return t.String() 59 | } 60 | 61 | // Size MavenGoTypes implementation 62 | func (m MavenGoTypes) Size(t types.Type) int64 { 63 | return m.sizes.Sizeof(t) 64 | } 65 | 66 | // Align MavenGoTypes implementation 67 | func (m MavenGoTypes) Align(t types.Type) int64 { 68 | return m.sizes.Alignof(t) 69 | } 70 | 71 | // Ptr MavenGoTypes implementation 72 | func (m MavenGoTypes) Ptr(t types.Type) int64 { 73 | return m.sizes.Ptr(t) 74 | } 75 | -------------------------------------------------------------------------------- /typepkg/maven_test.go: -------------------------------------------------------------------------------- 1 | package typepkg 2 | 3 | import ( 4 | "errors" 5 | "fmt" 6 | "go/token" 7 | "go/types" 8 | "reflect" 9 | "testing" 10 | 11 | "github.com/1pkg/gopium/gopium" 12 | ) 13 | 14 | func TestNewMavenGoTypes(t *testing.T) { 15 | // prepare 16 | table := map[string]struct { 17 | compiler string 18 | arch string 19 | caches []int64 20 | maven gopium.Maven 21 | err error 22 | }{ 23 | "invalid compiler should return error": { 24 | compiler: "test", 25 | arch: "amd64", 26 | maven: MavenGoTypes{}, 27 | err: errors.New(`unsuported compiler "test" arch "amd64" combination`), 28 | }, 29 | "invalid arch should return error": { 30 | compiler: "gc", 31 | arch: "test", 32 | maven: MavenGoTypes{}, 33 | err: errors.New(`unsuported compiler "gc" arch "test" combination`), 34 | }, 35 | "valid compiler and arch pair should return expected maven": { 36 | compiler: "gc", 37 | arch: "amd64", 38 | caches: []int64{2, 4, 8, 16, 32}, 39 | maven: MavenGoTypes{ 40 | sizes: stdsizes{types.SizesFor("gc", "amd64")}, 41 | caches: map[uint]int64{ 42 | 1: 2, 43 | 2: 4, 44 | 3: 8, 45 | 4: 16, 46 | 5: 32, 47 | }, 48 | }, 49 | }, 50 | } 51 | for name, tcase := range table { 52 | t.Run(name, func(t *testing.T) { 53 | // exec 54 | maven, err := NewMavenGoTypes(tcase.compiler, tcase.arch, tcase.caches...) 55 | // check 56 | if fmt.Sprintf("%v", err) != fmt.Sprintf("%v", tcase.err) { 57 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 58 | } 59 | if !reflect.DeepEqual(maven, tcase.maven) { 60 | t.Errorf("actual %v doesn't equal to expected %v", maven, tcase.maven) 61 | } 62 | }) 63 | } 64 | } 65 | 66 | func TestMavenGoTypesCurator(t *testing.T) { 67 | // prepare 68 | maven, err := NewMavenGoTypes("gc", "amd64", 2, 4, 8, 16, 32) 69 | if !reflect.DeepEqual(err, nil) { 70 | t.Fatalf("actual %v doesn't equal to %v", err, nil) 71 | } 72 | table := map[string]struct { 73 | maven gopium.Maven 74 | word int64 75 | align int64 76 | caches []int64 77 | }{ 78 | "gc/amd64 maven should return expected results": { 79 | maven: maven, 80 | word: 8, 81 | align: 8, 82 | caches: []int64{64, 2, 4, 8, 16, 32, 64, 64, 64}, 83 | }, 84 | } 85 | for name, tcase := range table { 86 | t.Run(name, func(t *testing.T) { 87 | // exec 88 | word := maven.SysWord() 89 | align := maven.SysAlign() 90 | caches := make([]int64, len(tcase.caches)) 91 | for i := range caches { 92 | caches[i] = maven.SysCache(uint(i)) 93 | } 94 | // check 95 | if !reflect.DeepEqual(word, tcase.word) { 96 | t.Errorf("actual %v doesn't equal to %v", word, tcase.word) 97 | } 98 | if !reflect.DeepEqual(align, tcase.align) { 99 | t.Errorf("actual %v doesn't equal to %v", align, tcase.align) 100 | } 101 | if !reflect.DeepEqual(caches, tcase.caches) { 102 | t.Errorf("actual %v doesn't equal to %v", caches, tcase.caches) 103 | } 104 | }) 105 | } 106 | } 107 | 108 | func TestMavenGoTypesExposer(t *testing.T) { 109 | // prepare 110 | maven, err := NewMavenGoTypes("gc", "amd64", 2, 4, 8, 16, 32) 111 | if err != nil { 112 | t.Fatalf("actual %v doesn't equal to %v", err, nil) 113 | } 114 | table := map[string]struct { 115 | tp types.Type 116 | name string 117 | size int64 118 | align int64 119 | ptr int64 120 | }{ 121 | "int64 type should return expected resultss": { 122 | tp: types.Typ[types.Int64], 123 | name: "int64", 124 | size: 8, 125 | align: 8, 126 | ptr: 0, 127 | }, 128 | "string type should return expected results": { 129 | tp: types.Typ[types.String], 130 | name: "string", 131 | size: 16, 132 | align: 8, 133 | ptr: 8, 134 | }, 135 | "string slice type should return expected results": { 136 | tp: types.NewSlice(types.Typ[types.String]), 137 | name: "[]string", 138 | size: 24, 139 | align: 8, 140 | ptr: 8, 141 | }, 142 | "float32 arr type should return expected results": { 143 | tp: types.NewArray(types.Typ[types.Float32], 8), 144 | name: "[8]float32", 145 | size: 32, 146 | align: 4, 147 | ptr: 0, 148 | }, 149 | "struct type should return expected results": { 150 | tp: types.NewStruct( 151 | []*types.Var{ 152 | types.NewVar(token.Pos(0), nil, "a", types.Typ[types.Int64]), 153 | types.NewVar(token.Pos(0), nil, "b", types.NewSlice(types.Typ[types.Int64])), 154 | types.NewVar(token.Pos(0), nil, "c", types.Typ[types.Complex128]), 155 | types.NewVar(token.Pos(0), nil, "d", types.NewArray(types.Typ[types.Byte], 16)), 156 | }, 157 | nil, 158 | ), 159 | name: "struct{a int64; b []int64; c complex128; d [16]uint8}", 160 | size: 64, 161 | align: 8, 162 | ptr: 16, 163 | }, 164 | } 165 | for name, tcase := range table { 166 | t.Run(name, func(t *testing.T) { 167 | // exec 168 | name := maven.Name(tcase.tp) 169 | size := maven.Size(tcase.tp) 170 | align := maven.Align(tcase.tp) 171 | ptr := maven.Ptr(tcase.tp) 172 | // check 173 | if !reflect.DeepEqual(name, tcase.name) { 174 | t.Errorf("actual %v doesn't equal to %v", name, tcase.name) 175 | } 176 | if !reflect.DeepEqual(size, tcase.size) { 177 | t.Errorf("actual %v doesn't equal to %v", size, tcase.size) 178 | } 179 | if !reflect.DeepEqual(align, tcase.align) { 180 | t.Errorf("actual %v doesn't equal to %v", align, tcase.align) 181 | } 182 | if !reflect.DeepEqual(ptr, tcase.ptr) { 183 | t.Errorf("actual %v doesn't equal to %v", ptr, tcase.ptr) 184 | } 185 | }) 186 | } 187 | 188 | } 189 | -------------------------------------------------------------------------------- /typepkg/sizes.go: -------------------------------------------------------------------------------- 1 | package typepkg 2 | 3 | import ( 4 | "go/types" 5 | 6 | "github.com/1pkg/gopium/collections" 7 | ) 8 | 9 | // ftype is a stub to extract WordSize and MaxAlign out of types.Sizes interface across gc and gccgo compilers. 10 | type stubtype struct{} 11 | 12 | func (t stubtype) Underlying() types.Type { 13 | return stubtype{} 14 | } 15 | 16 | func (t stubtype) String() string { 17 | return "" 18 | } 19 | 20 | // stdsizes implements sizes interface using types std sizes 21 | type stdsizes struct { 22 | types.Sizes `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 23 | } // struct size: 16 bytes; struct align: 8 bytes; struct aligned size: 16 bytes; struct ptr scan size: 16 bytes; - 🌺 gopium @1pkg 24 | 25 | func (s stdsizes) WordSize() int64 { 26 | // This should work for both gc and gccgo types as default case returning proper WordSize. 27 | return s.Sizeof(stubtype{}) 28 | } 29 | 30 | func (s stdsizes) MaxAlign() int64 { 31 | // This should work for both gc and gccgo types as default case returning proper MaxAlign. 32 | return s.Alignof(types.Typ[types.Complex128]) 33 | } 34 | 35 | // Ptr implementation is vendored from 36 | // https://cs.opensource.google/go/x/tools/+/refs/tags/v0.9.3:go/analysis/passes/fieldalignment/fieldalignment.go;l=330 37 | func (s stdsizes) Ptr(t types.Type) int64 { 38 | switch t := t.Underlying().(type) { 39 | case *types.Basic: 40 | switch t.Kind() { 41 | case types.String, types.UnsafePointer: 42 | return s.WordSize() 43 | } 44 | return 0 45 | case *types.Chan, *types.Map, *types.Pointer, *types.Signature, *types.Slice: 46 | return s.WordSize() 47 | case *types.Interface: 48 | return 2 * s.WordSize() 49 | case *types.Array: 50 | n := t.Len() 51 | if n == 0 { 52 | return 0 53 | } 54 | a := s.Ptr(t.Elem()) 55 | if a == 0 { 56 | return 0 57 | } 58 | z := s.Sizeof(t.Elem()) 59 | return (n-1)*z + a 60 | case *types.Struct: 61 | nf := t.NumFields() 62 | if nf == 0 { 63 | return 0 64 | } 65 | 66 | var o, p int64 67 | for i := 0; i < nf; i++ { 68 | ft := t.Field(i).Type() 69 | a, sz := s.Alignof(ft), s.Sizeof(ft) 70 | fp := s.Ptr(ft) 71 | o = collections.Align(o, a) 72 | if fp != 0 { 73 | p = o + fp 74 | } 75 | o += sz 76 | } 77 | return p 78 | } 79 | 80 | return 0 81 | } 82 | -------------------------------------------------------------------------------- /walkers/builder.go: -------------------------------------------------------------------------------- 1 | package walkers 2 | 3 | import ( 4 | "fmt" 5 | 6 | "github.com/1pkg/gopium/gopium" 7 | ) 8 | 9 | // list of registered types walkers 10 | const ( 11 | // wast walkers 12 | AstStd gopium.WalkerName = "ast_std" 13 | AstGo gopium.WalkerName = "ast_go" 14 | AstGoTree gopium.WalkerName = "ast_go_tree" 15 | AstGopium gopium.WalkerName = "ast_gopium" 16 | // wout walkers 17 | FileJsonb gopium.WalkerName = "file_json" 18 | FileXmlb gopium.WalkerName = "file_xml" 19 | FileCsvb gopium.WalkerName = "file_csv" 20 | FileMdt gopium.WalkerName = "file_md_table" 21 | // wdiff walkers 22 | SizeAlignFileMdt gopium.WalkerName = "size_align_file_md_table" 23 | FieldsFileHtmlt gopium.WalkerName = "fields_file_html_table" 24 | ) 25 | 26 | // Builder defines types gopium.WalkerBuilder implementation 27 | // that uses parser and exposer to pass it to related walkers 28 | type Builder struct { 29 | Parser gopium.Parser `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 30 | Exposer gopium.Exposer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 31 | Printer gopium.Printer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 32 | Deep bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 33 | Bref bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 34 | _ [14]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 35 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 48 bytes; - 🌺 gopium @1pkg 36 | 37 | // Build Builder implementation 38 | func (b Builder) Build(name gopium.WalkerName) (gopium.Walker, error) { 39 | switch name { 40 | // wast walkers 41 | case AstStd: 42 | return aststd.With( 43 | b.Parser, 44 | b.Exposer, 45 | b.Printer, 46 | b.Deep, 47 | b.Bref, 48 | ), nil 49 | case AstGo: 50 | return astgo.With( 51 | b.Parser, 52 | b.Exposer, 53 | b.Printer, 54 | b.Deep, 55 | b.Bref, 56 | ), nil 57 | case AstGoTree: 58 | return astgotree.With( 59 | b.Parser, 60 | b.Exposer, 61 | b.Printer, 62 | b.Deep, 63 | b.Bref, 64 | ), nil 65 | case AstGopium: 66 | return astgopium.With( 67 | b.Parser, 68 | b.Exposer, 69 | b.Printer, 70 | b.Deep, 71 | b.Bref, 72 | ), nil 73 | // wout walkers 74 | case FileJsonb: 75 | return filejson.With( 76 | b.Parser, 77 | b.Exposer, 78 | b.Deep, 79 | b.Bref, 80 | ), nil 81 | case FileXmlb: 82 | return filexml.With( 83 | b.Parser, 84 | b.Exposer, 85 | b.Deep, 86 | b.Bref, 87 | ), nil 88 | case FileCsvb: 89 | return filecsv.With( 90 | b.Parser, 91 | b.Exposer, 92 | b.Deep, 93 | b.Bref, 94 | ), nil 95 | case FileMdt: 96 | return filemdt.With( 97 | b.Parser, 98 | b.Exposer, 99 | b.Deep, 100 | b.Bref, 101 | ), nil 102 | // wdiff walkers 103 | case SizeAlignFileMdt: 104 | return safilemdt.With( 105 | b.Parser, 106 | b.Exposer, 107 | b.Deep, 108 | b.Bref, 109 | ), nil 110 | case FieldsFileHtmlt: 111 | return ffilehtml.With( 112 | b.Parser, 113 | b.Exposer, 114 | b.Deep, 115 | b.Bref, 116 | ), nil 117 | default: 118 | return nil, fmt.Errorf("walker %q wasn't found", name) 119 | } 120 | } 121 | -------------------------------------------------------------------------------- /walkers/builder_test.go: -------------------------------------------------------------------------------- 1 | package walkers 2 | 3 | import ( 4 | "fmt" 5 | "reflect" 6 | "testing" 7 | 8 | "github.com/1pkg/gopium/gopium" 9 | "github.com/1pkg/gopium/tests/mocks" 10 | ) 11 | 12 | func TestBuilder(t *testing.T) { 13 | // prepare 14 | b := Builder{ 15 | Parser: mocks.Parser{}, 16 | Exposer: mocks.Maven{}, 17 | Deep: true, 18 | Bref: true, 19 | } 20 | table := map[string]struct { 21 | name gopium.WalkerName 22 | w gopium.Walker 23 | err error 24 | }{ 25 | // wast walkers 26 | "`ast_std` name should return expected walker": { 27 | name: AstStd, 28 | w: aststd.With( 29 | b.Parser, 30 | b.Exposer, 31 | b.Printer, 32 | b.Deep, 33 | b.Bref, 34 | ), 35 | }, 36 | "`ast_go` name should return expected walker": { 37 | name: AstGo, 38 | w: astgo.With( 39 | b.Parser, 40 | b.Exposer, 41 | b.Printer, 42 | b.Deep, 43 | b.Bref, 44 | ), 45 | }, 46 | "`ast_go_tree` name should return expected walker": { 47 | name: AstGoTree, 48 | w: astgotree.With( 49 | b.Parser, 50 | b.Exposer, 51 | b.Printer, 52 | b.Deep, 53 | b.Bref, 54 | ), 55 | }, 56 | "`ast_gopium` name should return expected walker": { 57 | name: AstGopium, 58 | w: astgopium.With( 59 | b.Parser, 60 | b.Exposer, 61 | b.Printer, 62 | b.Deep, 63 | b.Bref, 64 | ), 65 | }, 66 | // wout walkers 67 | "`file_json` name should return expected walker": { 68 | name: FileJsonb, 69 | w: filejson.With( 70 | b.Parser, 71 | b.Exposer, 72 | b.Deep, 73 | b.Bref, 74 | ), 75 | }, 76 | "`file_xml` name should return expected walker": { 77 | name: FileXmlb, 78 | w: filexml.With( 79 | b.Parser, 80 | b.Exposer, 81 | b.Deep, 82 | b.Bref, 83 | ), 84 | }, 85 | "`file_csv` name should return expected walker": { 86 | name: FileCsvb, 87 | w: filecsv.With( 88 | b.Parser, 89 | b.Exposer, 90 | b.Deep, 91 | b.Bref, 92 | ), 93 | }, 94 | "`file_md_table` name should return expected walker": { 95 | name: FileMdt, 96 | w: filemdt.With( 97 | b.Parser, 98 | b.Exposer, 99 | b.Deep, 100 | b.Bref, 101 | ), 102 | }, 103 | // wdiff walkers 104 | "`size_align_file_md_table` name should return expected walker": { 105 | name: SizeAlignFileMdt, 106 | w: safilemdt.With( 107 | b.Parser, 108 | b.Exposer, 109 | b.Deep, 110 | b.Bref, 111 | ), 112 | }, 113 | "`fields_file_html_table` name should return expected walker": { 114 | name: FieldsFileHtmlt, 115 | w: ffilehtml.With( 116 | b.Parser, 117 | b.Exposer, 118 | b.Deep, 119 | b.Bref, 120 | ), 121 | }, 122 | // others 123 | "invalid name should return builder error": { 124 | name: "test", 125 | err: fmt.Errorf(`walker "test" wasn't found`), 126 | }, 127 | } 128 | for name, tcase := range table { 129 | t.Run(name, func(t *testing.T) { 130 | // exec 131 | w, err := b.Build(tcase.name) 132 | // check 133 | // we can't compare functions directly in go 134 | // so apply this hack to compare with nil 135 | if tcase.w != nil && reflect.DeepEqual(w, nil) { 136 | t.Errorf("actual %v doesn't equal to expected %v", w, tcase.w) 137 | } 138 | if tcase.w == nil && !reflect.DeepEqual(w, nil) { 139 | t.Errorf("actual %v doesn't equal to expected not %v", w, tcase.w) 140 | } 141 | if fmt.Sprintf("%v", err) != fmt.Sprintf("%v", tcase.err) { 142 | t.Errorf("actual %v doesn't equal to expected %v", err, tcase.err) 143 | } 144 | }) 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /walkers/maven.go: -------------------------------------------------------------------------------- 1 | package walkers 2 | 3 | import ( 4 | "go/types" 5 | "sync" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/gopium" 9 | ) 10 | 11 | // ptrsizealign defines data transfer 12 | // object that holds type triplet 13 | // of ptr, size and align vals 14 | type ptrsizealign struct { 15 | ptr int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 16 | size int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 17 | align int64 `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 18 | _ [8]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 19 | } // struct size: 32 bytes; struct align: 8 bytes; struct aligned size: 32 bytes; struct ptr scan size: 0 bytes; - 🌺 gopium @1pkg 20 | 21 | // maven defines visiting helper 22 | // that aggregates some useful 23 | // operations on underlying facilities 24 | type maven struct { 25 | exp gopium.Exposer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 26 | loc gopium.Locator `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 27 | ref *collections.Reference `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 28 | store sync.Map `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 29 | _ [48]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 30 | } // struct size: 128 bytes; struct align: 8 bytes; struct aligned size: 128 bytes; struct ptr scan size: 72 bytes; - 🌺 gopium @1pkg 31 | 32 | // has defines struct store id helper 33 | // that uses locator to build id 34 | // for a structure and check that 35 | // builded id has not been stored already 36 | func (m *maven) has(tn *types.TypeName) (id string, loc string, ok bool) { 37 | // build id for the structure 38 | id = m.loc.ID(tn.Pos()) 39 | // build loc for the structure 40 | loc = m.loc.Loc(tn.Pos()) 41 | // in case id of structure 42 | // has been already stored 43 | if _, ok := m.store.Load(id); ok { 44 | return id, loc, true 45 | } 46 | // mark id of structure as stored 47 | m.store.Store(id, struct{}{}) 48 | return id, loc, false 49 | } 50 | 51 | // enum defines struct enumerating converting helper 52 | // that goes through all structure fields 53 | // and uses exposer to expose field DTO 54 | // for each field and puts them back 55 | // to resulted struct object 56 | func (m *maven) enum(name string, st *types.Struct) gopium.Struct { 57 | // set structure name 58 | r := gopium.Struct{} 59 | r.Name = name 60 | // get number of struct fields 61 | nf := st.NumFields() 62 | // prefill Fields 63 | r.Fields = make([]gopium.Field, 0, nf) 64 | for i := 0; i < nf; i++ { 65 | // get field 66 | f := st.Field(i) 67 | // get size and align for field 68 | sa := m.refpsa(f.Type()) 69 | // fill field structure 70 | r.Fields = append(r.Fields, gopium.Field{ 71 | Name: f.Name(), 72 | Type: m.exp.Name(f.Type()), 73 | Size: sa.size, 74 | Align: sa.align, 75 | Ptr: sa.ptr, 76 | Tag: st.Tag(i), 77 | Exported: f.Exported(), 78 | Embedded: f.Embedded(), 79 | }) 80 | } 81 | return r 82 | } 83 | 84 | // refsa defines ptr and size and align getter 85 | // with reference helper that uses reference 86 | // if it has been provided 87 | // or uses exposer to expose type size 88 | func (m *maven) refpsa(t types.Type) ptrsizealign { 89 | // in case we don't have a reference 90 | // just use default exposer size 91 | if m.ref == nil { 92 | return ptrsizealign{ 93 | ptr: m.exp.Ptr(t), 94 | size: m.exp.Size(t), 95 | align: m.exp.Align(t), 96 | } 97 | } 98 | // for refsize only named structures 99 | // and arrays should be calculated 100 | // not with default exposer size 101 | switch tp := t.(type) { 102 | case *types.Array: 103 | // note: copied from `go/types/sizes.go` 104 | n := tp.Len() 105 | if n <= 0 { 106 | return ptrsizealign{} 107 | } 108 | // n > 0 109 | sa := m.refpsa(tp.Elem()) 110 | sa.size = collections.Align(sa.size, sa.align)*(n-1) + sa.size 111 | sa.ptr = sa.ptr * n 112 | return sa 113 | case *types.Named: 114 | // in case it's not a struct skip it 115 | if _, ok := tp.Underlying().(*types.Struct); !ok { 116 | break 117 | } 118 | // get id for named structures 119 | id := m.loc.ID(tp.Obj().Pos()) 120 | // get size of the structure from ref 121 | if sa, ok := m.ref.Get(id).(ptrsizealign); ok { 122 | return sa 123 | } 124 | } 125 | // just use default exposer size 126 | return ptrsizealign{ 127 | ptr: m.exp.Ptr(t), 128 | size: m.exp.Size(t), 129 | align: m.exp.Align(t), 130 | } 131 | } 132 | 133 | // refst helps to create struct 134 | // size refence for provided key 135 | // by preallocating the key and then 136 | // pushing total struct size to ref with closure 137 | func (m *maven) refst(name string) func(gopium.Struct) { 138 | // preallocate the key 139 | m.ref.Alloc(name) 140 | // return the pushing closure 141 | return func(st gopium.Struct) { 142 | // calculate structure align, aligned size and ptr size 143 | stsize, stalign, ptrsize := collections.SizeAlignPtr(st) 144 | // set ref key size and align 145 | m.ref.Set(name, ptrsizealign{size: stsize, align: stalign, ptr: ptrsize}) 146 | } 147 | } 148 | -------------------------------------------------------------------------------- /walkers/wast.go: -------------------------------------------------------------------------------- 1 | package walkers 2 | 3 | import ( 4 | "context" 5 | "regexp" 6 | 7 | "github.com/1pkg/gopium/collections" 8 | "github.com/1pkg/gopium/fmtio" 9 | "github.com/1pkg/gopium/fmtio/astutil" 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | // list of wast presets 14 | var ( 15 | aststd = wast{ 16 | apply: astutil.UFFN, 17 | persister: astutil.Package{}, 18 | writer: fmtio.Origin{Writter: fmtio.Stdout{}}, 19 | } 20 | astgo = wast{ 21 | apply: astutil.UFFN, 22 | persister: astutil.Package{}, 23 | writer: fmtio.Origin{Writter: fmtio.Files{Ext: fmtio.GO}}, 24 | } 25 | astgotree = wast{ 26 | apply: astutil.UFFN, 27 | persister: astutil.Package{}, 28 | writer: &fmtio.Suffix{Writter: fmtio.Files{Ext: fmtio.GO}, Suffix: gopium.NAME}, 29 | } 30 | astgopium = wast{ 31 | apply: astutil.UFFN, 32 | persister: astutil.Package{}, 33 | writer: fmtio.Origin{Writter: fmtio.Files{Ext: fmtio.GOPIUM}}, 34 | } 35 | ) 36 | 37 | // wast defines packages walker ast sync implementation 38 | type wast struct { 39 | persister gopium.Persister `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 40 | writer gopium.CategoryWriter `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 41 | parser gopium.Parser `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 42 | exposer gopium.Exposer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 43 | printer gopium.Printer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 44 | apply gopium.Apply `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 45 | deep bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 46 | bref bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 47 | _ [38]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 48 | } // struct size: 128 bytes; struct align: 8 bytes; struct aligned size: 128 bytes; struct ptr scan size: 88 bytes; - 🌺 gopium @1pkg 49 | 50 | // With erich wast walker with external visiting parameters 51 | // parser, exposer, printer instances and additional visiting flags 52 | func (w wast) With(xp gopium.Parser, exp gopium.Exposer, p gopium.Printer, deep bool, bref bool) wast { 53 | w.parser = xp 54 | w.exposer = exp 55 | w.printer = p 56 | w.deep = deep 57 | w.bref = bref 58 | return w 59 | } 60 | 61 | // Visit wast implementation uses visit function helper 62 | // to go through all structs decls inside the package 63 | // and applies strategy to them to get results, 64 | // then overrides ast files with astutil helpers 65 | func (w wast) Visit(ctx context.Context, regex *regexp.Regexp, stg gopium.Strategy) error { 66 | // use parser to parse types pkg data 67 | // we don't care about fset 68 | pkg, loc, err := w.parser.ParseTypes(ctx) 69 | if err != nil { 70 | return err 71 | } 72 | // create govisit func 73 | // using visit helper 74 | // and run it on pkg scope 75 | ch := make(appliedCh) 76 | gvisit := with(w.exposer, loc, w.bref). 77 | visit(regex, stg, ch, w.deep) 78 | // prepare separate cancelation 79 | // context for visiting 80 | gctx, cancel := context.WithCancel(ctx) 81 | defer cancel() 82 | // run visiting in separate goroutine 83 | go gvisit(gctx, pkg.Scope()) 84 | // prepare struct storage 85 | h := collections.NewHierarchic("") 86 | for applied := range ch { 87 | // in case any error happened 88 | // just return error back 89 | // it auto cancels context 90 | if applied.Err != nil { 91 | return applied.Err 92 | } 93 | // push struct to storage 94 | h.Push(applied.ID, applied.Loc, applied.R) 95 | } 96 | // run sync write 97 | // with collected strategies results 98 | return w.write(gctx, h) 99 | } 100 | 101 | // write wast helps to sync and persist 102 | // strategies results to ast files 103 | func (w wast) write(ctx context.Context, h collections.Hierarchic) error { 104 | // skip empty writes 105 | if h.Len() == 0 { 106 | return nil 107 | } 108 | // use parser to parse ast pkg data 109 | pkg, loc, err := w.parser.ParseAst(ctx) 110 | if err != nil { 111 | return err 112 | } 113 | // run ast apply with strategy result 114 | // to update ast.Package 115 | // in case any error happened 116 | // just return error back 117 | pkg, err = w.apply(ctx, pkg, loc, h) 118 | if err != nil { 119 | return err 120 | } 121 | // add writer root category 122 | // in case any error happened 123 | // just return error back 124 | if err := w.writer.Category(h.Rcat()); err != nil { 125 | return err 126 | } 127 | // run persister with printer 128 | // in case any error happened 129 | // just return error back 130 | return w.persister.Persist(ctx, w.printer, w.writer, loc, pkg) 131 | } 132 | -------------------------------------------------------------------------------- /walkers/wdiff.go: -------------------------------------------------------------------------------- 1 | package walkers 2 | 3 | import ( 4 | "context" 5 | "path/filepath" 6 | "regexp" 7 | 8 | "github.com/1pkg/gopium/collections" 9 | "github.com/1pkg/gopium/fmtio" 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | // list of wdiff presets 14 | var ( 15 | safilemdt = wdiff{ 16 | fmt: fmtio.SizeAlignMdt, 17 | writer: fmtio.File{Name: gopium.NAME, Ext: fmtio.MD}, 18 | } 19 | ffilehtml = wdiff{ 20 | fmt: fmtio.FieldsHtmlt, 21 | writer: fmtio.File{Name: gopium.NAME, Ext: fmtio.HTML}, 22 | } 23 | ) 24 | 25 | // wdiff defines packages walker difference implementation 26 | type wdiff struct { 27 | writer gopium.Writer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 28 | parser gopium.TypeParser `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 29 | exposer gopium.Exposer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 30 | fmt gopium.Diff `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 31 | deep bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 32 | bref bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 33 | _ [6]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 34 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 56 bytes; - 🌺 gopium @1pkg 35 | 36 | // With erich wast walker with external visiting parameters 37 | // parser, exposer instances and additional visiting flags 38 | func (w wdiff) With(p gopium.TypeParser, exp gopium.Exposer, deep bool, bref bool) wdiff { 39 | w.parser = p 40 | w.exposer = exp 41 | w.deep = deep 42 | w.bref = bref 43 | return w 44 | } 45 | 46 | // Visit wdiff implementation uses visit function helper 47 | // to go through all structs decls inside the package 48 | // and applies strategy to them to get results, 49 | // then uses diff formatter to format strategy results 50 | // and use writer to write results to output 51 | func (w wdiff) Visit(ctx context.Context, regex *regexp.Regexp, stg gopium.Strategy) error { 52 | // use parser to parse types pkg data 53 | // we don't care about fset 54 | pkg, loc, err := w.parser.ParseTypes(ctx) 55 | if err != nil { 56 | return err 57 | } 58 | // create govisit func 59 | // using gopium.Visit helper 60 | // and run it on pkg scope 61 | ch := make(appliedCh) 62 | gvisit := with(w.exposer, loc, w.bref). 63 | visit(regex, stg, ch, w.deep) 64 | // prepare separate cancelation 65 | // context for visiting 66 | gctx, cancel := context.WithCancel(ctx) 67 | defer cancel() 68 | // run visiting in separate goroutine 69 | go gvisit(gctx, pkg.Scope()) 70 | // prepare struct storages 71 | ho, hr := collections.NewHierarchic(""), collections.NewHierarchic("") 72 | for applied := range ch { 73 | // in case any error happened 74 | // just return error back 75 | // it auto cancels context 76 | if applied.Err != nil { 77 | return applied.Err 78 | } 79 | // push structs to storages 80 | ho.Push(applied.ID, applied.Loc, applied.O) 81 | hr.Push(applied.ID, applied.Loc, applied.R) 82 | } 83 | // run sync write 84 | // with collected results 85 | return w.write(gctx, ho, hr) 86 | } 87 | 88 | // write wast helps to apply formatter 89 | // to format strategies results and writer 90 | // to write result to output 91 | func (w wdiff) write(_ context.Context, ho collections.Hierarchic, hr collections.Hierarchic) error { 92 | // skip empty writes 93 | if ho.Len() == 0 || hr.Len() == 0 { 94 | return nil 95 | } 96 | // apply formatter 97 | buf, err := w.fmt(ho, hr) 98 | // in case any error happened 99 | // in formatter return error back 100 | if err != nil { 101 | return err 102 | } 103 | // generate writer 104 | loc := filepath.Join(ho.Rcat(), "gopium") 105 | writer, err := w.writer.Generate(loc) 106 | if err != nil { 107 | return err 108 | } 109 | // write results and close writer 110 | // in case any error happened 111 | // in writer return error 112 | if _, err := writer.Write(buf); err != nil { 113 | return err 114 | } 115 | return writer.Close() 116 | } 117 | -------------------------------------------------------------------------------- /walkers/wout.go: -------------------------------------------------------------------------------- 1 | package walkers 2 | 3 | import ( 4 | "context" 5 | "path/filepath" 6 | "regexp" 7 | 8 | "github.com/1pkg/gopium/collections" 9 | "github.com/1pkg/gopium/fmtio" 10 | "github.com/1pkg/gopium/gopium" 11 | ) 12 | 13 | // list of wout presets 14 | var ( 15 | filejson = wout{ 16 | fmt: fmtio.Jsonb, 17 | writer: fmtio.File{Name: gopium.NAME, Ext: fmtio.JSON}, 18 | } 19 | filexml = wout{ 20 | fmt: fmtio.Xmlb, 21 | writer: fmtio.File{Name: gopium.NAME, Ext: fmtio.XML}, 22 | } 23 | filecsv = wout{ 24 | fmt: fmtio.Csvb(fmtio.Buffer()), 25 | writer: fmtio.File{Name: gopium.NAME, Ext: fmtio.CSV}, 26 | } 27 | filemdt = wout{ 28 | fmt: fmtio.Mdtb, 29 | writer: fmtio.File{Name: gopium.NAME, Ext: fmtio.MD}, 30 | } 31 | ) 32 | 33 | // wout defines packages walker out implementation 34 | type wout struct { 35 | writer gopium.Writer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 36 | parser gopium.TypeParser `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 37 | exposer gopium.Exposer `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 38 | fmt gopium.Bytes `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 39 | deep bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 40 | bref bool `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 41 | _ [6]byte `gopium:"filter_pads,memory_pack,cache_rounding_cpu_l1_discrete,struct_annotate_comment,add_tag_group_force"` 42 | } // struct size: 64 bytes; struct align: 8 bytes; struct aligned size: 64 bytes; struct ptr scan size: 56 bytes; - 🌺 gopium @1pkg 43 | 44 | // With erich wast walker with external visiting parameters 45 | // parser, exposer instances and additional visiting flags 46 | func (w wout) With(p gopium.TypeParser, exp gopium.Exposer, deep bool, bref bool) wout { 47 | w.parser = p 48 | w.exposer = exp 49 | w.deep = deep 50 | w.bref = bref 51 | return w 52 | } 53 | 54 | // Visit wout implementation uses visit function helper 55 | // to go through all structs decls inside the package 56 | // and applies strategy to them to get results, 57 | // then uses bytes formatter to format strategy results 58 | // and use writer to write results to output 59 | func (w wout) Visit(ctx context.Context, regex *regexp.Regexp, stg gopium.Strategy) error { 60 | // use parser to parse types pkg data 61 | // we don't care about fset 62 | pkg, loc, err := w.parser.ParseTypes(ctx) 63 | if err != nil { 64 | return err 65 | } 66 | // create govisit func 67 | // using gopium.Visit helper 68 | // and run it on pkg scope 69 | ch := make(appliedCh) 70 | gvisit := with(w.exposer, loc, w.bref). 71 | visit(regex, stg, ch, w.deep) 72 | // prepare separate cancelation 73 | // context for visiting 74 | gctx, cancel := context.WithCancel(ctx) 75 | defer cancel() 76 | // run visiting in separate goroutine 77 | go gvisit(gctx, pkg.Scope()) 78 | // prepare struct storage 79 | h := collections.NewHierarchic("") 80 | for applied := range ch { 81 | // in case any error happened 82 | // just return error back 83 | // it auto cancels context 84 | if applied.Err != nil { 85 | return applied.Err 86 | } 87 | // push struct to storage 88 | h.Push(applied.ID, applied.Loc, applied.R) 89 | } 90 | // run sync write 91 | // with collected strategies results 92 | return w.write(gctx, h) 93 | } 94 | 95 | // write wout helps to apply formatter 96 | // to format strategies result and writer 97 | // to write result to output 98 | func (w wout) write(_ context.Context, h collections.Hierarchic) error { 99 | // skip empty writes 100 | f := h.Flat() 101 | if len(f) == 0 { 102 | return nil 103 | } 104 | // apply formatter 105 | buf, err := w.fmt(f.Sorted()) 106 | // in case any error happened 107 | // in formatter return error back 108 | if err != nil { 109 | return err 110 | } 111 | // generate writer 112 | loc := filepath.Join(h.Rcat(), "gopium") 113 | writer, err := w.writer.Generate(loc) 114 | if err != nil { 115 | return err 116 | } 117 | // write results and close writer 118 | // in case any error happened 119 | // in writer return error 120 | if _, err := writer.Write(buf); err != nil { 121 | return err 122 | } 123 | return writer.Close() 124 | } 125 | --------------------------------------------------------------------------------