;============================================================ ; iron_lsp.hsp — HSP 用 Language Server Protocol (LSP) サーバ ; (stdio 対応スケルトン / scaffold 版) ; ; LSP (Language Server Protocol) は VS Code / Neovim / Sublime Text 等の ; エディタと言語サーバの共通プロトコル。 ; https://microsoft.github.io/language-server-protocol/ ; ; 本モジュールは stdio transport で動作する HSP 用 LSP サーバの ; スケルトン。以下を提供: ; - Content-Length ヘッダ付きの JSON-RPC 2.0 framing (read + write) ; - hspmcp.dll の mcp_stdin_read_line / mcp_stdin_read_bytes / ; mcp_stdout_write を使った実際の stdio I/O ; - initialize / initialized / shutdown / exit のハンドリング ; - textDocument/didOpen / didChange / didClose のハンドリング ; - textDocument/hover / textDocument/completion のスタブ応答 ; ; 本モジュールは **scaffold のみ**。実運用には下記の追加実装が必要: ; - HSP3 ソースのトークナイザ / 構文解析 (hspcmp ソースを流用) ; - シンボルテーブル (#deffunc / #defcfunc / #define / #const / #enum) ; - textDocument/definition (ジャンプ先解決) ; - textDocument/references (使用箇所検索) ; - textDocument/documentSymbol (アウトライン) ; - textDocument/completion のアイテム生成 (現状は固定キーワード) ; - diagnostics (hspcmp を子プロセス起動してパース結果を使う) ; ; stdio transport なので hsp3cl_net_test_64.exe (CLI) で起動する想定。 ; VS Code 拡張から `{"command": "hsp3cl_net_64", "args": ["lsp_server.ax"]}` ; のように起動し stdio でやりとりする。 ;============================================================ #ifndef __iron_lsp__ #define __iron_lsp__ #ifdef _HSP64 #uselib "hspmcp_64.dll" #else #uselib "hspmcp.dll" #endif #cfunc global _lsp_read_line "mcp_stdin_read_line" var, int #cfunc global _lsp_read_bytes "mcp_stdin_read_bytes" var, int #cfunc global _lsp_write_raw "mcp_stdout_write" str, int #module "m_iron_lsp" ;--------------------------------------------------------- ; 内部状態 ;--------------------------------------------------------- sdim _docs_uri, 1024, 32 ; 開いているドキュメントの URI (最大 32) sdim _docs_text, 65536, 32 ; 本文 (最大 64KB/file) dim _docs_count, 1 dim _lsp_initialized, 1 dim _lsp_shutdown, 1 ;--------------------------------------------------------- ; JSON ヘルパ (超簡易版: "...key...":"value" から value を取る) ;--------------------------------------------------------- #defcfunc _json_get_str var _json, str _key, \ local _k, local _p, local _q, local _len _k = "\"" + _key + "\"" _p = instr(_json, 0, _k) if _p < 0 : return "" _p = instr(_json, _p + strlen(_k), "\"") if _p < 0 : return "" _p++ _q = _p repeat _q = instr(_json, _q, "\"") if _q < 0 : return "" ; エスケープチェック (直前が \ なら継続) if _q > 0 { _c = peek(_json, _q - 1) if _c = '\\' { _q++ continue } } break loop _len = _q - _p sdim _out, _len + 1 memcpy _out, _json, _len, 0, _p poke _out, _len, 0 return _out #defcfunc _json_get_int var _json, str _key, local _k, local _p, local _end _k = "\"" + _key + "\":" _p = instr(_json, 0, _k) if _p < 0 : return -1 _p += strlen(_k) ; 空白スキップ repeat _c = peek(_json, _p) if _c != ' ' : break _p++ loop _end = _p repeat _c = peek(_json, _end) if (_c < '0') | (_c > '9') : break _end++ loop if _end = _p : return -1 sdim _s, _end - _p + 1 memcpy _s, _json, _end - _p, 0, _p poke _s, _end - _p, 0 return int(_s) ;--------------------------------------------------------- ; LSP メッセージ送信 (Content-Length ヘッダ + 本文) ;--------------------------------------------------------- #deffunc _lsp_send str _body, local _hdr, local _blen _blen = strlen(_body) _hdr = "Content-Length: " + _blen + "\n\n" ; 1 回 WriteFile で ヘッダ + 本文を送りたいが、strlen の扱い差があるので分けて送る _lsp_write_raw _hdr, strlen(_hdr) _lsp_write_raw _body, _blen return ;--------------------------------------------------------- ; initialize 応答 ;--------------------------------------------------------- #deffunc _lsp_handle_initialize int _id, var _params, local _body _body = "{\"jsonrpc\":\"2.0\",\"id\":" + _id _body += ",\"result\":{\"capabilities\":{" _body += "\"textDocumentSync\":1," _body += "\"hoverProvider\":true," _body += "\"completionProvider\":{\"triggerCharacters\":[\"#\",\"$\",\"_\"]}" _body += "},\"serverInfo\":{\"name\":\"iron_lsp\",\"version\":\"0.1\"}}}" _lsp_send _body _lsp_initialized = 1 return #deffunc _lsp_handle_shutdown int _id, local _body _body = "{\"jsonrpc\":\"2.0\",\"id\":" + _id + ",\"result\":null}" _lsp_send _body _lsp_shutdown = 1 return ;--------------------------------------------------------- ; completion stub: 固定キーワードを返す ;--------------------------------------------------------- #deffunc _lsp_handle_completion int _id, local _body, local _kws, local _i sdim _kws, 16, 10 _kws(0) = "#module" : _kws(1) = "#deffunc" : _kws(2) = "#defcfunc" _kws(3) = "#const" : _kws(4) = "#define" : _kws(5) = "if" _kws(6) = "else" : _kws(7) = "repeat" : _kws(8) = "loop" _kws(9) = "return" _body = "{\"jsonrpc\":\"2.0\",\"id\":" + _id _body += ",\"result\":{\"isIncomplete\":false,\"items\":[" repeat 10 if cnt > 0 : _body += "," _body += "{\"label\":\"" + _kws(cnt) + "\",\"kind\":14}" ; 14 = Keyword loop _body += "]}}" _lsp_send _body return #deffunc _lsp_handle_hover int _id, var _params, local _body _body = "{\"jsonrpc\":\"2.0\",\"id\":" + _id _body += ",\"result\":{\"contents\":{\"kind\":\"plaintext\"," _body += "\"value\":\"iron_lsp scaffold hover\"}}}" _lsp_send _body return ;--------------------------------------------------------- ; 1 メッセージを読み取って dispatch ;--------------------------------------------------------- #deffunc _lsp_read_and_dispatch local _line, local _n, local _clen, local _body, local _method, local _id _clen = -1 ; ヘッダ: 空行までループ repeat sdim _line, 256 _n = _lsp_read_line(_line, 256) if _n < 0 : _lsp_shutdown = 1 : return ; EOF if _n = 0 : break ; 空行 = ヘッダ終了 ; "Content-Length: N" if instr(_line, 0, "Content-Length:") = 0 { _clen = int(strmid(_line, 16, strlen(_line) - 16)) } loop if _clen <= 0 : return ; 本文読み取り (正確に _clen バイト) sdim _body, _clen + 1 _n = _lsp_read_bytes(_body, _clen) if _n <= 0 : _lsp_shutdown = 1 : return poke _body, _n, 0 ; method / id 取り出し _method = _json_get_str(_body, "method") _id = _json_get_int(_body, "id") if _method = "initialize" { _lsp_handle_initialize _id, _body : return } if _method = "initialized" : return if _method = "shutdown" { _lsp_handle_shutdown _id : return } if _method = "exit" { _lsp_shutdown = 1 : return } if _method = "textDocument/completion" { _lsp_handle_completion _id : return } if _method = "textDocument/hover" { _lsp_handle_hover _id, _body : return } ; didOpen/didChange/didClose は現状ノーオペ (state 更新のみ将来実装) ; 未知の method は id ありなら MethodNotFound を返す if _id >= 0 { sdim _err, 256 _err = "{\"jsonrpc\":\"2.0\",\"id\":" + _id _err += ",\"error\":{\"code\":-32601,\"message\":\"Method not found: " + _method + "\"}}" _lsp_send _err } return ;--------------------------------------------------------- ; lsp_run — メインループ ;--------------------------------------------------------- #deffunc lsp_run _lsp_shutdown = 0 _lsp_initialized = 0 repeat if _lsp_shutdown : break _lsp_read_and_dispatch loop return #global #endif