Better formatting and expression handling

This commit is contained in:
Martino Ferrari
2026-02-02 17:22:39 +01:00
parent 12615aa6d2
commit 749eab0a32
14 changed files with 735 additions and 67 deletions

View File

@@ -0,0 +1,78 @@
package integration
import (
"testing"
"github.com/marte-community/marte-dev-tools/internal/parser"
"github.com/marte-community/marte-dev-tools/internal/formatter"
"bytes"
)
func TestAdvancedNumbers(t *testing.T) {
content := `
Hex = 0xFF
HexLower = 0xee
Binary = 0b1011
Decimal = 123
Scientific = 1e-3
`
p := parser.NewParser(content)
cfg, err := p.Parse()
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
// Verify values
foundHex := false
foundHexLower := false
foundBinary := false
for _, def := range cfg.Definitions {
if f, ok := def.(*parser.Field); ok {
if f.Name == "Hex" {
if v, ok := f.Value.(*parser.IntValue); ok {
if v.Value != 255 {
t.Errorf("Expected 255 for Hex, got %d", v.Value)
}
foundHex = true
}
}
if f.Name == "HexLower" {
if v, ok := f.Value.(*parser.IntValue); ok {
if v.Value != 238 {
t.Errorf("Expected 238 for HexLower, got %d", v.Value)
}
foundHexLower = true
} else {
t.Errorf("HexLower was parsed as %T, expected *parser.IntValue", f.Value)
}
}
if f.Name == "Binary" {
if v, ok := f.Value.(*parser.IntValue); ok {
if v.Value == 11 {
foundBinary = true
}
}
}
}
}
if !foundHex { t.Error("Hex field not found") }
if !foundHexLower { t.Error("HexLower field not found") }
if !foundBinary { t.Error("Binary field not found") }
// Verify formatting
var buf bytes.Buffer
formatter.Format(cfg, &buf)
formatted := buf.String()
if !contains(formatted, "Hex = 0xFF") {
t.Errorf("Formatted content missing Hex = 0xFF:\n%s", formatted)
}
if !contains(formatted, "HexLower = 0xee") {
t.Errorf("Formatted content missing HexLower = 0xee:\n%s", formatted)
}
if !contains(formatted, "Binary = 0b1011") {
t.Errorf("Formatted content missing Binary = 0b1011:\n%s", formatted)
}
}
func contains(s, substr string) bool {
return bytes.Contains([]byte(s), []byte(substr))
}

View File

@@ -0,0 +1,88 @@
package integration
import (
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/index"
"github.com/marte-community/marte-dev-tools/internal/parser"
"github.com/marte-community/marte-dev-tools/internal/validator"
)
func TestEvaluatedSignalProperties(t *testing.T) {
content := `
#let N: uint32 = 10
+DS = {
Class = FileReader
Filename = "test.bin"
Signals = {
Sig1 = { Type = uint32 NumberOfElements = @N }
}
}
+GAM = {
Class = IOGAM
InputSignals = {
Sig1 = { DataSource = DS Type = uint32 NumberOfElements = 10 }
}
}
`
p := parser.NewParser(content)
cfg, err := p.Parse()
if err != nil {
t.Fatal(err)
}
tree := index.NewProjectTree()
tree.AddFile("test.marte", cfg)
tree.ResolveReferences()
v := validator.NewValidator(tree, ".")
v.ValidateProject()
// There should be no errors because @N evaluates to 10
for _, d := range v.Diagnostics {
if d.Level == validator.LevelError {
t.Errorf("Unexpected error: %s", d.Message)
}
}
// Test mismatch with expression
contentErr := `
#let N: uint32 = 10
+DS = {
Class = FileReader
Filename = "test.bin"
Signals = {
Sig1 = { Type = uint32 NumberOfElements = @N + 5 }
}
}
+GAM = {
Class = IOGAM
InputSignals = {
Sig1 = { DataSource = DS Type = uint32 NumberOfElements = 10 }
}
}
`
p2 := parser.NewParser(contentErr)
cfg2, _ := p2.Parse()
tree2 := index.NewProjectTree()
tree2.AddFile("test_err.marte", cfg2)
tree2.ResolveReferences()
v2 := validator.NewValidator(tree2, ".")
v2.ValidateProject()
found := false
for _, d := range v2.Diagnostics {
if strings.Contains(d.Message, "property 'NumberOfElements' mismatch") {
found = true
if !strings.Contains(d.Message, "defined '15'") {
t.Errorf("Expected defined '15', got message: %s", d.Message)
}
break
}
}
if !found {
t.Error("Expected property mismatch error for @N + 5")
}
}

125
test/let_macro_test.go Normal file
View File

@@ -0,0 +1,125 @@
package integration
import (
"os"
"strings"
"testing"
"github.com/marte-community/marte-dev-tools/internal/builder"
"github.com/marte-community/marte-dev-tools/internal/index"
"github.com/marte-community/marte-dev-tools/internal/parser"
"github.com/marte-community/marte-dev-tools/internal/validator"
)
func TestLetMacroFull(t *testing.T) {
content := `
//# My documentation
#let MyConst: uint32 = 10 + 20
+Obj = {
Value = @MyConst
}
`
tmpFile, _ := os.CreateTemp("", "let_*.marte")
defer os.Remove(tmpFile.Name())
os.WriteFile(tmpFile.Name(), []byte(content), 0644)
// 1. Test Parsing & Indexing
p := parser.NewParser(content)
cfg, err := p.Parse()
if err != nil {
t.Fatalf("Parse failed: %v", err)
}
tree := index.NewProjectTree()
tree.AddFile(tmpFile.Name(), cfg)
vars := tree.Root.Variables
if iso, ok := tree.IsolatedFiles[tmpFile.Name()]; ok {
vars = iso.Variables
}
info, ok := vars["MyConst"]
if !ok || !info.Def.IsConst {
t.Fatal("#let variable not indexed correctly as Const")
}
if info.Doc != "My documentation" {
t.Errorf("Expected doc 'My documentation', got '%s'", info.Doc)
}
// 2. Test Builder Evaluation
out, _ := os.CreateTemp("", "let_out.cfg")
defer os.Remove(out.Name())
b := builder.NewBuilder([]string{tmpFile.Name()}, nil)
if err := b.Build(out); err != nil {
t.Fatalf("Build failed: %v", err)
}
outContent, _ := os.ReadFile(out.Name())
if !strings.Contains(string(outContent), "Value = 30") {
t.Errorf("Expected Value = 30 (evaluated @MyConst), got:\n%s", string(outContent))
}
// 3. Test Override Protection
out2, _ := os.CreateTemp("", "let_out2.cfg")
defer os.Remove(out2.Name())
b2 := builder.NewBuilder([]string{tmpFile.Name()}, map[string]string{"MyConst": "100"})
if err := b2.Build(out2); err != nil {
t.Fatalf("Build failed: %v", err)
}
outContent2, _ := os.ReadFile(out2.Name())
if !strings.Contains(string(outContent2), "Value = 30") {
t.Errorf("Constant was overridden! Expected 30, got:\n%s", string(outContent2))
}
// 4. Test Validator (Mandatory Value)
contentErr := "#let BadConst: uint32"
p2 := parser.NewParser(contentErr)
cfg2, err2 := p2.Parse()
// Parser might fail if = is missing?
// parseLet expects =.
if err2 == nil {
// If parser didn't fail (maybe it was partial), validator should catch it
tree2 := index.NewProjectTree()
tree2.AddFile("err.marte", cfg2)
v := validator.NewValidator(tree2, ".")
v.ValidateProject()
found := false
for _, d := range v.Diagnostics {
if strings.Contains(d.Message, "must have an initial value") {
found = true
break
}
}
if !found && cfg2 != nil {
// If p2.Parse() failed and added error to p2.errors, it's also fine.
// But check if it reached validator.
}
}
// 5. Test Duplicate Detection
contentDup := `
#let MyConst: uint32 = 10
#var MyConst: uint32 = 20
`
p3 := parser.NewParser(contentDup)
cfg3, _ := p3.Parse()
tree3 := index.NewProjectTree()
tree3.AddFile("dup.marte", cfg3)
v3 := validator.NewValidator(tree3, ".")
v3.ValidateProject()
foundDup := false
for _, d := range v3.Diagnostics {
if strings.Contains(d.Message, "Duplicate variable definition") {
foundDup = true
break
}
}
if !foundDup {
t.Error("Expected duplicate variable definition error")
}
}

View File

@@ -0,0 +1,88 @@
package integration
import (
"os"
"path/filepath"
"testing"
"github.com/marte-community/marte-dev-tools/internal/index"
"github.com/marte-community/marte-dev-tools/internal/lsp"
)
func TestLSPRecursiveIndexing(t *testing.T) {
// Setup directory structure
rootDir, err := os.MkdirTemp("", "lsp_recursive")
if err != nil {
t.Fatal(err)
}
defer os.RemoveAll(rootDir)
// root/main.marte
mainContent := `
#package App
+Main = {
Ref = SubComp
}
`
if err := os.WriteFile(filepath.Join(rootDir, "main.marte"), []byte(mainContent), 0644); err != nil {
t.Fatal(err)
}
// root/subdir/sub.marte
subDir := filepath.Join(rootDir, "subdir")
if err := os.Mkdir(subDir, 0755); err != nil {
t.Fatal(err)
}
subContent := `
#package App
+SubComp = { Class = Component }
`
if err := os.WriteFile(filepath.Join(subDir, "sub.marte"), []byte(subContent), 0644); err != nil {
t.Fatal(err)
}
// Initialize LSP
lsp.Tree = index.NewProjectTree()
lsp.Documents = make(map[string]string)
// Simulate ScanDirectory
if err := lsp.Tree.ScanDirectory(rootDir); err != nil {
t.Fatalf("ScanDirectory failed: %v", err)
}
lsp.Tree.ResolveReferences()
// Check if SubComp is in the tree
// Root -> App -> SubComp
appNode := lsp.Tree.Root.Children["App"]
if appNode == nil {
t.Fatal("App package not found")
}
subComp := appNode.Children["SubComp"]
if subComp == nil {
t.Fatal("SubComp not found in tree (recursive scan failed)")
}
mainURI := "file://" + filepath.Join(rootDir, "main.marte")
// Definition Request
params := lsp.DefinitionParams{
TextDocument: lsp.TextDocumentIdentifier{URI: mainURI},
Position: lsp.Position{Line: 3, Character: 12},
}
res := lsp.HandleDefinition(params)
if res == nil {
t.Fatal("Definition not found for SubComp")
}
locs, ok := res.([]lsp.Location)
if !ok || len(locs) == 0 {
t.Fatal("Expected location list")
}
expectedFile := filepath.Join(subDir, "sub.marte")
if locs[0].URI != "file://"+expectedFile {
t.Errorf("Expected definition in %s, got %s", expectedFile, locs[0].URI)
}
}

View File

@@ -0,0 +1,54 @@
package integration
import (
"os"
"path/filepath"
"testing"
"github.com/marte-community/marte-dev-tools/internal/index"
)
func TestRecursiveIndexing(t *testing.T) {
// Setup: root/level1/level2/deep.marte
rootDir, _ := os.MkdirTemp("", "rec_index")
defer os.RemoveAll(rootDir)
l1 := filepath.Join(rootDir, "level1")
l2 := filepath.Join(l1, "level2")
if err := os.MkdirAll(l2, 0755); err != nil {
t.Fatal(err)
}
content := "#package Deep\n+DeepObj = { Class = A }"
if err := os.WriteFile(filepath.Join(l2, "deep.marte"), []byte(content), 0644); err != nil {
t.Fatal(err)
}
// Also add a file in root to ensure mixed levels work
os.WriteFile(filepath.Join(rootDir, "root.marte"), []byte("#package Root\n+RootObj = { Class = A }"), 0644)
// Scan
tree := index.NewProjectTree()
err := tree.ScanDirectory(rootDir)
if err != nil {
t.Fatalf("Scan failed: %v", err)
}
// Verify Deep
deepPkg := tree.Root.Children["Deep"]
if deepPkg == nil {
t.Fatal("Package Deep not found")
}
if deepPkg.Children["DeepObj"] == nil {
t.Fatal("DeepObj not found in Deep package")
}
// Verify Root
rootPkg := tree.Root.Children["Root"]
if rootPkg == nil {
t.Fatal("Package Root not found")
}
if rootPkg.Children["RootObj"] == nil {
t.Fatal("RootObj not found in Root package")
}
}