lazar f7f1062b90 feat: test suite expansion + case-insensitive builtin lookup (Phase 6)
Add 3 new test files:

test_grammar_spec.lua (36 tests):
- Block structure: RETURN, GOTO, AUTHOR/FAMILY, VAR DB_SPECIFIC, typed
  DATA_BLOCK, END_STRUCT; semicolon, case-insensitive keywords
- Data types: REF_TO, VARIANT, anonymous STRUCT, ARRAY[*], multidim
  ARRAY, all type_builtin aliases (LWORD, LINT, ULINT, UDINT, DATE,
  TIME_OF_DAY, DATE_AND_TIME, S5TIME, LTIME, DTL, LTOD, LDT)
- Literals: binary (2#), octal (8#), BOOL#TRUE, typed int (INT#),
  combined time (T#1h30m), WSTRING#, compound assignments, CASE labels
- Parser: case-insensitive var_temp, VAR DB_SPECIFIC, lowercase blocks

test_diagnostics_spec.lua (6 tests):
- Unknown attribute warning (SCL005: ExternalWriteable typo)
- Valid attributes pass (ExternalWritable, S7_SetPoint)
- Unknown type warning (SCL003)
- Known types pass (VARIANT, LWORD, DTL)
- IEC types pass (IEC_COUNTER, IEC_TIMER)

test_builtin_instructions.lua (5 tests):
- All 221 spec instructions are known
- All spec data types are known
- Key instructions have parameter signatures
- Total count > 300 functions, > 35 FBs

Fix builtin_instructions.lua:
- Make is_known_function/is_known_function_block/get_parameters
  case-insensitive (check both name:upper() and name) — fixes 45
  mixed-case instruction names like Chars_TO_Strg, AssignmentAttempt
- Add missing Chars_TO_Strg to FUNCTIONS table

Fix test_harness.lua:
- Update REF_PROJECT path to ~/Documents/siemens/...

All 85 tests pass (38 formatter + 3 plc_json + 36 grammar + 6
diagnostics + 5 builtin). 3 grammar tests marked as expected failures
for known edge cases (GOTO labels, typed DB, combined time values).
2026-07-18 20:22:00 +02:00
2026-02-14 15:05:12 +01:00
2026-02-14 15:05:12 +01:00
2026-02-14 15:05:12 +01:00

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 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

# 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

Mason Installation (Neovim)

For Neovim users, install via mason.nvim using a custom registry. See the tia-lsp.nvim README for full setup instructions.

Quick summary:

  1. Add the tia-mason-registry as a lazy.nvim plugin
  2. Register it with mason: registries = { "file:~/.local/share/nvim/lazy/tia-mason-registry", "github:mason-org/mason-registry" }
  3. :MasonInstall tia-lsp

External UDT Support

Option 1: Create .plc.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

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

// Access global DB members
"HmiData".units
"Parameter".machine.unit100.paramTimeStampApplied

// Inside FB call parameter
#instParameterManager(hmiInterface:="HmiData".units, ...)

Building Tree-sitter Parser

# 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

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

# 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).

S
Description
No description provided
Readme
897 KiB
Languages
C 93.3%
Lua 5.7%
JavaScript 0.6%
C++ 0.4%