diff --git a/internal/parser/lexer.go b/internal/parser/lexer.go index 82a9dba..84bb052 100644 --- a/internal/parser/lexer.go +++ b/internal/parser/lexer.go @@ -109,7 +109,7 @@ func (l *Lexer) NextToken() Token { return l.emit(TokenEOF) } - if unicode.IsSpace(r) { + if unicode.IsSpace(r) || r == ',' { l.ignore() continue } @@ -148,7 +148,7 @@ func (l *Lexer) NextToken() Token { func (l *Lexer) lexIdentifier() Token { for { r := l.next() - if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' { + if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' || r == '.' || r == ':' { continue } l.backup() @@ -186,7 +186,7 @@ func (l *Lexer) lexString() Token { func (l *Lexer) lexNumber() Token { for { 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 } l.backup() @@ -206,6 +206,20 @@ func (l *Lexer) lexComment() Token { } 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() return l.emit(TokenError) } diff --git a/internal/schema/marte.json b/internal/schema/marte.json index ff5a90e..a568fb8 100644 --- a/internal/schema/marte.json +++ b/internal/schema/marte.json @@ -9,7 +9,7 @@ }, "StateMachine": { "fields": [ - {"name": "States", "type": "node", "mandatory": true} + {"name": "States", "type": "node", "mandatory": false} ] }, "GAMScheduler": { diff --git a/internal/validator/validator.go b/internal/validator/validator.go index ec09e6f..2a4178f 100644 --- a/internal/validator/validator.go +++ b/internal/validator/validator.go @@ -47,6 +47,22 @@ func (v *Validator) ValidateProject() { } 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 fields := make(map[string][]*parser.Field) fieldOrder := []string{} // Keep track of order of appearance (approximate across fragments) @@ -94,6 +110,9 @@ func (v *Validator) validateNode(node *index.ProjectNode) { 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 { pos := v.getNodePosition(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 for _, child := range node.Children { v.validateNode(child) @@ -161,19 +185,13 @@ func (v *Validator) validateClass(node *index.ProjectNode, classDef schema.Class // Check Field Order 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 for _, nodeFieldName := range fieldOrder { - // Find this field in schema foundInSchema := false for i, fd := range classDef.Fields { if fd.Name == nodeFieldName { foundInSchema = true - // Check if this field appears AFTER the current expected position 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{ Level: LevelError, 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 { - // 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 { switch expectedType { case "int": @@ -202,8 +268,9 @@ func (v *Validator) checkType(val parser.Value, expectedType string) bool { _, ok := val.(*parser.FloatValue) return ok case "string": - _, ok := val.(*parser.StringValue) - return ok + _, okStr := val.(*parser.StringValue) + _, okRef := val.(*parser.ReferenceValue) + return okStr || okRef case "bool": _, ok := val.(*parser.BoolValue) return ok @@ -214,15 +281,7 @@ func (v *Validator) checkType(val parser.Value, expectedType string) bool { _, ok := val.(*parser.ReferenceValue) return ok case "node": - // This is tricky. A field cannot really be a "node" type in the parser sense (Node = { ... } is an ObjectNode, not a Field). - // 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 + return true case "any": return true } @@ -271,14 +330,16 @@ func (v *Validator) checkUnusedRecursive(node *index.ProjectNode, referenced map // Heuristic for DataSource and its signals if isDataSource(node) { - for _, signal := range node.Children { - if !referenced[signal] { - v.Diagnostics = append(v.Diagnostics, Diagnostic{ - Level: LevelWarning, - Message: fmt.Sprintf("Unused Signal: %s is defined in DataSource %s but never referenced", signal.RealName, node.RealName), - Position: v.getNodePosition(signal), - File: v.getNodeFile(signal), - }) + if signalsNode, ok := node.Children["Signals"]; ok { + for _, signal := range signalsNode.Children { + if !referenced[signal] { + v.Diagnostics = append(v.Diagnostics, Diagnostic{ + Level: LevelWarning, + Message: fmt.Sprintf("Unused Signal: %s is defined in DataSource %s but never referenced", signal.RealName, node.RealName), + Position: v.getNodePosition(signal), + File: v.getNodeFile(signal), + }) + } } } } @@ -304,6 +365,15 @@ func isDataSource(node *index.ProjectNode) bool { 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 { if len(node.Fragments) > 0 { return node.Fragments[0].ObjectPos @@ -316,4 +386,4 @@ func (v *Validator) getNodeFile(node *index.ProjectNode) string { return node.Fragments[0].File } return "" -} +} \ No newline at end of file diff --git a/mdt b/mdt index 461a05e..46a0977 100755 Binary files a/mdt and b/mdt differ