Initial support to variables and to producer/consumer logic
This commit is contained in:
@@ -8,13 +8,18 @@ import (
|
||||
"github.com/marte-community/marte-dev-tools/internal/parser"
|
||||
)
|
||||
|
||||
type VariableInfo struct {
|
||||
Def *parser.VariableDefinition
|
||||
File string
|
||||
}
|
||||
|
||||
type ProjectTree struct {
|
||||
Root *ProjectNode
|
||||
References []Reference
|
||||
IsolatedFiles map[string]*ProjectNode
|
||||
GlobalPragmas map[string][]string
|
||||
NodeMap map[string][]*ProjectNode
|
||||
Variables map[string]*parser.VariableDefinition
|
||||
Variables map[string]VariableInfo
|
||||
}
|
||||
|
||||
func (pt *ProjectTree) ScanDirectory(rootPath string) error {
|
||||
@@ -74,7 +79,7 @@ func NewProjectTree() *ProjectTree {
|
||||
},
|
||||
IsolatedFiles: make(map[string]*ProjectNode),
|
||||
GlobalPragmas: make(map[string][]string),
|
||||
Variables: make(map[string]*parser.VariableDefinition),
|
||||
Variables: make(map[string]VariableInfo),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -224,7 +229,7 @@ func (pt *ProjectTree) populateNode(node *ProjectNode, file string, config *pars
|
||||
pt.indexValue(file, d.Value)
|
||||
case *parser.VariableDefinition:
|
||||
fileFragment.Definitions = append(fileFragment.Definitions, d)
|
||||
pt.Variables[d.Name] = d
|
||||
pt.Variables[d.Name] = VariableInfo{Def: d, File: file}
|
||||
case *parser.ObjectNode:
|
||||
fileFragment.Definitions = append(fileFragment.Definitions, d)
|
||||
norm := NormalizeName(d.Name)
|
||||
@@ -282,7 +287,7 @@ func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *pa
|
||||
pt.extractFieldMetadata(node, d)
|
||||
case *parser.VariableDefinition:
|
||||
frag.Definitions = append(frag.Definitions, d)
|
||||
pt.Variables[d.Name] = d
|
||||
pt.Variables[d.Name] = VariableInfo{Def: d, File: file}
|
||||
case *parser.ObjectNode:
|
||||
frag.Definitions = append(frag.Definitions, d)
|
||||
norm := NormalizeName(d.Name)
|
||||
@@ -418,7 +423,7 @@ func (pt *ProjectTree) ResolveReferences() {
|
||||
ref := &pt.References[i]
|
||||
|
||||
if v, ok := pt.Variables[ref.Name]; ok {
|
||||
ref.TargetVariable = v
|
||||
ref.TargetVariable = v.Def
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
@@ -413,7 +413,7 @@ package schema
|
||||
OPCUA: {...}
|
||||
SysLogger: {...}
|
||||
GAMDataSource: {
|
||||
#meta: multithreaded: bool | *false
|
||||
#meta: multithreaded: false
|
||||
#meta: direction: "INOUT"
|
||||
#meta: type: "datasource"
|
||||
...
|
||||
@@ -421,7 +421,7 @@ package schema
|
||||
}
|
||||
|
||||
#Meta: {
|
||||
direction?: "IN" | "OUT" | "INOUT"
|
||||
direction?: "IN" | "OUT" | "INOUT"
|
||||
multithreaded?: bool
|
||||
...
|
||||
}
|
||||
@@ -430,7 +430,7 @@ package schema
|
||||
// It must have a Class field.
|
||||
// Based on Class, it validates against #Classes.
|
||||
#Object: {
|
||||
Class: string
|
||||
Class: string
|
||||
"#meta"?: #Meta
|
||||
// Allow any other field by default (extensibility),
|
||||
// unless #Classes definition is closed.
|
||||
|
||||
@@ -56,6 +56,7 @@ func (v *Validator) ValidateProject() {
|
||||
v.CheckUnused()
|
||||
v.CheckDataSourceThreading()
|
||||
v.CheckINOUTOrdering()
|
||||
v.CheckVariables()
|
||||
}
|
||||
|
||||
func (v *Validator) validateNode(node *index.ProjectNode) {
|
||||
@@ -936,53 +937,111 @@ func (v *Validator) CheckINOUTOrdering() {
|
||||
}
|
||||
|
||||
for _, thread := range threads {
|
||||
producedSignals := make(map[*index.ProjectNode]bool)
|
||||
producedSignals := make(map[*index.ProjectNode]map[string][]*index.ProjectNode)
|
||||
consumedSignals := make(map[*index.ProjectNode]map[string]bool)
|
||||
|
||||
gams := v.getThreadGAMs(thread)
|
||||
for _, gam := range gams {
|
||||
v.processGAMSignalsForOrdering(gam, "InputSignals", producedSignals, true, thread, state)
|
||||
v.processGAMSignalsForOrdering(gam, "OutputSignals", producedSignals, false, thread, state)
|
||||
v.processGAMSignalsForOrdering(gam, "InputSignals", producedSignals, consumedSignals, true, thread, state)
|
||||
v.processGAMSignalsForOrdering(gam, "OutputSignals", producedSignals, consumedSignals, false, thread, state)
|
||||
}
|
||||
|
||||
// Check for produced but not consumed
|
||||
for ds, signals := range producedSignals {
|
||||
for sigName, producers := range signals {
|
||||
consumed := false
|
||||
if cSet, ok := consumedSignals[ds]; ok {
|
||||
if cSet[sigName] {
|
||||
consumed = true
|
||||
}
|
||||
}
|
||||
if !consumed {
|
||||
for _, prod := range producers {
|
||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||
Level: LevelWarning,
|
||||
Message: fmt.Sprintf("INOUT Signal '%s' (DS '%s') is produced in thread '%s' but never consumed in the same thread.", sigName, ds.RealName, thread.RealName),
|
||||
Position: v.getNodePosition(prod),
|
||||
File: v.getNodeFile(prod),
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, containerName string, produced map[*index.ProjectNode]bool, isInput bool, thread, state *index.ProjectNode) {
|
||||
func (v *Validator) processGAMSignalsForOrdering(gam *index.ProjectNode, containerName string, produced map[*index.ProjectNode]map[string][]*index.ProjectNode, consumed map[*index.ProjectNode]map[string]bool, isInput bool, thread, state *index.ProjectNode) {
|
||||
container := gam.Children[containerName]
|
||||
if container == nil {
|
||||
return
|
||||
}
|
||||
|
||||
for _, sig := range container.Children {
|
||||
if sig.Target == nil {
|
||||
fields := v.getFields(sig)
|
||||
var dsNode *index.ProjectNode
|
||||
var sigName string
|
||||
|
||||
if sig.Target != nil {
|
||||
if sig.Target.Parent != nil && sig.Target.Parent.Parent != nil {
|
||||
dsNode = sig.Target.Parent.Parent
|
||||
sigName = sig.Target.RealName
|
||||
}
|
||||
}
|
||||
|
||||
if dsNode == nil {
|
||||
if dsFields, ok := fields["DataSource"]; ok && len(dsFields) > 0 {
|
||||
dsName := v.getFieldValue(dsFields[0])
|
||||
dsNode = v.resolveReference(dsName, v.getNodeFile(sig), isDataSource)
|
||||
}
|
||||
if aliasFields, ok := fields["Alias"]; ok && len(aliasFields) > 0 {
|
||||
sigName = v.getFieldValue(aliasFields[0])
|
||||
} else {
|
||||
sigName = sig.RealName
|
||||
}
|
||||
}
|
||||
|
||||
if dsNode == nil || sigName == "" {
|
||||
continue
|
||||
}
|
||||
|
||||
targetSig := sig.Target
|
||||
if targetSig.Parent == nil || targetSig.Parent.Parent == nil {
|
||||
continue
|
||||
}
|
||||
ds := targetSig.Parent.Parent
|
||||
sigName = index.NormalizeName(sigName)
|
||||
|
||||
if v.isMultithreaded(ds) {
|
||||
if v.isMultithreaded(dsNode) {
|
||||
continue
|
||||
}
|
||||
|
||||
dir := v.getDataSourceDirection(ds)
|
||||
dir := v.getDataSourceDirection(dsNode)
|
||||
if dir != "INOUT" {
|
||||
continue
|
||||
}
|
||||
|
||||
if isInput {
|
||||
if !produced[targetSig] {
|
||||
isProduced := false
|
||||
if set, ok := produced[dsNode]; ok {
|
||||
if len(set[sigName]) > 0 {
|
||||
isProduced = true
|
||||
}
|
||||
}
|
||||
|
||||
if !isProduced {
|
||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||
Level: LevelError,
|
||||
Message: fmt.Sprintf("INOUT Signal '%s' (DS '%s') is consumed by GAM '%s' in thread '%s' (State '%s') before being produced by any previous GAM.", targetSig.RealName, ds.RealName, gam.RealName, thread.RealName, state.RealName),
|
||||
Message: fmt.Sprintf("INOUT Signal '%s' (DS '%s') is consumed by GAM '%s' in thread '%s' (State '%s') before being produced by any previous GAM.", sigName, dsNode.RealName, gam.RealName, thread.RealName, state.RealName),
|
||||
Position: v.getNodePosition(sig),
|
||||
File: v.getNodeFile(sig),
|
||||
})
|
||||
}
|
||||
|
||||
if consumed[dsNode] == nil {
|
||||
consumed[dsNode] = make(map[string]bool)
|
||||
}
|
||||
consumed[dsNode][sigName] = true
|
||||
} else {
|
||||
produced[targetSig] = true
|
||||
if produced[dsNode] == nil {
|
||||
produced[dsNode] = make(map[string][]*index.ProjectNode)
|
||||
}
|
||||
produced[dsNode][sigName] = append(produced[dsNode][sigName], sig)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1003,3 +1062,42 @@ func (v *Validator) getDataSourceDirection(ds *index.ProjectNode) string {
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (v *Validator) CheckVariables() {
|
||||
if v.Schema == nil {
|
||||
return
|
||||
}
|
||||
ctx := v.Schema.Context
|
||||
|
||||
for _, info := range v.Tree.Variables {
|
||||
def := info.Def
|
||||
|
||||
// Compile Type
|
||||
typeVal := ctx.CompileString(def.TypeExpr)
|
||||
if typeVal.Err() != nil {
|
||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||
Level: LevelError,
|
||||
Message: fmt.Sprintf("Invalid type expression for variable '%s': %v", def.Name, typeVal.Err()),
|
||||
Position: def.Position,
|
||||
File: info.File,
|
||||
})
|
||||
continue
|
||||
}
|
||||
|
||||
if def.DefaultValue != nil {
|
||||
valInterface := v.valueToInterface(def.DefaultValue)
|
||||
valVal := ctx.Encode(valInterface)
|
||||
|
||||
// Unify
|
||||
res := typeVal.Unify(valVal)
|
||||
if err := res.Validate(cue.Concrete(true)); err != nil {
|
||||
v.Diagnostics = append(v.Diagnostics, Diagnostic{
|
||||
Level: LevelError,
|
||||
Message: fmt.Sprintf("Variable '%s' value mismatch: %v", def.Name, err),
|
||||
Position: def.Position,
|
||||
File: info.File,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user