Skip to content

Instantly share code, notes, and snippets.

@gustavopch
Created April 25, 2025 16:58
Show Gist options
  • Save gustavopch/4b07acc9a27f55ce594cd3e738ff0b2d to your computer and use it in GitHub Desktop.
Save gustavopch/4b07acc9a27f55ce594cd3e738ff0b2d to your computer and use it in GitHub Desktop.
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