openpuppet-language 2.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- openpuppet_language-2.0.data/data/share/puppet/conformance/README.md +274 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/binding.json +238 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/capabilities.json +155 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/grammar.json +248 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/interaction.json +220 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/listen.json +133 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/platform.json +274 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/probes.json +117 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/render.json +180 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/tabs.json +39 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/cases/templates.json +143 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_bad_result.py +11 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_basic.py +24 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_deps_mismatch.py +11 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/fixtures/capabilities_no_doc.py +10 -0
- openpuppet_language-2.0.data/data/share/puppet/conformance/runner.py +721 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/01-grammar.md +283 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/02-ir.md +77 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/03-semantics.md +167 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/04-vocabulary.md +227 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/05-render-contract.md +126 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/06-diagnostics.md +151 -0
- openpuppet_language-2.0.data/data/share/puppet/spec/README.md +73 -0
- openpuppet_language-2.0.dist-info/METADATA +50 -0
- openpuppet_language-2.0.dist-info/RECORD +40 -0
- openpuppet_language-2.0.dist-info/WHEEL +5 -0
- openpuppet_language-2.0.dist-info/entry_points.txt +2 -0
- openpuppet_language-2.0.dist-info/top_level.txt +1 -0
- puppet/__init__.py +19 -0
- puppet/adapter.py +112 -0
- puppet/capabilities.py +172 -0
- puppet/cli.py +132 -0
- puppet/diag.py +42 -0
- puppet/engine.py +1038 -0
- puppet/eval.py +243 -0
- puppet/ir.py +538 -0
- puppet/lang.py +755 -0
- puppet/raster_adapter.py +348 -0
- puppet/tk_adapter.py +374 -0
- puppet/vocab.py +147 -0
puppet/eval.py
ADDED
|
@@ -0,0 +1,243 @@
|
|
|
1
|
+
"""L3 表达式求值(`spec/01-grammar.md` 第 4 节)。
|
|
2
|
+
|
|
3
|
+
求值只读取当前值,不产生副作用;失败一律抛出 `EvalError`(不得静默)。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from __future__ import annotations
|
|
7
|
+
|
|
8
|
+
from .lang import Bin, Call, DictLit, Expr, ListLit, Lit, Local, Ref, Un
|
|
9
|
+
from .vocab import BUILTINS
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class EvalError(Exception):
|
|
13
|
+
def __init__(self, code: str, message: str):
|
|
14
|
+
super().__init__(message)
|
|
15
|
+
self.code = code
|
|
16
|
+
self.message = message
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class CollectionRef:
|
|
20
|
+
"""指向数据源本身的引用,供 `count` / `len` / `first` 等使用。"""
|
|
21
|
+
|
|
22
|
+
__slots__ = ("source",)
|
|
23
|
+
|
|
24
|
+
def __init__(self, source: str):
|
|
25
|
+
self.source = source
|
|
26
|
+
|
|
27
|
+
def __repr__(self):
|
|
28
|
+
return "CollectionRef(%s)" % self.source
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def type_name(value) -> str:
|
|
32
|
+
if isinstance(value, bool):
|
|
33
|
+
return "bool"
|
|
34
|
+
if isinstance(value, int):
|
|
35
|
+
return "int"
|
|
36
|
+
if isinstance(value, float):
|
|
37
|
+
return "float"
|
|
38
|
+
if isinstance(value, str):
|
|
39
|
+
return "str"
|
|
40
|
+
if isinstance(value, list):
|
|
41
|
+
return "list"
|
|
42
|
+
if isinstance(value, dict):
|
|
43
|
+
return "dict"
|
|
44
|
+
if isinstance(value, CollectionRef):
|
|
45
|
+
return "collection"
|
|
46
|
+
return type(value).__name__
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def as_text(value) -> str:
|
|
50
|
+
if isinstance(value, bool):
|
|
51
|
+
return "true" if value else "false"
|
|
52
|
+
if isinstance(value, float) and value.is_integer():
|
|
53
|
+
return str(int(value))
|
|
54
|
+
if isinstance(value, (str, int)):
|
|
55
|
+
return str(value)
|
|
56
|
+
raise EvalError("EXPR_TYPE", "无法转换为字符串:%s" % type_name(value))
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _num(value):
|
|
60
|
+
if isinstance(value, bool) or not isinstance(value, (int, float)):
|
|
61
|
+
raise EvalError("EXPR_TYPE", "需要数字,实际 %s" % type_name(value))
|
|
62
|
+
return value
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _items(value, ctx=None):
|
|
66
|
+
if isinstance(value, CollectionRef):
|
|
67
|
+
return ctx.source_items(value.source)
|
|
68
|
+
if isinstance(value, list):
|
|
69
|
+
return list(value)
|
|
70
|
+
raise EvalError("EXPR_TYPE", "需要列表或数据源,实际 %s" % type_name(value))
|
|
71
|
+
|
|
72
|
+
|
|
73
|
+
def _map(fn, args):
|
|
74
|
+
return [fn(a) for a in args]
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def evaluate(expr: Expr, ctx):
|
|
78
|
+
if isinstance(expr, Lit):
|
|
79
|
+
return expr.value
|
|
80
|
+
if isinstance(expr, Ref):
|
|
81
|
+
return ctx.read_ref(expr.addr, expr.field)
|
|
82
|
+
if isinstance(expr, Local):
|
|
83
|
+
return ctx.read_local(expr.name, expr.field)
|
|
84
|
+
if isinstance(expr, Un):
|
|
85
|
+
value = evaluate(expr.operand, ctx)
|
|
86
|
+
if expr.op == "not":
|
|
87
|
+
if not isinstance(value, bool):
|
|
88
|
+
raise EvalError("EXPR_TYPE", "not 需要布尔,实际 %s" % type_name(value))
|
|
89
|
+
return not value
|
|
90
|
+
return -_num(value)
|
|
91
|
+
if isinstance(expr, Bin):
|
|
92
|
+
return _binary(expr, ctx)
|
|
93
|
+
if isinstance(expr, ListLit):
|
|
94
|
+
return [evaluate(i, ctx) for i in expr.items]
|
|
95
|
+
if isinstance(expr, DictLit):
|
|
96
|
+
return {k: evaluate(v, ctx) for k, v in expr.fields.items()}
|
|
97
|
+
if isinstance(expr, Call):
|
|
98
|
+
return _call(expr, ctx)
|
|
99
|
+
raise EvalError("EXPR_TYPE", "无法求值的表达式")
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
def _binary(expr: Bin, ctx):
|
|
103
|
+
if expr.op == "and":
|
|
104
|
+
left = evaluate(expr.left, ctx)
|
|
105
|
+
if not isinstance(left, bool):
|
|
106
|
+
raise EvalError("EXPR_TYPE", "and 需要布尔")
|
|
107
|
+
return evaluate(expr.right, ctx) if left else False
|
|
108
|
+
if expr.op == "or":
|
|
109
|
+
left = evaluate(expr.left, ctx)
|
|
110
|
+
if not isinstance(left, bool):
|
|
111
|
+
raise EvalError("EXPR_TYPE", "or 需要布尔")
|
|
112
|
+
return True if left else evaluate(expr.right, ctx)
|
|
113
|
+
|
|
114
|
+
left = evaluate(expr.left, ctx)
|
|
115
|
+
right = evaluate(expr.right, ctx)
|
|
116
|
+
op = expr.op
|
|
117
|
+
|
|
118
|
+
if op == "+":
|
|
119
|
+
if isinstance(left, str) or isinstance(right, str):
|
|
120
|
+
if isinstance(left, (str, int, float, bool)) and isinstance(right, (str, int, float, bool)):
|
|
121
|
+
return as_text(left) + as_text(right)
|
|
122
|
+
raise EvalError("EXPR_TYPE", "加法不支持 %s + %s" % (type_name(left), type_name(right)))
|
|
123
|
+
return _num(left) + _num(right)
|
|
124
|
+
if op == "-":
|
|
125
|
+
return _num(left) - _num(right)
|
|
126
|
+
if op == "*":
|
|
127
|
+
return _num(left) * _num(right)
|
|
128
|
+
if op == "/":
|
|
129
|
+
divisor = _num(right)
|
|
130
|
+
if divisor == 0:
|
|
131
|
+
raise EvalError("EXPR_DIV_ZERO", "除以零")
|
|
132
|
+
return _num(left) / divisor
|
|
133
|
+
if op == "==":
|
|
134
|
+
return left == right
|
|
135
|
+
if op == "!=":
|
|
136
|
+
return left != right
|
|
137
|
+
if op in ("<", "<=", ">", ">="):
|
|
138
|
+
if isinstance(left, bool) or isinstance(right, bool) \
|
|
139
|
+
or not isinstance(left, (int, float, str)) or not isinstance(right, (int, float, str)):
|
|
140
|
+
raise EvalError("EXPR_TYPE", "比较不支持 %s 与 %s" % (type_name(left), type_name(right)))
|
|
141
|
+
if type(left) is not type(right):
|
|
142
|
+
raise EvalError("EXPR_TYPE", "比较的两侧类型不同:%s / %s"
|
|
143
|
+
% (type_name(left), type_name(right)))
|
|
144
|
+
return {"<": left < right, "<=": left <= right,
|
|
145
|
+
">": left > right, ">=": left >= right}[op]
|
|
146
|
+
raise EvalError("EXPR_TYPE", "未知运算符 %s" % op)
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _call(expr: Call, ctx):
|
|
150
|
+
name = expr.name
|
|
151
|
+
if name not in BUILTINS:
|
|
152
|
+
raise EvalError("EXPR_UNKNOWN_FUNC", "未知函数 %s" % name)
|
|
153
|
+
low, high = BUILTINS[name]
|
|
154
|
+
if len(expr.args) < low or (high is not None and len(expr.args) > high):
|
|
155
|
+
raise EvalError("EXPR_TYPE", "函数 %s 参数个数不符" % name)
|
|
156
|
+
args = [evaluate(a, ctx) for a in expr.args]
|
|
157
|
+
|
|
158
|
+
if name == "count":
|
|
159
|
+
return len(_items(args[0], ctx))
|
|
160
|
+
if name == "len":
|
|
161
|
+
v = args[0]
|
|
162
|
+
if isinstance(v, str) or isinstance(v, list):
|
|
163
|
+
return len(v)
|
|
164
|
+
if isinstance(v, dict):
|
|
165
|
+
return len(v)
|
|
166
|
+
return len(_items(v, ctx))
|
|
167
|
+
if name == "upper":
|
|
168
|
+
return as_text(args[0]).upper()
|
|
169
|
+
if name == "lower":
|
|
170
|
+
return as_text(args[0]).lower()
|
|
171
|
+
if name == "trim":
|
|
172
|
+
return as_text(args[0]).strip()
|
|
173
|
+
if name == "contains":
|
|
174
|
+
if isinstance(args[0], (list, str, dict)) and not isinstance(args[0], CollectionRef):
|
|
175
|
+
return args[1] in args[0]
|
|
176
|
+
return args[1] in _items(args[0], ctx)
|
|
177
|
+
if name == "num":
|
|
178
|
+
v = args[0]
|
|
179
|
+
if isinstance(v, bool):
|
|
180
|
+
raise EvalError("EXPR_TYPE", "布尔不能转数字")
|
|
181
|
+
if isinstance(v, (int, float)):
|
|
182
|
+
return v
|
|
183
|
+
if isinstance(v, str):
|
|
184
|
+
try:
|
|
185
|
+
return float(v) if "." in v else int(v)
|
|
186
|
+
except ValueError as ex:
|
|
187
|
+
raise EvalError("EXPR_TYPE", "无法转换为数字:%r" % v) from ex
|
|
188
|
+
raise EvalError("EXPR_TYPE", "无法转换为数字:%s" % type_name(v))
|
|
189
|
+
if name == "str":
|
|
190
|
+
return as_text(args[0])
|
|
191
|
+
if name == "abs":
|
|
192
|
+
return abs(_num(args[0]))
|
|
193
|
+
if name == "min":
|
|
194
|
+
return min(_num(args[0]), _num(args[1]))
|
|
195
|
+
if name == "max":
|
|
196
|
+
return max(_num(args[0]), _num(args[1]))
|
|
197
|
+
if name == "sum":
|
|
198
|
+
return sum(_num(x) for x in _items(args[0], ctx))
|
|
199
|
+
if name == "round":
|
|
200
|
+
digits = int(_num(args[1])) if len(args) > 1 else 0
|
|
201
|
+
return round(_num(args[0]), digits)
|
|
202
|
+
if name == "join":
|
|
203
|
+
return as_text(args[1]).join(as_text(x) for x in _items(args[0], ctx))
|
|
204
|
+
if name == "split":
|
|
205
|
+
return as_text(args[0]).split(as_text(args[1]))
|
|
206
|
+
if name == "at":
|
|
207
|
+
items = _items(args[0], ctx)
|
|
208
|
+
idx = int(_num(args[1]))
|
|
209
|
+
if idx < 0 or idx >= len(items):
|
|
210
|
+
raise EvalError("EXPR_TYPE", "下标越界:%d" % idx)
|
|
211
|
+
return items[idx]
|
|
212
|
+
if name == "first":
|
|
213
|
+
items = _items(args[0], ctx)
|
|
214
|
+
if not items:
|
|
215
|
+
raise EvalError("EXPR_TYPE", "空列表没有首项")
|
|
216
|
+
return items[0]
|
|
217
|
+
if name == "last":
|
|
218
|
+
items = _items(args[0], ctx)
|
|
219
|
+
if not items:
|
|
220
|
+
raise EvalError("EXPR_TYPE", "空列表没有末项")
|
|
221
|
+
return items[-1]
|
|
222
|
+
if name == "slice":
|
|
223
|
+
items = _items(args[0], ctx)
|
|
224
|
+
return items[int(_num(args[1])):int(_num(args[2]))]
|
|
225
|
+
if name == "keys":
|
|
226
|
+
if not isinstance(args[0], dict):
|
|
227
|
+
raise EvalError("EXPR_TYPE", "keys 需要字典")
|
|
228
|
+
return list(args[0].keys())
|
|
229
|
+
if name == "values":
|
|
230
|
+
if not isinstance(args[0], dict):
|
|
231
|
+
raise EvalError("EXPR_TYPE", "values 需要字典")
|
|
232
|
+
return list(args[0].values())
|
|
233
|
+
if name == "has":
|
|
234
|
+
if not isinstance(args[0], dict):
|
|
235
|
+
raise EvalError("EXPR_TYPE", "has 需要字典")
|
|
236
|
+
return as_text(args[1]) in args[0]
|
|
237
|
+
if name == "fmt":
|
|
238
|
+
template = as_text(args[0])
|
|
239
|
+
out = template
|
|
240
|
+
for value in args[1:]:
|
|
241
|
+
out = out.replace("{}", as_text(value), 1)
|
|
242
|
+
return out
|
|
243
|
+
raise EvalError("EXPR_UNKNOWN_FUNC", "未实现的函数 %s" % name)
|