39 lines
1.8 KiB
TypeScript
39 lines
1.8 KiB
TypeScript
// 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<string, string> = { ")": "(", "]": "[", "}": "{" };
|
|
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;
|
|
}
|