1# Copyright 2019 The Chromium Authors. All rights reserved.
2# Use of this source code is governed by a BSD-style license that can be
3# found in the LICENSE file.
4
5import web_idl
6
7_CODE_GEN_EXPR_PASS_KEY = object()
8
9
10class CodeGenExpr(object):
11    """
12    Represents an expression which is composable to produce another expression.
13
14    This is designed primarily to represent conditional expressions and basic
15    logical operators (expr_not, expr_and, expr_or) come along with.
16    """
17
18    def __init__(self, expr, is_compound=False, pass_key=None):
19        assert isinstance(expr, (bool, str))
20        assert isinstance(is_compound, bool)
21        assert pass_key is _CODE_GEN_EXPR_PASS_KEY
22
23        if isinstance(expr, bool):
24            self._text = "true" if expr else "false"
25        else:
26            self._text = expr
27        self._is_compound = is_compound
28        self._is_always_false = expr is False
29        self._is_always_true = expr is True
30
31    def __eq__(self, other):
32        if not isinstance(self, other.__class__):
33            return NotImplemented
34        # Assume that, as long as the two texts are the same, the two
35        # expressions must be the same, i.e. |_is_compound|, etc. must be the
36        # same or do not matter.
37        return self.to_text() == other.to_text()
38
39    def __ne__(self, other):
40        return not (self == other)
41
42    def __hash__(self):
43        return hash(self.to_text())
44
45    def __str__(self):
46        """
47        __str__ is designed to be used when composing another expression.  If
48        you'd only like to have a string representation, |to_text| works better.
49        """
50        if self._is_compound:
51            return "({})".format(self.to_text())
52        return self.to_text()
53
54    def to_text(self):
55        return self._text
56
57    @property
58    def is_always_false(self):
59        """
60        The expression is always False, and code generators have chances of
61        optimizations.
62        """
63        return self._is_always_false
64
65    @property
66    def is_always_true(self):
67        """
68        The expression is always True, and code generators have chances of
69        optimizations.
70        """
71        return self._is_always_true
72
73
74def _Expr(*args, **kwargs):
75    return CodeGenExpr(*args, pass_key=_CODE_GEN_EXPR_PASS_KEY, **kwargs)
76
77
78def _unary_op(op, term):
79    assert isinstance(op, str)
80    assert isinstance(term, CodeGenExpr)
81
82    return _Expr("{}{}".format(op, term), is_compound=True)
83
84
85def _binary_op(op, terms):
86    assert isinstance(op, str)
87    assert isinstance(terms, (list, tuple))
88    assert all(isinstance(term, CodeGenExpr) for term in terms)
89    assert all(
90        not (term.is_always_false or term.is_always_true) for term in terms)
91
92    return _Expr(op.join(map(str, terms)), is_compound=True)
93
94
95def expr_not(term):
96    assert isinstance(term, CodeGenExpr)
97
98    if term.is_always_false:
99        return _Expr(True)
100    if term.is_always_true:
101        return _Expr(False)
102    return _unary_op("!", term)
103
104
105def expr_and(terms):
106    assert isinstance(terms, (list, tuple))
107    assert all(isinstance(term, CodeGenExpr) for term in terms)
108    assert terms
109
110    if any(term.is_always_false for term in terms):
111        return _Expr(False)
112    terms = filter(lambda x: not x.is_always_true, terms)
113    if not terms:
114        return _Expr(True)
115    if len(terms) == 1:
116        return terms[0]
117    return _binary_op(" && ", expr_uniq(terms))
118
119
120def expr_or(terms):
121    assert isinstance(terms, (list, tuple))
122    assert all(isinstance(term, CodeGenExpr) for term in terms)
123    assert terms
124
125    if any(term.is_always_true for term in terms):
126        return _Expr(True)
127    terms = filter(lambda x: not x.is_always_false, terms)
128    if not terms:
129        return _Expr(False)
130    if len(terms) == 1:
131        return terms[0]
132    return _binary_op(" || ", expr_uniq(terms))
133
134
135def expr_uniq(terms):
136    assert isinstance(terms, (list, tuple))
137    assert all(isinstance(term, CodeGenExpr) for term in terms)
138
139    uniq_terms = []
140    for term in terms:
141        if term not in uniq_terms:
142            uniq_terms.append(term)
143    return uniq_terms
144
145
146def expr_from_exposure(exposure, global_names=None):
147    """
148    Args:
149        exposure: web_idl.Exposure of the target construct.
150        global_names: When specified, it's taken into account that the global
151            object implements |global_names|.
152    """
153    assert isinstance(exposure, web_idl.Exposure)
154    assert (global_names is None
155            or (isinstance(global_names, (list, tuple))
156                and all(isinstance(name, str) for name in global_names)))
157
158    def ref_enabled(feature):
159        arg = "${execution_context}" if feature.is_context_dependent else ""
160        return _Expr("RuntimeEnabledFeatures::{}Enabled({})".format(
161            feature, arg))
162
163    top_terms = [_Expr(True)]
164
165    # [Exposed]
166    GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST = {
167        "AnimationWorklet": "IsAnimationWorkletGlobalScope",
168        "AudioWorklet": "IsAudioWorkletGlobalScope",
169        "DedicatedWorker": "IsDedicatedWorkerGlobalScope",
170        "LayoutWorklet": "IsLayoutWorkletGlobalScope",
171        "PaintWorklet": "IsPaintWorkletGlobalScope",
172        "ServiceWorker": "IsServiceWorkerGlobalScope",
173        "SharedWorker": "IsSharedWorkerGlobalScope",
174        "Window": "IsDocument",
175        "Worker": "IsWorkerGlobalScope",
176        "Worklet": "IsWorkletGlobalScope",
177    }
178    exposed_terms = []
179    if global_names:
180        matched_global_count = 0
181        for entry in exposure.global_names_and_features:
182            if entry.global_name not in global_names:
183                continue
184            matched_global_count += 1
185            if entry.feature:
186                exposed_terms.append(ref_enabled(entry.feature))
187        assert (not exposure.global_names_and_features
188                or matched_global_count > 0)
189    else:
190        for entry in exposure.global_names_and_features:
191            terms = []
192            pred = GLOBAL_NAME_TO_EXECUTION_CONTEXT_TEST[entry.global_name]
193            terms.append(_Expr("${{execution_context}}->{}()".format(pred)))
194            if entry.feature:
195                terms.append(ref_enabled(entry.feature))
196            if terms:
197                exposed_terms.append(expr_and(terms))
198    if exposed_terms:
199        top_terms.append(expr_or(exposed_terms))
200
201    # [RuntimeEnabled]
202    if exposure.runtime_enabled_features:
203        terms = map(ref_enabled, exposure.runtime_enabled_features)
204        top_terms.append(expr_or(terms))
205
206    # [SecureContext]
207    if exposure.only_in_secure_contexts is True:
208        top_terms.append(_Expr("${is_in_secure_context}"))
209    elif exposure.only_in_secure_contexts is False:
210        top_terms.append(_Expr(True))
211    else:
212        terms = map(ref_enabled, exposure.only_in_secure_contexts)
213        top_terms.append(
214            expr_or(
215                [_Expr("${is_in_secure_context}"),
216                 expr_not(expr_and(terms))]))
217
218    return expr_and(top_terms)
219