Upgrade to https://github.com/acornjs/acorn.git commit 84eda6bf
[jst.git] / src / tokencontext.js
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
4
5 import {Parser} from "./state.js"
6 import {types as tt} from "./tokentype.js"
7 import {lineBreak} from "./whitespace.js"
8
9 export class TokContext {
10   constructor(token, isExpr, preserveSpace, override, generator) {
11     this.token = token
12     this.isExpr = !!isExpr
13     this.preserveSpace = !!preserveSpace
14     this.override = override
15     this.generator = !!generator
16   }
17 }
18
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)
30 }
31
32 const pp = Parser.prototype
33
34 pp.initialContext = function() {
35   return [types.b_stat]
36 }
37
38 pp.curContext = function() {
39   return this.context[this.context.length - 1]
40 }
41
42 pp.braceIsBlock = function(prevType) {
43   let parent = this.curContext()
44   if (parent === types.f_expr || parent === types.f_stat)
45     return true
46   if (prevType === tt.colon && (parent === types.b_stat || parent === types.b_expr))
47     return !parent.isExpr
48
49   // The check for `tt.name && exprAllowed` detects whether we are
50   // after a `yield` or `of` construct. See the `updateContext` for
51   // `tt.name`.
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)
55     return true
56   if (prevType === tt.braceL)
57     return parent === types.b_stat
58   if (prevType === tt._var || prevType === tt._const || prevType === tt.name)
59     return false
60   return !this.exprAllowed
61 }
62
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
68   }
69   return false
70 }
71
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)
78   else
79     this.exprAllowed = type.beforeExpr
80 }
81
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
86   }
87 }
88
89 // Token-specific context update code
90
91 tt.parenR.updateContext = tt.braceR.updateContext = function() {
92   if (this.context.length === 1) {
93     this.exprAllowed = true
94     return
95   }
96   let out = this.context.pop()
97   if (out === types.b_stat && this.curContext().token === "function") {
98     out = this.context.pop()
99   }
100   this.exprAllowed = !out.isExpr
101 }
102
103 tt.braceL.updateContext = function(prevType) {
104   this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr)
105   this.exprAllowed = true
106 }
107
108 tt.dollarBraceL.updateContext = function() {
109   this.context.push(types.b_tmpl)
110   this.exprAllowed = true
111 }
112
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
117 }
118
119 tt.incDec.updateContext = function() {
120   // tokExprAllowed stays unchanged
121 }
122
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)
129   else
130     this.context.push(types.f_stat)
131   this.exprAllowed = false
132 }
133
134 tt.backQuote.updateContext = function() {
135   if (this.curContext() === types.q_tmpl)
136     this.context.pop()
137   else
138     this.context.push(types.q_tmpl)
139   this.exprAllowed = false
140 }
141
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
147     else
148       this.context[index] = types.f_gen
149   }
150   this.exprAllowed = true
151 }
152
153 tt.name.updateContext = function(prevType) {
154   let allowed = false
155   if (this.options.ecmaVersion >= 6 && prevType !== tt.dot) {
156     if (this.value === "of" && !this.exprAllowed ||
157         this.value === "yield" && this.inGeneratorContext())
158       allowed = true
159   }
160   this.exprAllowed = allowed
161 }