Skip to content

Commit

Permalink
Make 'let' a contextual keyword
Browse files Browse the repository at this point in the history
  • Loading branch information
marijnh committed Jan 15, 2016
1 parent 3b4ab11 commit 8f46eb5
Show file tree
Hide file tree
Showing 6 changed files with 98 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/identifier.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

21 changes: 13 additions & 8 deletions src/loose/statement.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,12 @@ lp.parseTopLevel = function() {
}

lp.parseStatement = function() {
let starttype = this.tok.type, node = this.startNode()
let starttype = this.tok.type, node = this.startNode(), kind

if (this.toks.isLet()) {
starttype = tt._var
kind = "let"
}

switch (starttype) {
case tt._break: case tt._continue:
Expand Down Expand Up @@ -47,8 +52,9 @@ lp.parseStatement = function() {
this.pushCx()
this.expect(tt.parenL)
if (this.tok.type === tt.semi) return this.parseFor(node, null)
if (this.tok.type === tt._var || this.tok.type === tt._let || this.tok.type === tt._const) {
let init = this.parseVar(true)
let isLet = this.toks.isLet()
if (isLet || this.tok.type === tt._var || this.tok.type === tt._const) {
let init = this.parseVar(true, isLet ? "let" : this.tok.value)
if (init.declarations.length === 1 && (this.tok.type === tt._in || this.isContextual("of"))) {
return this.parseForIn(node, init)
}
Expand Down Expand Up @@ -133,9 +139,8 @@ lp.parseStatement = function() {
return this.finishNode(node, "TryStatement")

case tt._var:
case tt._let:
case tt._const:
return this.parseVar()
return this.parseVar(false, kind || this.tok.value)

case tt._while:
this.next()
Expand Down Expand Up @@ -218,9 +223,9 @@ lp.parseForIn = function(node, init) {
return this.finishNode(node, type)
}

lp.parseVar = function(noIn) {
lp.parseVar = function(noIn, kind) {
let node = this.startNode()
node.kind = this.tok.type.keyword
node.kind = kind
this.next()
node.declarations = []
do {
Expand Down Expand Up @@ -329,7 +334,7 @@ lp.parseExport = function() {
this.semicolon()
return this.finishNode(node, "ExportDefaultDeclaration")
}
if (this.tok.type.keyword) {
if (this.tok.type.keyword || this.toks.isLet()) {
node.declaration = this.parseStatement()
node.specifiers = []
node.source = null
Expand Down
45 changes: 34 additions & 11 deletions src/statement.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import {types as tt} from "./tokentype"
import {Parser} from "./state"
import {lineBreak} from "./whitespace"
import {lineBreak, skipWhiteSpace} from "./whitespace"
import {isIdentifierStart, isIdentifierChar} from "./identifier"

const pp = Parser.prototype

Expand Down Expand Up @@ -31,6 +32,20 @@ pp.parseTopLevel = function(node) {

const loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"}

pp.isLet = function() {
if (this.type !== tt.name || this.options.ecmaVersion < 6 || this.value != "let") return false
skipWhiteSpace.lastIndex = this.pos
let skip = skipWhiteSpace.exec(this.input)
let next = this.pos + skip[0].length, nextCh = this.input.charCodeAt(next)
if (nextCh === 91 || nextCh == 123) return true // '{' and '['
if (isIdentifierStart(nextCh, true)) {
for (var pos = next + 1; isIdentifierChar(this.input.charCodeAt(pos, true)); ++pos) {}
let ident = this.input.slice(next, pos)
if (!this.isKeyword(ident)) return true
}
return false
}

// Parse a single statement.
//
// If expecting a statement and finding a slash operator, parse a
Expand All @@ -39,7 +54,12 @@ const loopLabel = {kind: "loop"}, switchLabel = {kind: "switch"}
// does not help.

pp.parseStatement = function(declaration, topLevel) {
let starttype = this.type, node = this.startNode()
let starttype = this.type, node = this.startNode(), kind

if (this.isLet()) {
starttype = tt._var
kind = "let"
}

// Most types of statements are recognized by the keyword they
// start with. Many are trivial to parse, some require a bit of
Expand All @@ -61,8 +81,10 @@ pp.parseStatement = function(declaration, topLevel) {
case tt._switch: return this.parseSwitchStatement(node)
case tt._throw: return this.parseThrowStatement(node)
case tt._try: return this.parseTryStatement(node)
case tt._let: case tt._const: if (!declaration) this.unexpected() // NOTE: falls through to _var
case tt._var: return this.parseVarStatement(node, starttype)
case tt._const: case tt._var:
kind = kind || this.value
if (!declaration && kind != "var") this.unexpected()
return this.parseVarStatement(node, kind)
case tt._while: return this.parseWhileStatement(node)
case tt._with: return this.parseWithStatement(node)
case tt.braceL: return this.parseBlock()
Expand Down Expand Up @@ -146,13 +168,14 @@ pp.parseForStatement = function(node) {
this.labels.push(loopLabel)
this.expect(tt.parenL)
if (this.type === tt.semi) return this.parseFor(node, null)
if (this.type === tt._var || this.type === tt._let || this.type === tt._const) {
let init = this.startNode(), varKind = this.type
let isLet = this.isLet()
if (this.type === tt._var || this.type === tt._const || isLet) {
let init = this.startNode(), kind = isLet ? "let" : this.value
this.next()
this.parseVar(init, true, varKind)
this.parseVar(init, true, kind)
this.finishNode(init, "VariableDeclaration")
if ((this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of"))) && init.declarations.length === 1 &&
!(varKind !== tt._var && init.declarations[0].init))
!(kind !== "var" && init.declarations[0].init))
return this.parseForIn(node, init)
return this.parseFor(node, init)
}
Expand Down Expand Up @@ -374,13 +397,13 @@ pp.parseForIn = function(node, init) {

pp.parseVar = function(node, isFor, kind) {
node.declarations = []
node.kind = kind.keyword
node.kind = kind
for (;;) {
let decl = this.startNode()
this.parseVarId(decl)
if (this.eat(tt.eq)) {
decl.init = this.parseMaybeAssign(isFor)
} else if (kind === tt._const && !(this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of")))) {
} else if (kind === "const" && !(this.type === tt._in || (this.options.ecmaVersion >= 6 && this.isContextual("of")))) {
this.unexpected()
} else if (decl.id.type != "Identifier" && !(isFor && (this.type === tt._in || this.isContextual("of")))) {
this.raise(this.lastTokEnd, "Complex binding patterns require an initialization value")
Expand Down Expand Up @@ -542,7 +565,7 @@ pp.parseExport = function(node) {
}

pp.shouldParseExportStatement = function() {
return this.type.keyword
return this.type.keyword || this.isLet()
}

// Parses a comma-separated list of module exports.
Expand Down
1 change: 0 additions & 1 deletion src/tokentype.js
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,6 @@ kw("switch")
kw("throw", beforeExpr)
kw("try")
kw("var")
kw("let")
kw("const")
kw("while", {isLoop: true})
kw("with")
Expand Down
1 change: 1 addition & 0 deletions src/whitespace.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,3 +10,4 @@ export function isNewLine(code) {

export const nonASCIIwhitespace = /[\u1680\u180e\u2000-\u200a\u202f\u205f\u3000\ufeff]/

export const skipWhiteSpace = /(?:\s|\/\/.*|\/\*[^]*?\*\/)*/g
50 changes: 49 additions & 1 deletion test/tests-harmony.js
Original file line number Diff line number Diff line change
Expand Up @@ -13353,6 +13353,54 @@ test("(function () { yield* 10 })", {
locations: true
});

test("let + 1", {
"type": "Program",
"body": [
{
"type": "ExpressionStatement",
"expression": {
"type": "BinaryExpression",
"left": {
"type": "Identifier",
"name": "let"
},
"operator": "+",
"right": {
"type": "Literal",
"value": 1,
"raw": "1"
}
}
}
]
}, {ecmaVersion: 6})

test("var let = 1", {
"type": "Program",
"body": [
{
"type": "VariableDeclaration",
"declarations": [
{
"type": "VariableDeclarator",
"id": {
"type": "Identifier",
"name": "let"
},
"init": {
"type": "Literal",
"value": 1,
"raw": "1"
}
}
],
"kind": "var"
}
]
}, {ecmaVersion: 6})

testFail("'use strict'; let + 1", "The keyword 'let' is reserved (1:14)", {ecmaVersion: 6})

testFail("(function() { \"use strict\"; f(yield v) })", "Unexpected token (1:36)", {ecmaVersion: 6});

testFail("var obj = { *test** }", "Unexpected token (1:17)", {ecmaVersion: 6});
Expand All @@ -13371,7 +13419,7 @@ testFail("`hello ${10;test`", "Unexpected token (1:11)", {ecmaVersion: 6});

testFail("function a() 1 // expression closure is not supported", "Unexpected token (1:13)", {ecmaVersion: 6});

testFail("[for (let x of []) x]", "Unexpected token (1:6)", {ecmaVersion: 7});
testFail("[for (let x of []) x]", "Unexpected token (1:10)", {ecmaVersion: 7});

testFail("[for (const x of []) x]", "Unexpected token (1:6)", {ecmaVersion: 7});

Expand Down

0 comments on commit 8f46eb5

Please sign in to comment.