not bad
This commit is contained in:
@@ -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)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
},
|
},
|
||||||
"StateMachine": {
|
"StateMachine": {
|
||||||
"fields": [
|
"fields": [
|
||||||
{"name": "States", "type": "node", "mandatory": true}
|
{"name": "States", "type": "node", "mandatory": false}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
"GAMScheduler": {
|
"GAMScheduler": {
|
||||||
|
|||||||
@@ -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,14 +330,16 @@ 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 {
|
||||||
if !referenced[signal] {
|
for _, signal := range signalsNode.Children {
|
||||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
if !referenced[signal] {
|
||||||
Level: LevelWarning,
|
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||||
Message: fmt.Sprintf("Unused Signal: %s is defined in DataSource %s but never referenced", signal.RealName, node.RealName),
|
Level: LevelWarning,
|
||||||
Position: v.getNodePosition(signal),
|
Message: fmt.Sprintf("Unused Signal: %s is defined in DataSource %s but never referenced", signal.RealName, node.RealName),
|
||||||
File: v.getNodeFile(signal),
|
Position: v.getNodePosition(signal),
|
||||||
})
|
File: v.getNodeFile(signal),
|
||||||
|
})
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user