Initial commit

This commit is contained in:
Elyan 2025-12-23 17:27:44 +01:00
commit 7781cf26b5
10 changed files with 800 additions and 0 deletions

21
Makefile Normal file
View File

@ -0,0 +1,21 @@
GO_MODULE_IMPORT := go.datafortress.dev/pkg/central-arch-spec-models
GO_MODELS_PKG := spec
SPEC_XML_NS := https://datafortress.dev/xml/central-arch-specification
SPEC_SCHEMA_DIR := ../central-arch-spec-schema
SPEC_SCHEMA_FILE := specification.xsd
.PHONY: all
all: tests
.PHONY: tests
tests:
go test $(shell find . -type f -name '*_test.go' -print)
.PHONY: deps
deps:
go install github.com/gocomply/xsd2go/cli/gocomply_xsd2go@latest
.PHONY: generate
generate:
gocomply_xsd2go convert --xmlns-override=${SPEC_XML_NS}=${GO_MODELS_PKG} ${SPEC_SCHEMA_DIR}/${SPEC_SCHEMA_FILE} ${GO_MODULE_IMPORT} .

9
README.md Normal file
View File

@ -0,0 +1,9 @@
# Central Arch: XML Specification Models
## Install GoComply Xsd2Go
```bash
make deps
```
## Regenerate data model from XSD
```bash
make models
```

11
go.mod Normal file
View File

@ -0,0 +1,11 @@
module go.datafortress.dev/pkg/central-arch-spec-models
go 1.23.2
require github.com/stretchr/testify v1.11.1
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

10
go.sum Normal file
View File

@ -0,0 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U=
github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

116
query/indexer.go Normal file
View File

@ -0,0 +1,116 @@
package query
import "fmt"
type IndexedElement struct {
Name string
DisplayName *string
ReferencedData any
Parent *IndexedContainer
Containers []*IndexedContainer
}
func (e *IndexedElement) Init() {
for _, container := range e.Containers {
container.Parent = e
container.Init()
}
}
func (e *IndexedElement) String() string {
if e.Parent == nil {
panic("IndexedElement has no parent!")
}
return fmt.Sprintf("%s:%s", e.Parent.Parent, e.Name)
}
func (e *IndexedElement) PrintableName() string {
var result = e.Name
if e.DisplayName != nil {
result = *e.DisplayName
}
return result
}
func (e *IndexedElement) Find(c *Container) QueryResult {
return findInContainers(c, e.Containers)
}
type IndexedContainer struct {
Name string
Parent *IndexedElement
Elements []*IndexedElement
}
func (c *IndexedContainer) Init() {
for _, element := range c.Elements {
element.Parent = c
element.Init()
}
}
func (c *IndexedContainer) String() string {
var parentStr = ""
if c.Parent != nil {
parentStr = c.Parent.String()
}
return fmt.Sprintf("%s%s", parentStr, c.Name)
}
func (c *IndexedContainer) PrintableName() string {
return c.Name
}
func (c *IndexedContainer) Find(e *Element) QueryResult {
for _, element := range c.Elements {
if e.Name == element.Name {
if e.NextContainer != nil {
// If the element is found but the query expression continues
// with a container query, delegate the search to the element
return element.Find(e.NextContainer)
} else {
// If not, return the found element
return QueryResult{element}
}
}
}
// Return an empty list if the element is not found
return make(QueryResult, 0)
}
// A query tree is a bipartite graph composed of containers and elements.
// Only elements can be queried.
type IndexedRoot struct {
Containers []*IndexedContainer
}
func (r *IndexedRoot) Find(q *Query) QueryResult {
return findInContainers(&q.Container, r.Containers)
}
func (r *IndexedRoot) FindByRef(ref Reference) (QueryResult, error) {
var query, err = ref.Parse()
if err != nil {
return nil, err
}
return r.Find(query), nil
}
func findInContainers(queried *Container, containers []*IndexedContainer) QueryResult {
for _, container := range containers {
if queried.Name == container.Name {
if queried.NextElement != nil {
// If the container is found but the query expression continues
// with an element query, delegate the search to the container
return container.Find(queried.NextElement)
} else {
// If not, return all the elements in the found container
return container.Elements
}
}
}
// Return an empty list if the container is not found
return make(QueryResult, 0)
}

229
query/ref.go Normal file
View File

@ -0,0 +1,229 @@
package query
import (
"fmt"
"strings"
"unicode"
"unicode/utf8"
)
/*
A reference is composed of one or more matchers separated by periods.
A matcher can be composed by a single container name or by both a
container name and an element identifier.
A matcher only composed of a container name can only appear as the last
matcher of a reference.
A reference the last matcher of which is composed of both a container
name and an element identifier is a fully qualified reference.
A reference the last matcher of which is only composed of a container
returns all elements inside the container or an empty list.
A reference the last matcher of which is composed of both a container
name and an element identifier returns only the referenced element or
an empty list.
Example, given the following query tree:
(with "r:" meaning "root", "c:" container and "e:" element).
<r: />-+--<c: format>-+--<e: T1>
| |--<e: T2>
|
+--<c: instr>--+--<e: BKPT>-+--<c: binding>
Ref: "format"
Returns: [T1, T2]
Ref: "format:T1"
Returns: [T1]
Ref: "instr:BKPT"
Returns: [BKPT]
Ref: "instr:BKPT.binding"
Returns: []
Ref: "instr:BKPT.binding:base"
Returns: []
Ref: "instr.binding"
Returns: Error
=> Container-only matcher can only be used as the last matcher in a reference.
*/
type Reference string
func (ref *Reference) Parse() (*Query, error) {
var container, err = parseContainerComponent(*ref, 0)
if err != nil {
return nil, err
}
return &Query{Container: *container}, nil
}
func (ref *Reference) IsValid() bool {
var _, err = ref.Parse()
return err == nil
}
func (ref *Reference) IsFullyQualified() bool {
var query, err = ref.Parse()
if err != nil {
return false
}
return query.IsFullyQualified()
}
type RefParseError struct {
Ref Reference
Index int
Msg string
}
func (err *RefParseError) Error() string {
var error_fmt = `Parsing error at character %d in the following query reference:
"%s"
%s^
Error: %s`
return fmt.Sprintf(error_fmt, err.Index+1, err.Ref, strings.Repeat(" ", err.Index), err.Msg)
}
func isValidNameChar(r rune) bool {
return unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-'
}
func isMatcherSeparator(r rune) bool {
return r == '.'
}
func isElementIdSeparator(r rune) bool {
return r == ':'
}
func parseContainerComponent(ref Reference, index int) (*Container, error) {
var currentName = ""
if index >= len(ref) {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected a container name but reached end of reference",
}
}
for i, w := index, 0; i < len(ref); i += w {
var runeValue, width = utf8.DecodeRuneInString(string(ref[i:]))
w = width
if isElementIdSeparator(runeValue) {
if len(currentName) > 0 {
var e, err = parseElementComponent(ref, index)
if err != nil {
return nil, err
}
return &Container{Name: currentName, NextElement: e}, nil
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected a container name before an element identifier",
}
}
}
if isMatcherSeparator(runeValue) {
if len(currentName) > 0 {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected an element identifier before another container name",
}
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected both a container name and an element identifier before another container name",
}
}
}
if isValidNameChar(runeValue) {
currentName += string(runeValue)
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: fmt.Sprintf("Character '%c' is not permitted in a container name", runeValue),
}
}
}
return &Container{Name: currentName, NextElement: nil}, nil
}
func parseElementComponent(ref Reference, index int) (*Element, error) {
var currentName = ""
if index >= len(ref) {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected an element identifier but reached end of reference",
}
}
for i, w := index, 0; i < len(ref); i += w {
var runeValue, width = utf8.DecodeRuneInString(string(ref[i:]))
w = width
if isElementIdSeparator(runeValue) {
if len(currentName) > 0 {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected a container name before another element identifier",
}
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected both a container name and an element identifier before another element identifier",
}
}
}
if isMatcherSeparator(runeValue) {
if len(currentName) > 0 {
var e, err = parseContainerComponent(ref, index)
if err != nil {
return nil, err
}
return &Element{Name: currentName, NextContainer: e}, nil
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: "Expected an element identifier before a container name",
}
}
}
if isValidNameChar(runeValue) {
currentName += string(runeValue)
} else {
return nil, &RefParseError{
Ref: ref,
Index: index,
Msg: fmt.Sprintf("Character '%c' is not permitted in an element identifier", runeValue),
}
}
}
return &Element{Name: currentName, NextContainer: nil}, nil
}

31
query/ref_test.go Normal file
View File

@ -0,0 +1,31 @@
package query_test
import (
"testing"
"github.com/stretchr/testify/require"
"go.datafortress.dev/pkg/central-arch-spec-models/query"
)
const (
MULTIPLE_LEVELS_QUERY_REF = "instruction:LDR_Base_Offset.binding:base"
FULLY_QUALIFIED_QUERY_REF = "c1:e1.c2:e2"
NOT_FULLY_QUALIFIED_QUERY_REF = "c1:e1.c2"
)
func TestRefParsing(t *testing.T) {
var ref query.Reference = MULTIPLE_LEVELS_QUERY_REF
var query, err = ref.Parse()
require.Nil(t, err)
var instrContainer = query.Container
require.Equal(t, "instruction", instrContainer.Name)
require.NotNil(t, instrContainer.NextElement)
var instrElement = instrContainer.NextElement
require.Equal(t, "LDR_Base_Offset", instrElement.Name)
require.NotNil(t, instrElement.NextContainer)
// TODO Continue testing
}

61
query/tree.go Normal file
View File

@ -0,0 +1,61 @@
package query
import (
"fmt"
)
type QueryResult []*IndexedElement
type Element struct {
Name string
NextContainer *Container
}
func (e *Element) String() string {
var containerStr = ""
if e.NextContainer != nil {
containerStr = fmt.Sprintf(".%s", e.NextContainer)
}
return fmt.Sprintf("%s%s", e.Name, containerStr)
}
type Container struct {
Name string
NextElement *Element
}
func (c *Container) String() string {
var elementStr = ""
if c.NextElement != nil {
elementStr = fmt.Sprintf(":%s", c.NextElement)
}
return fmt.Sprintf("%s%s", c.Name, elementStr)
}
type Query struct {
Container Container
}
func (q *Query) String() string {
return q.Container.String()
}
func (q *Query) IsFullyQualified() bool {
var container *Container = &q.Container
for container != nil {
if container.NextElement == nil {
// Current container has no next element, so the query ended on a container.
// Thus, it is not fully qualified.
return false
} else {
container = container.NextElement.NextContainer
}
}
// Next container is nil, so the query ended on an element.
// Thus, it is fully qualified.
return true
}

307
spec/models.go Normal file
View File

@ -0,0 +1,307 @@
// Code generated by https://github.com/gocomply/xsd2go; DO NOT EDIT.
// Models for https://datafortress.dev/xml/central-arch-specification
package spec
import (
"encoding/xml"
)
// Element
type Specification struct {
XMLName xml.Name `xml:"specification"`
Exceptions ExceptionList `xml:"exceptions"`
Formats FormatList `xml:"formats"`
Instructions InstructionList `xml:"instructions"`
}
// XSD ComplexType declarations
// SpecificationType: Central Architecture CPU XML Specification file.
type SpecificationType struct {
XMLName xml.Name
Exceptions ExceptionList `xml:"exceptions"`
Formats FormatList `xml:"formats"`
Instructions InstructionList `xml:"instructions"`
}
// FormatList: Supported instruction encoding formats.
type FormatList struct {
XMLName xml.Name
Format []Format `xml:",any"`
}
// InstructionList: Supported instructions.
type InstructionList struct {
XMLName xml.Name
Instruction []Instruction `xml:",any"`
}
// ExceptionList: Supported exceptions.
type ExceptionList struct {
XMLName xml.Name
Exception []Exception `xml:",any"`
}
// DocText: Documentation text.
type DocText struct {
XMLName xml.Name
Ref []RefText `xml:"ref"`
Todo []TodoText `xml:"todo"`
InnerXml string `xml:",innerxml"`
}
// RefText: A reference to an element.
type RefText struct {
XMLName xml.Name
// Type: Reference type. Used to set a specific styling to the reference text. Can also generate a link to a reference in the documentation.
Type string `xml:"type,attr"`
Text string `xml:",chardata"`
}
// TodoText: A TODO annotation.
type TodoText struct {
XMLName xml.Name
Text string `xml:",chardata"`
}
// Exception: The specification of an exception.
type Exception struct {
XMLName xml.Name
// Id: The unique identifier of this exception.
Id string `xml:"id,attr"`
// DisplayName: The name of the exception that will be displayed in the documentation.
DisplayName string `xml:"display-name"`
// Brief: A brief description of this exception.
Brief DocText `xml:"brief"`
// Details: Details about this exception. Used to give info about where the exception handler PSW is stored and what are the info given to it by the system.
Details DocText `xml:"details"`
}
// Format: The specification of an instruction encoding format.
type Format struct {
XMLName xml.Name
// Id: The unique identifier of an instruction encoding format.
Id string `xml:"id,attr"`
// DisplayName: The name of the instruction encoding format as it will be displayed in the documentation.
DisplayName string `xml:"display-name"`
Description DocText `xml:"description"`
Fields FormatFieldList `xml:"fields"`
}
// FormatFieldList: The fields used in this instruction encoding format.
type FormatFieldList struct {
XMLName xml.Name
Field []FormatField `xml:",any"`
}
// FormatField: Description of a field in this instruction encoding format.
type FormatField struct {
XMLName xml.Name
// Bits: Field length, in bits.
Bits int64 `xml:"bits,attr"`
Type FormatFieldType `xml:"type,attr"`
// Name: Field name. Ignored for opcode and condition fields.
Name string `xml:"name,attr,omitempty"`
}
// Instruction: The specification of an instruction.
type Instruction struct {
XMLName xml.Name
// Id: The unique identifier of this instruction.
Id string `xml:"id,attr"`
// Opcode: The opcode of this instruction.
Opcode uint8 `xml:"opcode,attr"`
// Format: The encoding format of the instruction.
Format string `xml:"format,attr"`
// DisplayName: The name of the instruction as it will be displayed in the documentation.
DisplayName string `xml:"display-name"`
// Description: The description of this instruction.
Description DocText `xml:"description"`
Syntax Syntax `xml:"syntax"`
Examples ExampleList `xml:"examples"`
Bindings BindingList `xml:"bindings"`
Operation Operation `xml:"operation"`
Exceptions InstructionExceptionList `xml:"exceptions"`
}
// Syntax: The assembler syntax of the instruction.
type Syntax struct {
XMLName xml.Name
Mnemonic Mnemonic `xml:",any"`
}
// MnemonicArgRegister: A register passed as an argument of the instruction.
type MnemonicArgRegister struct {
XMLName xml.Name
Binding BindingName `xml:"binding,attr"`
Ref []RefText `xml:"ref"`
Todo []TodoText `xml:"todo"`
}
// MnemonicArgImmediate: An immediate value passed as an argument of the instruction.
type MnemonicArgImmediate struct {
XMLName xml.Name
Ref BindingName `xml:"ref,attr"`
RefElm []RefText `xml:"ref"`
Todo []TodoText `xml:"todo"`
}
// MnemonicArgGroup: A group of both registers and immediate values passed as arguments of the instruction.
type MnemonicArgGroup struct {
XMLName xml.Name
Register []MnemonicArgRegister `xml:"register"`
Immediate []MnemonicArgImmediate `xml:"immediate"`
}
// ExampleList: Instruction syntax examples.
type ExampleList struct {
XMLName xml.Name
Example []Example `xml:",any"`
}
// Example: An example of the instruction syntax.
type Example struct {
XMLName xml.Name
Code Code `xml:"code"`
Comment CodeComment `xml:"comment"`
}
// CodeComment: Comment about the assembly code of the example.
type CodeComment struct {
XMLName xml.Name
Ref []RefText `xml:"ref"`
Todo []TodoText `xml:"todo"`
}
// BindingList: Value bindings to instruction fields.
type BindingList struct {
XMLName xml.Name
Binding []Binding `xml:",any"`
}
// Binding: A value binding to an instruction field.
type Binding struct {
XMLName xml.Name
Name BindingName `xml:"name,attr"`
// Field: Reference to the instruction field to bind the value to.
Field string `xml:"field,attr"`
// Aligned: Optional immediate value alignement, must be a power of 2. Dictates how the value is encoded: an alignment of 2 means that the value is bitshifted right by one bit, an alignment of 4 means that it is bitshifted right by two bits, etc... Defaults to 1, i.e. no bitshifting is involved.
Aligned uint64 `xml:"aligned,attr"`
// MinusOne: Specifies whether the value is "-1 encoded". As an example, if an 8-bits value is -1 encoded then it means that one can use values in the range 1..256 and it will be encoded in the range 0..255. Defaults to false.
MinusOne bool `xml:"minus-one,attr"`
// Signed: Indicate whether the immediate value is signed. Defaults to false.
Signed bool `xml:"signed,attr"`
}
// InstructionExceptionList: The exceptions that can be generated during execution of this instruction.
type InstructionExceptionList struct {
XMLName xml.Name
Exception []InstructionException `xml:",any"`
}
// InstructionException: An exception that can be generated during the execution of this instruction.
type InstructionException struct {
XMLName xml.Name
// Ref: The unique identifier of the exception.
Ref string `xml:"ref,attr"`
RefElm []RefText `xml:"ref"`
Todo []TodoText `xml:"todo"`
}
// XSD SimpleType declarations
// FormatFieldType: Field type.
type FormatFieldType string
const FormatFieldTypeOpcode FormatFieldType = "opcode"
const FormatFieldTypeRegister FormatFieldType = "register"
const FormatFieldTypeRegisterSrc FormatFieldType = "register.src"
const FormatFieldTypeRegisterDest FormatFieldType = "register.dest"
const FormatFieldTypeRegisterOffset FormatFieldType = "register.offset"
const FormatFieldTypeImmediate FormatFieldType = "immediate"
const FormatFieldTypeImmediateOffset FormatFieldType = "immediate.offset"
const FormatFieldTypeCondition FormatFieldType = "condition"
const FormatFieldTypeFlag FormatFieldType = "flag"
const FormatFieldTypeUnused FormatFieldType = "unused"
// Mnemonic: Instruction mnemonic.
type Mnemonic string
// Code: Single-line assembly code of the example.
type Code string
// BindingName: The name of the value binding.
type BindingName string
// Operation: Instruction microcoded operation.
type Operation string

5
spec/visitor.go Normal file
View File

@ -0,0 +1,5 @@
package spec
type SpecVisitor interface {
VisitSpecification(node *Specification)
}