omlish 0.0.0.dev117__py3-none-any.whl → 0.0.0.dev118__py3-none-any.whl

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,14 +1,18 @@
1
- import collections
1
+ import typing as ta
2
2
 
3
3
 
4
- class ScopedChainDict:
4
+ K = ta.TypeVar('K')
5
+ V = ta.TypeVar('V')
6
+
7
+
8
+ class ScopedChainDict(ta.Generic[K, V]):
5
9
  """
6
10
  Dictionary that can delegate lookups to multiple dicts. This provides a basic get/set dict interface that is backed
7
11
  by multiple dicts. Each dict is searched from the top most (most recently pushed) scope dict until a match is
8
12
  found.
9
13
  """
10
14
 
11
- def __init__(self, *scopes):
15
+ def __init__(self, *scopes: ta.Mapping[K, V]) -> None:
12
16
  super().__init__()
13
17
 
14
18
  # The scopes are evaluated starting at the top of the stack (the most recently pushed scope via .push_scope()).
@@ -16,22 +20,22 @@ class ScopedChainDict:
16
20
  # call reversed(self._scopes) whenever we resolve a key, because the end of the list is the top of the stack.
17
21
  # To avoid this, we're using a deque so we can append to the front of the list via .appendleft() in constant
18
22
  # time, and iterate over scopes without having to do so with a reversed() call each time.
19
- self._scopes = collections.deque(scopes)
23
+ self._scopes: list[ta.Mapping[K, V]] = list(reversed(scopes))
20
24
 
21
- def __getitem__(self, key):
22
- for scope in self._scopes:
25
+ def __getitem__(self, key: K) -> V:
26
+ for scope in reversed(self._scopes):
23
27
  if key in scope:
24
28
  return scope[key]
25
29
  raise KeyError(key)
26
30
 
27
- def get(self, key, default=None):
31
+ def get(self, key: K, default: V | None = None) -> V | None:
28
32
  try:
29
33
  return self[key]
30
34
  except KeyError:
31
35
  return default
32
36
 
33
- def push_scope(self, scope):
34
- self._scopes.appendleft(scope)
37
+ def push_scope(self, scope: ta.Mapping[K, V]) -> None:
38
+ self._scopes.append(scope)
35
39
 
36
- def pop_scope(self):
37
- self._scopes.popleft()
40
+ def pop_scope(self) -> None:
41
+ self._scopes.pop()
@@ -2,12 +2,54 @@ import numbers
2
2
  import operator
3
3
  import typing as ta
4
4
 
5
- from . import exceptions
6
- from . import functions
5
+ from ... import check
6
+ from ... import lang
7
+ from .ast import AndExpression
8
+ from .ast import Arithmetic
9
+ from .ast import ArithmeticUnary
10
+ from .ast import Assign
11
+ from .ast import Comparator
12
+ from .ast import CurrentNode
13
+ from .ast import Expref
14
+ from .ast import Field
15
+ from .ast import FilterProjection
16
+ from .ast import Flatten
17
+ from .ast import FunctionExpression
18
+ from .ast import Identity
19
+ from .ast import Index
20
+ from .ast import IndexExpression
21
+ from .ast import KeyValPair
22
+ from .ast import LetExpression
23
+ from .ast import Literal
24
+ from .ast import MultiSelectDict
25
+ from .ast import MultiSelectList
7
26
  from .ast import Node
27
+ from .ast import NotExpression
28
+ from .ast import OrExpression
29
+ from .ast import Pipe
30
+ from .ast import Projection
31
+ from .ast import RootNode
32
+ from .ast import Slice
33
+ from .ast import Subexpression
34
+ from .ast import ValueProjection
35
+ from .ast import VariableRef
36
+ from .exceptions import UndefinedVariableError
37
+ from .functions import DefaultFunctions
38
+ from .functions import Functions
8
39
  from .scope import ScopedChainDict
9
40
 
10
41
 
42
+ if ta.TYPE_CHECKING:
43
+ import html
44
+ import json
45
+ else:
46
+ html = lang.proxy_import('html')
47
+ json = lang.proxy_import('json')
48
+
49
+
50
+ ##
51
+
52
+
11
53
  def _equals(x: ta.Any, y: ta.Any) -> bool:
12
54
  if _is_special_number_case(x, y):
13
55
  return False
@@ -58,58 +100,69 @@ def _is_actual_number(x: ta.Any) -> bool:
58
100
  return isinstance(x, numbers.Number)
59
101
 
60
102
 
103
+ def node_type(n: Node) -> str:
104
+ return lang.snake_case(type(n).__name__)
105
+
106
+
107
+ ##
108
+
109
+
110
+ class Visitor:
111
+ def __init__(self) -> None:
112
+ super().__init__()
113
+
114
+ self._method_cache: dict[str, ta.Callable] = {}
115
+
116
+ def visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
117
+ nty = node_type(node)
118
+ method = self._method_cache.get(nty)
119
+ if method is None:
120
+ method = check.not_none(getattr(self, f'visit_{nty}', self.default_visit))
121
+ self._method_cache[nty] = method
122
+ return method(node, *args, **kwargs)
123
+
124
+ def default_visit(self, node, *args, **kwargs):
125
+ raise NotImplementedError('default_visit')
126
+
127
+
128
+ ##
129
+
130
+
61
131
  class Options:
62
132
  """Options to control how a Jmespath function is evaluated."""
63
133
 
64
134
  def __init__(
65
135
  self,
66
136
  dict_cls: type | None = None,
67
- custom_functions=None,
137
+ custom_functions: Functions | None = None,
68
138
  enable_legacy_literals: bool = False,
69
139
  ) -> None:
70
140
  super().__init__()
71
141
 
72
- #: The class to use when creating a dict. The interpreter may create dictionaries during the evaluation of a
73
- # Jmespath expression. For example, a multi-select hash will create a dictionary. By default we use a dict()
74
- # type. You can set this value to change what dict type is used. The most common reason you would change this
75
- # is if you want to set a collections.OrderedDict so that you can have predictable key ordering.
142
+ # The class to use when creating a dict. The interpreter may create dictionaries during the evaluation of a
143
+ # Jmespath expression. For example, a multi-select hash will create a dictionary. By default we use a dict()
144
+ # type. You can set this value to change what dict type is used. The most common reason you would change this is
145
+ # if you want to set a collections.OrderedDict so that you can have predictable key ordering.
76
146
  self.dict_cls = dict_cls
77
147
  self.custom_functions = custom_functions
78
148
 
79
- #: The flag to enable pre-JEP-12 literal compatibility.
80
- # JEP-12 deprecates `foo` -> "foo" syntax.
81
- # Valid expressions MUST use: `"foo"` -> "foo"
82
- # Setting this flag to `True` enables support for legacy syntax.
149
+ # The flag to enable pre-JEP-12 literal compatibility.
150
+ # JEP-12 deprecates `foo` -> "foo" syntax.
151
+ # Valid expressions MUST use: `"foo"` -> "foo"
152
+ # Setting this flag to `True` enables support for legacy syntax.
83
153
  self.enable_legacy_literals = enable_legacy_literals
84
154
 
85
155
 
86
156
  class _Expression:
87
- def __init__(self, expression, interpreter):
157
+ def __init__(self, expression: Node, interpreter: 'TreeInterpreter') -> None:
88
158
  super().__init__()
89
159
  self.expression = expression
90
160
  self.interpreter = interpreter
91
161
 
92
- def visit(self, node, *args, **kwargs):
162
+ def visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.Any:
93
163
  return self.interpreter.visit(node, *args, **kwargs)
94
164
 
95
165
 
96
- class Visitor:
97
- def __init__(self):
98
- super().__init__()
99
- self._method_cache = {}
100
-
101
- def visit(self, node, *args, **kwargs):
102
- node_type = node['type']
103
- method = self._method_cache.get(node_type)
104
- if method is None:
105
- method = getattr(self, f'visit_{node["type"]}', self.default_visit)
106
- self._method_cache[node_type] = method
107
- return method(node, *args, **kwargs) # type: ignore
108
-
109
- def default_visit(self, node, *args, **kwargs):
110
- raise NotImplementedError('default_visit')
111
-
112
-
113
166
  class TreeInterpreter(Visitor):
114
167
  COMPARATOR_FUNC: ta.Mapping[str, ta.Callable] = {
115
168
  'eq': _equals,
@@ -120,7 +173,10 @@ class TreeInterpreter(Visitor):
120
173
  'gte': operator.ge,
121
174
  }
122
175
 
123
- _EQUALITY_OPS: ta.Sequence[str] = ['eq', 'ne']
176
+ _EQUALITY_OPS: ta.AbstractSet[str] = {
177
+ 'eq',
178
+ 'ne',
179
+ }
124
180
 
125
181
  _ARITHMETIC_UNARY_FUNC: ta.Mapping[str, ta.Callable] = {
126
182
  'minus': operator.neg,
@@ -136,115 +192,113 @@ class TreeInterpreter(Visitor):
136
192
  'plus': operator.add,
137
193
  }
138
194
 
139
- MAP_TYPE = dict
140
-
141
- def __init__(self, options=None):
195
+ def __init__(self, options: Options | None = None) -> None:
142
196
  super().__init__()
143
197
 
144
- self._dict_cls = self.MAP_TYPE
198
+ self._dict_cls: type = dict
145
199
 
146
200
  if options is None:
147
201
  options = Options()
148
202
  self._options = options
149
203
 
150
204
  if options.dict_cls is not None:
151
- self._dict_cls = self._options.dict_cls
205
+ self._dict_cls = options.dict_cls
152
206
 
153
207
  if options.custom_functions is not None:
154
- self._functions = self._options.custom_functions
208
+ self._functions: Functions = options.custom_functions
155
209
  else:
156
- self._functions = functions.Functions()
210
+ self._functions = DefaultFunctions()
157
211
 
158
- self._root = None
159
- self._scope = ScopedChainDict()
212
+ self._root: Node | None = None
213
+ self._scope: ScopedChainDict = ScopedChainDict()
160
214
 
161
- def default_visit(self, node, *args, **kwargs):
162
- raise NotImplementedError(node['type'])
215
+ def default_visit(self, node: Node, *args: ta.Any, **kwargs: ta.Any) -> ta.NoReturn:
216
+ raise NotImplementedError(node_type(node))
163
217
 
164
- def evaluate(self, ast, root: Node) -> ta.Any:
218
+ def evaluate(self, ast: Node, root: Node) -> ta.Any:
165
219
  self._root = root
166
220
  return self.visit(ast, root)
167
221
 
168
- def visit_subexpression(self, node, value):
222
+ def visit_subexpression(self, node: Subexpression, value: ta.Any) -> ta.Any:
169
223
  result = value
170
- for child in node['children']:
224
+ for child in node.children_nodes:
171
225
  result = self.visit(child, result)
172
226
  if result is None:
173
227
  return None
174
228
  return result
175
229
 
176
- def visit_field(self, node, value):
230
+ def visit_field(self, node: Field, value: ta.Any) -> ta.Any:
177
231
  try:
178
- return value.get(node['value'])
232
+ return value.get(node.name)
179
233
  except AttributeError:
180
234
  return None
181
235
 
182
- def visit_comparator(self, node, value):
236
+ def visit_comparator(self, node: Comparator, value: ta.Any) -> ta.Any:
183
237
  # Common case: comparator is == or !=
184
- comparator_func = self.COMPARATOR_FUNC[node['value']]
185
- if node['value'] in self._EQUALITY_OPS:
238
+ comparator_func = self.COMPARATOR_FUNC[node.name]
239
+ if node.name in self._EQUALITY_OPS:
186
240
  return comparator_func(
187
- self.visit(node['children'][0], value),
188
- self.visit(node['children'][1], value),
241
+ self.visit(node.first, value),
242
+ self.visit(node.second, value),
189
243
  )
190
244
 
191
245
  else:
192
246
  # Ordering operators are only valid for numbers. Evaluating any other type with a comparison operator will
193
247
  # yield a None value.
194
- left = self.visit(node['children'][0], value)
195
- right = self.visit(node['children'][1], value)
248
+ left = self.visit(node.first, value)
249
+ right = self.visit(node.second, value)
196
250
  # num_types = (int, float)
197
251
  if not (_is_comparable(left) and _is_comparable(right)):
198
252
  return None
199
253
  return comparator_func(left, right)
200
254
 
201
- def visit_arithmetic_unary(self, node, value):
202
- operation = self._ARITHMETIC_UNARY_FUNC[node['value']]
255
+ def visit_arithmetic_unary(self, node: ArithmeticUnary, value: ta.Any) -> ta.Any:
256
+ operation = self._ARITHMETIC_UNARY_FUNC[node.operator]
203
257
  return operation(
204
- self.visit(node['children'][0], value),
258
+ self.visit(node.expression, value),
205
259
  )
206
260
 
207
- def visit_arithmetic(self, node, value):
208
- operation = self._ARITHMETIC_FUNC[node['value']]
261
+ def visit_arithmetic(self, node: Arithmetic, value: ta.Any) -> ta.Any:
262
+ operation = self._ARITHMETIC_FUNC[node.operator]
209
263
  return operation(
210
- self.visit(node['children'][0], value),
211
- self.visit(node['children'][1], value),
264
+ self.visit(node.left, value),
265
+ self.visit(node.right, value),
212
266
  )
213
267
 
214
- def visit_current(self, node, value):
268
+ def visit_current_node(self, node: CurrentNode, value: ta.Any) -> ta.Any:
215
269
  return value
216
270
 
217
- def visit_root(self, node, value):
271
+ def visit_root_node(self, node: RootNode, value: ta.Any) -> ta.Any:
218
272
  return self._root
219
273
 
220
- def visit_expref(self, node, value):
221
- return _Expression(node['children'][0], self)
274
+ def visit_expref(self, node: Expref, value: ta.Any) -> ta.Any:
275
+ return _Expression(node.expression, self)
222
276
 
223
- def visit_function_expression(self, node, value):
277
+ def visit_function_expression(self, node: FunctionExpression, value: ta.Any) -> ta.Any:
224
278
  resolved_args = []
225
- for child in node['children']:
279
+ for child in node.args:
226
280
  current = self.visit(child, value)
227
281
  resolved_args.append(current)
228
282
 
229
- return self._functions.call_function(node['value'], resolved_args)
283
+ return self._functions.call_function(node.name, resolved_args)
230
284
 
231
- def visit_filter_projection(self, node, value):
232
- base = self.visit(node['children'][0], value)
285
+ def visit_filter_projection(self, node: FilterProjection, value: ta.Any) -> ta.Any:
286
+ base = self.visit(node.left, value)
233
287
  if not isinstance(base, list):
234
288
  return None
235
289
 
236
- comparator_node = node['children'][2]
290
+ comparator_node = node.comparator
237
291
  collected = []
238
292
  for element in base:
239
293
  if self._is_true(self.visit(comparator_node, element)):
240
- current = self.visit(node['children'][1], element)
294
+ current = self.visit(node.right, element)
241
295
  if current is not None:
242
296
  collected.append(current)
243
297
 
244
298
  return collected
245
299
 
246
- def visit_flatten(self, node, value):
247
- base = self.visit(node['children'][0], value)
300
+ def visit_flatten(self, node: Flatten, value: ta.Any) -> ta.Any:
301
+ base = self.visit(node.node, value)
248
302
  if not isinstance(base, list):
249
303
  # Can't flatten the object if it's not a list.
250
304
  return None
@@ -258,77 +312,74 @@ class TreeInterpreter(Visitor):
258
312
 
259
313
  return merged_list
260
314
 
261
- def visit_identity(self, node, value):
315
+ def visit_identity(self, node: Identity, value: ta.Any) -> ta.Any:
262
316
  return value
263
317
 
264
- def visit_index(self, node, value):
318
+ def visit_index(self, node: Index, value: ta.Any) -> ta.Any:
265
319
  # Even though we can index strings, we don't want to support that.
266
320
  if not isinstance(value, list):
267
321
  return None
268
322
 
269
323
  try:
270
- return value[node['value']]
324
+ return value[node.index]
271
325
  except IndexError:
272
326
  return None
273
327
 
274
- def visit_index_expression(self, node, value):
328
+ def visit_index_expression(self, node: IndexExpression, value: ta.Any) -> ta.Any:
275
329
  result = value
276
- for child in node['children']:
330
+ for child in node.nodes:
277
331
  result = self.visit(child, result)
278
332
 
279
333
  return result
280
334
 
281
- def visit_slice(self, node, value):
335
+ def visit_slice(self, node: Slice, value: ta.Any) -> ta.Any:
282
336
  if isinstance(value, str):
283
- start = node['children'][0]
284
- end = node['children'][1]
285
- step = node['children'][2]
286
- return value[start:end:step]
337
+ return value[node.start:node.end:node.step]
287
338
 
288
339
  if not isinstance(value, list):
289
340
  return None
290
341
 
291
- s = slice(*node['children'])
342
+ s = slice(node.start, node.end, node.step)
292
343
  return value[s]
293
344
 
294
- def visit_key_val_pair(self, node, value):
295
- return self.visit(node['children'][0], value)
345
+ def visit_key_val_pair(self, node: KeyValPair, value: ta.Any) -> ta.Any:
346
+ return self.visit(node.node, value)
296
347
 
297
- def visit_literal(self, node, value):
298
- return node['value']
348
+ def visit_literal(self, node: Literal, value: ta.Any):
349
+ return node.literal_value
299
350
 
300
- def visit_multi_select_dict(self, node, value):
351
+ def visit_multi_select_dict(self, node: MultiSelectDict, value: ta.Any) -> ta.Any:
301
352
  collected = self._dict_cls()
302
- for child in node['children']:
303
- collected[child['value']] = self.visit(child, value)
353
+ for child in node.nodes:
354
+ collected[child.key_name] = self.visit(child, value)
304
355
 
305
356
  return collected
306
357
 
307
- def visit_multi_select_list(self, node, value):
358
+ def visit_multi_select_list(self, node: MultiSelectList, value: ta.Any) -> ta.Any:
308
359
  collected = []
309
- for child in node['children']:
360
+ for child in node.nodes:
310
361
  collected.append(self.visit(child, value))
311
362
 
312
363
  return collected
313
364
 
314
- def visit_or_expression(self, node, value):
315
- matched = self.visit(node['children'][0], value)
365
+ def visit_or_expression(self, node: OrExpression, value: ta.Any) -> ta.Any:
366
+ matched = self.visit(node.left, value)
316
367
 
317
368
  if self._is_false(matched):
318
- matched = self.visit(node['children'][1], value)
369
+ matched = self.visit(node.right, value)
319
370
 
320
371
  return matched
321
372
 
322
- def visit_and_expression(self, node, value):
323
- matched = self.visit(node['children'][0], value)
373
+ def visit_and_expression(self, node: AndExpression, value: ta.Any) -> ta.Any:
374
+ matched = self.visit(node.left, value)
324
375
 
325
376
  if self._is_false(matched):
326
377
  return matched
327
378
 
328
- return self.visit(node['children'][1], value)
379
+ return self.visit(node.right, value)
329
380
 
330
- def visit_not_expression(self, node, value):
331
- original_result = self.visit(node['children'][0], value)
381
+ def visit_not_expression(self, node: NotExpression, value: ta.Any) -> ta.Any:
382
+ original_result = self.visit(node.expr, value)
332
383
 
333
384
  if _is_actual_number(original_result) and original_result == 0:
334
385
  # Special case for 0, !0 should be false, not true. 0 is not a special cased integer in jmespath.
@@ -336,59 +387,59 @@ class TreeInterpreter(Visitor):
336
387
 
337
388
  return not original_result
338
389
 
339
- def visit_pipe(self, node, value):
390
+ def visit_pipe(self, node: Pipe, value: ta.Any) -> ta.Any:
340
391
  result = value
341
- for child in node['children']:
392
+ for child in [node.left, node.right]:
342
393
  result = self.visit(child, result)
343
394
  return result
344
395
 
345
- def visit_projection(self, node, value):
346
- base = self.visit(node['children'][0], value)
396
+ def visit_projection(self, node: Projection, value: ta.Any) -> ta.Any:
397
+ base = self.visit(node.left, value)
347
398
 
348
399
  allow_string = False
349
- first_child = node['children'][0]
350
- if first_child['type'] == 'index_expression':
351
- nested_children = first_child['children']
352
- if len(nested_children) > 1 and nested_children[1]['type'] == 'slice':
400
+ first_child = node.left
401
+ if isinstance(first_child, IndexExpression):
402
+ nested_children = first_child.nodes
403
+ if len(nested_children) > 1 and isinstance(nested_children[1], Slice):
353
404
  allow_string = True
354
405
 
355
406
  if isinstance(base, str) and allow_string:
356
407
  # projections are really sub-expressions in disguise evaluate the rhs when lhs is a sliced string
357
- return self.visit(node['children'][1], base)
408
+ return self.visit(node.right, base)
358
409
 
359
410
  if not isinstance(base, list):
360
411
  return None
412
+
361
413
  collected = []
362
414
  for element in base:
363
- current = self.visit(node['children'][1], element)
415
+ current = self.visit(node.right, element)
364
416
  if current is not None:
365
417
  collected.append(current)
366
418
 
367
419
  return collected
368
420
 
369
- def visit_let_expression(self, node, value):
370
- *bindings, expr = node['children']
421
+ def visit_let_expression(self, node: LetExpression, value: ta.Any) -> ta.Any:
371
422
  scope = {}
372
- for assign in bindings:
423
+ for assign in node.bindings:
373
424
  scope.update(self.visit(assign, value))
374
425
  self._scope.push_scope(scope)
375
- result = self.visit(expr, value)
426
+ result = self.visit(node.expr, value)
376
427
  self._scope.pop_scope()
377
428
  return result
378
429
 
379
- def visit_assign(self, node, value):
380
- name = node['value']
381
- value = self.visit(node['children'][0], value)
430
+ def visit_assign(self, node: Assign, value: ta.Any) -> ta.Any:
431
+ name = node.name
432
+ value = self.visit(node.expr, value)
382
433
  return {name: value}
383
434
 
384
- def visit_variable_ref(self, node, value):
435
+ def visit_variable_ref(self, node: VariableRef, value: ta.Any) -> ta.Any:
385
436
  try:
386
- return self._scope[node['value']]
437
+ return self._scope[node.name]
387
438
  except KeyError:
388
- raise exceptions.UndefinedVariableError(node['value']) # noqa
439
+ raise UndefinedVariableError(node.name) # noqa
389
440
 
390
- def visit_value_projection(self, node, value):
391
- base = self.visit(node['children'][0], value)
441
+ def visit_value_projection(self, node: ValueProjection, value: ta.Any) -> ta.Any:
442
+ base = self.visit(node.left, value)
392
443
  try:
393
444
  base = base.values()
394
445
  except AttributeError:
@@ -396,39 +447,62 @@ class TreeInterpreter(Visitor):
396
447
 
397
448
  collected = []
398
449
  for element in base:
399
- current = self.visit(node['children'][1], element)
450
+ current = self.visit(node.right, element)
400
451
  if current is not None:
401
452
  collected.append(current)
402
453
 
403
454
  return collected
404
455
 
405
- def _is_false(self, value):
456
+ def _is_false(self, value: ta.Any) -> bool:
406
457
  # This looks weird, but we're explicitly using equality checks because the truth/false values are different
407
458
  # between python and jmespath.
408
- return (value == '' or value == [] or value == {} or value is None or value is False) # noqa
459
+ return (
460
+ value == '' or # noqa
461
+ value == [] or
462
+ value == {} or
463
+ value is None or
464
+ value is False
465
+ )
409
466
 
410
- def _is_true(self, value):
467
+ def _is_true(self, value: ta.Any) -> bool:
411
468
  return not self._is_false(value)
412
469
 
413
470
 
414
- class GraphvizVisitor(Visitor):
415
- def __init__(self):
471
+ class GraphvizVisitor:
472
+ def __init__(self) -> None:
416
473
  super().__init__()
417
- self._lines = []
474
+ self._lines: list[str] = []
418
475
  self._count = 1
419
476
 
420
- def visit(self, node, *args, **kwargs):
477
+ def visit(self, node: Node) -> str:
421
478
  self._lines.append('digraph AST {')
422
- current = f"{node['type']}{self._count}"
479
+ current = f'{node_type(node)}{self._count}'
423
480
  self._count += 1
424
481
  self._visit(node, current)
425
482
  self._lines.append('}')
426
483
  return '\n'.join(self._lines)
427
484
 
428
- def _visit(self, node, current):
429
- self._lines.append('%s [label="%s(%s)"]' % (current, node['type'], node.get('value', ''))) # noqa
430
- for child in node.get('children', []):
431
- child_name = f"{child['type']}{self._count}"
485
+ def _node_value(self, node: Node) -> lang.Maybe[ta.Any]:
486
+ if isinstance(node, (ArithmeticUnary, Arithmetic)):
487
+ return lang.just(node.operator)
488
+ elif isinstance(node, (Assign, Comparator, FunctionExpression, Field, VariableRef)):
489
+ return lang.just(node.name)
490
+ elif isinstance(node, Index):
491
+ return lang.just(node.index)
492
+ elif isinstance(node, KeyValPair):
493
+ return lang.just(node.key_name)
494
+ elif isinstance(node, Literal):
495
+ return lang.just(node.literal_value)
496
+ else:
497
+ return lang.empty()
498
+
499
+ def _visit(self, node: Node, current: str) -> None:
500
+ self._lines.append(
501
+ f'{current} '
502
+ f'[label="{node_type(node)}({self._node_value(node).map(json.dumps).map(html.escape).or_else("")})"]',
503
+ )
504
+ for child in node.children:
505
+ child_name = f'{node_type(child)}{self._count}'
432
506
  self._count += 1
433
507
  self._lines.append(f' {current} -> {child_name}')
434
508
  self._visit(child, child_name)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: omlish
3
- Version: 0.0.0.dev117
3
+ Version: 0.0.0.dev118
4
4
  Summary: omlish
5
5
  Author: wrmsr
6
6
  License: BSD-3-Clause