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
5import {Parser} from "./state"
6import {types as tt} from "./tokentype"
7import {lineBreak} from "./whitespace"
8
9export class TokContext {
10  constructor(token, isExpr, preserveSpace, override) {
11    this.token = token
12    this.isExpr = !!isExpr
13    this.preserveSpace = !!preserveSpace
14    this.override = override
15  }
16}
17
18export const types = {
19  b_stat: new TokContext("{", false),
20  b_expr: new TokContext("{", true),
21  b_tmpl: new TokContext("${", true),
22  p_stat: new TokContext("(", false),
23  p_expr: new TokContext("(", true),
24  q_tmpl: new TokContext("`", true, true, p => p.readTmplToken()),
25  f_expr: new TokContext("function", true)
26}
27
28const pp = Parser.prototype
29
30pp.initialContext = function() {
31  return [types.b_stat]
32}
33
34pp.braceIsBlock = function(prevType) {
35  if (prevType === tt.colon) {
36    let parent = this.curContext()
37    if (parent === types.b_stat || parent === types.b_expr)
38      return !parent.isExpr
39  }
40  if (prevType === tt._return)
41    return lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
42  if (prevType === tt._else || prevType === tt.semi || prevType === tt.eof || prevType === tt.parenR)
43    return true
44  if (prevType == tt.braceL)
45    return this.curContext() === types.b_stat
46  return !this.exprAllowed
47}
48
49pp.updateContext = function(prevType) {
50  let update, type = this.type
51  if (type.keyword && prevType == tt.dot)
52    this.exprAllowed = false
53  else if (update = type.updateContext)
54    update.call(this, prevType)
55  else
56    this.exprAllowed = type.beforeExpr
57}
58
59// Token-specific context update code
60
61tt.parenR.updateContext = tt.braceR.updateContext = function() {
62  if (this.context.length == 1) {
63    this.exprAllowed = true
64    return
65  }
66  let out = this.context.pop()
67  if (out === types.b_stat && this.curContext() === types.f_expr) {
68    this.context.pop()
69    this.exprAllowed = false
70  } else if (out === types.b_tmpl) {
71    this.exprAllowed = true
72  } else {
73    this.exprAllowed = !out.isExpr
74  }
75}
76
77tt.braceL.updateContext = function(prevType) {
78  this.context.push(this.braceIsBlock(prevType) ? types.b_stat : types.b_expr)
79  this.exprAllowed = true
80}
81
82tt.dollarBraceL.updateContext = function() {
83  this.context.push(types.b_tmpl)
84  this.exprAllowed = true
85}
86
87tt.parenL.updateContext = function(prevType) {
88  let statementParens = prevType === tt._if || prevType === tt._for || prevType === tt._with || prevType === tt._while
89  this.context.push(statementParens ? types.p_stat : types.p_expr)
90  this.exprAllowed = true
91}
92
93tt.incDec.updateContext = function() {
94  // tokExprAllowed stays unchanged
95}
96
97tt._function.updateContext = function(prevType) {
98  if (prevType.beforeExpr && prevType !== tt.semi && prevType !== tt._else &&
99      !((prevType === tt.colon || prevType === tt.braceL) && this.curContext() === types.b_stat))
100    this.context.push(types.f_expr)
101  this.exprAllowed = false
102}
103
104tt.backQuote.updateContext = function() {
105  if (this.curContext() === types.q_tmpl)
106    this.context.pop()
107  else
108    this.context.push(types.q_tmpl)
109  this.exprAllowed = false
110}
111