# AGENTS.md - SCL Language Server A Neovim/LazyVim plugin for Siemens SCL language support providing LSP, linting, formatting, syntax highlighting, and auto-completion. ## Build/Lint/Test Commands ```bash # LSP Server make start # Start LSP server make test # Run LSP in test mode lua src/main.lua --test # Direct test execution # Tree-sitter Parser tree-sitter generate # Generate parser from grammar.js tree-sitter test # Run all parser tests tree-sitter parse # Parse single SCL file # Lua Linting luacheck src/ # Lint LSP server code luacheck lua/scl/ # Lint plugin modules # Unit & Integration Tests lua test/run_tests.lua # Run all tests lua test/test_udt.lua # Run UDT parser tests lua test/test_db.lua # Run DB parser tests lua test/test_fb.lua # Run FB parser tests lua test/test_formatter.lua # Run formatter tests lua test/test_plc_json.lua # Run plc_json tests lua test/test_integration.lua # Run integration tests # Reference Project # Tests use files from: ~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/ # Override with: SCL_REF_PROJECT=/path/to/ref lua tests/run_tests.lua ``` ## Project Structure ``` scl_lsp/ ├── src/ # LSP server (uses dofile) │ ├── main.lua # Entry point, JSON-RPC protocol │ ├── parser.lua # SCL parser │ ├── diagnostics.lua # Linter │ ├── formatter.lua # Document formatter │ ├── treesitter.lua # Tree-sitter integration │ ├── plc_json.lua # External UDT/DB loading │ ├── builtin_instructions.lua # TIA Portal built-in types │ └── json.lua # JSON encoder/decoder ├── lua/scl/ # Neovim plugin (uses require) │ ├── init.lua # Main plugin setup │ ├── udt_parser.lua # UDT parser │ ├── db_parser.lua # Global DB parser │ ├── fb_parser.lua # Function Block parser │ ├── variables.lua # Variable extraction │ ├── workspace_types.lua # Workspace scanning │ ├── multiline_params.lua │ ├── auto_prefix.lua │ └── attr_toggle.lua ├── queries/ # Tree-sitter queries └── grammar.js # Tree-sitter grammar ``` ## Filetype Detection Required in Neovim config (ftdetect/scl.vim): ```vim au BufRead,BufNewFile *.scl set filetype=scl au BufRead,BufNewFile *.udt set filetype=scl au BufRead,BufNewFile *.db set filetype=scl ``` ## Code Style Guidelines ### Module Pattern ```lua local M = {} local cache = {} function M.public_function() end local function private_function() end return M ``` ### Naming Conventions - Functions/variables: `snake_case` (e.g., `get_udt_members`) - Constants: `UPPER_SNAKE_CASE` (e.g., `PROJECT_MARKERS`) - Private functions: declare as `local function` before public functions - Booleans: prefix with `is_`, `has_` (e.g., `is_udt`, `has_members`) ### Imports - `lua/scl/` modules: `require("scl.module_name")` - `src/` modules: `dofile(script_path .. "/module.lua")` - External dependencies: wrap in `pcall()` for safety ### Code Formatting - `lua/scl/`: 2 spaces indentation - `src/`: tab-based indentation - Max line length: 120 characters - No trailing whitespace ### Comments **DO NOT ADD COMMENTS** in code unless explicitly requested by the user. ### Parser Module API All parser modules must implement: - `parse_*_file(filepath)` - Parse and cache - `parse_*_content(content, filename)` - Parse string - `get_*(name)` - Get cached item - `get_all_*_names()` - List all cached names - `get_*_members(name)` - Get members/fields - `is_*_type(name)` - Check if known type - `clear_cache()` - Clear cached data - `get_cache_count()` - Return cache size ### Cache Management ```lua local cache = {} function M.clear_cache() for k in pairs(cache) do cache[k] = nil end end ``` ### Error Handling ```lua function M.parse_file(filepath) local file = io.open(filepath, "r") if not file then return nil, "Cannot open file: " .. filepath end return result end -- External dependencies local ok, module = pcall(require, "some_module") if ok and module then module.do_something() end ``` ## Lua Reserved Keywords Cannot use reserved keywords as table keys directly: ```lua -- WRONG local range = { start = pos1, end = pos2 } -- CORRECT local range = { start = pos1, ["end"] = pos2 } ``` Critical for LSP range objects with `end` field. ## Type System The LSP validates data types and provides warnings for unknown types: - **Elementary**: BOOL, BYTE, WORD, DWORD, INT, DINT, REAL, TIME, STRING, etc. - **Timer/counter**: IEC_TIMER, TON_TIME, TOF_TIME, CTU, CTD, CTUD - **Built-in FBs**: TON, TOF, TP, R_TRIG, F_TRIG, etc. - **Built-in functions**: ADD, SUB, MUL, DIV, SIN, COS, SQRT, etc. - **Workspace types**: UDTs from `.udt` files, DBs from `.db` files ## Testing Use the reference project for testing: ``` ~/dev/siemens/projects/scl_lang_support_lazyvim_ref_project/ ``` Contains `.scl`, `.db`, `.udt`, and `.xml` files for comprehensive testing of: - LSP features (hover, completion, go-to-definition) - Formatter (`:SCLFormat`) - Attribute block toggle - Workspace type scanning