Skip to content

Commit 8c53ed7

Browse files
kzantownishakm
andcommitted
feat: initial golang support
Co-authored-by: Nisha Kumar <nisha.kumar@oracle.com> Signed-off-by: Keith Zantow <kzantow@gmail.com>
1 parent ae7ba4e commit 8c53ed7

File tree

8 files changed

+1701
-1
lines changed

8 files changed

+1701
-1
lines changed

src/shacl2code/lang/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,3 +9,4 @@
99
from .jinja import JinjaRender # noqa: F401
1010
from .python import PythonRender # noqa: F401
1111
from .jsonschema import JsonSchemaRender # noqa: F401
12+
from .golang import GolangRender # noqa: F401

src/shacl2code/lang/common.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,10 +72,10 @@ def abort_helper(msg):
7272
env.globals["abort"] = abort_helper
7373
env.globals["SHACL2CODE"] = SHACL2CODE
7474
env.globals["SH"] = SH
75+
7576
template = env.get_template(template.name)
7677

7778
render = template.render(
78-
disclaimer=f"This file was automatically generated by {os.path.basename(sys.argv[0])}. DO NOT MANUALLY MODIFY IT",
7979
**render_args,
8080
)
8181

@@ -135,6 +135,7 @@ def get_all_named_individuals(cls):
135135
"abstract_classes": abstract_classes,
136136
"enums": enums,
137137
"context": model.context,
138+
"disclaimer": f"This file was automatically generated by {os.path.basename(sys.argv[0])}. DO NOT MANUALLY MODIFY IT",
138139
**self.get_additional_render_args(),
139140
}
140141

Lines changed: 314 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,314 @@
1+
package runtime
2+
3+
import (
4+
"fmt"
5+
"reflect"
6+
"strings"
7+
)
8+
9+
type graphBuilder struct {
10+
ldc ldContext
11+
input []any
12+
graph []any
13+
idPrefix string
14+
nextID map[reflect.Type]int
15+
ids map[reflect.Value]string
16+
}
17+
18+
func (b *graphBuilder) toGraph() []any {
19+
return b.graph
20+
}
21+
22+
func (b *graphBuilder) add(o any) (context *serializationContext, err error) {
23+
v := reflect.ValueOf(o)
24+
if v.Type().Kind() != reflect.Pointer {
25+
if v.CanAddr() {
26+
v = v.Addr()
27+
} else {
28+
newV := reflect.New(v.Type())
29+
newV.Elem().Set(v)
30+
v = newV
31+
}
32+
}
33+
val, err := b.toValue(v)
34+
// objects with IDs get added to the graph during object traversal
35+
if _, isTopLevel := val.(map[string]any); isTopLevel && err == nil {
36+
b.graph = append(b.graph, val)
37+
}
38+
ctx := b.findContext(v.Type())
39+
return ctx, err
40+
}
41+
42+
func (b *graphBuilder) findContext(t reflect.Type) *serializationContext {
43+
t = baseType(t) // object may be a pointer, but we want the base types
44+
for _, context := range b.ldc {
45+
for _, typ := range context.iriToType {
46+
if t == typ.typ {
47+
return context
48+
}
49+
}
50+
}
51+
return nil
52+
}
53+
54+
func (b *graphBuilder) toStructMap(v reflect.Value) (value any, err error) {
55+
t := v.Type()
56+
if t.Kind() != reflect.Struct {
57+
return nil, fmt.Errorf("expected struct type, got: %v", stringify(v))
58+
}
59+
60+
meta, ok := fieldByType[ldType](t)
61+
if !ok {
62+
return nil, fmt.Errorf("struct does not have LDType metadata: %v", stringify(v))
63+
}
64+
65+
iri := meta.Tag.Get(typeIriCompactTag)
66+
if iri == "" {
67+
iri = meta.Tag.Get(typeIriTag)
68+
}
69+
70+
context := b.findContext(t)
71+
tc := context.typeToContext[t]
72+
73+
typeProp := ldTypeProp
74+
if context.typeAlias != "" {
75+
typeProp = context.typeAlias
76+
}
77+
out := map[string]any{
78+
typeProp: iri,
79+
}
80+
81+
hasValues := false
82+
id := ""
83+
84+
for i := 0; i < t.NumField(); i++ {
85+
f := t.Field(i)
86+
if skipField(f) {
87+
continue
88+
}
89+
90+
prop := f.Tag.Get(propIriCompactTag)
91+
if prop == "" {
92+
prop = f.Tag.Get(propIriTag)
93+
}
94+
95+
fieldV := v.Field(i)
96+
97+
if !isRequired(f) && isEmpty(fieldV) {
98+
continue
99+
}
100+
101+
val, err := b.toValue(fieldV)
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
if isIdField(f) {
107+
id, _ = val.(string)
108+
if id == "" {
109+
// if this struct does not have an ID set, and does not have multiple references,
110+
// it is output inline, it does not need an ID, but does need an ID
111+
// when it is moved to the top-level graph and referenced elsewhere
112+
if !b.hasMultipleReferences(v.Addr()) {
113+
continue
114+
}
115+
val, _ = b.ensureID(v.Addr())
116+
} else if tc != nil {
117+
// compact named IRIs
118+
if _, ok := tc.iriToName[id]; ok {
119+
id = tc.iriToName[id]
120+
}
121+
}
122+
} else {
123+
hasValues = true
124+
}
125+
126+
out[prop] = val
127+
}
128+
129+
if id != "" && !hasValues {
130+
// if we _only_ have an ID set and no other values, consider this a named individual
131+
return id, nil
132+
}
133+
134+
return out, nil
135+
}
136+
137+
func isIdField(f reflect.StructField) bool {
138+
return f.Tag.Get(propIriTag) == ldIDProp
139+
}
140+
141+
func isEmpty(v reflect.Value) bool {
142+
return !v.IsValid() || v.IsZero()
143+
}
144+
145+
func isRequired(f reflect.StructField) bool {
146+
if isIdField(f) {
147+
return true
148+
}
149+
required := f.Tag.Get(propIsRequiredTag)
150+
return required != "" && !strings.EqualFold(required, "false")
151+
}
152+
153+
func (b *graphBuilder) toValue(v reflect.Value) (any, error) {
154+
if !v.IsValid() {
155+
return nil, nil
156+
}
157+
158+
switch v.Type().Kind() {
159+
case reflect.Interface:
160+
return b.toValue(v.Elem())
161+
case reflect.Pointer:
162+
if v.IsNil() {
163+
return nil, nil
164+
}
165+
if !b.hasMultipleReferences(v) {
166+
return b.toValue(v.Elem())
167+
}
168+
return b.ensureID(v)
169+
case reflect.Struct:
170+
return b.toStructMap(v)
171+
case reflect.Slice:
172+
var out []any
173+
for i := 0; i < v.Len(); i++ {
174+
val, err := b.toValue(v.Index(i))
175+
if err != nil {
176+
return nil, err
177+
}
178+
out = append(out, val)
179+
}
180+
return out, nil
181+
case reflect.String:
182+
return v.String(), nil
183+
default:
184+
if v.CanInterface() {
185+
return v.Interface(), nil
186+
}
187+
return nil, fmt.Errorf("unable to convert value to maps: %v", stringify(v))
188+
}
189+
}
190+
191+
func (b *graphBuilder) ensureID(ptr reflect.Value) (string, error) {
192+
if ptr.Type().Kind() != reflect.Pointer {
193+
return "", fmt.Errorf("expected pointer, got: %v", stringify(ptr))
194+
}
195+
if id, ok := b.ids[ptr]; ok {
196+
return id, nil
197+
}
198+
199+
v := ptr.Elem()
200+
t := v.Type()
201+
202+
id, err := b.getID(v)
203+
if err != nil {
204+
return "", err
205+
}
206+
if id == "" {
207+
if b.nextID == nil {
208+
b.nextID = map[reflect.Type]int{}
209+
}
210+
nextID := b.nextID[t] + 1
211+
b.nextID[t] = nextID
212+
id = fmt.Sprintf("_:%s-%v", t.Name(), nextID)
213+
}
214+
b.ids[ptr] = id
215+
val, err := b.toValue(v)
216+
if err != nil {
217+
return "", err
218+
}
219+
b.graph = append(b.graph, val)
220+
return id, nil
221+
}
222+
223+
func (b *graphBuilder) getID(v reflect.Value) (string, error) {
224+
t := v.Type()
225+
if t.Kind() != reflect.Struct {
226+
return "", fmt.Errorf("expected struct, got: %v", stringify(v))
227+
}
228+
for i := 0; i < t.NumField(); i++ {
229+
f := t.Field(i)
230+
if isIdField(f) {
231+
fv := v.Field(i)
232+
if f.Type.Kind() != reflect.String {
233+
return "", fmt.Errorf("invalid type for ID field %v in: %v", f, stringify(v))
234+
}
235+
return fv.String(), nil
236+
}
237+
}
238+
return "", nil
239+
}
240+
241+
// hasMultipleReferences returns true if the ptr value has multiple references in the input slice
242+
func (b *graphBuilder) hasMultipleReferences(ptr reflect.Value) bool {
243+
if !ptr.IsValid() {
244+
return false
245+
}
246+
count := 0
247+
visited := map[reflect.Value]struct{}{}
248+
for _, v := range b.input {
249+
count += refCountR(ptr, visited, reflect.ValueOf(v))
250+
if count > 1 {
251+
return true
252+
}
253+
}
254+
return false
255+
}
256+
257+
// refCount returns the reference count of the value in the container object
258+
func refCount(find any, container any) int {
259+
visited := map[reflect.Value]struct{}{}
260+
ptrV := reflect.ValueOf(find)
261+
if !ptrV.IsValid() {
262+
return 0
263+
}
264+
return refCountR(ptrV, visited, reflect.ValueOf(container))
265+
}
266+
267+
// refCountR recursively searches for the value, find, in the value v
268+
func refCountR(find reflect.Value, visited map[reflect.Value]struct{}, v reflect.Value) int {
269+
if find.Equal(v) {
270+
return 1
271+
}
272+
if !v.IsValid() {
273+
return 0
274+
}
275+
if _, ok := visited[v]; ok {
276+
return 0
277+
}
278+
visited[v] = struct{}{}
279+
switch v.Kind() {
280+
case reflect.Interface:
281+
return refCountR(find, visited, v.Elem())
282+
case reflect.Pointer:
283+
if v.IsNil() {
284+
return 0
285+
}
286+
return refCountR(find, visited, v.Elem())
287+
case reflect.Struct:
288+
count := 0
289+
for i := 0; i < v.NumField(); i++ {
290+
count += refCountR(find, visited, v.Field(i))
291+
}
292+
return count
293+
case reflect.Slice:
294+
count := 0
295+
for i := 0; i < v.Len(); i++ {
296+
count += refCountR(find, visited, v.Index(i))
297+
}
298+
return count
299+
default:
300+
return 0
301+
}
302+
}
303+
304+
func stringify(o any) string {
305+
if v, ok := o.(reflect.Value); ok {
306+
if !v.IsValid() {
307+
return "invalid value"
308+
}
309+
if !v.IsZero() && v.CanInterface() {
310+
o = v.Interface()
311+
}
312+
}
313+
return fmt.Sprintf("%#v", o)
314+
}

0 commit comments

Comments
 (0)