Skip to content

Commit

Permalink
Added golang struct exporter
Browse files Browse the repository at this point in the history
Signed-off-by: Sebastian Schleemilch <[email protected]>
  • Loading branch information
sschleemilch committed Sep 20, 2024
1 parent 4e186f2 commit 2219a38
Show file tree
Hide file tree
Showing 10 changed files with 539 additions and 6 deletions.
68 changes: 68 additions & 0 deletions docs/go.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Go lang struct exporter

This exporter produces type struct definitions for the [go](https://go.dev/) programming language.

## Exporter specific arguments

### `--package`

The name of the package the generated sources (output and types output) will have.

# Example

Input model:
```yaml
# model.vspec
Vehicle:
type: branch
description: Vehicle
Vehicle.Speed:
type: sensor
description: Speed
datatype: uint16
Vehicle.Location:
type: sensor
description: Location
datatype: Types.GPSLocation
```
Input type definitions:
```yaml
# types.vspec
Types:
type: branch
description: Custom Types
Types.GPSLocation:
type: struct
description: GPS Location
Types.GPSLocation.Longitude:
type: property
description: Longitude
datatype: float
Types.GPSLocation.Latitude:
type: property
description: Latitude
datatype: float
```
Generator call:
```bash
vspec export go --vspec model.vspec --types types.vspec --package vss --output vss.go
```

Generated file:
```go
// vss.go
package vss

type Vehicle struct {
Speed uint16
Location GPSLocation
}
type GPSLocation struct {
Longitude float32
Latitude float32
}
```

`--types-output` can be used to write `Types*` definitions into a separate file with the same package name.
1 change: 1 addition & 0 deletions docs/vspec.md
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ vspec export json --vspec spec/VehicleSignalSpecification.vspec --output vss.jso
- [id](./id.md)
- [protobuf](./protobuf.md)
- [samm](./samm.md)
- [go](./go.md)

## Argument Explanations

Expand Down
1 change: 1 addition & 0 deletions src/vss_tools/vspec/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ def cli(ctx: click.Context, log_level: str, log_file: Path):
"yaml": "vss_tools.vspec.vssexporters.vss2yaml:cli",
"tree": "vss_tools.vspec.vssexporters.vss2tree:cli",
"samm": "vss_tools.vspec.vssexporters.vss2samm.vss2samm:cli",
"go": "vss_tools.vspec.vssexporters.vss2go:cli",
},
)
@click.pass_context
Expand Down
212 changes: 212 additions & 0 deletions src/vss_tools/vspec/vssexporters/vss2go.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,212 @@
# Copyright (c) 2024 Contributors to COVESA
#
# This program and the accompanying materials are made available under the
# terms of the Mozilla Public License 2.0 which is available at
# https://www.mozilla.org/en-US/MPL/2.0/
#
# SPDX-License-Identifier: MPL-2.0


from __future__ import annotations

from pathlib import Path

import rich_click as click
from anytree import PreOrderIter

import vss_tools.vspec.cli_options as clo
from vss_tools.vspec.datatypes import Datatypes, is_array
from vss_tools.vspec.main import get_trees
from vss_tools.vspec.model import VSSDataBranch, VSSDataDatatype, VSSDataStruct
from vss_tools.vspec.tree import VSSNode

datatype_map = {
Datatypes.INT8_ARRAY[0]: "[]int8",
Datatypes.INT16_ARRAY[0]: "[]int16",
Datatypes.INT32_ARRAY[0]: "[]int32",
Datatypes.INT64_ARRAY[0]: "[]int64",
Datatypes.UINT8_ARRAY[0]: "[]uint8",
Datatypes.UINT16_ARRAY[0]: "[]uint16",
Datatypes.UINT32_ARRAY[0]: "[]uint32",
Datatypes.UINT64_ARRAY[0]: "[]uint64",
Datatypes.FLOAT[0]: "float32",
Datatypes.FLOAT_ARRAY[0]: "[]float32",
Datatypes.DOUBLE[0]: "float64",
Datatypes.DOUBLE_ARRAY[0]: "[]float64",
Datatypes.STRING_ARRAY[0]: "[]string",
Datatypes.NUMERIC[0]: "float64",
Datatypes.NUMERIC_ARRAY[0]: "[]float64",
Datatypes.BOOLEAN_ARRAY[0]: "[]bool",
Datatypes.BOOLEAN[0]: "bool",
}


class NoInstanceRootException(Exception):
pass


def get_instance_root(root: VSSNode, depth: int = 1) -> tuple[VSSNode, int]:
if root.parent is None:
raise NoInstanceRootException()
if isinstance(root.parent.data, VSSDataBranch):
if root.parent.data.is_instance:
return get_instance_root(root.parent, depth + 1)
else:
return root.parent, depth
else:
raise NoInstanceRootException()


def add_children_map_entries(root: VSSNode, fqn: str, replace: str, map: dict[str, str]) -> None:
child: VSSNode
for child in root.children:
child_fqn = child.get_fqn()
map[child_fqn] = child_fqn.replace(fqn, replace)
add_children_map_entries(child, fqn, replace, map)


def get_instance_mapping(root: VSSNode | None) -> dict[str, str]:
if root is None:
return {}
instance_map: dict[str, str] = {}
for node in PreOrderIter(root):
if isinstance(node.data, VSSDataBranch):
if node.data.is_instance:
instance_root, depth = get_instance_root(node)
new_name = instance_root.get_fqn() + "." + "I" + str(depth)
fqn = node.get_fqn()
instance_map[fqn] = new_name
add_children_map_entries(node, fqn, new_name, instance_map)
return instance_map


def get_datatype(node: VSSNode) -> str | None:
"""
Gets the datatype string of a node.
"""
datatype = None
if isinstance(node.data, VSSDataDatatype):
if node.data.datatype in datatype_map:
return datatype_map[node.data.datatype]
d = Datatypes.get_type(node.data.datatype)
if d:
datatype = d[0]
# Struct type
d_raw = node.data.datatype
array = is_array(d_raw)
struct_datatype = node.data.datatype.rstrip("[]")
if array:
struct_datatype = f"[]{struct_datatype}"
datatype = struct_datatype
return datatype


class GoStructMember:
def __init__(self, name: str, datatype: str) -> None:
self.name = name
self.datatype = datatype


class GoStruct:
def __init__(self, name: str) -> None:
self.name = name
self.members: list[GoStructMember] = []

def __str__(self) -> str:
r = f"type {self.name.replace('.', '')} struct {{\n"
for member in self.members:
r += f"\t{member.name} {member.datatype.replace('.', '')}\n"
r += "}\n"
return r

def __eq__(self, other: object) -> bool:
if not isinstance(other, GoStruct):
return False
return self.name == other.name


def get_struct_name(fqn: str, map: dict[str, str]) -> str:
if fqn in map:
return map[fqn]
else:
return fqn


def get_go_structs(root: VSSNode | None, map: dict[str, str], type_tree: bool = False) -> dict[str, GoStruct]:
structs: dict[str, GoStruct] = {}
if root is None:
return structs
for node in PreOrderIter(root):
if isinstance(node.data, VSSDataBranch) or isinstance(node.data, VSSDataStruct):
struct = GoStruct(get_struct_name(node.get_fqn(), map))
for child in node.children:
datatype = get_datatype(child)
if not datatype:
datatype = get_struct_name(child.get_fqn(), map)
member = GoStructMember(child.name, datatype)
struct.members.append(member)
if type_tree and isinstance(node.data, VSSDataBranch):
pass
else:
structs[struct.name] = struct
return structs


@click.command()
@clo.vspec_opt
@clo.output_required_opt
@clo.include_dirs_opt
@clo.extended_attributes_opt
@clo.strict_opt
@clo.aborts_opt
@clo.overlays_opt
@clo.quantities_opt
@clo.units_opt
@clo.types_opt
@clo.types_output_opt
@click.option("--package", default="vss", help="Go package name", show_default=True)
def cli(
vspec: Path,
output: Path,
include_dirs: tuple[Path],
extended_attributes: tuple[str],
strict: bool,
aborts: tuple[str],
overlays: tuple[Path],
quantities: tuple[Path],
units: tuple[Path],
types: tuple[Path],
types_output: Path | None,
package: str,
):
"""
Export as Go structs.
"""
tree, datatype_tree = get_trees(
vspec=vspec,
include_dirs=include_dirs,
aborts=aborts,
strict=strict,
extended_attributes=extended_attributes,
quantities=quantities,
units=units,
types=types,
overlays=overlays,
)
instance_map = get_instance_mapping(tree)
structs = get_go_structs(tree, instance_map)
datatype_structs = get_go_structs(datatype_tree, instance_map, True)

with open(output, "w") as f:
f.write(f"package {package}\n\n")
for struct in structs.values():
f.write(str(struct))
if not types_output:
for dstruct in datatype_structs.values():
f.write(str(dstruct))

if types_output:
with open(types_output, "w") as f:
f.write(f"package {package}\n\n")
for struct in datatype_structs.values():
f.write(str(struct))
15 changes: 15 additions & 0 deletions tests/vspec/test_datatypes/expected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package vss

type A struct {
UInt8 uint8
Int8 int8
UInt16 uint16
Int16 int16
UInt32 uint16
Int32 int32
UInt64 uint64
Int64 int64
IsBoolean bool
Float float32
Double float64
}
42 changes: 42 additions & 0 deletions tests/vspec/test_exporter_go/expected.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package vss

type Vehicle struct {
Struct TypesStruct
StructA []TypesStruct
AllTypes VehicleAllTypes
}
type VehicleAllTypes struct {
Uint8 uint8
Uint8A []uint8
Uint16 uint16
Uint16A []uint16
Uint32 uint32
Uint32A []uint32
Uint64 uint64
Uint64A []uint64
Int8 int8
Int8A []int8
Int16 int16
Int16A []int16
Int32 int32
Int32A []int32
Int64 int64
Int64A []int64
Bool bool
BoolA []bool
Float float32
FloatA []float32
Double float64
DoubleA []float64
String string
StringA []string
Numeric float64
NumericA []float64
}
type TypesStruct struct {
x TypesStructEmbedded
y uint8
}
type TypesStructEmbedded struct {
z []uint8
}
Loading

0 comments on commit 2219a38

Please sign in to comment.