Another statement expression-related proposal

# Isiah Meadows (8 years ago)

Yes, there's been a bit of talk about modifying the do proposal in various ways (like allowing more than a block, using = to differentiate, etc.), but each of these have potential ambiguities (like conflicting with do-while or object literal syntax, respectively), and give unintuitive results with loops.

Here's my idea: prepend a :: to any non-expression statement, and it becomes an expression. The last statement's value (as if through eval) is used as the return value, with exception of loops, which return an array generated from their loop body. Here's a few examples to clarify:

// plain block
const foo = :: { let a = 1; a }
assert.equal(foo, 1)

// if-else
let cond = false
const bar = :: if (cond) "hi" else "bye"
assert.equal(bar, "bye")

// try-catch
let e = new Error()
const error = :: try { throw e } catch (e) { e }
assert.equal(error, e)

// while loop
let i = 0
const range = :: while (i < 10) i++
assert.deepEqual(range, [
  0, 1, 2, 3, 4, 5, 6, 7, 8, 9,
])

// for + nested if-else
const fizzBuzz = :: for (let i = 0; i < 100; i++) {
  if (i % 15) "FizzBuzz"
  else if (i % 3) "Fizz"
  else if (i % 5) "Buzz"
  // If no expression is evaluated in the body, then no value
  // is pushed.
}
assert.deepEqual(fizzBuzz, [
  // 100 lines of the classic "Fizz", "Buzz", and "FizzBuzz"
])

What do you all think of this?


To clarify, here's some more detailed semantics:

Grammar

  • :: ExpressionStatement
  • :: Declaration
  • :: VariableStatement

These are early errors, and they are all redundant, anyways, since you already have a value you could use.

  • :: ContinueStatement
  • :: BreakStatement
  • :: ReturnStatement
  • :: DebuggerStatement
  • :: ThrowStatement
  • :: EmptyStatement

These are also early errors, since there's no real value you can associate with them.

  • :: BlockStatement
  • :: IfStatement
  • :: SwitchStatement
  • :: WithStatement
  • :: TryStatement
  1. Let completion be ? ExecuteStatement(statement), where statement is one of the statements above.
  2. If completion is none, return undefined
  3. Otherwise, return completion.
  • :: IterationStatement
  1. Return ? ExecuteIterationStatement(statement), where statement is one of the statements above.
  • :: LabelledStatement

This works similarly, but has an outer label instead. The same above statements are also similarly banned as LabelledItem entries. Additionally, the Annex B extension for FunctionDeclaraions are also early errors.

(Should these even be allowed? I'm open to making this an early error instead.)

Abstract Operation ExecuteStatement(statement)

  1. If statement is an IterationStatement:
  2. Let list be a new empty list.
  3. Evaluate statement, but for each child statement:
  4. Let result be ? ExecuteStatement(child).
  5. If result is not empty, then append result to list.
  6. Return list.
  7. If statement is an ExpressionStatement:
  8. Return the result of evaluating the expression within.
  9. If statement is a BreakStatement or ContinueStatement:
  10. Evaluate statement
  11. Return empty.
  12. If statement is a Declaration:
  13. Evaluate statement.
  14. Return the value of the resulting declaration
  15. Otherwise:
  16. Let last be empty
  17. Evaluate statement. For each child statement:
  18. Let last be ? ExecuteStatement(child).
  19. Note: last is the final expression's value, or empty if there was no final expression.
  20. Return last.

(I probably missed several edge cases, but this is just a mailing list strawman.)

# Bob Myers (8 years ago)

Just a random thought, but would {= =} work for expression blocks?

// plain block
const foo = {= let a = 1; a =};
assert.equal(foo, 1)

// if-else
let cond = false
const bar = {= if (cond) "hi" else "bye" =};
assert.equal(bar, "bye")

// try-catch
let e = new Error()
const error = {= try { throw e } catch (e) { e } =};
assert.equal(error, e)

I don't know about the loop idea. It seems like it's trying to do too much.

# Isiah Meadows (8 years ago)

Inline.

On Wed, Nov 2, 2016, 01:01 Bob Myers <rtm at gol.com> wrote:

Just a random thought, but would {= =} work for expression blocks?

// plain block
const foo = {= let a = 1; a =};

assert.equal(foo, 1)

// if-else
let cond = false
const bar = {= if (cond) "hi" else "bye" =};

assert.equal(bar, "bye")

// try-catch
let e = new Error()
const error = {= try { throw e } catch (e) { e } =};
assert.equal(error, e)

In theory, yes, but it doesn't exactly look very pretty (too many equals signs and curly braces).

I don't know about the loop idea. It seems like it's trying to do too much.

The loop thing is a bit extra, complicating things some, but I feel it's merited by the ability to avoid a lot of the confusion when using loops with the current do expression proposal (loops currently act like reduce, which is incredibly unintuitive).