@hatchingpoint/point 0.0.9 → 0.0.11

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hatchingpoint/point",
3
- "version": "0.0.9",
3
+ "version": "0.0.11",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Point language compiler and CLI.",
package/src/core/cli.ts CHANGED
@@ -9,6 +9,7 @@ import { emitPointCoreJavaScript } from "./emit-javascript.ts";
9
9
  import { formatPointSource } from "./format.ts";
10
10
  import { isCacheHit, isIncrementalEnabled, readBuildCache, recordCacheEntry, writeBuildCache } from "./incremental.ts";
11
11
  import { parsePointSource } from "./parser.ts";
12
+ import { runPointLspServer } from "../lsp/server.ts";
12
13
 
13
14
  const DEFAULT_INPUT = "examples/math.point";
14
15
  const DEFAULT_OUTPUT = "generated/math.ast.json";
@@ -28,6 +29,11 @@ export async function main() {
28
29
  return;
29
30
  }
30
31
 
32
+ if (command === "lsp") {
33
+ await runPointLspServer();
34
+ return;
35
+ }
36
+
31
37
  const inputPath = resolve(process.cwd(), input);
32
38
  const source = await Bun.file(inputPath).text();
33
39
  const program = parsePointSource(source);
@@ -0,0 +1,326 @@
1
+ import type { PointSourceSpan } from "../core/ast.ts";
2
+ import { checkPointCore } from "../core/check.ts";
3
+ import type { PointCoreDiagnostic } from "../core/check.ts";
4
+ import { formatPointSource } from "../core/format.ts";
5
+ import { parsePointSource } from "../core/parser.ts";
6
+ import type { PointSemanticSymbol, PointSemanticSymbolKind } from "../semantic/context.ts";
7
+ import { createSemanticIndex, explainSemanticRef, mapPublicDiagnostics } from "../semantic/context.ts";
8
+
9
+ export interface LspRange {
10
+ start: { line: number; character: number };
11
+ end: { line: number; character: number };
12
+ }
13
+
14
+ export interface LspDiagnostic {
15
+ range: LspRange;
16
+ severity: 1;
17
+ code: string;
18
+ source: string;
19
+ message: string;
20
+ }
21
+
22
+ export interface LspDocumentSymbol {
23
+ name: string;
24
+ detail?: string;
25
+ kind: number;
26
+ range: LspRange;
27
+ selectionRange: LspRange;
28
+ }
29
+
30
+ export interface LspHover {
31
+ contents: string;
32
+ }
33
+
34
+ export interface LspCompletionItem {
35
+ label: string;
36
+ kind: number;
37
+ detail?: string;
38
+ }
39
+
40
+ const BLOCK_KEYWORDS = [
41
+ "module",
42
+ "use",
43
+ "record",
44
+ "calculation",
45
+ "rule",
46
+ "label",
47
+ "external",
48
+ "action",
49
+ "policy",
50
+ "view",
51
+ "route",
52
+ "workflow",
53
+ "command",
54
+ ];
55
+
56
+ const STATEMENT_KEYWORDS = [
57
+ "input",
58
+ "output",
59
+ "return",
60
+ "when",
61
+ "otherwise",
62
+ "add",
63
+ "subtract",
64
+ "set",
65
+ "for",
66
+ "each",
67
+ "in",
68
+ "starts",
69
+ "at",
70
+ "as",
71
+ "to",
72
+ "from",
73
+ "is",
74
+ "render",
75
+ "method",
76
+ "path",
77
+ "step",
78
+ "await",
79
+ "touches",
80
+ "and",
81
+ "or",
82
+ "none",
83
+ "true",
84
+ "false",
85
+ ];
86
+
87
+ const TYPE_KEYWORDS = ["Text", "Int", "Float", "Bool", "Void", "List", "Maybe"];
88
+
89
+ export interface PointDocumentAnalysis {
90
+ diagnostics: LspDiagnostic[];
91
+ symbols: PointSemanticSymbol[];
92
+ }
93
+
94
+ const OUTLINE_KINDS = new Set<PointSemanticSymbolKind>([
95
+ "record",
96
+ "calculation",
97
+ "rule",
98
+ "label",
99
+ "action",
100
+ "policy",
101
+ "external",
102
+ "view",
103
+ "route",
104
+ "workflow",
105
+ "command",
106
+ ]);
107
+
108
+ export function pointSpanToLspRange(span: PointSourceSpan): LspRange {
109
+ return {
110
+ start: { line: Math.max(0, span.start.line - 1), character: Math.max(0, span.start.column - 1) },
111
+ end: { line: Math.max(0, span.end.line - 1), character: Math.max(0, span.end.column - 1) },
112
+ };
113
+ }
114
+
115
+ export function lspPositionToPoint(line: number, character: number): { line: number; column: number } {
116
+ return { line: line + 1, column: character + 1 };
117
+ }
118
+
119
+ export function analyzePointSource(source: string): PointDocumentAnalysis {
120
+ try {
121
+ const program = parsePointSource(source);
122
+ const diagnostics = mapPublicDiagnostics(program, checkPointCore(program)).map(toLspDiagnostic);
123
+ const symbols = program.semanticSource ? createSemanticIndex(program.semanticSource).refs : [];
124
+ return { diagnostics, symbols };
125
+ } catch (error) {
126
+ const message = error instanceof Error ? error.message : String(error);
127
+ return {
128
+ diagnostics: [parseErrorDiagnostic(message)],
129
+ symbols: [],
130
+ };
131
+ }
132
+ }
133
+
134
+ export function outlineSymbols(symbols: PointSemanticSymbol[]): LspDocumentSymbol[] {
135
+ return symbols
136
+ .filter((symbol) => OUTLINE_KINDS.has(symbol.kind) && symbol.span)
137
+ .map((symbol) => {
138
+ const range = pointSpanToLspRange(symbol.span!);
139
+ return {
140
+ name: symbol.name,
141
+ detail: symbol.ref,
142
+ kind: symbolKindToLsp(symbol.kind),
143
+ range,
144
+ selectionRange: range,
145
+ };
146
+ });
147
+ }
148
+
149
+ export function symbolAtPoint(symbols: PointSemanticSymbol[], line: number, column: number): PointSemanticSymbol | undefined {
150
+ const matches = symbols.filter((symbol) => symbolContainsPoint(symbol, line, column));
151
+ return matches.sort((left, right) => spanSize(left.span) - spanSize(right.span))[0];
152
+ }
153
+
154
+ export function hoverForPosition(source: string, line: number, column: number): LspHover | null {
155
+ const analysis = analyzePointSource(source);
156
+ const symbol = symbolAtPoint(analysis.symbols, line, column);
157
+ if (!symbol?.span) return null;
158
+ try {
159
+ const program = parsePointSource(source);
160
+ if (!program.semanticSource) return { contents: symbol.ref };
161
+ const explanation = explainSemanticRef(program.semanticSource, symbol.ref);
162
+ return { contents: explanation.summary };
163
+ } catch {
164
+ return { contents: symbol.ref };
165
+ }
166
+ }
167
+
168
+ export function definitionForPosition(
169
+ symbols: PointSemanticSymbol[],
170
+ line: number,
171
+ column: number,
172
+ ): LspRange | null {
173
+ const symbol = symbolAtPoint(symbols, line, column);
174
+ if (!symbol?.span) return null;
175
+ return pointSpanToLspRange(symbol.span);
176
+ }
177
+
178
+ export function formatPointDocument(source: string): string {
179
+ try {
180
+ return formatPointSource(source);
181
+ } catch {
182
+ return source;
183
+ }
184
+ }
185
+
186
+ export function completionsForPosition(source: string, line: number, column: number): LspCompletionItem[] {
187
+ const analysis = analyzePointSource(source);
188
+ const lines = source.split(/\r?\n/);
189
+ const lineText = lines[line - 1] ?? "";
190
+ const before = lineText.slice(0, Math.max(0, column - 1));
191
+ const items: LspCompletionItem[] = [];
192
+ const seen = new Set<string>();
193
+ const add = (label: string, kind: number, detail?: string) => {
194
+ if (seen.has(label)) return;
195
+ seen.add(label);
196
+ items.push({ label, kind, detail });
197
+ };
198
+
199
+ if (/^\s*$/.test(before)) {
200
+ for (const keyword of BLOCK_KEYWORDS) add(keyword, 14);
201
+ }
202
+
203
+ for (const keyword of [...STATEMENT_KEYWORDS, ...TYPE_KEYWORDS]) add(keyword, 14);
204
+ for (const symbol of analysis.symbols) {
205
+ const kind =
206
+ symbol.kind === "field" ? 5 : symbol.kind === "record" ? 7 : symbol.kind === "param" ? 6 : 3;
207
+ add(symbol.name, kind, symbol.kind);
208
+ }
209
+
210
+ return items;
211
+ }
212
+
213
+ export function renameSymbolInDocument(
214
+ source: string,
215
+ line: number,
216
+ column: number,
217
+ newName: string,
218
+ ): { range: LspRange; newText: string } | null {
219
+ const analysis = analyzePointSource(source);
220
+ const symbol = symbolAtPoint(analysis.symbols, line, column);
221
+ if (!symbol?.span || !newName.trim()) return null;
222
+ const oldName = symbol.name;
223
+ if (oldName === newName) {
224
+ return { range: pointSpanToLspRange(symbol.span), newText: oldName };
225
+ }
226
+ const escaped = oldName.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
227
+ const updated = source.replace(new RegExp(escaped, "g"), newName);
228
+ if (updated === source) return null;
229
+ return {
230
+ range: fullDocumentRange(source),
231
+ newText: updated,
232
+ };
233
+ }
234
+
235
+ export function prepareRenameAtPosition(
236
+ source: string,
237
+ line: number,
238
+ column: number,
239
+ ): { range: LspRange; placeholder: string } | null {
240
+ const analysis = analyzePointSource(source);
241
+ const symbol = symbolAtPoint(analysis.symbols, line, column);
242
+ if (!symbol?.span) return null;
243
+ return {
244
+ range: pointSpanToLspRange(symbol.span),
245
+ placeholder: symbol.name,
246
+ };
247
+ }
248
+
249
+ function fullDocumentRange(text: string): LspRange {
250
+ const lines = text.split(/\r?\n/);
251
+ const lastLine = Math.max(0, lines.length - 1);
252
+ const lastCharacter = lines[lastLine]?.length ?? 0;
253
+ return {
254
+ start: { line: 0, character: 0 },
255
+ end: { line: lastLine, character: lastCharacter },
256
+ };
257
+ }
258
+
259
+ function toLspDiagnostic(diagnostic: PointCoreDiagnostic): LspDiagnostic {
260
+ const range = diagnostic.span
261
+ ? pointSpanToLspRange(diagnostic.span)
262
+ : { start: { line: 0, character: 0 }, end: { line: 0, character: 1 } };
263
+ return {
264
+ range,
265
+ severity: 1,
266
+ code: diagnostic.code,
267
+ source: diagnostic.ref,
268
+ message: diagnostic.message,
269
+ };
270
+ }
271
+
272
+ function parseErrorDiagnostic(message: string): LspDiagnostic {
273
+ const match = message.match(/\bat (\d+)\b/);
274
+ const line = match ? Math.max(0, Number(match[1]) - 1) : 0;
275
+ return {
276
+ range: { start: { line, character: 0 }, end: { line, character: 1 } },
277
+ severity: 1,
278
+ code: "parse-error",
279
+ source: "point",
280
+ message,
281
+ };
282
+ }
283
+
284
+ function symbolContainsPoint(symbol: PointSemanticSymbol, line: number, column: number): boolean {
285
+ if (!symbol.span) return false;
286
+ const { start, end } = symbol.span;
287
+ if (line < start.line || line > end.line) return false;
288
+ if (line === start.line && column < start.column) return false;
289
+ if (line === end.line && column > end.column) return false;
290
+ return true;
291
+ }
292
+
293
+ function spanSize(span: PointSourceSpan | null | undefined): number {
294
+ if (!span) return Number.MAX_SAFE_INTEGER;
295
+ const lines = span.end.line - span.start.line;
296
+ const columns = span.end.column - span.start.column;
297
+ return lines * 10_000 + columns;
298
+ }
299
+
300
+ function symbolKindToLsp(kind: PointSemanticSymbolKind): number {
301
+ switch (kind) {
302
+ case "record":
303
+ return 23;
304
+ case "field":
305
+ return 8;
306
+ case "calculation":
307
+ case "rule":
308
+ case "label":
309
+ case "action":
310
+ case "policy":
311
+ case "external":
312
+ case "view":
313
+ case "route":
314
+ case "workflow":
315
+ case "command":
316
+ return 12;
317
+ case "module":
318
+ return 2;
319
+ case "use":
320
+ return 3;
321
+ case "param":
322
+ return 6;
323
+ default:
324
+ return 13;
325
+ }
326
+ }
@@ -0,0 +1,79 @@
1
+ import { stdin, stdout } from "node:process";
2
+
3
+ export interface JsonRpcMessage {
4
+ jsonrpc: "2.0";
5
+ id?: number | string | null;
6
+ method?: string;
7
+ params?: unknown;
8
+ result?: unknown;
9
+ error?: { code: number; message: string; data?: unknown };
10
+ }
11
+
12
+ export class LspReader {
13
+ private buffer = Buffer.alloc(0);
14
+
15
+ async *messages(): AsyncGenerator<JsonRpcMessage> {
16
+ for (;;) {
17
+ const message = this.tryReadMessage();
18
+ if (message) {
19
+ yield message;
20
+ continue;
21
+ }
22
+ const chunk = await readStdinChunk();
23
+ if (chunk.length === 0) return;
24
+ this.buffer = Buffer.concat([this.buffer, chunk]);
25
+ }
26
+ }
27
+
28
+ private tryReadMessage(): JsonRpcMessage | null {
29
+ const headerEnd = this.buffer.indexOf("\r\n\r\n");
30
+ if (headerEnd === -1) return null;
31
+ const header = this.buffer.subarray(0, headerEnd).toString("utf8");
32
+ const lengthMatch = header.match(/Content-Length:\s*(\d+)/i);
33
+ if (!lengthMatch) {
34
+ this.buffer = this.buffer.subarray(headerEnd + 4);
35
+ return null;
36
+ }
37
+ const contentLength = Number(lengthMatch[1]);
38
+ const bodyStart = headerEnd + 4;
39
+ if (this.buffer.length < bodyStart + contentLength) return null;
40
+ const body = this.buffer.subarray(bodyStart, bodyStart + contentLength).toString("utf8");
41
+ this.buffer = this.buffer.subarray(bodyStart + contentLength);
42
+ return JSON.parse(body) as JsonRpcMessage;
43
+ }
44
+ }
45
+
46
+ export function writeMessage(message: JsonRpcMessage): void {
47
+ const body = JSON.stringify(message);
48
+ const header = `Content-Length: ${Buffer.byteLength(body, "utf8")}\r\n\r\n`;
49
+ stdout.write(header + body);
50
+ }
51
+
52
+ function readStdinChunk(): Promise<Buffer> {
53
+ return new Promise((resolve, reject) => {
54
+ const onReadable = () => {
55
+ const chunk = stdin.read() as Buffer | null;
56
+ if (chunk) {
57
+ cleanup();
58
+ resolve(chunk);
59
+ }
60
+ };
61
+ const onEnd = () => {
62
+ cleanup();
63
+ resolve(Buffer.alloc(0));
64
+ };
65
+ const onError = (error: Error) => {
66
+ cleanup();
67
+ reject(error);
68
+ };
69
+ const cleanup = () => {
70
+ stdin.off("readable", onReadable);
71
+ stdin.off("end", onEnd);
72
+ stdin.off("error", onError);
73
+ };
74
+ stdin.on("readable", onReadable);
75
+ stdin.on("end", onEnd);
76
+ stdin.on("error", onError);
77
+ if (stdin.isPaused()) stdin.resume();
78
+ });
79
+ }
@@ -0,0 +1,237 @@
1
+ import {
2
+ analyzePointSource,
3
+ completionsForPosition,
4
+ definitionForPosition,
5
+ formatPointDocument,
6
+ hoverForPosition,
7
+ lspPositionToPoint,
8
+ outlineSymbols,
9
+ prepareRenameAtPosition,
10
+ renameSymbolInDocument,
11
+ type LspRange,
12
+ } from "./analyze.ts";
13
+ import { readFileSync } from "node:fs";
14
+ import { join } from "node:path";
15
+ import { LspReader, writeMessage, type JsonRpcMessage } from "./protocol.ts";
16
+
17
+ type DocumentState = { version: number; text: string };
18
+
19
+ const documents = new Map<string, DocumentState>();
20
+
21
+ function lspVersion(): string {
22
+ try {
23
+ const pkgPath = join(import.meta.dir, "../../package.json");
24
+ return (JSON.parse(readFileSync(pkgPath, "utf8")) as { version: string }).version;
25
+ } catch {
26
+ return "0.0.0";
27
+ }
28
+ }
29
+
30
+ export async function runPointLspServer(): Promise<void> {
31
+ const reader = new LspReader();
32
+ for await (const message of reader.messages()) {
33
+ await handleMessage(message);
34
+ }
35
+ }
36
+
37
+ async function handleMessage(message: JsonRpcMessage): Promise<void> {
38
+ if (message.method === "initialize") {
39
+ respond(message.id, {
40
+ capabilities: {
41
+ textDocumentSync: 1,
42
+ documentSymbolProvider: true,
43
+ definitionProvider: true,
44
+ hoverProvider: true,
45
+ documentFormattingProvider: true,
46
+ completionProvider: { triggerCharacters: [".", " "] },
47
+ renameProvider: { prepareProvider: true },
48
+ },
49
+ serverInfo: { name: "point-lsp", version: lspVersion() },
50
+ });
51
+ return;
52
+ }
53
+
54
+ if (message.method === "initialized" || message.method === "exit") {
55
+ return;
56
+ }
57
+
58
+ if (message.method === "shutdown") {
59
+ respond(message.id, null);
60
+ return;
61
+ }
62
+
63
+ if (message.method === "textDocument/didOpen") {
64
+ const params = message.params as { textDocument: { uri: string; version: number; text: string } };
65
+ documents.set(params.textDocument.uri, { version: params.textDocument.version, text: params.textDocument.text });
66
+ publishDiagnostics(params.textDocument.uri);
67
+ return;
68
+ }
69
+
70
+ if (message.method === "textDocument/didChange") {
71
+ const params = message.params as {
72
+ textDocument: { uri: string; version: number };
73
+ contentChanges: Array<{ text: string }>;
74
+ };
75
+ const change = params.contentChanges[0];
76
+ if (!change) return;
77
+ documents.set(params.textDocument.uri, { version: params.textDocument.version, text: change.text });
78
+ publishDiagnostics(params.textDocument.uri);
79
+ return;
80
+ }
81
+
82
+ if (message.method === "textDocument/didClose") {
83
+ const params = message.params as { textDocument: { uri: string } };
84
+ documents.delete(params.textDocument.uri);
85
+ notify("textDocument/publishDiagnostics", { uri: params.textDocument.uri, diagnostics: [] });
86
+ return;
87
+ }
88
+
89
+ if (message.method === "textDocument/documentSymbol") {
90
+ const params = message.params as { textDocument: { uri: string } };
91
+ const document = documents.get(params.textDocument.uri);
92
+ if (!document) {
93
+ respond(message.id, []);
94
+ return;
95
+ }
96
+ const analysis = analyzePointSource(document.text);
97
+ respond(message.id, outlineSymbols(analysis.symbols));
98
+ return;
99
+ }
100
+
101
+ if (message.method === "textDocument/definition") {
102
+ const params = message.params as { textDocument: { uri: string }; position: { line: number; character: number } };
103
+ const document = documents.get(params.textDocument.uri);
104
+ if (!document) {
105
+ respond(message.id, null);
106
+ return;
107
+ }
108
+ const point = lspPositionToPoint(params.position.line, params.position.character);
109
+ const analysis = analyzePointSource(document.text);
110
+ const range = definitionForPosition(analysis.symbols, point.line, point.column);
111
+ respond(message.id, range ? { uri: params.textDocument.uri, range } : null);
112
+ return;
113
+ }
114
+
115
+ if (message.method === "textDocument/hover") {
116
+ const params = message.params as { textDocument: { uri: string }; position: { line: number; character: number } };
117
+ const document = documents.get(params.textDocument.uri);
118
+ if (!document) {
119
+ respond(message.id, null);
120
+ return;
121
+ }
122
+ const point = lspPositionToPoint(params.position.line, params.position.character);
123
+ const hover = hoverForPosition(document.text, point.line, point.column);
124
+ respond(message.id, hover ? { contents: { kind: "markdown", value: hover.contents } } : null);
125
+ return;
126
+ }
127
+
128
+ if (message.method === "textDocument/formatting") {
129
+ const params = message.params as { textDocument: { uri: string } };
130
+ const document = documents.get(params.textDocument.uri);
131
+ if (!document) {
132
+ respond(message.id, []);
133
+ return;
134
+ }
135
+ const formatted = formatPointDocument(document.text);
136
+ if (formatted === document.text) {
137
+ respond(message.id, []);
138
+ return;
139
+ }
140
+ respond(message.id, [
141
+ {
142
+ range: fullDocumentRange(document.text),
143
+ newText: formatted,
144
+ },
145
+ ]);
146
+ return;
147
+ }
148
+
149
+ if (message.method === "textDocument/completion") {
150
+ const params = message.params as {
151
+ textDocument: { uri: string };
152
+ position: { line: number; character: number };
153
+ };
154
+ const document = documents.get(params.textDocument.uri);
155
+ if (!document) {
156
+ respond(message.id, { isIncomplete: false, items: [] });
157
+ return;
158
+ }
159
+ const point = lspPositionToPoint(params.position.line, params.position.character);
160
+ const items = completionsForPosition(document.text, point.line, point.column);
161
+ respond(message.id, { isIncomplete: false, items });
162
+ return;
163
+ }
164
+
165
+ if (message.method === "textDocument/prepareRename") {
166
+ const params = message.params as {
167
+ textDocument: { uri: string };
168
+ position: { line: number; character: number };
169
+ };
170
+ const document = documents.get(params.textDocument.uri);
171
+ if (!document) {
172
+ respond(message.id, null);
173
+ return;
174
+ }
175
+ const point = lspPositionToPoint(params.position.line, params.position.character);
176
+ const prepared = prepareRenameAtPosition(document.text, point.line, point.column);
177
+ respond(message.id, prepared);
178
+ return;
179
+ }
180
+
181
+ if (message.method === "textDocument/rename") {
182
+ const params = message.params as {
183
+ textDocument: { uri: string };
184
+ position: { line: number; character: number };
185
+ newName: string;
186
+ };
187
+ const document = documents.get(params.textDocument.uri);
188
+ if (!document) {
189
+ respond(message.id, null);
190
+ return;
191
+ }
192
+ const point = lspPositionToPoint(params.position.line, params.position.character);
193
+ const edit = renameSymbolInDocument(document.text, point.line, point.column, params.newName);
194
+ if (!edit) {
195
+ respond(message.id, null);
196
+ return;
197
+ }
198
+ documents.set(params.textDocument.uri, { version: document.version, text: edit.newText });
199
+ publishDiagnostics(params.textDocument.uri);
200
+ respond(message.id, {
201
+ changes: {
202
+ [params.textDocument.uri]: [edit],
203
+ },
204
+ });
205
+ return;
206
+ }
207
+
208
+ if (message.id !== undefined) {
209
+ respond(message.id, null);
210
+ }
211
+ }
212
+
213
+ function publishDiagnostics(uri: string): void {
214
+ const document = documents.get(uri);
215
+ if (!document) return;
216
+ const analysis = analyzePointSource(document.text);
217
+ notify("textDocument/publishDiagnostics", { uri, diagnostics: analysis.diagnostics });
218
+ }
219
+
220
+ function fullDocumentRange(text: string): LspRange {
221
+ const lines = text.split(/\r?\n/);
222
+ const lastLine = Math.max(0, lines.length - 1);
223
+ const lastCharacter = lines[lastLine]?.length ?? 0;
224
+ return {
225
+ start: { line: 0, character: 0 },
226
+ end: { line: lastLine, character: lastCharacter },
227
+ };
228
+ }
229
+
230
+ function respond(id: number | string | null | undefined, result: unknown): void {
231
+ if (id === undefined) return;
232
+ writeMessage({ jsonrpc: "2.0", id, result });
233
+ }
234
+
235
+ function notify(method: string, params: unknown): void {
236
+ writeMessage({ jsonrpc: "2.0", method, params });
237
+ }