diff --git a/internal/index/index.go b/internal/index/index.go index 48cdb77..67e0845 100644 --- a/internal/index/index.go +++ b/internal/index/index.go @@ -1,6 +1,8 @@ package index import ( + "fmt" + "os" "strings" "github.com/marte-dev/marte-dev-tools/internal/parser" @@ -31,8 +33,8 @@ type ProjectNode struct { type Fragment struct { File string Definitions []parser.Definition - IsObject bool - ObjectPos parser.Position + IsObject bool + ObjectPos parser.Position Doc string // Documentation for this fragment (if object) } @@ -83,7 +85,7 @@ func (pt *ProjectTree) removeFileFromNode(node *ProjectNode, file string) { node.Doc += frag.Doc } } - + // Re-aggregate metadata node.Metadata = make(map[string]string) pt.rebuildMetadata(node) @@ -114,7 +116,7 @@ func (pt *ProjectTree) extractFieldMetadata(node *ProjectNode, f *parser.Field) case *parser.IntValue: val = v.Raw } - + if val == "" { return } @@ -126,7 +128,7 @@ func (pt *ProjectTree) extractFieldMetadata(node *ProjectNode, f *parser.Field) } func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) { - pt.RemoveFile(file) + pt.RemoveFile(file) node := pt.Root if config.Package != nil { @@ -139,7 +141,7 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) { if _, ok := node.Children[part]; !ok { node.Children[part] = &ProjectNode{ Name: part, - RealName: part, + RealName: part, Children: make(map[string]*ProjectNode), Parent: node, Metadata: make(map[string]string), @@ -153,10 +155,10 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) { File: file, IsObject: false, } - + for _, def := range config.Definitions { doc := pt.findDoc(config.Comments, def.Pos()) - + switch d := def.(type) { case *parser.Field: fileFragment.Definitions = append(fileFragment.Definitions, d) @@ -177,18 +179,18 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) { if child.RealName == norm && d.Name != norm { child.RealName = d.Name } - + if doc != "" { if child.Doc != "" { child.Doc += "\n\n" } child.Doc += doc } - + pt.addObjectFragment(child, file, d, doc, config.Comments) } } - + if len(fileFragment.Definitions) > 0 { node.Fragments = append(node.Fragments, fileFragment) } @@ -196,15 +198,15 @@ func (pt *ProjectTree) AddFile(file string, config *parser.Configuration) { func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *parser.ObjectNode, doc string, comments []parser.Comment) { frag := &Fragment{ - File: file, - IsObject: true, - ObjectPos: obj.Position, - Doc: doc, + File: file, + IsObject: true, + ObjectPos: obj.Position, + Doc: doc, } - + for _, def := range obj.Subnode.Definitions { subDoc := pt.findDoc(comments, def.Pos()) - + switch d := def.(type) { case *parser.Field: frag.Definitions = append(frag.Definitions, d) @@ -225,18 +227,18 @@ func (pt *ProjectTree) addObjectFragment(node *ProjectNode, file string, obj *pa if child.RealName == norm && d.Name != norm { child.RealName = d.Name } - + if subDoc != "" { if child.Doc != "" { child.Doc += "\n\n" } child.Doc += subDoc } - + pt.addObjectFragment(child, file, d, subDoc, comments) } } - + node.Fragments = append(node.Fragments, frag) } @@ -244,7 +246,7 @@ func (pt *ProjectTree) findDoc(comments []parser.Comment, pos parser.Position) s var docBuilder strings.Builder targetLine := pos.Line - 1 var docIndices []int - + for i := len(comments) - 1; i >= 0; i-- { c := comments[i] if c.Position.Line > pos.Line { @@ -253,7 +255,7 @@ func (pt *ProjectTree) findDoc(comments []parser.Comment, pos parser.Position) s if c.Position.Line == pos.Line { continue } - + if c.Position.Line == targetLine { if c.Doc { docIndices = append(docIndices, i) @@ -265,7 +267,7 @@ func (pt *ProjectTree) findDoc(comments []parser.Comment, pos parser.Position) s break } } - + for i := len(docIndices) - 1; i >= 0; i-- { txt := strings.TrimPrefix(comments[docIndices[i]].Text, "//#") txt = strings.TrimSpace(txt) @@ -274,7 +276,7 @@ func (pt *ProjectTree) findDoc(comments []parser.Comment, pos parser.Position) s } docBuilder.WriteString(txt) } - + return docBuilder.String() } @@ -319,7 +321,9 @@ type QueryResult struct { } func (pt *ProjectTree) Query(file string, line, col int) *QueryResult { + fmt.Fprintf(os.Stderr, "File: %s:%d:%d\n", file, line, col) for i := range pt.References { + fmt.Fprintf(os.Stderr, "%s\n", pt.Root.Name) ref := &pt.References[i] if ref.File == file { if line == ref.Position.Line && col >= ref.Position.Column && col < ref.Position.Column+len(ref.Name) { @@ -339,7 +343,7 @@ func (pt *ProjectTree) queryNode(node *ProjectNode, file string, line, col int) return &QueryResult{Node: node} } } - + for _, def := range frag.Definitions { if f, ok := def.(*parser.Field); ok { if line == f.Position.Line && col >= f.Position.Column && col < f.Position.Column+len(f.Name) { @@ -349,7 +353,7 @@ func (pt *ProjectTree) queryNode(node *ProjectNode, file string, line, col int) } } } - + for _, child := range node.Children { if res := pt.queryNode(child, file, line, col); res != nil { return res diff --git a/internal/lsp/server.go b/internal/lsp/server.go index 6dcc0c9..4e9813e 100644 --- a/internal/lsp/server.go +++ b/internal/lsp/server.go @@ -148,9 +148,16 @@ func handleMessage(msg *JsonRpcMessage) { case "textDocument/hover": var params HoverParams if err := json.Unmarshal(msg.Params, ¶ms); err == nil { + fmt.Fprintf(os.Stderr, "Hover: %s:%d\n", params.TextDocument.URI, params.Position.Line) res := handleHover(params) + if res != nil { + fmt.Fprintf(os.Stderr, "Res: %v\n", res.Contents) + } else { + fmt.Fprint(os.Stderr, "Res: NIL\n") + } respond(msg.ID, res) } else { + fmt.Fprint(os.Stderr, "not recovered hover parameters\n") respond(msg.ID, nil) } } @@ -191,6 +198,7 @@ func handleHover(params HoverParams) *Hover { res := tree.Query(path, line, col) if res == nil { + fmt.Fprint(os.Stderr, "No object/node/reference found\n") return nil } diff --git a/mdt b/mdt index 8b863fc..5a8d32b 100755 Binary files a/mdt and b/mdt differ diff --git a/specification.md b/specification.md index db44817..41599ad 100644 --- a/specification.md +++ b/specification.md @@ -10,6 +10,7 @@ ## CLI Commands The executable should support the following subcommands: + - `lsp`: Starts the Language Server Protocol server. - `build`: Merges files with the same base namespace into a single output. - `check`: Runs diagnostics and validations on the configuration files. @@ -18,12 +19,13 @@ The executable should support the following subcommands: ## LSP Features The LSP server should provide the following capabilities: + - **Diagnostics**: Report syntax errors and validation issues. - **Hover Documentation**: - - **Objects**: Display `CLASS::Name` and any associated docstrings. - - **Signals**: Display `DataSource.Name TYPE (SIZE) [IN/OUT/INOUT]` along with docstrings. - - **GAMs**: Show the list of States where the GAM is referenced. - - **Referenced Signals**: Show the list of GAMs where the signal is referenced. + - **Objects**: Display `CLASS::Name` and any associated docstrings. + - **Signals**: Display `DataSource.Name TYPE (SIZE) [IN/OUT/INOUT]` along with docstrings. + - **GAMs**: Show the list of States where the GAM is referenced. + - **Referenced Signals**: Show the list of GAMs where the signal is referenced. - **Go to Definition**: Jump to the definition of a reference, supporting navigation across any file in the current project. - **Go to References**: Find usages of a node or field, supporting navigation across any file in the current project. - **Code Completion**: Autocomplete fields, values, and references. @@ -34,14 +36,14 @@ The LSP server should provide the following capabilities: - **File Extension**: `.marte` - **Project Structure**: Files can be distributed across sub-folders. - **Namespaces**: The `#package` macro defines the namespace for the file. - - **Semantic**: `#package PROJECT.NODE` implies that all definitions within the file are treated as children/fields of the node `NODE`. - - **URI Symbols**: The symbols `+` and `$` used for object nodes are **not** written in the URI of the `#package` macro (e.g., use `PROJECT.NODE` even if the node is defined as `+NODE`). + - **Semantic**: `#package PROJECT.NODE` implies that all definitions within the file are treated as children/fields of the node `NODE`. + - **URI Symbols**: The symbols `+` and `$` used for object nodes are **not** written in the URI of the `#package` macro (e.g., use `PROJECT.NODE` even if the node is defined as `+NODE`). - **Build Process**: - - The build tool merges all files sharing the same base namespace. - - **Multi-File Nodes**: Nodes can be defined across multiple files. The build tool and validator must merge these definitions before processing. - - **Merging Order**: For objects defined across multiple files, the **first file** to be considered is the one containing the `Class` field definition. - - **Field Order**: Within a single file, the relative order of defined fields must be maintained. - - The LSP indexes only files belonging to the same project/namespace scope. + - The build tool merges all files sharing the same base namespace. + - **Multi-File Nodes**: Nodes can be defined across multiple files. The build tool and validator must merge these definitions before processing. + - **Merging Order**: For objects defined across multiple files, the **first file** to be considered is the one containing the `Class` field definition. + - **Field Order**: Within a single file, the relative order of defined fields must be maintained. + - The LSP indexes only files belonging to the same project/namespace scope. - **Output**: The output format is the same as the input configuration but without the `#package` macro. ## MARTe Configuration Language @@ -73,7 +75,7 @@ The LSP server should provide the following capabilities: ### Semantics - **Nodes (`+` / `$`)**: The prefixes `+` and `$` indicate that the node represents an object. - - **Constraint**: These nodes *must* contain a field named `Class` within their subnode definition. + - **Constraint**: These nodes _must_ contain a field named `Class` within their subnode definition. - **Signals**: Signals are considered nodes but **not** objects. They do not require a `Class` field. - **Pragmas (`//!`)**: Used to suppress specific diagnostics. The developer can use these to explain why a rule is being ignored. - **Structure**: A configuration is composed by one or more definitions. @@ -81,47 +83,49 @@ The LSP server should provide the following capabilities: ### Core MARTe Classes MARTe configurations typically involve several main categories of objects: + - **State Machine (`StateMachine`)**: Defines state machines and transition logic. - **Real-Time Application (`RealTimeApplication`)**: Defines a real-time application, including its data sources, functions, states, and scheduler. - **Data Source**: Multiple classes used to define input and/or output signal sources. - **GAM (Generic Application Module)**: Multiple classes used to process signals. - - **Constraint**: A GAM node must contain at least one `InputSignals` sub-node, one `OutputSignals` sub-node, or both. + - **Constraint**: A GAM node must contain at least one `InputSignals` sub-node, one `OutputSignals` sub-node, or both. ### Signals and Data Flow - **Signal Definition**: - - **Explicit**: Signals defined within the `DataSource` definition. - - **Implicit**: Signals defined only within a `GAM`, which are then automatically managed. - - **Requirements**: - - All signal definitions **must** include a `Type` field with a valid value. - - **Size Information**: Signals can optionally include `NumberOfDimensions` and `NumberOfElements` fields. If not explicitly defined, these default to `1`. - - **Extensibility**: Signal definitions can include additional fields as required by the specific application context. + - **Explicit**: Signals defined within the `DataSource` definition. + - **Implicit**: Signals defined only within a `GAM`, which are then automatically managed. + - **Requirements**: + - All signal definitions **must** include a `Type` field with a valid value. + - **Size Information**: Signals can optionally include `NumberOfDimensions` and `NumberOfElements` fields. If not explicitly defined, these default to `1`. + - **Extensibility**: Signal definitions can include additional fields as required by the specific application context. - **Signal Reference Syntax**: - - Signals are referenced or defined in `InputSignals` or `OutputSignals` sub-nodes using one of the following formats: - 1. **Direct Reference**: - ``` - SIGNAL_NAME = { - DataSource = SIGNAL_DATASOURCE - // Other fields if necessary - } - ``` - 2. **Aliased Reference**: - ``` - NAME = { - Alias = SIGNAL_NAME - DataSource = SIGNAL_DATASOURCE - // ... - } - ``` - - **Implicit Definition Constraint**: If a signal is implicitly defined within a GAM, the `Type` field **must** be present in the reference block to define the signal's properties. + - Signals are referenced or defined in `InputSignals` or `OutputSignals` sub-nodes using one of the following formats: + 1. **Direct Reference**: + ``` + SIGNAL_NAME = { + DataSource = SIGNAL_DATASOURCE + // Other fields if necessary + } + ``` + 2. **Aliased Reference**: + ``` + NAME = { + Alias = SIGNAL_NAME + DataSource = SIGNAL_DATASOURCE + // ... + } + ``` + - **Implicit Definition Constraint**: If a signal is implicitly defined within a GAM, the `Type` field **must** be present in the reference block to define the signal's properties. - **Directionality**: DataSources and their signals are directional: - - `Input`: Only providing data. - - `Output`: Only receiving data. - - `Inout`: Bidirectional data flow. + - `Input`: Only providing data. + - `Output`: Only receiving data. + - `Inout`: Bidirectional data flow. ### Object Indexing & References The tool must build an index of the configuration to support LSP features and validations: + - **GAMs**: Referenced in `$APPLICATION.States.$STATE_NAME.Threads.$THREAD_NAME.Functions` (where `$APPLICATION` is a `RealTimeApplication` node). - **Signals**: Referenced within the `InputSignals` and `OutputSignals` sub-nodes of a GAM. - **DataSources**: Referenced within the `DataSource` field of a signal reference/definition. @@ -131,29 +135,30 @@ The tool must build an index of the configuration to support LSP features and va - **Consistency**: The `lsp`, `check`, and `build` commands **must share the same validation engine** to ensure consistent results across all tools. - **Class Validation**: - - For each known `Class`, the validator checks: - - **Mandatory Fields**: Verification that all required fields are present. - - **Field Types**: Verification that values assigned to fields match the expected types (e.g., `int`, `string`, `bool`). - - **Field Order**: Verification that specific fields appear in a prescribed order when required by the class definition. - - **Conditional Fields**: Validation of fields whose presence or value depends on the values of other fields within the same node or context. - - **Schema Definition**: - - Class validation rules must be defined in a separate schema file. - - **Project-Specific Classes**: Developers can define their own project-specific classes and corresponding validation rules, expanding the validation capabilities for their specific needs. + - For each known `Class`, the validator checks: + - **Mandatory Fields**: Verification that all required fields are present. + - **Field Types**: Verification that values assigned to fields match the expected types (e.g., `int`, `string`, `bool`). + - **Field Order**: Verification that specific fields appear in a prescribed order when required by the class definition. + - **Conditional Fields**: Validation of fields whose presence or value depends on the values of other fields within the same node or context. + - **Schema Definition**: + - Class validation rules must be defined in a separate schema file. + - **Project-Specific Classes**: Developers can define their own project-specific classes and corresponding validation rules, expanding the validation capabilities for their specific needs. - **Duplicate Fields**: - - **Constraint**: A field must not be defined more than once within the same object/node scope. - - **Multi-File Consideration**: Validation must account for nodes being defined across multiple files (merged) when checking for duplicates. + - **Constraint**: A field must not be defined more than once within the same object/node scope. + - **Multi-File Consideration**: Validation must account for nodes being defined across multiple files (merged) when checking for duplicates. ### Formatting Rules The `fmt` command must format the code according to the following rules: + - **Indentation**: 2 spaces per indentation level. - **Assignment**: 1 space before and after the `=` operator (e.g., `Field = Value`). -- **Comments**: - - 1 space after `//`, `//#`, or `//!`. - - Comments should "stick" to the next definition (no empty lines between the comment and the code it documents). - - **Placement**: - - Comments can be placed inline after a definition (e.g., `field = value // comment`). - - Comments can be placed after a subnode opening bracket (e.g., `node = { // comment`) or after an object definition. +- **Comments**: + - 1 space after `//`, `//#`, or `//!`. + - Comments should "stick" to the next definition (no empty lines between the comment and the code it documents). + - **Placement**: + - Comments can be placed inline after a definition (e.g., `field = value // comment`). + - Comments can be placed after a subnode opening bracket (e.g., `node = { // comment`) or after an object definition. - **Arrays**: 1 space after the opening bracket `{` and 1 space before the closing bracket `}` (e.g., `{ 1 2 3 }`). - **Strings**: Quoted strings must preserve their quotes during formatting. @@ -162,15 +167,15 @@ The `fmt` command must format the code according to the following rules: The LSP and `check` command should report the following: - **Warnings**: - - **Unused GAM**: A GAM is defined but not referenced in any thread or scheduler. - - **Unused Signal**: A signal is explicitly defined in a `DataSource` but never referenced in any `GAM`. - - **Implicitly Defined Signal**: A signal is defined only within a `GAM` and not in its parent `DataSource`. + - **Unused GAM**: A GAM is defined but not referenced in any thread or scheduler. + - **Unused Signal**: A signal is explicitly defined in a `DataSource` but never referenced in any `GAM`. + - **Implicitly Defined Signal**: A signal is defined only within a `GAM` and not in its parent `DataSource`. - **Errors**: - - **Type Inconsistency**: A signal is referenced with a type different from its definition. - - **Size Inconsistency**: A signal is referenced with a size (dimensions/elements) different from its definition. - - **Duplicate Field Definition**: A field is defined multiple times within the same node scope (including across multiple files). - - **Validation Errors**: - - Missing mandatory fields. - - Field type mismatches. - - Grammar errors (e.g., missing closing brackets). + - **Type Inconsistency**: A signal is referenced with a type different from its definition. + - **Size Inconsistency**: A signal is referenced with a size (dimensions/elements) different from its definition. + - **Duplicate Field Definition**: A field is defined multiple times within the same node scope (including across multiple files). + - **Validation Errors**: + - Missing mandatory fields. + - Field type mismatches. + - Grammar errors (e.g., missing closing brackets). diff --git a/test/integration/fmt.marte b/test/integration/fmt.marte index 4ee5a38..ad28797 100644 --- a/test/integration/fmt.marte +++ b/test/integration/fmt.marte @@ -8,4 +8,4 @@ // Sticky comment Field = 123 Array = {1 2 3} -} \ No newline at end of file +}