Created
April 25, 2025 16:58
-
-
Save gustavopch/4b07acc9a27f55ce594cd3e738ff0b2d to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
export function formatFormula(formula: string): string { | |
const INDENT = ' ' // 2 spaces | |
let out = '' | |
let depth = 0 | |
/** Stack of the current call-chain. */ | |
const stack: Array<{ fn: string; argCount: number }> = [] | |
/** Helper → current indentation string */ | |
const indent = () => INDENT.repeat(depth) | |
/** Is char inside quotes right now? */ | |
let inString: false | '"' | "'" = false | |
for (let i = 0; i < formula.length; i++) { | |
const ch = formula[i] | |
/* ────────── Handle string literals ────────── */ | |
if (inString) { | |
out += ch | |
// spreadsheet strings escape quotes by doubling them ( "") | |
if (ch === inString && formula[i + 1] !== inString) { | |
inString = false | |
} else if (ch === inString && formula[i + 1] === inString) { | |
// skip the escaped quote partner, copy it too | |
out += formula[++i] | |
} | |
continue | |
} | |
if (ch === '"' || ch === "'") { | |
inString = ch | |
out += ch | |
continue | |
} | |
/* ────────── Open-parenthesis: new function level ────────── */ | |
if (ch === '(') { | |
// look backwards to fetch the function/token name | |
let j = out.length - 1 | |
while (j >= 0 && /\s/.test(out[j])) j-- | |
const end = j | |
while (j >= 0 && /[\w.|$]/.test(out[j])) j-- // include sheet names “.” “|” “$” | |
const fn = out.slice(j + 1, end + 1).toUpperCase() | |
stack.push({ fn, argCount: 0 }) | |
out += '(' | |
depth++ | |
out += '\n' + indent() | |
continue | |
} | |
/* ────────── Comma: argument separator ────────── */ | |
if (ch === ',') { | |
if (stack.length) { | |
const top = stack[stack.length - 1] | |
top.argCount += 1 | |
// Inside PAIR2JSON we keep first comma of each pair inline | |
if (top.fn === 'PAIR2JSON' && top.argCount % 2 === 1) { | |
out += ', ' | |
continue | |
} | |
} | |
out += ',\n' + indent() | |
continue | |
} | |
/* ────────── Close-parenthesis: end function level ────────── */ | |
if (ch === ')') { | |
depth-- | |
out += '\n' + indent() + ')' | |
stack.pop() | |
// if next char is another “)” or “,” we’ll let loop handle it | |
continue | |
} | |
/* ────────── Any other char ────────── */ | |
out += ch | |
} | |
return out.trim() | |
} | |
// TESTS | |
// ============================================================================= | |
const tests = [ | |
{ | |
name: 'Função simples com 2 args', | |
input: '=SOMA(1,2)', | |
expected: `=SOMA( | |
1, | |
2 | |
)`, | |
}, | |
{ | |
name: 'Aninhamento profundo', | |
input: '=IF(AND(A1>0,B1<5),SUM(A1,B1),IF(C1>10,MAX(A1,C1),0))', | |
expected: `=IF( | |
AND( | |
A1>0, | |
B1<5 | |
), | |
SUM( | |
A1, | |
B1 | |
), | |
IF( | |
C1>10, | |
MAX( | |
A1, | |
C1 | |
), | |
0 | |
) | |
)`, | |
}, | |
{ | |
name: 'PAIR2JSON especial com pares na mesma linha', | |
input: '=PAIR2JSON("a",1,"b",2,"c",FUNC(3,4))', | |
expected: `=PAIR2JSON( | |
"a", 1, | |
"b", 2, | |
"c", FUNC( | |
3, | |
4 | |
) | |
)`, | |
}, | |
] | |
for (const { name, input, expected } of tests) { | |
const result = formatFormula(input) | |
const passed = result === expected | |
console.log(`Test: ${name}`) | |
console.log(passed ? '✅ Passed' : '❌ Failed') | |
if (!passed) { | |
console.log('--- Expected:\n' + expected) | |
console.log('--- Received:\n' + result) | |
} | |
console.log() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment