function skipWitespace(program) {
  const nonWhitespaceCharacter = program.search(/\S/);
  return program.substring(nonWhitespaceCharacter);
}

function parseExpression(program) {
  program = skipWitespace(program);

  const stringValue = /^"([^"]*)"/.exec(program);
  if (stringValue) {
    return {
      expression: {
        type: 'string',
        value : stringValue[1]
      },
      // +2 for the quote chars ("(something)")
      program: program.substr(stringValue[1].length + 2)
    }
  }

  const numberValue = /^(\d+)/.exec(program);
  if (numberValue) {
    return {
      expression: {
        type: 'integer',
        value: parseInt(numberValue[1], 10)
      },
      program: program.substr(numberValue[1].length)
    }
  }

  const word = /^([^\(\),\s]+)/.exec(program);

  if (word) {
    program = skipWitespace(program.substr(word[1].length));

    if (program[0] === '(') {
      return parseApply(word[1], program.substr(1))
    }

    return {
      expression: {
        type: 'word',
        value: word[1]
      },
      program
    }
  }

  throw new Error(`Token not recognized: ${program}`);
}

function parseApply(applyToken, program) {
  // ( has already been discarded, collect arguments until )
  const args = [];

  console.log(applyToken, program);

  for(;;) {
    const result = parseExpression(program);

    let expression = result.expression;
    program = result.program;

    args.push(expression);

    console.log(expression, program);

    program = skipWitespace(program);

    if (program[0] === ')') {
      break;
    }

    if (program[0] === ',') {
      program = skipWitespace(program.substr(1));
    } else {
      throw new Error(`Failed, expected comma as argument separator: ${program} ${JSON.stringify(expression)}`);
    }
  }

  // Advance beyond ')'
  return {
    expression: {
      type: 'apply',
      name: applyToken,
      args
    },
     program: program.substr(1)
  }
}

// ***********************
// TESTS
// ***********************
function testSingleExpression() {
  console.log(parseExpression("   \"yaya\""));
  console.log(parseExpression("   1234"));
  console.log(parseExpression("   do("));
  console.log(parseExpression("   a,b"));
  console.log(parseExpression("   a,   b"));
}

function testParseApply() {
  console.log(parseExpression("do(if(true, +(1,2), false))"))
}

testSingleExpression();
testParseApply();
@@ -1247,45 +1247,8 @@
 %5D;%0A%0A
-  console.log(applyToken, program);%0A%0A
   fo
@@ -1404,47 +1404,8 @@
 );%0A%0A
-    console.log(expression, program);%0A%0A
     
@@ -1933,27 +1933,29 @@
 unction 
-testSingleE
+prettyPrint(e
 xpressio
@@ -1955,17 +1955,16 @@
 pression
-(
 ) %7B%0A  co
@@ -1977,97 +1977,143 @@
 log(
-parseExpression(%22   %5C%22yaya%5C%22%22));%0A  console.log(parseExpression(%22   1234%22));%0A  console.log
+JSON.stringify(expression, null, 2));%0A%7D%0A%0Afunction testSingleExpression() %7B%0A  prettyPrint(parseExpression(%22   %5C%22yaya%5C%22%22));%0A  prettyPrint
 (par
@@ -2133,29 +2133,30 @@
 %22   
-do(
+1234
 %22));%0A  
-console.log
+prettyPrint
 (par
@@ -2182,27 +2182,27 @@
 b%22));%0A  
-console.log
+prettyPrint
 (parseEx
@@ -2258,27 +2258,27 @@
 y() %7B%0A  
-console.log
+prettyPrint
 (parseEx
@@ -2317,16 +2317,17 @@
 lse))%22))
+;
 %0A%7D%0A%0Atest

Clean up and pretty print syntax