This commit is contained in:
Martino Ferrari
2026-01-22 01:26:17 +01:00
parent 970b5697bd
commit 5b0834137b
4 changed files with 115 additions and 31 deletions

View File

@@ -109,7 +109,7 @@ func (l *Lexer) NextToken() Token {
return l.emit(TokenEOF) return l.emit(TokenEOF)
} }
if unicode.IsSpace(r) { if unicode.IsSpace(r) || r == ',' {
l.ignore() l.ignore()
continue continue
} }
@@ -148,7 +148,7 @@ func (l *Lexer) NextToken() Token {
func (l *Lexer) lexIdentifier() Token { func (l *Lexer) lexIdentifier() Token {
for { for {
r := l.next() r := l.next()
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' { if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' || r == '.' || r == ':' {
continue continue
} }
l.backup() l.backup()
@@ -186,7 +186,7 @@ func (l *Lexer) lexString() Token {
func (l *Lexer) lexNumber() Token { func (l *Lexer) lexNumber() Token {
for { for {
r := l.next() r := l.next()
if unicode.IsDigit(r) || r == '.' || r == 'x' || r == 'b' || r == 'e' || r == '-' { if unicode.IsDigit(r) || unicode.IsLetter(r) || r == '.' || r == '-' || r == '+' {
continue continue
} }
l.backup() l.backup()
@@ -206,6 +206,20 @@ func (l *Lexer) lexComment() Token {
} }
return l.lexUntilNewline(TokenComment) return l.lexUntilNewline(TokenComment)
} }
if r == '*' {
for {
r := l.next()
if r == -1 {
return l.emit(TokenError)
}
if r == '*' {
if l.peek() == '/' {
l.next() // consume /
return l.emit(TokenComment)
}
}
}
}
l.backup() l.backup()
return l.emit(TokenError) return l.emit(TokenError)
} }

View File

@@ -9,7 +9,7 @@
}, },
"StateMachine": { "StateMachine": {
"fields": [ "fields": [
{"name": "States", "type": "node", "mandatory": true} {"name": "States", "type": "node", "mandatory": false}
] ]
}, },
"GAMScheduler": { "GAMScheduler": {

View File

@@ -47,6 +47,22 @@ func (v *Validator) ValidateProject() {
} }
func (v *Validator) validateNode(node *index.ProjectNode) { func (v *Validator) validateNode(node *index.ProjectNode) {
// Check for invalid content in Signals container of DataSource
if node.RealName == "Signals" && node.Parent != nil && isDataSource(node.Parent) {
for _, frag := range node.Fragments {
for _, def := range frag.Definitions {
if f, ok := def.(*parser.Field); ok {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Invalid content in Signals container: Field '%s' is not allowed. Only Signal objects are allowed.", f.Name),
Position: f.Position,
File: frag.File,
})
}
}
}
}
// Collect fields and their definitions // Collect fields and their definitions
fields := make(map[string][]*parser.Field) fields := make(map[string][]*parser.Field)
fieldOrder := []string{} // Keep track of order of appearance (approximate across fragments) fieldOrder := []string{} // Keep track of order of appearance (approximate across fragments)
@@ -94,6 +110,9 @@ 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)
@@ -113,6 +132,11 @@ func (v *Validator) validateNode(node *index.ProjectNode) {
} }
} }
// 4. Signal Validation (for DataSource signals)
if isSignal(node) {
v.validateSignal(node, fields)
}
// Recursively validate children // Recursively validate children
for _, child := range node.Children { for _, child := range node.Children {
v.validateNode(child) v.validateNode(child)
@@ -161,19 +185,13 @@ func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.Class
// Check Field Order // Check Field Order
if classDef.Ordered { if classDef.Ordered {
// Verify that fields present in the node appear in the order defined in the schema
// Only consider fields that are actually in the schema's field list
schemaIdx := 0 schemaIdx := 0
for _, nodeFieldName := range fieldOrder { for _, nodeFieldName := range fieldOrder {
// Find this field in schema
foundInSchema := false foundInSchema := false
for i, fd := range classDef.Fields { for i, fd := range classDef.Fields {
if fd.Name == nodeFieldName { if fd.Name == nodeFieldName {
foundInSchema = true foundInSchema = true
// Check if this field appears AFTER the current expected position
if i < schemaIdx { if i < schemaIdx {
// This field appears out of order (it should have appeared earlier, or previous fields were missing but this one came too late? No, simple relative order)
// Actually, simple check: `i` must be >= `lastSeenSchemaIdx`.
v.Diagnostics = append(v.Diagnostics, Diagnostic{ v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError, Level: LevelError,
Message: fmt.Sprintf("Field '%s' is out of order", nodeFieldName), Message: fmt.Sprintf("Field '%s' is out of order", nodeFieldName),
@@ -187,12 +205,60 @@ func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.Class
} }
} }
if !foundInSchema { if !foundInSchema {
// Ignore extra fields for order check? Spec doesn't say strict closed schema. // Ignore extra fields
} }
} }
} }
} }
func (v *Validator) validateSignal(node *index.ProjectNode, fields map[string][]*parser.Field) {
// Check mandatory Type
if typeFields, ok := fields["Type"]; !ok || len(typeFields) == 0 {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Signal '%s' is missing mandatory field 'Type'", node.RealName),
Position: v.getNodePosition(node),
File: v.getNodeFile(node),
})
} else {
// Check valid Type value
typeVal := typeFields[0].Value
var typeStr string
switch t := typeVal.(type) {
case *parser.StringValue:
typeStr = t.Value
case *parser.ReferenceValue:
typeStr = t.Value
default:
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Field 'Type' in Signal '%s' must be a type name", node.RealName),
Position: typeFields[0].Position,
File: v.getFileForField(typeFields[0], node),
})
return
}
if !isValidType(typeStr) {
v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError,
Message: fmt.Sprintf("Invalid Type '%s' for Signal '%s'", typeStr, node.RealName),
Position: typeFields[0].Position,
File: v.getFileForField(typeFields[0], node),
})
}
}
}
func isValidType(t string) bool {
switch t {
case "uint8", "int8", "uint16", "int16", "uint32", "int32", "uint64", "int64",
"float32", "float64", "string", "bool", "char8":
return true
}
return false
}
func (v *Validator) checkType(val parser.Value, expectedType string) bool { func (v *Validator) checkType(val parser.Value, expectedType string) bool {
switch expectedType { switch expectedType {
case "int": case "int":
@@ -202,8 +268,9 @@ func (v *Validator) checkType(val parser.Value, expectedType string) bool {
_, ok := val.(*parser.FloatValue) _, ok := val.(*parser.FloatValue)
return ok return ok
case "string": case "string":
_, ok := val.(*parser.StringValue) _, okStr := val.(*parser.StringValue)
return ok _, okRef := val.(*parser.ReferenceValue)
return okStr || okRef
case "bool": case "bool":
_, ok := val.(*parser.BoolValue) _, ok := val.(*parser.BoolValue)
return ok return ok
@@ -214,15 +281,7 @@ func (v *Validator) checkType(val parser.Value, expectedType string) bool {
_, ok := val.(*parser.ReferenceValue) _, ok := val.(*parser.ReferenceValue)
return ok return ok
case "node": case "node":
// This is tricky. A field cannot really be a "node" type in the parser sense (Node = { ... } is an ObjectNode, not a Field). return true
// But if the schema says "FieldX" is type "node", maybe it means it expects a reference to a node?
// Or maybe it means it expects a Subnode?
// In MARTe, `Field = { ... }` is parsed as ArrayValue usually.
// If `Field = SubNode`, it's `ObjectNode`.
// Schema likely refers to `+SubNode = { ... }`.
// But `validateClass` iterates `fields`.
// If schema defines a "field" of type "node", it might mean it expects a child node with that name.
return true // skip for now
case "any": case "any":
return true return true
} }
@@ -271,7 +330,8 @@ func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map
// Heuristic for DataSource and its signals // Heuristic for DataSource and its signals
if isDataSource(node) { if isDataSource(node) {
for _, signal := range node.Children { if signalsNode, ok := node.Children["Signals"]; ok {
for _, signal := range signalsNode.Children {
if !referenced[signal] { if !referenced[signal] {
v.Diagnostics = append(v.Diagnostics, Diagnostic{ v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelWarning, Level: LevelWarning,
@@ -282,6 +342,7 @@ func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map
} }
} }
} }
}
for _, child := range node.Children { for _, child := range node.Children {
v.checkUnusedRecursive(child, referenced) v.checkUnusedRecursive(child, referenced)
@@ -304,6 +365,15 @@ func isDataSource(node *index.ProjectNode) bool {
return false return false
} }
func isSignal(node *index.ProjectNode) bool {
if node.Parent != nil && node.Parent.Name == "Signals" {
if isDataSource(node.Parent.Parent) {
return true
}
}
return false
}
func (v *Validator) getNodePosition(node *index.ProjectNode) parser.Position { func (v *Validator) getNodePosition(node *index.ProjectNode) parser.Position {
if len(node.Fragments) > 0 { if len(node.Fragments) > 0 {
return node.Fragments[0].ObjectPos return node.Fragments[0].ObjectPos

BIN
mdt

Binary file not shown.