Files
tia-lsp/README.md
T

223 lines
7.4 KiB
Markdown

# tia-lsp - Language Server for Siemens TIA Portal
A standalone LSP (Language Server Protocol) server for Siemens TIA Portal languages. Currently supports **SCL** (Structured Control Language), with planned support for **STL/AWL**, **LAD**, and **FBD**.
The server speaks JSON-RPC over stdio and is editor-agnostic — usable from Neovim, VS Code, or any LSP client. For Neovim users, the companion plugin [`tia-lsp.nvim`](https://gitea.l-tech.rs/lazar/tia-lsp.nvim) provides seamless integration (auto-prefix, blink.cmp source, workspace scanning, attribute toggle, etc.).
## Features
### LSP (Language Server Protocol)
| Feature | Description |
|---------|-------------|
| **Hover** | Variable type and struct fields |
| **Completion** | `#` for variables, `.` for struct fields, `(` for functions |
| **Go to Definition** | Jump to function, FB, DB, or UDT definition |
| **Document Symbols** | Shows functions, variables, types |
| **Workspace Symbols** | Search across all `.scl` files with fuzzy matching |
| **Semantic Tokens** | Keywords, variables highlighted |
| **Inlay Hints** | Shows variable types after declaration |
| **External UDT** | Load types from `.plc.json` or `data_types/` folder |
### Linter (Diagnostics)
| Feature | Description |
|---------|-------------|
| **Undefined Variables** | Detects variables used without declaration |
| **Invalid # Prefix** | Detects `#` prefix in VAR sections |
| **Unknown Types** | Warns about unknown data types |
| **Type Validation** | Basic type checking |
### Formatter
| Feature | Description |
|---------|-------------|
| **Document Formatting** | Format entire SCL files |
| **Multi-line Assignments** | Preserves and formats multi-line expressions with proper alignment |
| **FB Call Formatting** | Formats function block calls in multiline style |
| **Indentation** | Proper block indentation |
| **Keyword Casing** | Maintains SCL keyword casing |
### Tree-sitter Grammar
- Full SCL syntax support via tree-sitter
- Block definitions: ORGANIZATION_BLOCK, FUNCTION_BLOCK, FUNCTION
- Variable sections: VAR_INPUT, VAR_OUTPUT, VAR_IN_OUT, VAR_TEMP, VAR_CONSTANT
- Control structures: IF/THEN/ELSIF/ELSE/END_IF, CASE/OF/END_CASE, FOR/TO/BY/DO/END_FOR, WHILE/END_WHILE, REPEAT/UNTIL/END_REPEAT
- Built-in data types: BOOL, INT, DINT, REAL, STRING, TIME, etc.
- Function block instances highlighted in calls
- FB parameter names (IN, PT, CU, etc.) highlighted as properties
- `#` prefix for local variables highlighted
- Operators: AND, OR, NOT, XOR, MOD
- Comments: line (`//`), block (`(* *)`), C-style (`/* */`)
## Running the Server
```bash
# Start the LSP server (reads JSON-RPC from stdin, writes to stdout)
lua src/main.lua
# Run server tests
make test
# or: lua src/main.lua --test
```
### Neovim + Mason (Recommended)
The recommended way is via [mason.nvim](https://github.com/mason-org/mason.nvim) using the
[`tia-mason-registry`](https://gitea.l-tech.rs/lazar/tia-mason-registry). See the
[`tia-lsp.nvim` README](https://gitea.l-tech.rs/lazar/tia-lsp.nvim) for full step-by-step instructions.
### Manual Setup
```bash
# 1. Clone this repository
git clone --depth 1 https://gitea.l-tech.rs/lazar/tia-lsp.git ~/tia-lsp
# 2. Compile the tree-sitter parser
cc -fPIC -I ~/tia-lsp/src/tree_sitter -c ~/tia-lsp/src/parser.c -o ~/tia-lsp/parser.o
cc -shared ~/tia-lsp/parser.o -o ~/tia-lsp/parser.so
# 3. Start the server (or configure your editor to run this)
lua ~/tia-lsp/src/main.lua
```
## External UDT Support
### Option 1: Create `.plc.json`
```json
{
"dataTypes": [
{
"name": "equipmentData",
"struct": [
{"name": "id", "dataTypeName": "INT"},
{"name": "status", "dataTypeName": "BOOL"},
{"name": "temperature", "dataTypeName": "REAL"}
]
}
]
}
```
### Option 2: Auto-scan `data_types/` folder
The LSP automatically scans for SCL files in `data_types/` folder and extracts TYPE definitions.
### Option 3: Generate `.plc.json`
```bash
lua src/plc_json.lua --generate
```
## Global Data Block Support
The server scans for `.db` files in the workspace and provides auto-completion for global data blocks and their members.
### How it works
1. **Workspace Scanning**: When opening a `.scl` file, the server scans the project for `.db` files
2. **DB Parsing**: Parses `DATA_BLOCK` definitions and extracts members from `VAR` sections
3. **Auto-completion**: Provides completion for DB names and members
### Example
```scl
// Access global DB members
"HmiData".units
"Parameter".machine.unit100.paramTimeStampApplied
// Inside FB call parameter
#instParameterManager(hmiInterface:="HmiData".units, ...)
```
## Building Tree-sitter Parser
```bash
# Generate parser from grammar.js
tree-sitter generate
# Run parser tests
tree-sitter test
# Parse a single SCL file
tree-sitter parse <file.scl>
```
## Project Structure
```
tia-lsp/
├── src/ # LSP server (uses dofile)
│ ├── main.lua # Entry point, JSON-RPC protocol
│ ├── parser.lua # SCL parser (will become parser_scl.lua + dispatch)
│ ├── diagnostics.lua # Linter (source = "tia_lsp")
│ ├── formatter.lua # Document formatter
│ ├── treesitter.lua # Tree-sitter integration
│ ├── plc_json.lua # External UDT/DB loading (language-agnostic)
│ ├── builtin_instructions.lua # TIA Portal built-in types
│ ├── json.lua # JSON encoder/decoder
│ ├── grammar.json # Generated tree-sitter grammar
│ ├── node-types.json # Generated node types
│ ├── parser.c # Generated tree-sitter parser
│ └── tree_sitter/ # Tree-sitter C runtime headers
├── grammar.js # Tree-sitter grammar (language name: 'scl')
├── tree-sitter.json # Parser config
├── package.json # NPM scripts (tree-sitter)
├── Makefile # Build commands
├── generate_plc_json.lua # .plc.json generator utility
└── test/ # Server tests
├── run_tests.lua
├── test_harness.lua
├── test_formatter.lua
└── test_plc_json.lua
```
## Architecture
The server (`src/main.lua`) communicates via JSON-RPC over stdio and handles:
- Text document synchronization
- Diagnostics (linting)
- Formatting
- Completion, hover, go-to-definition
- Semantic tokens
- Inlay hints
- Workspace symbols
The Neovim plugin (`tia-lsp.nvim`) manages the LSP client lifecycle, editor features, and workspace scanning. See its README for details.
## SCL Code Example
```pascal
FUNCTION_BLOCK "Em101Sequence"
VAR
x : INT;
myVar : equipmentData; // User-defined type
END_VAR
IF x > 5 THEN
myVar.id := 10;
END_IF
myVar.status := TRUE;
myVar.temperature := 25.5;
END_FUNCTION_BLOCK
```
## Requirements
- **Lua 5.1+** — LSP server runtime
- **tree-sitter CLI** — For regenerating the parser from `grammar.js` (only needed during development)
- **Node.js** — For tree-sitter CLI (only needed during development)
## Testing
```bash
# Run server tests
lua test/run_tests.lua
# Run individual test files
lua test/test_formatter.lua
lua test/test_plc_json.lua
# Run LSP server in test mode
lua src/main.lua --test
```
Tests use files from a reference project (override with `SCL_REF_PROJECT=/path/to/ref lua test/run_tests.lua`).