1 // The algorithm used to determine whether a regexp can appear at a
2 // given point in the program is loosely based on sweet.js' approach.
3 // See https://github.com/mozilla/sweet.js/wiki/design
5 import {Parser} from "./state.js"
6 import {types as tt} from "./tokentype.js"
7 import {lineBreak} from "./whitespace.js"
9 export class TokContext {
10 constructor(token, isExpr, preserveSpace, override, generator) {
12 this.isExpr = !!isExpr
13 this.preserveSpace = !!preserveSpace
14 this.override = override
15 this.generator = !!generator
19 export const types = {
20 b_stat: new TokContext("{", false),
21 b_expr: new TokContext("{", true),
22 b_tmpl: new TokContext("${", false),
23 p_stat: new TokContext("(", false),
24 p_expr: new TokContext("(", true),
25 q_tmpl: new TokContext("`", true, true, p => p.tryReadTemplateToken()),
26 f_stat: new TokContext("function", false),
27 f_expr: new TokContext("function", true),
28 f_expr_gen: new TokContext("function", true, false, null, true),
29 f_gen: new TokContext("function", false, false, null, true)
32 const pp = Parser.prototype
34 pp.initialContext = function() {
38 pp.curContext = function() {
39 return this.context[this.context.length - 1]
42 pp.braceIsBlock = function(prevType) {
43 let parent = this.curContext()
44 if (parent === types.f_expr || parent === types.f_stat)
46 if (prevType === tt.colon && (parent === types.b_stat || parent === types.b_expr))
49 // The check for `tt.name && exprAllowed` detects whether we are
50 // after a `yield` or `of` construct. See the `updateContext` for
52 if (prevType === tt._return || prevType === tt.name && this.exprAllowed)
53 return lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
54 if (prevType === tt._else || prevType === tt.semi || prevType === tt.eof || prevType === tt.parenR || prevType === tt.arrow)
56 if (prevType === tt.braceL)
57 return parent === types.b_stat
58 if (prevType === tt._var || prevType === tt._const || prevType === tt.name)
60 return !this.exprAllowed
63 pp.inGeneratorContext = function() {
64 for (let i = this.context.length - 1; i >= 1; i--) {
65 let context = this.context[i]
66 if (context.token === "function")
67 return context.generator
72 pp.updateContext = function(prevType) {
73 let update, type = this.type
74 if (type.keyword && prevType === tt.dot)
75 this.exprAllowed = false
76 else if (update = type.updateContext)
77 update.call(this, prevType)
79 this.exprAllowed = type.beforeExpr
82 // Used to handle egde case when token context could not be inferred correctly in tokenize phase
83 pp.overrideContext = function(tokenCtx) {
84 if (this.curContext() !== tokenCtx) {
85 this.context[this.context.length - 1] = tokenCtx
89 // Token-specific context update code
91 tt.parenR.updateContext = tt.braceR.updateContext = function() {
92 if (this.context.length === 1) {
93 this.exprAllowed = true
96 let out = this.context.pop()
97 if (out === types.b_stat && this.curContext().token === "function") {
98 out = this.context.pop()
100 this.exprAllowed = !out.isExpr
103 tt.braceL.updateContext = function(prevType) {
104 this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr)
105 this.exprAllowed = true
108 tt.dollarBraceL.updateContext = function() {
109 this.context.push(types.b_tmpl)
110 this.exprAllowed = true
113 tt.parenL.updateContext = function(prevType) {
114 let statementParens = prevType === tt._if || prevType === tt._for || prevType === tt._with || prevType === tt._while
115 this.context.push(statementParens ? types.p_stat : types.p_expr)
116 this.exprAllowed = true
119 tt.incDec.updateContext = function() {
120 // tokExprAllowed stays unchanged
123 tt._function.updateContext = tt._class.updateContext = function(prevType) {
124 if (prevType.beforeExpr && prevType !== tt._else &&
125 !(prevType === tt.semi && this.curContext() !== types.p_stat) &&
126 !(prevType === tt._return && lineBreak.test(this.input.slice(this.lastTokEnd, this.start))) &&
127 !((prevType === tt.colon || prevType === tt.braceL) && this.curContext() === types.b_stat))
128 this.context.push(types.f_expr)
130 this.context.push(types.f_stat)
131 this.exprAllowed = false
134 tt.backQuote.updateContext = function() {
135 if (this.curContext() === types.q_tmpl)
138 this.context.push(types.q_tmpl)
139 this.exprAllowed = false
142 tt.star.updateContext = function(prevType) {
143 if (prevType === tt._function) {
144 let index = this.context.length - 1
145 if (this.context[index] === types.f_expr)
146 this.context[index] = types.f_expr_gen
148 this.context[index] = types.f_gen
150 this.exprAllowed = true
153 tt.name.updateContext = function(prevType) {
155 if (this.options.ecmaVersion >= 6 && prevType !== tt.dot) {
156 if (this.value === "of" && !this.exprAllowed ||
157 this.value === "yield" && this.inGeneratorContext())
160 this.exprAllowed = allowed