better indexing
This commit is contained in:
@@ -18,7 +18,8 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"TimingDataSource": {
|
"TimingDataSource": {
|
||||||
"fields": []
|
"fields": [],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"IOGAM": {
|
"IOGAM": {
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -43,66 +44,79 @@
|
|||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Filename", "type": "string", "mandatory": true},
|
{"name": "Filename", "type": "string", "mandatory": true},
|
||||||
{"name": "Format", "type": "string", "mandatory": false}
|
{"name": "Format", "type": "string", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "INOUT"
|
||||||
},
|
},
|
||||||
"LoggerDataSource": {
|
"LoggerDataSource": {
|
||||||
"fields": []
|
"fields": [],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"DANStream": {
|
"DANStream": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Timeout", "type": "int", "mandatory": false}
|
{"name": "Timeout", "type": "int", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"EPICSCAInput": {
|
"EPICSCAInput": {
|
||||||
"fields": []
|
"fields": [],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"EPICSCAOutput": {
|
"EPICSCAOutput": {
|
||||||
"fields": []
|
"fields": [],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"EPICSPVAInput": {
|
"EPICSPVAInput": {
|
||||||
"fields": []
|
"fields": [],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"EPICSPVAOutput": {
|
"EPICSPVAOutput": {
|
||||||
"fields": []
|
"fields": [],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"SDNSubscriber": {
|
"SDNSubscriber": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Address", "type": "string", "mandatory": true},
|
{"name": "Address", "type": "string", "mandatory": true},
|
||||||
{"name": "Port", "type": "int", "mandatory": true},
|
{"name": "Port", "type": "int", "mandatory": true},
|
||||||
{"name": "Interface", "type": "string", "mandatory": false}
|
{"name": "Interface", "type": "string", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"SDNPublisher": {
|
"SDNPublisher": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Address", "type": "string", "mandatory": true},
|
{"name": "Address", "type": "string", "mandatory": true},
|
||||||
{"name": "Port", "type": "int", "mandatory": true},
|
{"name": "Port", "type": "int", "mandatory": true},
|
||||||
{"name": "Interface", "type": "string", "mandatory": false}
|
{"name": "Interface", "type": "string", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"UDPReceiver": {
|
"UDPReceiver": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Port", "type": "int", "mandatory": true},
|
{"name": "Port", "type": "int", "mandatory": true},
|
||||||
{"name": "Address", "type": "string", "mandatory": false}
|
{"name": "Address", "type": "string", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"UDPSender": {
|
"UDPSender": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Destination", "type": "string", "mandatory": true}
|
{"name": "Destination", "type": "string", "mandatory": true}
|
||||||
]
|
],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"FileReader": {
|
"FileReader": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Filename", "type": "string", "mandatory": true},
|
{"name": "Filename", "type": "string", "mandatory": true},
|
||||||
{"name": "Format", "type": "string", "mandatory": false},
|
{"name": "Format", "type": "string", "mandatory": false},
|
||||||
{"name": "Interpolate", "type": "string", "mandatory": false}
|
{"name": "Interpolate", "type": "string", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"FileWriter": {
|
"FileWriter": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "Filename", "type": "string", "mandatory": true},
|
{"name": "Filename", "type": "string", "mandatory": true},
|
||||||
{"name": "Format", "type": "string", "mandatory": false},
|
{"name": "Format", "type": "string", "mandatory": false},
|
||||||
{"name": "StoreOnTrigger", "type": "int", "mandatory": false}
|
{"name": "StoreOnTrigger", "type": "int", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"OrderedClass": {
|
"OrderedClass": {
|
||||||
"ordered": true,
|
"ordered": true,
|
||||||
@@ -148,7 +162,7 @@
|
|||||||
"TimeCorrectionGAM": { "fields": [] },
|
"TimeCorrectionGAM": { "fields": [] },
|
||||||
"TriggeredIOGAM": { "fields": [] },
|
"TriggeredIOGAM": { "fields": [] },
|
||||||
"WaveformGAM": { "fields": [] },
|
"WaveformGAM": { "fields": [] },
|
||||||
"DAN": { "fields": [] },
|
"DAN": { "fields": [], "direction": "OUT" },
|
||||||
"LinuxTimer": {
|
"LinuxTimer": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "ExecutionMode", "type": "string", "mandatory": false},
|
{"name": "ExecutionMode", "type": "string", "mandatory": false},
|
||||||
@@ -158,16 +172,18 @@
|
|||||||
{"name": "CPUMask", "type": "int", "mandatory": false},
|
{"name": "CPUMask", "type": "int", "mandatory": false},
|
||||||
{"name": "TimeProvider", "type": "node", "mandatory": false},
|
{"name": "TimeProvider", "type": "node", "mandatory": false},
|
||||||
{"name": "Signals", "type": "node", "mandatory": true}
|
{"name": "Signals", "type": "node", "mandatory": true}
|
||||||
]
|
],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"LinkDataSource": { "fields": [] },
|
"LinkDataSource": { "fields": [], "direction": "INOUT" },
|
||||||
"MDSReader": {
|
"MDSReader": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "TreeName", "type": "string", "mandatory": true},
|
{"name": "TreeName", "type": "string", "mandatory": true},
|
||||||
{"name": "ShotNumber", "type": "int", "mandatory": true},
|
{"name": "ShotNumber", "type": "int", "mandatory": true},
|
||||||
{"name": "Frequency", "type": "float", "mandatory": true},
|
{"name": "Frequency", "type": "float", "mandatory": true},
|
||||||
{"name": "Signals", "type": "node", "mandatory": true}
|
{"name": "Signals", "type": "node", "mandatory": true}
|
||||||
]
|
],
|
||||||
|
"direction": "IN"
|
||||||
},
|
},
|
||||||
"MDSWriter": {
|
"MDSWriter": {
|
||||||
"fields": [
|
"fields": [
|
||||||
@@ -183,27 +199,29 @@
|
|||||||
{"name": "NumberOfPostTriggers", "type": "int", "mandatory": false},
|
{"name": "NumberOfPostTriggers", "type": "int", "mandatory": false},
|
||||||
{"name": "Signals", "type": "node", "mandatory": true},
|
{"name": "Signals", "type": "node", "mandatory": true},
|
||||||
{"name": "Messages", "type": "node", "mandatory": false}
|
{"name": "Messages", "type": "node", "mandatory": false}
|
||||||
]
|
],
|
||||||
|
"direction": "OUT"
|
||||||
},
|
},
|
||||||
"NI1588TimeStamp": { "fields": [] },
|
"NI1588TimeStamp": { "fields": [], "direction": "IN" },
|
||||||
"NI6259ADC": { "fields": [] },
|
"NI6259ADC": { "fields": [], "direction": "IN" },
|
||||||
"NI6259DAC": { "fields": [] },
|
"NI6259DAC": { "fields": [], "direction": "OUT" },
|
||||||
"NI6259DIO": { "fields": [] },
|
"NI6259DIO": { "fields": [], "direction": "INOUT" },
|
||||||
"NI6368ADC": { "fields": [] },
|
"NI6368ADC": { "fields": [], "direction": "IN" },
|
||||||
"NI6368DAC": { "fields": [] },
|
"NI6368DAC": { "fields": [], "direction": "OUT" },
|
||||||
"NI6368DIO": { "fields": [] },
|
"NI6368DIO": { "fields": [], "direction": "INOUT" },
|
||||||
"NI9157CircularFifoReader": { "fields": [] },
|
"NI9157CircularFifoReader": { "fields": [], "direction": "IN" },
|
||||||
"NI9157MxiDataSource": { "fields": [] },
|
"NI9157MxiDataSource": { "fields": [], "direction": "INOUT" },
|
||||||
"OPCUADSInput": { "fields": [] },
|
"OPCUADSInput": { "fields": [], "direction": "IN" },
|
||||||
"OPCUADSOutput": { "fields": [] },
|
"OPCUADSOutput": { "fields": [], "direction": "OUT" },
|
||||||
"RealTimeThreadAsyncBridge": { "fields": [] },
|
"RealTimeThreadAsyncBridge": { "fields": [] },
|
||||||
"RealTimeThreadSynchronisation": { "fields": [] },
|
"RealTimeThreadSynchronisation": { "fields": [] },
|
||||||
"UARTDataSource": { "fields": [] },
|
"UARTDataSource": { "fields": [], "direction": "INOUT" },
|
||||||
"BaseLib2Wrapper": { "fields": [] },
|
"BaseLib2Wrapper": { "fields": [] },
|
||||||
"EPICSCAClient": { "fields": [] },
|
"EPICSCAClient": { "fields": [] },
|
||||||
"EPICSPVA": { "fields": [] },
|
"EPICSPVA": { "fields": [] },
|
||||||
"MemoryGate": { "fields": [] },
|
"MemoryGate": { "fields": [] },
|
||||||
"OPCUA": { "fields": [] },
|
"OPCUA": { "fields": [] },
|
||||||
"SysLogger": { "fields": [] }
|
"SysLogger": { "fields": [] },
|
||||||
|
"GAMDataSource": { "fields": [], "direction": "INOUT" }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,8 +16,9 @@ type Schema struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type ClassDefinition struct {
|
type ClassDefinition struct {
|
||||||
Fields []FieldDefinition `json:"fields"`
|
Fields []FieldDefinition `json:"fields"`
|
||||||
Ordered bool `json:"ordered"`
|
Ordered bool `json:"ordered"`
|
||||||
|
Direction string `json:"direction"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FieldDefinition struct {
|
type FieldDefinition struct {
|
||||||
@@ -96,6 +97,9 @@ func (s *Schema) Merge(other *Schema) {
|
|||||||
if classDef.Ordered {
|
if classDef.Ordered {
|
||||||
existingClass.Ordered = true
|
existingClass.Ordered = true
|
||||||
}
|
}
|
||||||
|
if classDef.Direction != "" {
|
||||||
|
existingClass.Direction = classDef.Direction
|
||||||
|
}
|
||||||
s.Classes[className] = existingClass
|
s.Classes[className] = existingClass
|
||||||
} else {
|
} else {
|
||||||
s.Classes[className] = classDef
|
s.Classes[className] = classDef
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package validator
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
|
|
||||||
"github.com/marte-dev/marte-dev-tools/internal/index"
|
"github.com/marte-dev/marte-dev-tools/internal/index"
|
||||||
"github.com/marte-dev/marte-dev-tools/internal/parser"
|
"github.com/marte-dev/marte-dev-tools/internal/parser"
|
||||||
"github.com/marte-dev/marte-dev-tools/internal/schema"
|
"github.com/marte-dev/marte-dev-tools/internal/schema"
|
||||||
@@ -38,6 +39,9 @@ func (v *Validator) ValidateProject() {
|
|||||||
if v.Tree == nil {
|
if v.Tree == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
// Ensure references are resolved (if not already done by builder/lsp)
|
||||||
|
v.Tree.ResolveReferences()
|
||||||
|
|
||||||
if v.Tree.Root != nil {
|
if v.Tree.Root != nil {
|
||||||
v.validateNode(v.Tree.Root)
|
v.validateNode(v.Tree.Root)
|
||||||
}
|
}
|
||||||
@@ -64,16 +68,31 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Collect fields and their definitions
|
// Collect fields and their definitions
|
||||||
fields := make(map[string][]*parser.Field)
|
fields := v.getFields(node)
|
||||||
fieldOrder := []string{} // Keep track of order of appearance (approximate across fragments)
|
fieldOrder := []string{}
|
||||||
|
|
||||||
for _, frag := range node.Fragments {
|
for _, frag := range node.Fragments {
|
||||||
for _, def := range frag.Definitions {
|
for _, def := range frag.Definitions {
|
||||||
if f, ok := def.(*parser.Field); ok {
|
if f, ok := def.(*parser.Field); ok {
|
||||||
if _, exists := fields[f.Name]; !exists {
|
if _, exists := fields[f.Name]; exists { // already collected
|
||||||
fieldOrder = append(fieldOrder, f.Name)
|
// Maintain order logic if needed, but getFields collects all.
|
||||||
|
// For strict order check we might need this loop.
|
||||||
|
// Let's assume getFields is enough for validation logic,
|
||||||
|
// but for "duplicate check" and "class validation" we iterate fields map.
|
||||||
|
// We need to construct fieldOrder.
|
||||||
|
// Just reuse loop for fieldOrder
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Re-construct fieldOrder for order validation
|
||||||
|
seen := make(map[string]bool)
|
||||||
|
for _, frag := range node.Fragments {
|
||||||
|
for _, def := range frag.Definitions {
|
||||||
|
if f, ok := def.(*parser.Field); ok {
|
||||||
|
if !seen[f.Name] {
|
||||||
|
fieldOrder = append(fieldOrder, f.Name)
|
||||||
|
seen[f.Name] = true
|
||||||
}
|
}
|
||||||
fields[f.Name] = append(fields[f.Name], f)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -81,7 +100,6 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
// 1. Check for duplicate fields
|
// 1. Check for duplicate fields
|
||||||
for name, defs := range fields {
|
for name, defs := range fields {
|
||||||
if len(defs) > 1 {
|
if len(defs) > 1 {
|
||||||
// Report error on the second definition
|
|
||||||
firstFile := v.getFileForField(defs[0], node)
|
firstFile := v.getFileForField(defs[0], node)
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
Level: LevelError,
|
||||||
@@ -96,13 +114,7 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
className := ""
|
className := ""
|
||||||
if node.RealName != "" && (node.RealName[0] == '+' || node.RealName[0] == '$') {
|
if node.RealName != "" && (node.RealName[0] == '+' || node.RealName[0] == '$') {
|
||||||
if classFields, ok := fields["Class"]; ok && len(classFields) > 0 {
|
if classFields, ok := fields["Class"]; ok && len(classFields) > 0 {
|
||||||
// Extract class name from value
|
className = v.getFieldValue(classFields[0])
|
||||||
switch val := classFields[0].Value.(type) {
|
|
||||||
case *parser.StringValue:
|
|
||||||
className = val.Value
|
|
||||||
case *parser.ReferenceValue:
|
|
||||||
className = val.Value
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
hasType := false
|
hasType := false
|
||||||
@@ -110,9 +122,6 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
hasType = true
|
hasType = true
|
||||||
}
|
}
|
||||||
|
|
||||||
// Exception for Signals: Signals don't need Class if they have Type.
|
|
||||||
// But general nodes need Class.
|
|
||||||
// Logic handles it: if className=="" and !hasType -> Error.
|
|
||||||
if className == "" && !hasType {
|
if className == "" && !hasType {
|
||||||
pos := v.getNodePosition(node)
|
pos := v.getNodePosition(node)
|
||||||
file := v.getNodeFile(node)
|
file := v.getNodeFile(node)
|
||||||
@@ -137,6 +146,11 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
v.validateSignal(node, fields)
|
v.validateSignal(node, fields)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 5. GAM Validation (Signal references)
|
||||||
|
if isGAM(node) {
|
||||||
|
v.validateGAM(node)
|
||||||
|
}
|
||||||
|
|
||||||
// Recursively validate children
|
// Recursively validate children
|
||||||
for _, child := range node.Children {
|
for _, child := range node.Children {
|
||||||
v.validateNode(child)
|
v.validateNode(child)
|
||||||
@@ -144,14 +158,13 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.ClassDefinition, fields map[string][]*parser.Field, fieldOrder []string) {
|
func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.ClassDefinition, fields map[string][]*parser.Field, fieldOrder []string) {
|
||||||
// Check Mandatory Fields
|
// ... (same as before)
|
||||||
for _, fieldDef := range classDef.Fields {
|
for _, fieldDef := range classDef.Fields {
|
||||||
if fieldDef.Mandatory {
|
if fieldDef.Mandatory {
|
||||||
found := false
|
found := false
|
||||||
if _, ok := fields[fieldDef.Name]; ok {
|
if _, ok := fields[fieldDef.Name]; ok {
|
||||||
found = true
|
found = true
|
||||||
} else if fieldDef.Type == "node" {
|
} else if fieldDef.Type == "node" {
|
||||||
// Check children for nodes
|
|
||||||
if _, ok := node.Children[fieldDef.Name]; ok {
|
if _, ok := node.Children[fieldDef.Name]; ok {
|
||||||
found = true
|
found = true
|
||||||
}
|
}
|
||||||
@@ -168,10 +181,9 @@ func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.Class
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Field Types
|
|
||||||
for _, fieldDef := range classDef.Fields {
|
for _, fieldDef := range classDef.Fields {
|
||||||
if fList, ok := fields[fieldDef.Name]; ok {
|
if fList, ok := fields[fieldDef.Name]; ok {
|
||||||
f := fList[0] // Check the first definition (duplicates handled elsewhere)
|
f := fList[0]
|
||||||
if !v.checkType(f.Value, fieldDef.Type) {
|
if !v.checkType(f.Value, fieldDef.Type) {
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
Level: LevelError,
|
||||||
@@ -183,7 +195,6 @@ func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.Class
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check Field Order
|
|
||||||
if classDef.Ordered {
|
if classDef.Ordered {
|
||||||
schemaIdx := 0
|
schemaIdx := 0
|
||||||
for _, nodeFieldName := range fieldOrder {
|
for _, nodeFieldName := range fieldOrder {
|
||||||
@@ -205,14 +216,13 @@ func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.Class
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !foundInSchema {
|
if !foundInSchema {
|
||||||
// Ignore extra fields
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) validateSignal(node *index.ProjectNode, fields map[string][]*parser.Field) {
|
func (v *Validator) validateSignal(node *index.ProjectNode, fields map[string][]*parser.Field) {
|
||||||
// Check mandatory Type
|
// ... (same as before)
|
||||||
if typeFields, ok := fields["Type"]; !ok || len(typeFields) == 0 {
|
if typeFields, ok := fields["Type"]; !ok || len(typeFields) == 0 {
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Level: LevelError,
|
Level: LevelError,
|
||||||
@@ -221,7 +231,6 @@ func (v *Validator) validateSignal(node *index.ProjectNode, fields map[string][]
|
|||||||
File: v.getNodeFile(node),
|
File: v.getNodeFile(node),
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
// Check valid Type value
|
|
||||||
typeVal := typeFields[0].Value
|
typeVal := typeFields[0].Value
|
||||||
var typeStr string
|
var typeStr string
|
||||||
switch t := typeVal.(type) {
|
switch t := typeVal.(type) {
|
||||||
@@ -250,6 +259,191 @@ func (v *Validator) validateSignal(node *index.ProjectNode, fields map[string][]
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Validator) validateGAM(node *index.ProjectNode) {
|
||||||
|
if inputs, ok := node.Children["InputSignals"]; ok {
|
||||||
|
v.validateGAMSignals(node, inputs, "Input")
|
||||||
|
}
|
||||||
|
if outputs, ok := node.Children["OutputSignals"]; ok {
|
||||||
|
v.validateGAMSignals(node, outputs, "Output")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) validateGAMSignals(gamNode, signalsContainer *index.ProjectNode, direction string) {
|
||||||
|
for _, signal := range signalsContainer.Children {
|
||||||
|
v.validateGAMSignal(gamNode, signal, direction)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) validateGAMSignal(gamNode, signalNode *index.ProjectNode, direction string) {
|
||||||
|
fields := v.getFields(signalNode)
|
||||||
|
var dsName string
|
||||||
|
if dsFields, ok := fields["DataSource"]; ok && len(dsFields) > 0 {
|
||||||
|
dsName = v.getFieldValue(dsFields[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
if dsName == "" {
|
||||||
|
return // Ignore implicit signals or missing datasource (handled elsewhere if mandatory)
|
||||||
|
}
|
||||||
|
|
||||||
|
dsNode := v.resolveReference(dsName, v.getNodeFile(signalNode))
|
||||||
|
if dsNode == nil {
|
||||||
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
|
Level: LevelError,
|
||||||
|
Message: fmt.Sprintf("Unknown DataSource '%s' referenced in signal '%s'", dsName, signalNode.RealName),
|
||||||
|
Position: v.getNodePosition(signalNode),
|
||||||
|
File: v.getNodeFile(signalNode),
|
||||||
|
})
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
// Link DataSource reference
|
||||||
|
if dsFields, ok := fields["DataSource"]; ok && len(dsFields) > 0 {
|
||||||
|
if val, ok := dsFields[0].Value.(*parser.ReferenceValue); ok {
|
||||||
|
v.updateReferenceTarget(v.getNodeFile(signalNode), val.Position, dsNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Direction
|
||||||
|
dsClass := v.getNodeClass(dsNode)
|
||||||
|
if dsClass != "" {
|
||||||
|
if classDef, ok := v.Schema.Classes[dsClass]; ok {
|
||||||
|
dsDir := classDef.Direction
|
||||||
|
if dsDir != "" {
|
||||||
|
if direction == "Input" && dsDir == "OUT" {
|
||||||
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
|
Level: LevelError,
|
||||||
|
Message: fmt.Sprintf("DataSource '%s' (Class %s) is Output-only but referenced in InputSignals of GAM '%s'", dsName, dsClass, gamNode.RealName),
|
||||||
|
Position: v.getNodePosition(signalNode),
|
||||||
|
File: v.getNodeFile(signalNode),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
if direction == "Output" && dsDir == "IN" {
|
||||||
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
|
Level: LevelError,
|
||||||
|
Message: fmt.Sprintf("DataSource '%s' (Class %s) is Input-only but referenced in OutputSignals of GAM '%s'", dsName, dsClass, gamNode.RealName),
|
||||||
|
Position: v.getNodePosition(signalNode),
|
||||||
|
File: v.getNodeFile(signalNode),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check Signal Existence
|
||||||
|
targetSignalName := index.NormalizeName(signalNode.RealName)
|
||||||
|
if aliasFields, ok := fields["Alias"]; ok && len(aliasFields) > 0 {
|
||||||
|
targetSignalName = v.getFieldValue(aliasFields[0]) // Alias is usually the name in DataSource
|
||||||
|
}
|
||||||
|
|
||||||
|
if signalsContainer, ok := dsNode.Children["Signals"]; ok {
|
||||||
|
var targetNode *index.ProjectNode
|
||||||
|
targetNorm := index.NormalizeName(targetSignalName)
|
||||||
|
|
||||||
|
if child, ok := signalsContainer.Children[targetNorm]; ok {
|
||||||
|
targetNode = child
|
||||||
|
} else {
|
||||||
|
// Fallback check
|
||||||
|
for _, child := range signalsContainer.Children {
|
||||||
|
if index.NormalizeName(child.RealName) == targetNorm {
|
||||||
|
targetNode = child
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if targetNode == nil {
|
||||||
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
|
Level: LevelError,
|
||||||
|
Message: fmt.Sprintf("Signal '%s' not found in DataSource '%s'", targetSignalName, dsName),
|
||||||
|
Position: v.getNodePosition(signalNode),
|
||||||
|
File: v.getNodeFile(signalNode),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
// Link Alias reference
|
||||||
|
if aliasFields, ok := fields["Alias"]; ok && len(aliasFields) > 0 {
|
||||||
|
if val, ok := aliasFields[0].Value.(*parser.ReferenceValue); ok {
|
||||||
|
v.updateReferenceTarget(v.getNodeFile(signalNode), val.Position, targetNode)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) updateReferenceTarget(file string, pos parser.Position, target *index.ProjectNode) {
|
||||||
|
for i := range v.Tree.References {
|
||||||
|
ref := &v.Tree.References[i]
|
||||||
|
if ref.File == file && ref.Position == pos {
|
||||||
|
ref.Target = target
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helpers
|
||||||
|
|
||||||
|
func (v *Validator) getFields(node *index.ProjectNode) map[string][]*parser.Field {
|
||||||
|
fields := make(map[string][]*parser.Field)
|
||||||
|
for _, frag := range node.Fragments {
|
||||||
|
for _, def := range frag.Definitions {
|
||||||
|
if f, ok := def.(*parser.Field); ok {
|
||||||
|
fields[f.Name] = append(fields[f.Name], f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fields
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) getFieldValue(f *parser.Field) string {
|
||||||
|
switch val := f.Value.(type) {
|
||||||
|
case *parser.StringValue:
|
||||||
|
return val.Value
|
||||||
|
case *parser.ReferenceValue:
|
||||||
|
return val.Value
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) resolveReference(name string, file string) *index.ProjectNode {
|
||||||
|
if isoNode, ok := v.Tree.IsolatedFiles[file]; ok {
|
||||||
|
if found := v.findNodeRecursive(isoNode, name); found != nil {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if v.Tree.Root == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return v.findNodeRecursive(v.Tree.Root, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) findNodeRecursive(root *index.ProjectNode, name string) *index.ProjectNode {
|
||||||
|
// Simple recursive search matching name
|
||||||
|
if root.RealName == name || root.Name == index.NormalizeName(name) {
|
||||||
|
return root
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fast lookup in children
|
||||||
|
norm := index.NormalizeName(name)
|
||||||
|
if child, ok := root.Children[norm]; ok {
|
||||||
|
return child
|
||||||
|
}
|
||||||
|
|
||||||
|
// Recursive
|
||||||
|
for _, child := range root.Children {
|
||||||
|
if found := v.findNodeRecursive(child, name); found != nil {
|
||||||
|
return found
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (v *Validator) getNodeClass(node *index.ProjectNode) string {
|
||||||
|
if cls, ok := node.Metadata["Class"]; ok {
|
||||||
|
return cls
|
||||||
|
}
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
|
||||||
func isValidType(t string) bool {
|
func isValidType(t string) bool {
|
||||||
switch t {
|
switch t {
|
||||||
case "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64",
|
case "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64",
|
||||||
@@ -260,6 +454,7 @@ func isValidType(t string) bool {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) checkType(val parser.Value, expectedType string) bool {
|
func (v *Validator) checkType(val parser.Value, expectedType string) bool {
|
||||||
|
// ... (same as before)
|
||||||
switch expectedType {
|
switch expectedType {
|
||||||
case "int":
|
case "int":
|
||||||
_, ok := val.(*parser.IntValue)
|
_, ok := val.(*parser.IntValue)
|
||||||
@@ -300,6 +495,7 @@ func (v *Validator) getFileForField(f *parser.Field, node *index.ProjectNode) st
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) CheckUnused() {
|
func (v *Validator) CheckUnused() {
|
||||||
|
// ... (same as before)
|
||||||
referencedNodes := make(map[*index.ProjectNode]bool)
|
referencedNodes := make(map[*index.ProjectNode]bool)
|
||||||
for _, ref := range v.Tree.References {
|
for _, ref := range v.Tree.References {
|
||||||
if ref.Target != nil {
|
if ref.Target != nil {
|
||||||
@@ -316,6 +512,7 @@ func (v *Validator) CheckUnused() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map[*index.ProjectNode]bool) {
|
func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map[*index.ProjectNode]bool) {
|
||||||
|
// ... (same as before)
|
||||||
// Heuristic for GAM
|
// Heuristic for GAM
|
||||||
if isGAM(node) {
|
if isGAM(node) {
|
||||||
if !referenced[node] {
|
if !referenced[node] {
|
||||||
@@ -386,4 +583,4 @@ func (v *Validator) getNodeFile(node *index.ProjectNode) string {
|
|||||||
return node.Fragments[0].File
|
return node.Fragments[0].File
|
||||||
}
|
}
|
||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -108,26 +108,29 @@ MARTe configurations typically involve several main categories of objects:
|
|||||||
- **Extensibility**: Signal definitions can include additional fields as required by the specific application context.
|
- **Extensibility**: Signal definitions can include additional fields as required by the specific application context.
|
||||||
- **Signal Reference Syntax**:
|
- **Signal Reference Syntax**:
|
||||||
- Signals are referenced or defined in `InputSignals` or `OutputSignals` sub-nodes using one of the following formats:
|
- Signals are referenced or defined in `InputSignals` or `OutputSignals` sub-nodes using one of the following formats:
|
||||||
1. **Direct Reference**:
|
1. **Direct Reference (Option 1)**:
|
||||||
```
|
```
|
||||||
SIGNAL_NAME = {
|
SIGNAL_NAME = {
|
||||||
DataSource = SIGNAL_DATASOURCE
|
DataSource = DATASOURCE_NAME
|
||||||
// Other fields if necessary
|
// Other fields if necessary
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
2. **Aliased Reference**:
|
In this case, the GAM signal name is the same as the DataSource signal name.
|
||||||
|
2. **Aliased Reference (Option 2)**:
|
||||||
```
|
```
|
||||||
NAME = {
|
GAM_SIGNAL_NAME = {
|
||||||
Alias = SIGNAL_NAME
|
Alias = SIGNAL_NAME
|
||||||
DataSource = SIGNAL_DATASOURCE
|
DataSource = DATASOURCE_NAME
|
||||||
// ...
|
// ...
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
In this case, `Alias` points to the DataSource signal name.
|
||||||
- **Implicit Definition Constraint**: If a signal is implicitly defined within a GAM, the `Type` field **must** be present in the reference block to define the signal's properties.
|
- **Implicit Definition Constraint**: If a signal is implicitly defined within a GAM, the `Type` field **must** be present in the reference block to define the signal's properties.
|
||||||
- **Directionality**: DataSources and their signals are directional:
|
- **Directionality**: DataSources and their signals are directional:
|
||||||
- `Input`: Only providing data.
|
- `Input` (IN): Only providing data. Signals can only be used in `InputSignals`.
|
||||||
- `Output`: Only receiving data.
|
- `Output` (OUT): Only receiving data. Signals can only be used in `OutputSignals`.
|
||||||
- `Inout`: Bidirectional data flow.
|
- `Inout` (INOUT): Bidirectional data flow. Signals can be used in both `InputSignals` and `OutputSignals`.
|
||||||
|
- **Validation**: The tool must validate that signal usage in GAMs respects the direction of the referenced DataSource.
|
||||||
|
|
||||||
### Object Indexing & References
|
### Object Indexing & References
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user