| // The algorithm used to determine whether a regexp can appear at a |
| // given point in the program is loosely based on sweet.js' approach. |
| // See https://github.com/mozilla/sweet.js/wiki/design |
| |
| import {Parser} from "./state" |
| import {types as tt} from "./tokentype" |
| import {lineBreak} from "./whitespace" |
| |
| export class TokContext { |
| constructor(token, isExpr, preserveSpace, override) { |
| this.token = token |
| this.isExpr = !!isExpr |
| this.preserveSpace = !!preserveSpace |
| this.override = override |
| } |
| } |
| |
| export const types = { |
| b_stat: new TokContext("{", false), |
| b_expr: new TokContext("{", true), |
| b_tmpl: new TokContext("${", true), |
| p_stat: new TokContext("(", false), |
| p_expr: new TokContext("(", true), |
| q_tmpl: new TokContext("`", true, true, p => p.readTmplToken()), |
| f_expr: new TokContext("function", true) |
| } |
| |
| const pp = Parser.prototype |
| |
| pp.initialContext = function() { |
| return [types.b_stat] |
| } |
| |
| pp.braceIsBlock = function(prevType) { |
| if (prevType === tt.colon) { |
| let parent = this.curContext() |
| if (parent === types.b_stat || parent === types.b_expr) |
| return !parent.isExpr |
| } |
| if (prevType === tt._return) |
| return lineBreak.test(this.input.slice(this.lastTokEnd, this.start)) |
| if (prevType === tt._else || prevType === tt.semi || prevType === tt.eof || prevType === tt.parenR) |
| return true |
| if (prevType == tt.braceL) |
| return this.curContext() === types.b_stat |
| return !this.exprAllowed |
| } |
| |
| pp.updateContext = function(prevType) { |
| let update, type = this.type |
| if (type.keyword && prevType == tt.dot) |
| this.exprAllowed = false |
| else if (update = type.updateContext) |
| update.call(this, prevType) |
| else |
| this.exprAllowed = type.beforeExpr |
| } |
| |
| // Token-specific context update code |
| |
| tt.parenR.updateContext = tt.braceR.updateContext = function() { |
| if (this.context.length == 1) { |
| this.exprAllowed = true |
| return |
| } |
| let out = this.context.pop() |
| if (out === types.b_stat && this.curContext() === types.f_expr) { |
| this.context.pop() |
| this.exprAllowed = false |
| } else if (out === types.b_tmpl) { |
| this.exprAllowed = true |
| } else { |
| this.exprAllowed = !out.isExpr |
| } |
| } |
| |
| tt.braceL.updateContext = function(prevType) { |
| this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr) |
| this.exprAllowed = true |
| } |
| |
| tt.dollarBraceL.updateContext = function() { |
| this.context.push(types.b_tmpl) |
| this.exprAllowed = true |
| } |
| |
| tt.parenL.updateContext = function(prevType) { |
| let statementParens = prevType === tt._if || prevType === tt._for || prevType === tt._with || prevType === tt._while |
| this.context.push(statementParens ? types.p_stat : types.p_expr) |
| this.exprAllowed = true |
| } |
| |
| tt.incDec.updateContext = function() { |
| // tokExprAllowed stays unchanged |
| } |
| |
| tt._function.updateContext = function(prevType) { |
| if (prevType.beforeExpr && prevType !== tt.semi && prevType !== tt._else && |
| !((prevType === tt.colon || prevType === tt.braceL) && this.curContext() === types.b_stat)) |
| this.context.push(types.f_expr) |
| this.exprAllowed = false |
| } |
| |
| tt.backQuote.updateContext = function() { |
| if (this.curContext() === types.q_tmpl) |
| this.context.pop() |
| else |
| this.context.push(types.q_tmpl) |
| this.exprAllowed = false |
| } |