// Split only at top level: loops, conditionals, functions and action parameters // must stay intact. Comments are removed without touching quoted strings. export function splitGcsimStatements(input: string): string[] { const statements: string[] = []; const stack: string[] = []; // Replacing comments up front also lets a comment appear before an else. input = input.replace(/"(?:\\.|[^"\\])*"|'(?:\\.|[^'\\])*'|#[^\n]*|\/\/[^\n]*/g, (match) => match.startsWith("#") || match.startsWith("//") ? " " : match); let text = ""; let quote = ""; let escaped = false; const flush = () => { if (text.trim()) statements.push(text.trim()); text = ""; }; for (let i = 0; i < input.length; i++) { const char = input[i]; if (quote) { text += char; if (escaped) escaped = false; else if (char === "\\") escaped = true; else if (char === quote) quote = ""; continue; } if (char === '"' || char === "'") { quote = char; text += char; continue; } if ("([{".includes(char)) { stack.push(char); if (stack.length > 64) throw new Error("Config is nested too deeply."); } else if (")]}".includes(char)) { const expected: Record = { ")": "(", "]": "[", "}": "{" }; if (stack.pop() !== expected[char]) throw new Error(`Unmatched '${char}' at position ${i + 1}.`); } text += char; if (stack.length === 0 && char === ";") flush(); // Keep an else branch attached to its if statement. if (stack.length === 0 && char === "}" && !/^\s*else\b/.test(input.slice(i + 1))) flush(); } if (quote) throw new Error("Unclosed quoted string."); if (stack.length) throw new Error("Unclosed bracket or parenthesis."); if (text.trim()) throw new Error("Missing ';' at the end of a statement."); return statements; }