@code-yeongyu/senpi-codemode 2026.7.25-2
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/CHANGELOG.md +250 -0
- package/LICENSE +22 -0
- package/README.md +161 -0
- package/package.json +58 -0
- package/src/bridge/http-server.ts +236 -0
- package/src/bridge/protocol.ts +198 -0
- package/src/bridge/reserved.ts +9 -0
- package/src/bridges/agent-bridge.ts +197 -0
- package/src/bridges/output-bridge.ts +96 -0
- package/src/bridges/schema-injection.ts +3 -0
- package/src/codemode/runtime.ts +258 -0
- package/src/codemode/tools.ts +106 -0
- package/src/completion/handler.ts +192 -0
- package/src/completion/tool-bridge.ts +55 -0
- package/src/config/settings.ts +215 -0
- package/src/extension/runtime-factory.ts +114 -0
- package/src/extension/session-manager-proxy.ts +116 -0
- package/src/extension/session-manager.ts +215 -0
- package/src/host-sdk.ts +1 -0
- package/src/index.ts +181 -0
- package/src/interpreters/detect.ts +161 -0
- package/src/kernels/jl/kernel.ts +37 -0
- package/src/kernels/jl/prelude.jl +283 -0
- package/src/kernels/jl/runner.jl +327 -0
- package/src/kernels/js/context-manager.ts +296 -0
- package/src/kernels/js/inline-worker-entry.js +23 -0
- package/src/kernels/js/inline-worker.ts +15 -0
- package/src/kernels/js/kernel-contract.ts +38 -0
- package/src/kernels/js/local-module-loader.ts +108 -0
- package/src/kernels/js/prelude.ts +15 -0
- package/src/kernels/js/rewrite-imports.ts +164 -0
- package/src/kernels/js/run-queue.ts +82 -0
- package/src/kernels/js/worker-core.d.ts +18 -0
- package/src/kernels/js/worker-core.js +94 -0
- package/src/kernels/js/worker-entry.js +23 -0
- package/src/kernels/js/worker-host.ts +117 -0
- package/src/kernels/js/worker-indirect-eval.js +88 -0
- package/src/kernels/js/worker-runtime.js +401 -0
- package/src/kernels/py/kernel-contract.ts +32 -0
- package/src/kernels/py/kernel.ts +290 -0
- package/src/kernels/py/prelude.py +954 -0
- package/src/kernels/py/process.ts +119 -0
- package/src/kernels/py/transport.ts +237 -0
- package/src/kernels/rb/kernel.ts +26 -0
- package/src/kernels/rb/prelude.rb +270 -0
- package/src/kernels/rb/runner.rb +204 -0
- package/src/kernels/shared/subprocess-contract.ts +22 -0
- package/src/kernels/shared/subprocess-kernel.ts +266 -0
- package/src/kernels/shared/subprocess-process.ts +174 -0
- package/src/kernels/shared/subprocess-queue.ts +101 -0
- package/src/kernels/shared/subprocess-run.ts +98 -0
- package/src/output/output-meta.ts +89 -0
- package/src/output/streaming-output.ts +296 -0
- package/src/prompt/eval-prompt.ts +319 -0
- package/src/timeouts/bridge-timeout.ts +16 -0
- package/src/timeouts/idle-timeout.ts +84 -0
- package/src/tool/cell-handler.ts +279 -0
- package/src/tool/eval-tool.ts +285 -0
- package/src/tool/image.ts +274 -0
- package/src/tool/json-tree.ts +247 -0
- package/src/tool/render.ts +876 -0
- package/src/tool/status-events.ts +12 -0
- package/src/tool/types.ts +114 -0
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import type { KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
|
+
import type { JavaScriptRunInput } from "./kernel-contract.ts";
|
|
3
|
+
|
|
4
|
+
type ResultMessage = Extract<KernelToHostMessage, { type: "result" }>;
|
|
5
|
+
|
|
6
|
+
export interface PendingJavaScriptRun {
|
|
7
|
+
readonly input: JavaScriptRunInput;
|
|
8
|
+
readonly resolve: (message: ResultMessage) => void;
|
|
9
|
+
readonly reject: (error: Error) => void;
|
|
10
|
+
startedAtMs: number | null;
|
|
11
|
+
settled: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class JavaScriptRunQueue {
|
|
15
|
+
#queue: PendingJavaScriptRun[] = [];
|
|
16
|
+
#active: PendingJavaScriptRun | null = null;
|
|
17
|
+
|
|
18
|
+
get active(): PendingJavaScriptRun | null {
|
|
19
|
+
return this.#active;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
get hasWaiting(): boolean {
|
|
23
|
+
return this.#queue.length > 0;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
enqueue(input: JavaScriptRunInput): Promise<ResultMessage> {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
this.#queue.push({ input, resolve, reject, startedAtMs: null, settled: false });
|
|
29
|
+
});
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
startNext(startedAtMs: number): PendingJavaScriptRun | null {
|
|
33
|
+
if (this.#active) return null;
|
|
34
|
+
const next = this.#queue.shift() ?? null;
|
|
35
|
+
if (next) next.startedAtMs = startedAtMs;
|
|
36
|
+
this.#active = next;
|
|
37
|
+
return next;
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
durationMs(run: PendingJavaScriptRun, finishedAtMs: number): number {
|
|
41
|
+
if (run.startedAtMs === null) return 0;
|
|
42
|
+
return Math.max(0, Math.round(finishedAtMs - run.startedAtMs));
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
takeInterruptTarget(): PendingJavaScriptRun | null {
|
|
46
|
+
if (!this.#active) return this.#queue.shift() ?? null;
|
|
47
|
+
const active = this.#active;
|
|
48
|
+
this.#active = null;
|
|
49
|
+
return active;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
releaseActive(run: PendingJavaScriptRun): boolean {
|
|
53
|
+
if (this.#active !== run) return false;
|
|
54
|
+
this.#active = null;
|
|
55
|
+
return true;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
settle(run: PendingJavaScriptRun, result: ResultMessage): void {
|
|
59
|
+
if (run.settled) return;
|
|
60
|
+
run.settled = true;
|
|
61
|
+
run.resolve(result);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
settleAll(message: string): void {
|
|
65
|
+
const active = this.#active;
|
|
66
|
+
this.#active = null;
|
|
67
|
+
if (active) this.settle(active, stoppedResult(active.input.cellId, message));
|
|
68
|
+
for (const queued of this.#queue.splice(0)) this.settle(queued, stoppedResult(queued.input.cellId, message));
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
rejectWaiting(error: Error): void {
|
|
72
|
+
for (const queued of this.#queue.splice(0)) {
|
|
73
|
+
if (queued.settled) continue;
|
|
74
|
+
queued.settled = true;
|
|
75
|
+
queued.reject(error);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function stoppedResult(cellId: string, message: string): ResultMessage {
|
|
81
|
+
return { type: "result", cellId, ok: false, error: { message }, durationMs: 0 };
|
|
82
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
import type { HostToKernelMessage, KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
2
|
+
|
|
3
|
+
export interface WorkerCoreTransport {
|
|
4
|
+
send(message: KernelToHostMessage): void;
|
|
5
|
+
onMessage(handler: (message: HostToKernelMessage) => void): () => void;
|
|
6
|
+
close(): void;
|
|
7
|
+
}
|
|
8
|
+
|
|
9
|
+
export interface WorkerCoreOptions {
|
|
10
|
+
cwd: string;
|
|
11
|
+
parallelPoolWidth: number;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface WorkerCoreHandle {
|
|
15
|
+
dispose(): void;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function createWorkerCore(transport: WorkerCoreTransport, options: WorkerCoreOptions): WorkerCoreHandle;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { JsWorkerRuntime } from "./worker-runtime.js";
|
|
2
|
+
|
|
3
|
+
export function createWorkerCore(transport, options) {
|
|
4
|
+
let runtime = null;
|
|
5
|
+
const pendingTools = new Map();
|
|
6
|
+
|
|
7
|
+
function emit(message) {
|
|
8
|
+
transport.send(message);
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
async function runCell(message) {
|
|
12
|
+
if (!runtime) {
|
|
13
|
+
emit({ type: "result", cellId: message.cellId, ok: false, error: { message: "JS runtime not initialized" }, durationMs: 0 });
|
|
14
|
+
return;
|
|
15
|
+
}
|
|
16
|
+
const startedAtMs = performance.now();
|
|
17
|
+
try {
|
|
18
|
+
const value = await runtime.run(message.code, message.cellId, {
|
|
19
|
+
emit,
|
|
20
|
+
callTool: async (toolName, args) => await callTool(toolName, args),
|
|
21
|
+
});
|
|
22
|
+
emit({ type: "result", cellId: message.cellId, ok: true, valueRepr: valueRepr(value), durationMs: durationMs(startedAtMs) });
|
|
23
|
+
} catch (error) {
|
|
24
|
+
emit({ type: "result", cellId: message.cellId, ok: false, error: bridgeError(error), durationMs: durationMs(startedAtMs) });
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
async function callTool(toolName, args) {
|
|
29
|
+
const callId = `js-${crypto.randomUUID()}`;
|
|
30
|
+
const promise = new Promise((resolve, reject) => pendingTools.set(callId, { resolve, reject }));
|
|
31
|
+
emit({ type: "tool-call", callId, toolName, args });
|
|
32
|
+
return await promise;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function onMessage(message) {
|
|
36
|
+
if (message.type === "init") {
|
|
37
|
+
runtime = new JsWorkerRuntime({
|
|
38
|
+
cwd: options.cwd,
|
|
39
|
+
parallelPoolWidth: options.parallelPoolWidth,
|
|
40
|
+
localRoots: message.connection.localRoots,
|
|
41
|
+
artifactsDir: message.connection.artifactsDir,
|
|
42
|
+
});
|
|
43
|
+
emit({ type: "ready" });
|
|
44
|
+
return;
|
|
45
|
+
}
|
|
46
|
+
if (message.type === "run") {
|
|
47
|
+
void runCell(message);
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (message.type === "tool-reply") {
|
|
51
|
+
const pending = pendingTools.get(message.callId);
|
|
52
|
+
if (!pending) return;
|
|
53
|
+
pendingTools.delete(message.callId);
|
|
54
|
+
if (message.ok) pending.resolve(message.value);
|
|
55
|
+
else pending.reject(errorFromBridge(message.error));
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
if (message.type === "close") {
|
|
59
|
+
emit({ type: "closed" });
|
|
60
|
+
transport.close();
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const unsubscribe = transport.onMessage(onMessage);
|
|
65
|
+
return {
|
|
66
|
+
dispose() {
|
|
67
|
+
unsubscribe();
|
|
68
|
+
globalThis.__senpi_restore_console__?.();
|
|
69
|
+
},
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function durationMs(startedAtMs) {
|
|
74
|
+
return Math.max(0, Math.round(performance.now() - startedAtMs));
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
function valueRepr(value) {
|
|
78
|
+
if (value === undefined) return undefined;
|
|
79
|
+
return JSON.stringify(value);
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function bridgeError(error) {
|
|
83
|
+
if (error instanceof Error) {
|
|
84
|
+
return { name: error.name, message: error.message, stack: error.stack };
|
|
85
|
+
}
|
|
86
|
+
return { message: String(error) };
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function errorFromBridge(error) {
|
|
90
|
+
const result = new Error(error.message);
|
|
91
|
+
if (error.name) result.name = error.name;
|
|
92
|
+
if (error.stack) result.stack = error.stack;
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { parentPort, workerData } from "node:worker_threads";
|
|
2
|
+
import { createWorkerCore } from "./worker-core.js";
|
|
3
|
+
|
|
4
|
+
if (!parentPort) throw new Error("JS kernel worker missing parentPort");
|
|
5
|
+
|
|
6
|
+
const transport = {
|
|
7
|
+
send(message) {
|
|
8
|
+
parentPort.postMessage(message);
|
|
9
|
+
},
|
|
10
|
+
onMessage(handler) {
|
|
11
|
+
parentPort.on("message", handler);
|
|
12
|
+
return () => parentPort.off("message", handler);
|
|
13
|
+
},
|
|
14
|
+
close() {
|
|
15
|
+
parentPort.close();
|
|
16
|
+
setTimeout(() => process.exit(0), 0);
|
|
17
|
+
},
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
createWorkerCore(transport, {
|
|
21
|
+
cwd: workerData.cwd,
|
|
22
|
+
parallelPoolWidth: workerData.parallelPoolWidth,
|
|
23
|
+
});
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
import { Worker } from "node:worker_threads";
|
|
2
|
+
import type { KernelToHostMessage } from "../../bridge/protocol.ts";
|
|
3
|
+
import type { WorkerLike } from "./inline-worker.ts";
|
|
4
|
+
import type { JavaScriptKernelMode } from "./kernel-contract.ts";
|
|
5
|
+
|
|
6
|
+
export class WorkerStartupCancelledError extends Error {
|
|
7
|
+
readonly name = "WorkerStartupCancelledError";
|
|
8
|
+
|
|
9
|
+
constructor() {
|
|
10
|
+
super("JavaScript worker startup was cancelled");
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export class JavaScriptWorkerExitedError extends Error {
|
|
15
|
+
readonly name = "JavaScriptWorkerExitedError";
|
|
16
|
+
readonly exitCode: number;
|
|
17
|
+
|
|
18
|
+
constructor(exitCode: number) {
|
|
19
|
+
super(`JavaScript worker exited with code ${exitCode}`);
|
|
20
|
+
this.exitCode = exitCode;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export function spawnNodeWorker(
|
|
25
|
+
url: URL,
|
|
26
|
+
cwd: string,
|
|
27
|
+
parallelPoolWidth: number,
|
|
28
|
+
mode: JavaScriptKernelMode = "worker",
|
|
29
|
+
): WorkerLike {
|
|
30
|
+
return wrapNodeWorker(
|
|
31
|
+
new Worker(url, {
|
|
32
|
+
workerData: { cwd, parallelPoolWidth },
|
|
33
|
+
}),
|
|
34
|
+
mode,
|
|
35
|
+
);
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
export function waitForReady(worker: WorkerLike, signal: AbortSignal): Promise<void> {
|
|
39
|
+
return new Promise((resolve, reject) => {
|
|
40
|
+
let offMessage = (): void => {};
|
|
41
|
+
let offError = (): void => {};
|
|
42
|
+
const cleanup = (): void => {
|
|
43
|
+
offMessage();
|
|
44
|
+
offError();
|
|
45
|
+
signal.removeEventListener("abort", onAbort);
|
|
46
|
+
};
|
|
47
|
+
const onAbort = (): void => {
|
|
48
|
+
cleanup();
|
|
49
|
+
reject(new WorkerStartupCancelledError());
|
|
50
|
+
};
|
|
51
|
+
offMessage = worker.onMessage((message) => {
|
|
52
|
+
if (message.type === "ready") {
|
|
53
|
+
cleanup();
|
|
54
|
+
resolve();
|
|
55
|
+
} else if (message.type === "init-failed") {
|
|
56
|
+
cleanup();
|
|
57
|
+
reject(errorFromBridge(message.error));
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
offError = worker.onError((error) => {
|
|
61
|
+
cleanup();
|
|
62
|
+
reject(error);
|
|
63
|
+
});
|
|
64
|
+
if (signal.aborted) onAbort();
|
|
65
|
+
else signal.addEventListener("abort", onAbort, { once: true });
|
|
66
|
+
});
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export function errorFromBridge(error: {
|
|
70
|
+
readonly message: string;
|
|
71
|
+
readonly name?: string;
|
|
72
|
+
readonly stack?: string;
|
|
73
|
+
}): Error {
|
|
74
|
+
const result = new Error(error.message);
|
|
75
|
+
if (error.name) result.name = error.name;
|
|
76
|
+
if (error.stack) result.stack = error.stack;
|
|
77
|
+
return result;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
export function bridgeError(error: Error): {
|
|
81
|
+
readonly message: string;
|
|
82
|
+
readonly name?: string;
|
|
83
|
+
readonly stack?: string;
|
|
84
|
+
} {
|
|
85
|
+
return { message: error.message, name: error.name, stack: error.stack };
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function wrapNodeWorker(worker: Worker, mode: JavaScriptKernelMode): WorkerLike {
|
|
89
|
+
return {
|
|
90
|
+
mode,
|
|
91
|
+
postMessage: (message) => worker.postMessage(message),
|
|
92
|
+
onMessage(handler) {
|
|
93
|
+
const listener = (message: KernelToHostMessage): void => handler(message);
|
|
94
|
+
worker.on("message", listener);
|
|
95
|
+
return () => worker.off("message", listener);
|
|
96
|
+
},
|
|
97
|
+
onError(handler) {
|
|
98
|
+
let reported = false;
|
|
99
|
+
const report = (error: Error): void => {
|
|
100
|
+
if (reported) return;
|
|
101
|
+
reported = true;
|
|
102
|
+
handler(error);
|
|
103
|
+
};
|
|
104
|
+
const onError = (error: Error): void => report(error);
|
|
105
|
+
const onExit = (code: number): void => report(new JavaScriptWorkerExitedError(code));
|
|
106
|
+
worker.on("error", onError);
|
|
107
|
+
worker.on("exit", onExit);
|
|
108
|
+
return () => {
|
|
109
|
+
worker.off("error", onError);
|
|
110
|
+
worker.off("exit", onExit);
|
|
111
|
+
};
|
|
112
|
+
},
|
|
113
|
+
async terminate() {
|
|
114
|
+
await worker.terminate();
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
export function indirectEval(source, filename) {
|
|
2
|
+
const withPragma = filename ? `${source}\n//# sourceURL=${filename}` : source;
|
|
3
|
+
const geval = globalThis.eval;
|
|
4
|
+
return geval(withPragma);
|
|
5
|
+
}
|
|
6
|
+
|
|
7
|
+
export async function awaitMaybePromise(value) {
|
|
8
|
+
if (!value || typeof value !== "object" || typeof value.then !== "function") return value;
|
|
9
|
+
return await value;
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export function wrapUserCode(code) {
|
|
13
|
+
const persistentCode = code.replace(/(^|\n)\s*(?:const|let|var)\s+([A-Za-z_$][\w$]*)\s*=/gu, "$1globalThis.$2 =");
|
|
14
|
+
if (/\breturn\b/u.test(persistentCode)) return `(async () => {\n${persistentCode}\n})()`;
|
|
15
|
+
return `(async () => {\n${captureLastExpression(persistentCode)}\n})()`;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function captureLastExpression(code) {
|
|
19
|
+
const start = findLastTopLevelStatementStart(code);
|
|
20
|
+
const head = code.slice(0, start);
|
|
21
|
+
const tail = code.slice(start).trim();
|
|
22
|
+
if (!tail || isStatementOnly(tail)) return code;
|
|
23
|
+
return `${head}return ${tail.replace(/;+$/u, "")};`;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function isStatementOnly(source) {
|
|
27
|
+
return /^(?:const|let|var|if|for|while|switch|try|catch|finally|class|function|import|export|throw|return|do|break|continue|debugger)\b/u.test(
|
|
28
|
+
source,
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function findLastTopLevelStatementStart(code) {
|
|
33
|
+
let start = 0;
|
|
34
|
+
let round = 0;
|
|
35
|
+
let square = 0;
|
|
36
|
+
let curly = 0;
|
|
37
|
+
let quote = "";
|
|
38
|
+
let escaped = false;
|
|
39
|
+
let lineComment = false;
|
|
40
|
+
let blockComment = false;
|
|
41
|
+
for (let index = 0; index < code.length; index += 1) {
|
|
42
|
+
const char = code[index];
|
|
43
|
+
const next = code[index + 1];
|
|
44
|
+
if (lineComment) {
|
|
45
|
+
if (char === "\n") lineComment = false;
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (blockComment) {
|
|
49
|
+
if (char === "*" && next === "/") {
|
|
50
|
+
blockComment = false;
|
|
51
|
+
index += 1;
|
|
52
|
+
}
|
|
53
|
+
continue;
|
|
54
|
+
}
|
|
55
|
+
if (quote) {
|
|
56
|
+
if (escaped) {
|
|
57
|
+
escaped = false;
|
|
58
|
+
} else if (char === "\\") {
|
|
59
|
+
escaped = true;
|
|
60
|
+
} else if (char === quote) {
|
|
61
|
+
quote = "";
|
|
62
|
+
}
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
if (char === "/" && next === "/") {
|
|
66
|
+
lineComment = true;
|
|
67
|
+
index += 1;
|
|
68
|
+
continue;
|
|
69
|
+
}
|
|
70
|
+
if (char === "/" && next === "*") {
|
|
71
|
+
blockComment = true;
|
|
72
|
+
index += 1;
|
|
73
|
+
continue;
|
|
74
|
+
}
|
|
75
|
+
if (char === "'" || char === '"' || char === "`") {
|
|
76
|
+
quote = char;
|
|
77
|
+
continue;
|
|
78
|
+
}
|
|
79
|
+
if (char === "(") round += 1;
|
|
80
|
+
else if (char === ")") round -= 1;
|
|
81
|
+
else if (char === "[") square += 1;
|
|
82
|
+
else if (char === "]") square -= 1;
|
|
83
|
+
else if (char === "{") curly += 1;
|
|
84
|
+
else if (char === "}") curly -= 1;
|
|
85
|
+
else if ((char === ";" || char === "\n") && round === 0 && square === 0 && curly === 0) start = index + 1;
|
|
86
|
+
}
|
|
87
|
+
return start;
|
|
88
|
+
}
|