Variable reference from $VAR to @VAR to avoid object conflict

This commit is contained in:
Martino Ferrari
2026-01-30 01:01:47 +01:00
parent 0cbbf5939a
commit c3f4d8f465
17 changed files with 52 additions and 39 deletions

View File

@@ -173,10 +173,10 @@ You can define variables using `#var`. The type expression supports CUE syntax.
``` ```
### Usage ### Usage
Reference a variable using `$`: Reference a variable using `@`:
```marte ```marte
Field = $MyVar Field = @MyVar
``` ```
### Expressions ### Expressions
@@ -187,7 +187,7 @@ You can use operators in field values. Supported operators:
```marte ```marte
Field1 = 10 + 20 * 2 // 50 Field1 = 10 + 20 * 2 // 50
Field2 = "Hello " .. "World" Field2 = "Hello " .. "World"
Field3 = $MyVar + 5 Field3 = @MyVar + 5
``` ```
### Build Override ### Build Override

View File

@@ -235,7 +235,7 @@ func (b *Builder) collectVariables(tree *index.ProjectTree) {
func (b *Builder) evaluate(val parser.Value) parser.Value { func (b *Builder) evaluate(val parser.Value) parser.Value {
switch v := val.(type) { switch v := val.(type) {
case *parser.VariableReferenceValue: case *parser.VariableReferenceValue:
name := strings.TrimPrefix(v.Name, "$") name := strings.TrimPrefix(v.Name, "@")
if res, ok := b.variables[name]; ok { if res, ok := b.variables[name]; ok {
return b.evaluate(res) return b.evaluate(res)
} }

View File

@@ -400,7 +400,7 @@ func (pt *ProjectTree) indexValue(file string, val parser.Value) {
}) })
case *parser.VariableReferenceValue: case *parser.VariableReferenceValue:
pt.References = append(pt.References, Reference{ pt.References = append(pt.References, Reference{
Name: strings.TrimPrefix(v.Name, "$"), Name: strings.TrimPrefix(v.Name, "@"),
Position: v.Position, Position: v.Position,
File: file, File: file,
IsVariable: true, IsVariable: true,

View File

@@ -611,7 +611,7 @@ func HandleHover(params HoverParams) *Hover {
} else if res.Reference.TargetVariable != nil { } else if res.Reference.TargetVariable != nil {
v := res.Reference.TargetVariable v := res.Reference.TargetVariable
targetName = v.Name targetName = v.Name
fullInfo = fmt.Sprintf("**Variable**: `%s`\nType: `%s`", v.Name, v.TypeExpr) fullInfo = fmt.Sprintf("**Variable**: `@%s`\nType: `%s`", v.Name, v.TypeExpr)
if v.DefaultValue != nil { if v.DefaultValue != nil {
fullInfo += fmt.Sprintf("\nDefault: `%s`", valueToString(v.DefaultValue)) fullInfo += fmt.Sprintf("\nDefault: `%s`", valueToString(v.DefaultValue))
} }

View File

@@ -36,6 +36,7 @@ const (
TokenCaret TokenCaret
TokenAmpersand TokenAmpersand
TokenConcat TokenConcat
TokenVariableReference
) )
type Token struct { type Token struct {
@@ -184,6 +185,8 @@ func (l *Lexer) NextToken() Token {
return l.lexString() return l.lexString()
case '#': case '#':
return l.lexHashIdentifier() return l.lexHashIdentifier()
case '@':
return l.lexVariableReference()
case '$': case '$':
return l.lexObjectIdentifier() return l.lexObjectIdentifier()
} }
@@ -311,3 +314,14 @@ func (l *Lexer) lexHashIdentifier() Token {
} }
return l.emit(TokenIdentifier) return l.emit(TokenIdentifier)
} }
func (l *Lexer) lexVariableReference() Token {
for {
r := l.next()
if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' {
continue
}
l.backup()
return l.emit(TokenVariableReference)
}
}

View File

@@ -297,7 +297,7 @@ func (p *Parser) parseAtom() (Value, bool) {
true true
case TokenIdentifier: case TokenIdentifier:
return &ReferenceValue{Position: tok.Position, Value: tok.Value}, true return &ReferenceValue{Position: tok.Position, Value: tok.Value}, true
case TokenObjectIdentifier: case TokenVariableReference:
return &VariableReferenceValue{Position: tok.Position, Name: tok.Value}, true return &VariableReferenceValue{Position: tok.Position, Name: tok.Value}, true
case TokenLBrace: case TokenLBrace:
arr := &ArrayValue{Position: tok.Position} arr := &ArrayValue{Position: tok.Position}

View File

@@ -223,7 +223,7 @@ func (v *Validator) valueToInterface(val parser.Value, ctx *index.ProjectNode) i
case *parser.ReferenceValue: case *parser.ReferenceValue:
return t.Value return t.Value
case *parser.VariableReferenceValue: case *parser.VariableReferenceValue:
name := strings.TrimPrefix(t.Name, "$") name := strings.TrimPrefix(t.Name, "@")
if info := v.Tree.ResolveVariable(ctx, name); info != nil { if info := v.Tree.ResolveVariable(ctx, name); info != nil {
if info.Def.DefaultValue != nil { if info.Def.DefaultValue != nil {
return v.valueToInterface(info.Def.DefaultValue, ctx) return v.valueToInterface(info.Def.DefaultValue, ctx)
@@ -525,7 +525,7 @@ func (v *Validator) getFieldValue(f *parser.Field, ctx *index.ProjectNode) strin
case *parser.BoolValue: case *parser.BoolValue:
return strconv.FormatBool(val.Value) return strconv.FormatBool(val.Value)
case *parser.VariableReferenceValue: case *parser.VariableReferenceValue:
name := strings.TrimPrefix(val.Name, "$") name := strings.TrimPrefix(val.Name, "@")
if info := v.Tree.ResolveVariable(ctx, name); info != nil { if info := v.Tree.ResolveVariable(ctx, name); info != nil {
if info.Def.DefaultValue != nil { if info.Def.DefaultValue != nil {
return v.getFieldValue(&parser.Field{Value: info.Def.DefaultValue}, ctx) return v.getFieldValue(&parser.Field{Value: info.Def.DefaultValue}, ctx)
@@ -1126,7 +1126,7 @@ func (v *Validator) CheckVariables() {
if ref.IsVariable && ref.TargetVariable == nil { if ref.IsVariable && ref.TargetVariable == nil {
v.Diagnostics = append(v.Diagnostics, Diagnostic{ v.Diagnostics = append(v.Diagnostics, Diagnostic{
Level: LevelError, Level: LevelError,
Message: fmt.Sprintf("Unresolved variable reference: '$%s'", ref.Name), Message: fmt.Sprintf("Unresolved variable reference: '@%s'", ref.Name),
Position: ref.Position, Position: ref.Position,
File: ref.File, File: ref.File,
}) })

View File

@@ -15,8 +15,8 @@ func TestFormatterVariables(t *testing.T) {
#var MyStr: string | "A" = "default" #var MyStr: string | "A" = "default"
+Obj = { +Obj = {
Field1 = $MyInt Field1 = @MyInt
Field2 = $MyStr Field2 = @MyStr
} }
` `
p := parser.NewParser(content) p := parser.NewParser(content)
@@ -39,7 +39,6 @@ func TestFormatterVariables(t *testing.T) {
if !strings.Contains(output, "#var MyStr: string | \"A\" = \"default\"") { if !strings.Contains(output, "#var MyStr: string | \"A\" = \"default\"") {
t.Errorf("Variable MyStr formatted incorrectly. Got:\n%s", output) t.Errorf("Variable MyStr formatted incorrectly. Got:\n%s", output)
} }
if !strings.Contains(output, "Field1 = $MyInt") { if !strings.Contains(output, "Field1 = @MyInt") {
t.Errorf("Variable reference $MyInt formatted incorrectly. Got:\n%s", output) t.Errorf("Variable reference @MyInt formatted incorrectly. Got:\n%s", output)
} }}
}

View File

@@ -38,7 +38,7 @@ func TestLSPAppTestRepro(t *testing.T) {
A = { A = {
DataSource = DDB DataSource = DDB
Type = uint32 Type = uint32
Value = $Value Value = @Value
} }
} }
OutputSignals = { OutputSignals = {
@@ -75,7 +75,7 @@ func TestLSPAppTestRepro(t *testing.T) {
output := buf.String() output := buf.String()
// Check Unresolved Variable // Check Unresolved Variable
if !strings.Contains(output, "Unresolved variable reference: '$Value'") { if !strings.Contains(output, "Unresolved variable reference: '@Value'") {
t.Error("LSP missing unresolved variable error") t.Error("LSP missing unresolved variable error")
} }

View File

@@ -151,7 +151,7 @@ diags := params["diagnostics"].([]interface{})
foundOrdering = true foundOrdering = true
t.Log("Found Ordering error") t.Log("Found Ordering error")
} }
if strings.Contains(m, "Unresolved variable reference: '$Value'") { if strings.Contains(m, "Unresolved variable reference: '@Value'") {
foundVariable = true foundVariable = true
t.Log("Found Variable error") t.Log("Found Variable error")
} }

View File

@@ -41,7 +41,7 @@ func TestLSPDiagnosticsAppTest(t *testing.T) {
A = { A = {
DataSource = DDB DataSource = DDB
Type = uint32 Type = uint32
Value = $Value Value = @Value
} }
} }
OutputSignals = { OutputSignals = {
@@ -87,9 +87,9 @@ func TestLSPDiagnosticsAppTest(t *testing.T) {
t.Fatal("LSP did not publish diagnostics") t.Fatal("LSP did not publish diagnostics")
} }
// 1. Check Unresolved Variable Error ($Value) // 1. Check Unresolved Variable Error (@Value)
if !strings.Contains(output, "Unresolved variable reference: '$Value'") { if !strings.Contains(output, "Unresolved variable reference: '@Value'") {
t.Error("Missing diagnostic for unresolved variable '$Value'") t.Error("Missing diagnostic for unresolved variable '@Value'")
} }
// 2. Check INOUT Ordering Error (Signal A consumed but not produced) // 2. Check INOUT Ordering Error (Signal A consumed but not produced)

View File

@@ -16,7 +16,7 @@ func TestLSPHoverVariable(t *testing.T) {
content := ` content := `
#var MyInt: int = 123 #var MyInt: int = 123
+Obj = { +Obj = {
Field = $MyInt Field = @MyInt
} }
` `
uri := "file://hover_var.marte" uri := "file://hover_var.marte"
@@ -47,8 +47,8 @@ func TestLSPHoverVariable(t *testing.T) {
t.Errorf("Hover def missing default value. Got: %s", contentDef) t.Errorf("Hover def missing default value. Got: %s", contentDef)
} }
// 2. Hover on Reference ($MyInt) // 2. Hover on Reference (@MyInt)
// Line 4 (index 3). $MyInt is at col 12. // Line 4 (index 3). @MyInt is at col 12.
paramsRef := lsp.HoverParams{ paramsRef := lsp.HoverParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri}, TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 3, Character: 12}, Position: lsp.Position{Line: 3, Character: 12},

View File

@@ -15,7 +15,7 @@ func TestLSPVariableRefs(t *testing.T) {
content := ` content := `
#var MyVar: int = 1 #var MyVar: int = 1
+Obj = { +Obj = {
Field = $MyVar Field = @MyVar
} }
` `
uri := "file://vars.marte" uri := "file://vars.marte"
@@ -29,11 +29,11 @@ func TestLSPVariableRefs(t *testing.T) {
lsp.Tree.ResolveReferences() lsp.Tree.ResolveReferences()
// 1. Definition from Usage // 1. Definition from Usage
// Line 4: " Field = $MyVar" // Line 4: " Field = @MyVar"
// $ is at col 12 (0-based) ? // @ is at col 12 (0-based) ?
// " Field = " is 4 + 6 + 3 = 13 chars? // " Field = " is 4 + 6 + 3 = 13 chars?
// 4 spaces. Field (5). " = " (3). 4+5+3 = 12. // 4 spaces. Field (5). " = " (3). 4+5+3 = 12.
// So $ is at 12. // So @ is at 12.
paramsDef := lsp.DefinitionParams{ paramsDef := lsp.DefinitionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: uri}, TextDocument: lsp.TextDocumentIdentifier{URI: uri},
Position: lsp.Position{Line: 3, Character: 12}, Position: lsp.Position{Line: 3, Character: 12},

View File

@@ -17,9 +17,9 @@ func TestOperators(t *testing.T) {
#var S2: string = "World" #var S2: string = "World"
+Obj = { +Obj = {
Math = $A + $B Math = @A + @B
Precedence = $A + $B * 2 Precedence = @A + @B * 2
Concat = $S1 .. " " .. $S2 Concat = @S1 .. " " .. @S2
} }
` `
// Check Parser // Check Parser

View File

@@ -15,7 +15,7 @@ func TestRegexVariable(t *testing.T) {
#var BadIP: string & =~"^[0-9.]+$" = "abc" #var BadIP: string & =~"^[0-9.]+$" = "abc"
+Obj = { +Obj = {
IP = $IP IP = @IP
} }
` `
// Test Validator // Test Validator

View File

@@ -44,7 +44,7 @@ func TestVariableValidation(t *testing.T) {
#var MyStr: string = "hello" #var MyStr: string = "hello"
+MyPID = { +MyPID = {
Class = PIDGAM Class = PIDGAM
Kp = $MyStr Kp = @MyStr
Ki = 0.0 Ki = 0.0
Kd = 0.0 Kd = 0.0
} }
@@ -79,7 +79,7 @@ func TestVariableValidation(t *testing.T) {
#var MyGain: float = 1.5 #var MyGain: float = 1.5
+MyPID = { +MyPID = {
Class = PIDGAM Class = PIDGAM
Kp = $MyGain Kp = @MyGain
Ki = 0.0 Ki = 0.0
Kd = 0.0 Kd = 0.0
} }

View File

@@ -16,8 +16,8 @@ func TestVariables(t *testing.T) {
+Obj = { +Obj = {
Class = Test Class = Test
Field1 = $MyInt Field1 = @MyInt
Field2 = $MyStr Field2 = @MyStr
} }
` `
// Test Parsing // Test Parsing