Compare commits
3 Commits
31996ae710
...
71c86f1dcb
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
71c86f1dcb | ||
|
|
ab22a939d7 | ||
|
|
01bcd66594 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -1,4 +1,3 @@
|
|||||||
build
|
build
|
||||||
*.log
|
*.log
|
||||||
mdt
|
|
||||||
*.out
|
*.out
|
||||||
|
|||||||
13
README.md
13
README.md
@@ -20,6 +20,13 @@ Few additional features have been added to the standard MARTe configuration lang
|
|||||||
- Doc-strings support
|
- Doc-strings support
|
||||||
- Pragmas for warning suppression / documentation
|
- Pragmas for warning suppression / documentation
|
||||||
|
|
||||||
|
## Documentation
|
||||||
|
|
||||||
|
- [Step-by-Step Tutorial](docs/TUTORIAL.md)
|
||||||
|
- [Editor Integration Guide](docs/EDITOR_INTEGRATION.md)
|
||||||
|
- [Configuration Guide](docs/CONFIGURATION_GUIDE.md)
|
||||||
|
- [Examples Readme](/examples/README.md)
|
||||||
|
|
||||||
## Installation
|
## Installation
|
||||||
|
|
||||||
### From Source
|
### From Source
|
||||||
@@ -34,13 +41,17 @@ go install github.com/marte-community/marte-dev-tools/cmd/mdt@latest
|
|||||||
|
|
||||||
### CLI Commands
|
### CLI Commands
|
||||||
|
|
||||||
|
- **Init**: Initialize a MARTe project.
|
||||||
|
```bash
|
||||||
|
mdt init project_name
|
||||||
|
```
|
||||||
- **Check**: Run validation on a file or project.
|
- **Check**: Run validation on a file or project.
|
||||||
```bash
|
```bash
|
||||||
mdt check path/to/project
|
mdt check path/to/project
|
||||||
```
|
```
|
||||||
- **Build**: Merge project files into a single output.
|
- **Build**: Merge project files into a single output.
|
||||||
```bash
|
```bash
|
||||||
mdt build -o output.marte main.marte
|
mdt build [-o output.marte] main.marte ...
|
||||||
```
|
```
|
||||||
- **Format**: Format configuration files.
|
- **Format**: Format configuration files.
|
||||||
```bash
|
```bash
|
||||||
|
|||||||
@@ -16,7 +16,11 @@ import (
|
|||||||
func main() {
|
func main() {
|
||||||
if len(os.Args) < 2 {
|
if len(os.Args) < 2 {
|
||||||
logger.Println("Usage: mdt <command> [arguments]")
|
logger.Println("Usage: mdt <command> [arguments]")
|
||||||
logger.Println("Commands: lsp, build, check, fmt")
|
logger.Println("Commands: lsp, build, check, fmt, init")
|
||||||
|
logger.Println(" build [-o output_file] <input_files...>")
|
||||||
|
logger.Println(" check <input_files...>")
|
||||||
|
logger.Println(" fmt <input_files...>")
|
||||||
|
logger.Println(" init <project_name>")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -30,6 +34,8 @@ func main() {
|
|||||||
runCheck(os.Args[2:])
|
runCheck(os.Args[2:])
|
||||||
case "fmt":
|
case "fmt":
|
||||||
runFmt(os.Args[2:])
|
runFmt(os.Args[2:])
|
||||||
|
case "init":
|
||||||
|
runInit(os.Args[2:])
|
||||||
default:
|
default:
|
||||||
logger.Printf("Unknown command: %s\n", command)
|
logger.Printf("Unknown command: %s\n", command)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -42,12 +48,45 @@ func runLSP() {
|
|||||||
|
|
||||||
func runBuild(args []string) {
|
func runBuild(args []string) {
|
||||||
if len(args) < 1 {
|
if len(args) < 1 {
|
||||||
logger.Println("Usage: mdt build <input_files...>")
|
logger.Println("Usage: mdt build [-o output_file] <input_files...>")
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
}
|
}
|
||||||
|
|
||||||
b := builder.NewBuilder(args)
|
var outputFilePath string
|
||||||
err := b.Build(os.Stdout)
|
var inputFiles []string
|
||||||
|
|
||||||
|
for i := 0; i < len(args); i++ {
|
||||||
|
if args[i] == "-o" {
|
||||||
|
if i+1 < len(args) {
|
||||||
|
outputFilePath = args[i+1]
|
||||||
|
i++
|
||||||
|
} else {
|
||||||
|
logger.Println("Error: -o requires a file path")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputFiles = append(inputFiles, args[i])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(inputFiles) < 1 {
|
||||||
|
logger.Println("Usage: mdt build [-o output_file] <input_files...>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
output := os.Stdout
|
||||||
|
if outputFilePath != "" {
|
||||||
|
f, err := os.Create(outputFilePath)
|
||||||
|
if err != nil {
|
||||||
|
logger.Printf("Error creating output file %s: %v\n", outputFilePath, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
output = f
|
||||||
|
}
|
||||||
|
|
||||||
|
b := builder.NewBuilder(inputFiles)
|
||||||
|
err := b.Build(output)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
logger.Printf("Build failed: %v\n", err)
|
logger.Printf("Build failed: %v\n", err)
|
||||||
os.Exit(1)
|
os.Exit(1)
|
||||||
@@ -61,7 +100,6 @@ func runCheck(args []string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
tree := index.NewProjectTree()
|
tree := index.NewProjectTree()
|
||||||
// configs := make(map[string]*parser.Configuration) // We don't strictly need this map if we just build the tree
|
|
||||||
|
|
||||||
for _, file := range args {
|
for _, file := range args {
|
||||||
content, err := os.ReadFile(file)
|
content, err := os.ReadFile(file)
|
||||||
@@ -80,13 +118,9 @@ func runCheck(args []string) {
|
|||||||
tree.AddFile(file, config)
|
tree.AddFile(file, config)
|
||||||
}
|
}
|
||||||
|
|
||||||
// idx.ResolveReferences() // Not implemented in new tree yet, but Validator uses Tree directly
|
|
||||||
v := validator.NewValidator(tree, ".")
|
v := validator.NewValidator(tree, ".")
|
||||||
v.ValidateProject()
|
v.ValidateProject()
|
||||||
|
|
||||||
// Legacy loop removed as ValidateProject covers it via recursion
|
|
||||||
|
|
||||||
|
|
||||||
for _, diag := range v.Diagnostics {
|
for _, diag := range v.Diagnostics {
|
||||||
level := "ERROR"
|
level := "ERROR"
|
||||||
if diag.Level == validator.LevelWarning {
|
if diag.Level == validator.LevelWarning {
|
||||||
@@ -133,3 +167,32 @@ func runFmt(args []string) {
|
|||||||
logger.Printf("Formatted %s\n", file)
|
logger.Printf("Formatted %s\n", file)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func runInit(args []string) {
|
||||||
|
if len(args) < 1 {
|
||||||
|
logger.Println("Usage: mdt init <project_name>")
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
|
||||||
|
projectName := args[0]
|
||||||
|
if err := os.MkdirAll("src", 0755); err != nil {
|
||||||
|
logger.Fatalf("Error creating project directories: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
files := map[string]string{
|
||||||
|
"Makefile": "MDT=mdt\n\nall: check build\n\ncheck:\n\t$(MDT) check src/*.marte\n\nbuild:\n\t$(MDT) build -o app.marte src/*.marte\n\nfmt:\n\t$(MDT) fmt src/*.marte\n",
|
||||||
|
".marte_schema.cue": "package schema\n\n#Classes: {\n // Add your project-specific classes here\n}\n",
|
||||||
|
"src/app.marte": "#package " + projectName + "\n\n+App = {\n Class = RealTimeApplication\n +Data = {\n Class = ReferenceContainer\n }\n +Functions = {\n Class = ReferenceContainer\n }\n +States = {\n Class = ReferenceContainer\n }\n +Scheduler = {\n Class = GAMScheduler\n TimingDataSource = TimingDataSource\n }\n}\n",
|
||||||
|
"src/data.marte": "#package " + projectName + ".App.Data\n\n// Define your DataSources here\nDefaultDataSource = DDB\n//# Default DB\n+DDB = {\n Class=GAMDataSource\n}\n//# Timing Data Source to track threads timings\n+TimingDataSource = {\n Class = TimingDataSource\n}",
|
||||||
|
"src/functions.marte": "#package " + projectName + ".App.Functions\n\n// Define your GAMs here\n",
|
||||||
|
}
|
||||||
|
|
||||||
|
for path, content := range files {
|
||||||
|
if err := os.WriteFile(path, []byte(content), 0644); err != nil {
|
||||||
|
logger.Fatalf("Error creating file %s: %v", path, err)
|
||||||
|
}
|
||||||
|
logger.Printf("Created %s\n", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
logger.Printf("Project '%s' initialized successfully.\n", projectName)
|
||||||
|
}
|
||||||
|
|||||||
106
docs/CODE_DOCUMENTATION.md
Normal file
106
docs/CODE_DOCUMENTATION.md
Normal file
@@ -0,0 +1,106 @@
|
|||||||
|
# mdt Internal Code Documentation
|
||||||
|
|
||||||
|
This document provides a detailed overview of the `mdt` codebase architecture and internal components.
|
||||||
|
|
||||||
|
## Architecture Overview
|
||||||
|
|
||||||
|
`mdt` is built as a modular system where core functionalities are separated into internal packages. The data flow typically follows this pattern:
|
||||||
|
|
||||||
|
1. **Parsing**: Source code is parsed into an Abstract Syntax Tree (AST).
|
||||||
|
2. **Indexing**: ASTs from multiple files are aggregated into a unified `ProjectTree`.
|
||||||
|
3. **Processing**: The `ProjectTree` is used by the Validator, Builder, and LSP server to perform their respective tasks.
|
||||||
|
|
||||||
|
## Package Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
cmd/
|
||||||
|
mdt/ # Application entry point (CLI)
|
||||||
|
internal/
|
||||||
|
builder/ # Logic for merging and building configurations
|
||||||
|
formatter/ # Code formatting engine
|
||||||
|
index/ # Symbol table and project structure management
|
||||||
|
logger/ # Centralized logging
|
||||||
|
lsp/ # Language Server Protocol implementation
|
||||||
|
parser/ # Lexer, Parser, and AST definitions
|
||||||
|
schema/ # CUE schema loading and integration
|
||||||
|
validator/ # Semantic analysis and validation logic
|
||||||
|
```
|
||||||
|
|
||||||
|
## Core Packages
|
||||||
|
|
||||||
|
### 1. `internal/parser`
|
||||||
|
|
||||||
|
Responsible for converting MARTe configuration text into structured data.
|
||||||
|
|
||||||
|
* **Lexer (`lexer.go`)**: Tokenizes the input stream. Handles MARTe specific syntax like `#package`, `//!` pragmas, and `//#` docstrings. Supports standard identifiers and `#`-prefixed identifiers.
|
||||||
|
* **Parser (`parser.go`)**: Recursive descent parser. Converts tokens into a `Configuration` object containing definitions, comments, and pragmas.
|
||||||
|
* **AST (`ast.go`)**: Defines the node types (`ObjectNode`, `Field`, `Value`, etc.). All nodes implement the `Node` interface providing position information.
|
||||||
|
|
||||||
|
### 2. `internal/index`
|
||||||
|
|
||||||
|
The brain of the system. It maintains a holistic view of the project.
|
||||||
|
|
||||||
|
* **ProjectTree**: The central data structure. It holds the root of the configuration hierarchy (`Root`), references, and isolated files.
|
||||||
|
* **ProjectNode**: Represents a logical node in the configuration. Since a node can be defined across multiple files (fragments), `ProjectNode` aggregates these fragments.
|
||||||
|
* **NodeMap**: A hash map index (`map[string][]*ProjectNode`) for $O(1)$ symbol lookups, optimizing `FindNode` operations.
|
||||||
|
* **Reference Resolution**: The `ResolveReferences` method links `Reference` objects to their target `ProjectNode` using the `NodeMap`.
|
||||||
|
|
||||||
|
### 3. `internal/validator`
|
||||||
|
|
||||||
|
Ensures configuration correctness.
|
||||||
|
|
||||||
|
* **Validator**: Iterates over the `ProjectTree` to check rules.
|
||||||
|
* **Checks**:
|
||||||
|
* **Structure**: Duplicate fields, invalid content.
|
||||||
|
* **Schema**: Unifies nodes with CUE schemas (loaded via `internal/schema`) to validate types and mandatory fields.
|
||||||
|
* **Signals**: Verifies that signals referenced in GAMs exist in DataSources and match types.
|
||||||
|
* **Threading**: Checks `checkDataSourceThreading` to ensure non-multithreaded DataSources are not shared across threads in the same state.
|
||||||
|
* **Unused**: Detects unused GAMs and Signals (suppressible via pragmas).
|
||||||
|
|
||||||
|
### 4. `internal/lsp`
|
||||||
|
|
||||||
|
Implements the Language Server Protocol.
|
||||||
|
|
||||||
|
* **Server (`server.go`)**: Handles JSON-RPC messages over stdio.
|
||||||
|
* **Incremental Sync**: Supports `textDocumentSync: 2`. `HandleDidChange` applies patches to the in-memory document buffers using `offsetAt` logic.
|
||||||
|
* **Features**:
|
||||||
|
* `HandleCompletion`: Context-aware suggestions (Schema fields, Signal references, Class names).
|
||||||
|
* `HandleHover`: Shows documentation, signal types, and usage analysis (e.g., "Used in GAMs: Controller (Input)").
|
||||||
|
* `HandleDefinition` / `HandleReferences`: specific lookup using the `index`.
|
||||||
|
|
||||||
|
### 5. `internal/builder`
|
||||||
|
|
||||||
|
Merges multiple MARTe files into a single output.
|
||||||
|
|
||||||
|
* **Logic**: It parses all input files, builds a temporary `ProjectTree`, and then reconstructs the source code.
|
||||||
|
* **Merging**: It interleaves fields and subnodes from different file fragments to produce a coherent single-file configuration, respecting the `#package` hierarchy.
|
||||||
|
|
||||||
|
### 6. `internal/schema`
|
||||||
|
|
||||||
|
Manages CUE schemas.
|
||||||
|
|
||||||
|
* **Loading**: Loads the embedded default schema (`marte.cue`) and merges it with any user-provided `.marte_schema.cue`.
|
||||||
|
* **Metadata**: Handles the `#meta` field in schemas to extract properties like `direction` and `multithreaded` support for the validator.
|
||||||
|
|
||||||
|
## Key Data Flows
|
||||||
|
|
||||||
|
### Reference Resolution
|
||||||
|
1. **Scan**: Files are parsed and added to the `ProjectTree`.
|
||||||
|
2. **Index**: `RebuildIndex` populates `NodeMap`.
|
||||||
|
3. **Resolve**: `ResolveReferences` iterates all recorded references (values) and calls `FindNode`.
|
||||||
|
4. **Link**: If found, `ref.Target` is set to the `ProjectNode`.
|
||||||
|
|
||||||
|
### Validation Lifecycle
|
||||||
|
1. `mdt check` or LSP `didChange` triggers validation.
|
||||||
|
2. A new `Validator` is created with the current `Tree`.
|
||||||
|
3. `ValidateProject` is called.
|
||||||
|
4. It walks the tree, runs checks, and populates `Diagnostics`.
|
||||||
|
5. Diagnostics are printed (CLI) or published via `textDocument/publishDiagnostics` (LSP).
|
||||||
|
|
||||||
|
### Threading Check Logic
|
||||||
|
1. Finds the `RealTimeApplication` node.
|
||||||
|
2. Iterates through `States` and `Threads`.
|
||||||
|
3. For each Thread, resolves the `Functions` (GAMs).
|
||||||
|
4. For each GAM, resolves connected `DataSources` via Input/Output signals.
|
||||||
|
5. Maps `DataSource -> Thread` within the context of a State.
|
||||||
|
6. If a DataSource is seen in >1 Thread, it checks the `#meta.multithreaded` property. If false (default), an error is raised.
|
||||||
162
docs/CONFIGURATION_GUIDE.md
Normal file
162
docs/CONFIGURATION_GUIDE.md
Normal file
@@ -0,0 +1,162 @@
|
|||||||
|
# MARTe Configuration Guide
|
||||||
|
|
||||||
|
This guide explains the syntax, features, and best practices for writing MARTe configurations using `mdt`.
|
||||||
|
|
||||||
|
## 1. Syntax Overview
|
||||||
|
|
||||||
|
MARTe configurations use a hierarchical object-oriented syntax.
|
||||||
|
|
||||||
|
### Objects (Nodes)
|
||||||
|
Objects are defined using `+` (public/instantiated) or `$` (template/class-like) prefixes. Every object **must** have a `Class` field.
|
||||||
|
|
||||||
|
```marte
|
||||||
|
+MyObject = {
|
||||||
|
Class = MyClass
|
||||||
|
Field1 = 100
|
||||||
|
Field2 = "Hello"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Fields and Values
|
||||||
|
- **Fields**: Alphanumeric identifiers (e.g., `Timeout`, `CycleTime`).
|
||||||
|
- **Values**:
|
||||||
|
- Integers: `10`, `-5`, `0xFA`
|
||||||
|
- Floats: `3.14`, `1e-3`
|
||||||
|
- Strings: `"Text"`
|
||||||
|
- Booleans: `true`, `false`
|
||||||
|
- References: `MyObject`, `MyObject.SubNode`
|
||||||
|
- Arrays: `{ 1 2 3 }` or `{ "A" "B" }`
|
||||||
|
|
||||||
|
### Comments and Documentation
|
||||||
|
- Line comments: `// This is a comment`
|
||||||
|
- Docstrings: `//# This documents the following node`. These appear in hover tooltips.
|
||||||
|
|
||||||
|
```marte
|
||||||
|
//# This is the main application
|
||||||
|
+App = { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
## 2. Signals and Data Flow
|
||||||
|
|
||||||
|
Signals define how data moves between DataSources (drivers) and GAMs (algorithms).
|
||||||
|
|
||||||
|
### Defining Signals
|
||||||
|
Signals are typically defined in a `DataSource`. They must have a `Type`.
|
||||||
|
|
||||||
|
```marte
|
||||||
|
+MyDataSource = {
|
||||||
|
Class = GAMDataSource
|
||||||
|
Signals = {
|
||||||
|
Signal1 = { Type = uint32 }
|
||||||
|
Signal2 = { Type = float32 }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using Signals in GAMs
|
||||||
|
GAMs declare inputs and outputs. You can refer to signals directly or alias them.
|
||||||
|
|
||||||
|
```marte
|
||||||
|
+MyGAM = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
Signal1 = {
|
||||||
|
DataSource = MyDataSource
|
||||||
|
Type = uint32 // Must match DataSource definition
|
||||||
|
}
|
||||||
|
MyAlias = {
|
||||||
|
Alias = Signal2
|
||||||
|
DataSource = MyDataSource
|
||||||
|
Type = float32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Threading Rules
|
||||||
|
**Validation Rule**: A DataSource that is **not** marked as multithreaded (default) cannot be used by GAMs running in different threads within the same State.
|
||||||
|
|
||||||
|
To allow sharing, the DataSource class in the schema must have `#meta: multithreaded: true`.
|
||||||
|
|
||||||
|
## 3. Schemas and Validation
|
||||||
|
|
||||||
|
`mdt` validates your configuration against CUE schemas.
|
||||||
|
|
||||||
|
### Built-in Schema
|
||||||
|
Common classes (`RealTimeApplication`, `StateMachine`, `IOGAM`, etc.) are built-in.
|
||||||
|
|
||||||
|
### Custom Schemas
|
||||||
|
You can extend the schema by creating a `.marte_schema.cue` file in your project root.
|
||||||
|
|
||||||
|
**Example: Adding a custom GAM**
|
||||||
|
|
||||||
|
```cue
|
||||||
|
package schema
|
||||||
|
|
||||||
|
#Classes: {
|
||||||
|
MyCustomGAM: {
|
||||||
|
// Metadata for Validator/LSP
|
||||||
|
#meta: {
|
||||||
|
direction: "INOUT" // "IN", "OUT", "INOUT"
|
||||||
|
multithreaded: false
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fields
|
||||||
|
Gain: float
|
||||||
|
Offset?: float // Optional
|
||||||
|
InputSignals: {...}
|
||||||
|
OutputSignals: {...}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. Multi-file Projects
|
||||||
|
|
||||||
|
You can split your configuration into multiple files.
|
||||||
|
|
||||||
|
### Namespaces
|
||||||
|
Use `#package` to define where the file's content fits in the hierarchy.
|
||||||
|
|
||||||
|
**file1.marte**
|
||||||
|
```marte
|
||||||
|
#package MyApp.Controller
|
||||||
|
+MyController = { ... }
|
||||||
|
```
|
||||||
|
|
||||||
|
This places `MyController` under `MyApp.Controller`.
|
||||||
|
|
||||||
|
### Building
|
||||||
|
The `build` command merges all files.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdt build -o final.marte src/*.marte
|
||||||
|
```
|
||||||
|
|
||||||
|
## 5. Pragmas (Suppressing Warnings)
|
||||||
|
|
||||||
|
If validation is too strict, you can suppress warnings using pragmas (`//!`).
|
||||||
|
|
||||||
|
- **Suppress Unused Warning**:
|
||||||
|
```marte
|
||||||
|
+MyGAM = {
|
||||||
|
Class = IOGAM
|
||||||
|
//! ignore(unused): This GAM is triggered externally
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Suppress Implicit Signal Warning**:
|
||||||
|
```marte
|
||||||
|
InputSignals = {
|
||||||
|
//! ignore(implicit)
|
||||||
|
ImplicitSig = { Type = uint32 }
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
- **Type Casting**:
|
||||||
|
```marte
|
||||||
|
Sig1 = {
|
||||||
|
//! cast(uint32, int32): Intentional mismatch
|
||||||
|
DataSource = DS
|
||||||
|
Type = int32
|
||||||
|
}
|
||||||
|
```
|
||||||
158
docs/EDITOR_INTEGRATION.md
Normal file
158
docs/EDITOR_INTEGRATION.md
Normal file
@@ -0,0 +1,158 @@
|
|||||||
|
# Editor Integration Guide
|
||||||
|
|
||||||
|
`mdt` includes a Language Server Protocol (LSP) implementation that provides features like:
|
||||||
|
|
||||||
|
- Syntax highlighting and error reporting
|
||||||
|
- Auto-completion
|
||||||
|
- Go to Definition / References
|
||||||
|
- Hover documentation
|
||||||
|
- Symbol renaming
|
||||||
|
|
||||||
|
The LSP server is started via the command:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdt lsp
|
||||||
|
```
|
||||||
|
|
||||||
|
It communicates via **stdio**.
|
||||||
|
|
||||||
|
## VS Code
|
||||||
|
|
||||||
|
You can use a generic LSP extension like [Generic LSP Client](https://marketplace.visualstudio.com/items?itemName=summne.vscode-generic-lsp-client) or configure a custom task.
|
||||||
|
|
||||||
|
**Using "Run on Save" or similar extensions is an option, but for true LSP support:**
|
||||||
|
|
||||||
|
1. Install the **"glspc"** (Generic LSP Client) extension or similar.
|
||||||
|
2. Configure it in your `settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"glspc.languageServer configurations": [
|
||||||
|
{
|
||||||
|
"languageId": "marte",
|
||||||
|
"command": "mdt",
|
||||||
|
"args": ["lsp"],
|
||||||
|
"rootUri": "${workspaceFolder}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
3. Associate `.marte` files with the language ID:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"files.associations": {
|
||||||
|
"*.marte": "marte"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Neovim (Native LSP)
|
||||||
|
|
||||||
|
Add the following to your `init.lua` or `init.vim` (using `nvim-lspconfig`):
|
||||||
|
|
||||||
|
```lua
|
||||||
|
local lspconfig = require'lspconfig'
|
||||||
|
local configs = require'lspconfig.configs'
|
||||||
|
|
||||||
|
if not configs.marte then
|
||||||
|
configs.marte = {
|
||||||
|
default_config = {
|
||||||
|
cmd = {'mdt', 'lsp'},
|
||||||
|
filetypes = {'marte'},
|
||||||
|
root_dir = lspconfig.util.root_pattern('.git', 'go.mod', '.marte_schema.cue'),
|
||||||
|
settings = {},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
|
lspconfig.marte.setup{}
|
||||||
|
|
||||||
|
-- Add filetype detection
|
||||||
|
vim.cmd([[
|
||||||
|
autocmd BufNewFile,BufRead *.marte setfiletype marte
|
||||||
|
]])
|
||||||
|
```
|
||||||
|
|
||||||
|
## Helix
|
||||||
|
|
||||||
|
Add this to your `languages.toml` (usually in `~/.config/helix/languages.toml`):
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[[language]]
|
||||||
|
name = "marte"
|
||||||
|
scope = "source.marte"
|
||||||
|
injection-regex = "marte"
|
||||||
|
file-types = ["marte"]
|
||||||
|
roots = [".git", ".marte_schema.cue"]
|
||||||
|
comment-token = "//"
|
||||||
|
indent = { tab-width = 2, unit = " " }
|
||||||
|
language-servers = [ "mdt-lsp" ]
|
||||||
|
|
||||||
|
[language-server.mdt-lsp]
|
||||||
|
command = "mdt"
|
||||||
|
args = ["lsp"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Vim
|
||||||
|
|
||||||
|
### Using `vim-lsp`
|
||||||
|
|
||||||
|
```vim
|
||||||
|
if executable('mdt')
|
||||||
|
au User lsp_setup call lsp#register_server({
|
||||||
|
\ 'name': 'mdt-lsp',
|
||||||
|
\ 'cmd': {server_info->['mdt', 'lsp']},
|
||||||
|
\ 'whitelist': ['marte'],
|
||||||
|
\ })
|
||||||
|
endif
|
||||||
|
|
||||||
|
au BufRead,BufNewFile *.marte set filetype=marte
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using `ALE`
|
||||||
|
|
||||||
|
```vim
|
||||||
|
call ale#linter#define('marte', {
|
||||||
|
\ 'name': 'mdt',
|
||||||
|
\ 'lsp': 'stdio',
|
||||||
|
\ 'executable': 'mdt',
|
||||||
|
\ 'command': '%e lsp',
|
||||||
|
\ 'project_root': function('ale#handlers#python#FindProjectRoot'),
|
||||||
|
\})
|
||||||
|
```
|
||||||
|
|
||||||
|
## Zed
|
||||||
|
|
||||||
|
Add to your `settings.json`:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"lsp": {
|
||||||
|
"marte": {
|
||||||
|
"binary": {
|
||||||
|
"path": "mdt",
|
||||||
|
"arguments": ["lsp"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Kakoune (kak-lsp)
|
||||||
|
|
||||||
|
In your `kak-lsp.toml`:
|
||||||
|
|
||||||
|
```toml
|
||||||
|
[language.marte]
|
||||||
|
filetypes = ["marte"]
|
||||||
|
roots = [".git", ".marte_schema.cue"]
|
||||||
|
command = "mdt"
|
||||||
|
args = ["lsp"]
|
||||||
|
```
|
||||||
|
|
||||||
|
## Eclipse
|
||||||
|
|
||||||
|
1. Install **LSP4E** plugin.
|
||||||
|
2. Go to **Preferences > Language Servers**.
|
||||||
|
3. Add a new Language Server:
|
||||||
|
- **Content Type**: Text / Custom (Associate `*.marte` with a content type).
|
||||||
|
- **Launch configuration**: Program.
|
||||||
|
- **Command**: `mdt`
|
||||||
|
- **Arguments**: `lsp`
|
||||||
|
- **Input/Output**: Standard Input/Output.
|
||||||
173
docs/TUTORIAL.md
Normal file
173
docs/TUTORIAL.md
Normal file
@@ -0,0 +1,173 @@
|
|||||||
|
# Creating a MARTe Application with mdt
|
||||||
|
|
||||||
|
This tutorial will guide you through creating, building, and validating a complete MARTe application using the `mdt` toolset.
|
||||||
|
|
||||||
|
## Prerequisites
|
||||||
|
|
||||||
|
- `mdt` installed and available in your PATH.
|
||||||
|
- `make` (optional but recommended).
|
||||||
|
|
||||||
|
## Step 1: Initialize the Project
|
||||||
|
|
||||||
|
Start by creating a new project named `MyControlApp`.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdt init MyControlApp
|
||||||
|
cd MyControlApp
|
||||||
|
```
|
||||||
|
|
||||||
|
This command creates a standard project structure:
|
||||||
|
|
||||||
|
- `Makefile`: For building and checking the project.
|
||||||
|
- `.marte_schema.cue`: For defining custom schemas (if needed).
|
||||||
|
- `src/app.marte`: The main application definition.
|
||||||
|
- `src/components.marte`: A placeholder for defining components (DataSources).
|
||||||
|
|
||||||
|
## Step 2: Define Components
|
||||||
|
|
||||||
|
Open `src/components.marte`. This file uses the `#package App.Data` namespace, meaning all definitions here will be children of `App.Data`.
|
||||||
|
|
||||||
|
Let's define a **Timer** (input source) and a **Logger** (output destination).
|
||||||
|
|
||||||
|
```marte
|
||||||
|
#package MyContollApp.App.Data
|
||||||
|
|
||||||
|
+DDB = {
|
||||||
|
Class = GAMDataSource
|
||||||
|
}
|
||||||
|
+TimingDataSource = {
|
||||||
|
Class = TimingDataSource
|
||||||
|
}
|
||||||
|
+Timer = {
|
||||||
|
Class = LinuxTimer
|
||||||
|
Signals = {
|
||||||
|
Counter = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
Time = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+Logger = {
|
||||||
|
Class = LoggerDataSource
|
||||||
|
Signals = {
|
||||||
|
LogValue = {
|
||||||
|
Type = float32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 3: Implement Logic (GAM)
|
||||||
|
|
||||||
|
Open `src/app.marte`. This file defines the `App` node.
|
||||||
|
|
||||||
|
We will add a GAM that takes the time from the Timer, converts it, and logs it.
|
||||||
|
|
||||||
|
Add the GAM definition inside the `+Main` object (or as a separate object if you prefer modularity). Let's modify `src/app.marte`:
|
||||||
|
|
||||||
|
```marte
|
||||||
|
#package MyContollApp
|
||||||
|
+App = {
|
||||||
|
Class = RealTimeApplication
|
||||||
|
+Functions = {
|
||||||
|
Class = RefenceContainer
|
||||||
|
// Define the GAM
|
||||||
|
+Converter = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
TimeIn = {
|
||||||
|
DataSource = Timer
|
||||||
|
Type = uint32
|
||||||
|
Frequency = 100 //Hz
|
||||||
|
Alias = Time // Refers to 'Time' signal in Timer
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutputSignals = {
|
||||||
|
LogOut = {
|
||||||
|
DataSource = Logger
|
||||||
|
Type = float32
|
||||||
|
Alias = LogValue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+States = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+Run = {
|
||||||
|
Class = RealTimeState
|
||||||
|
+MainThread = {
|
||||||
|
Class = RealTimeThread
|
||||||
|
Functions = { Converter } // Run our GAM
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
+Data = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
DefaultDataSource = DDB
|
||||||
|
}
|
||||||
|
+Scheduler = {
|
||||||
|
Class = GAMScheduler
|
||||||
|
TimingDataSource = TimingDataSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Step 4: Validate
|
||||||
|
|
||||||
|
Run the validation check to ensure everything is correct (types match, references are valid).
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdt check src/*.marte
|
||||||
|
```
|
||||||
|
|
||||||
|
Or using Make:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make check
|
||||||
|
```
|
||||||
|
|
||||||
|
If you made a mistake (e.g., mismatched types), `mdt` will report an error.
|
||||||
|
|
||||||
|
## Step 5: Build
|
||||||
|
|
||||||
|
Merge all files into a single configuration file.
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mdt build -o final_app.marte src/*.marte
|
||||||
|
```
|
||||||
|
|
||||||
|
Or using Make:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
This produces `app.marte` (or `final_app.marte`), which contains the flattened, merged configuration ready for the MARTe framework.
|
||||||
|
|
||||||
|
## Step 6: Advanced - Custom Schema
|
||||||
|
|
||||||
|
Suppose you want to enforce that your DataSources support multithreading. You can modify `.marte_schema.cue`.
|
||||||
|
|
||||||
|
```cue
|
||||||
|
package schema
|
||||||
|
|
||||||
|
#Classes: {
|
||||||
|
// Enforce that LinuxTimer must be multithreaded (example)
|
||||||
|
LinuxTimer: {
|
||||||
|
#meta: {
|
||||||
|
multithreaded: true
|
||||||
|
}
|
||||||
|
...
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
Now, if you use `LinuxTimer` in multiple threads, `mdt check` will allow it (because of `#meta.multithreaded: true`). By default, it would disallow it.
|
||||||
|
|
||||||
|
## Conclusion
|
||||||
|
|
||||||
|
You have successfully initialized, implemented, validated, and built a MARTe application using `mdt`.
|
||||||
44
examples/README.md
Normal file
44
examples/README.md
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
# Examples
|
||||||
|
|
||||||
|
This directory contains example projects demonstrating different features and usage patterns of `mdt`.
|
||||||
|
|
||||||
|
## Directory Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
examples/
|
||||||
|
simple/ # A basic, single-file application
|
||||||
|
complex/ # A multi-file project with custom schema
|
||||||
|
README.md # This file
|
||||||
|
```
|
||||||
|
|
||||||
|
## Running Examples
|
||||||
|
|
||||||
|
Prerequisite: `mdt` must be built (or installed). The Makefiles in the examples assume `mdt` is available at `../../build/mdt`.
|
||||||
|
|
||||||
|
### Simple Project
|
||||||
|
|
||||||
|
Demonstrates a minimal setup:
|
||||||
|
- Single `main.marte` file.
|
||||||
|
- Basic Thread and GAM definition.
|
||||||
|
|
||||||
|
**Run:**
|
||||||
|
```bash
|
||||||
|
cd simple
|
||||||
|
make check
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
### Complex Project
|
||||||
|
|
||||||
|
Demonstrates advanced features:
|
||||||
|
- **Multi-file Structure**: `src/app.marte` (Logic) and `src/components.marte` (Data).
|
||||||
|
- **Namespaces**: Use of `#package` to organize nodes.
|
||||||
|
- **Custom Schema**: `.marte_schema.cue` defines a custom class (`CustomController`) with specific metadata (`#meta.multithreaded`).
|
||||||
|
- **Validation**: Enforces strict typing and custom rules.
|
||||||
|
|
||||||
|
**Run:**
|
||||||
|
```bash
|
||||||
|
cd complex
|
||||||
|
make check
|
||||||
|
make build
|
||||||
|
```
|
||||||
12
examples/complex/.marte_schema.cue
Normal file
12
examples/complex/.marte_schema.cue
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package schema
|
||||||
|
|
||||||
|
#Classes: {
|
||||||
|
CustomController: {
|
||||||
|
#meta: {
|
||||||
|
multithreaded: false
|
||||||
|
}
|
||||||
|
Gain: float
|
||||||
|
InputSignals: {...}
|
||||||
|
OutputSignals: {...}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
examples/complex/Makefile
Normal file
12
examples/complex/Makefile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
MDT=../../build/mdt
|
||||||
|
|
||||||
|
all: check build
|
||||||
|
|
||||||
|
check:
|
||||||
|
$(MDT) check src/*.marte
|
||||||
|
|
||||||
|
build:
|
||||||
|
$(MDT) build -o app_full.marte src/*.marte
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
$(MDT) fmt src/*.marte
|
||||||
42
examples/complex/src/app.marte
Normal file
42
examples/complex/src/app.marte
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
#package complex_ex
|
||||||
|
|
||||||
|
+App = {
|
||||||
|
Class = RealTimeApplication
|
||||||
|
+States = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+Run = {
|
||||||
|
Class = RealTimeState
|
||||||
|
+ControlThread = {
|
||||||
|
Class = RealTimeThread
|
||||||
|
Functions = { Controller }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Functions = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+Controller = {
|
||||||
|
Class = CustomController // Defined in .marte_schema.cue
|
||||||
|
Gain = 10.5
|
||||||
|
InputSignals = {
|
||||||
|
Ref = {
|
||||||
|
DataSource = App.Data.References
|
||||||
|
Type = float32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutputSignals = {
|
||||||
|
Actuation = {
|
||||||
|
DataSource = App.Data.Actuators
|
||||||
|
Type = float32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Data = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
DefaultDataSource = DDB1
|
||||||
|
}
|
||||||
|
+Scheduler = {
|
||||||
|
Class = GAMScheduler
|
||||||
|
TimingDataSource = TimingDS
|
||||||
|
}
|
||||||
|
}
|
||||||
24
examples/complex/src/components.marte
Normal file
24
examples/complex/src/components.marte
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
#package complex_ex.App.Data
|
||||||
|
|
||||||
|
+References = {
|
||||||
|
Class = GAMDataSource
|
||||||
|
Signals = {
|
||||||
|
Ref = {
|
||||||
|
Type = float32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Actuators = {
|
||||||
|
Class = GAMDataSource
|
||||||
|
Signals = {
|
||||||
|
Actuation = {
|
||||||
|
Type = float32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+TimingDS = {
|
||||||
|
Class = TimingDataSource
|
||||||
|
}
|
||||||
|
+DDB1 = {
|
||||||
|
Class = GAMDataSource
|
||||||
|
}
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
//!allow(unused): Ignore unused GAMs in this file
|
|
||||||
//!allow(implicit): Ignore implicit signals in this file
|
|
||||||
|
|
||||||
+Data = {
|
|
||||||
Class = ReferenceContainer
|
|
||||||
+MyDS = {
|
|
||||||
Class = FileReader
|
|
||||||
Filename = "test"
|
|
||||||
Signals = {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
+MyGAM = {
|
|
||||||
Class = IOGAM
|
|
||||||
InputSignals = {
|
|
||||||
// Implicit signal (not in MyDS)
|
|
||||||
ImplicitSig = {
|
|
||||||
DataSource = MyDS
|
|
||||||
Type = uint32
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Unused GAM
|
|
||||||
+UnusedGAM = {
|
|
||||||
Class = IOGAM
|
|
||||||
}
|
|
||||||
12
examples/simple/Makefile
Normal file
12
examples/simple/Makefile
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
MDT=../../build/mdt
|
||||||
|
|
||||||
|
all: check build
|
||||||
|
|
||||||
|
check:
|
||||||
|
$(MDT) check main.marte
|
||||||
|
|
||||||
|
build:
|
||||||
|
$(MDT) build -o output.marte main.marte
|
||||||
|
|
||||||
|
fmt:
|
||||||
|
$(MDT) fmt main.marte
|
||||||
60
examples/simple/main.marte
Normal file
60
examples/simple/main.marte
Normal file
@@ -0,0 +1,60 @@
|
|||||||
|
//# Main Application
|
||||||
|
+App = {
|
||||||
|
Class = RealTimeApplication
|
||||||
|
+Data = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
DefaultDataSource = DDB1
|
||||||
|
+Timer = {
|
||||||
|
Class = LinuxTimer
|
||||||
|
Signals = {
|
||||||
|
Counter = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
//! unused: Time variable is not used
|
||||||
|
Time = {
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Logger = {
|
||||||
|
Class = LoggerDataSource
|
||||||
|
}
|
||||||
|
+DDB1 = {
|
||||||
|
Class = GAMDataSource
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+States = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+Idle = {
|
||||||
|
Class = RealTimeState
|
||||||
|
+Thread1 = {
|
||||||
|
Class = RealTimeThread
|
||||||
|
CPUs = 0x1
|
||||||
|
Functions = { MyGAM }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Functions = {
|
||||||
|
Class = ReferenceContainer
|
||||||
|
+MyGAM = {
|
||||||
|
Class = IOGAM
|
||||||
|
InputSignals = {
|
||||||
|
Counter = {
|
||||||
|
DataSource = Timer
|
||||||
|
Type = uint32
|
||||||
|
Frequency = 100 //Hz
|
||||||
|
}
|
||||||
|
}
|
||||||
|
OutputSignals = {
|
||||||
|
CounterCopy = {
|
||||||
|
DataSource = Logger
|
||||||
|
Type = uint32
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
+Scheduler = {
|
||||||
|
Class = GAMScheduler
|
||||||
|
TimingDataSource = Timer
|
||||||
|
}
|
||||||
|
}
|
||||||
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,7 @@ The LSP server should provide the following capabilities:
|
|||||||
- **Build Process**:
|
- **Build Process**:
|
||||||
- The build tool merges all files sharing the same base namespace into a **single output configuration**.
|
- The build tool merges all files sharing the same base namespace into a **single output configuration**.
|
||||||
- **Namespace Consistency**: The build tool must verify that all input files belong to the same project namespace (the first segment of the `#package` URI). If multiple project namespaces are detected, the build must fail with an error.
|
- **Namespace Consistency**: The build tool must verify that all input files belong to the same project namespace (the first segment of the `#package` URI). If multiple project namespaces are detected, the build must fail with an error.
|
||||||
- **Target**: The build output is written to a single target file (e.g., provided via CLI or API).
|
- **Target**: The build output is written to standard output (`stdout`) by default. It can be written to a target file if the `-o` (or `--output`) argument is provided via CLI.
|
||||||
- **Multi-File Definitions**: Nodes and objects can be defined across multiple files. The build tool, validator, and LSP must merge these definitions (including all fields and sub-nodes) from the entire project to create a unified view before processing or validating.
|
- **Multi-File Definitions**: Nodes and objects can be defined across multiple files. The build tool, validator, and LSP must merge these definitions (including all fields and sub-nodes) from the entire project to create a unified view before processing or validating.
|
||||||
- **Global References**: References to nodes, signals, or objects can point to definitions located in any file within the project. Support for dot-separated paths (e.g., `Node.SubNode`) is required.
|
- **Global References**: References to nodes, signals, or objects can point to definitions located in any file within the project. Support for dot-separated paths (e.g., `Node.SubNode`) is required.
|
||||||
- **Merging Order**: For objects defined across multiple files, definitions are merged. The build tool must preserve the relative order of fields and sub-nodes as they appear in the source files, interleaving them correctly in the final output.
|
- **Merging Order**: For objects defined across multiple files, definitions are merged. The build tool must preserve the relative order of fields and sub-nodes as they appear in the source files, interleaving them correctly in the final output.
|
||||||
|
|||||||
Reference in New Issue
Block a user