OILS / mycpp / util.py View on Github | oilshell.org

112 lines, 52 significant
1"""
2util.py
3"""
4from __future__ import print_function
5
6import sys
7from mypy.nodes import CallExpr, IfStmt, Block, Expression, MypyFile
8from mypy.types import Instance, Type
9
10from typing import Any, Sequence, Optional
11
12# Used by cppgen_pass and const_pass
13
14# mycpp/examples/small_str.py sorta works with this!
15#SMALL_STR = True
16
17SMALL_STR = False
18
19SymbolPath = Sequence[str]
20
21
22def log(msg: str, *args: Any) -> None:
23 if args:
24 msg = msg % args
25 print(msg, file=sys.stderr)
26
27
28def join_name(parts: SymbolPath,
29 strip_package: bool = False,
30 delim: str = '::') -> str:
31 """
32 Join the given name path into a string with the given delimiter.
33 Use strip_package to remove the top-level directory (e.g. `core`, `ysh`)
34 when dealing with C++ namespaces.
35 """
36 if not strip_package:
37 return delim.join(parts)
38
39 if len(parts) > 1:
40 return delim.join(('', ) + parts[1:])
41
42 return parts[0]
43
44
45def split_py_name(name: str) -> SymbolPath:
46 ret = tuple(name.split('.'))
47 if len(ret) and ret[0] == 'mycpp':
48 # Drop the prefix 'mycpp.' if present. This makes names compatible with
49 # the examples that use testpkg.
50 return ret[1:]
51
52 return ret
53
54
55def _collect_cases(module_path: str,
56 if_node: IfStmt,
57 out: list[tuple[Expression, Block]],
58 errors=None) -> Optional[Block] | bool:
59 """
60 The MyPy AST has a recursive structure for if-elif-elif rather than a
61 flat one. It's a bit confusing.
62
63 Appends (expr, block) cases to out param, and returns the default
64 block, which has no expression.
65
66 default block may be None.
67
68 Returns False if there is no default block.
69 """
70 assert isinstance(if_node, IfStmt), if_node
71 assert len(if_node.expr) == 1, if_node.expr
72 assert len(if_node.body) == 1, if_node.body
73
74 expr = if_node.expr[0]
75 body = if_node.body[0]
76
77 if not isinstance(expr, CallExpr):
78 if errors is not None:
79 errors.append((module_path, expr.line,
80 'Expected call like case(x), got %s' % expr))
81 return
82
83 out.append((expr, body))
84
85 if if_node.else_body:
86 first_of_block = if_node.else_body.body[0]
87 # BUG: this is meant for 'elif' only. But it also triggers for
88 #
89 # else:
90 # if 0:
91
92 if isinstance(first_of_block, IfStmt):
93 return _collect_cases(module_path, first_of_block, out, errors)
94 else:
95 # default case - no expression
96 return if_node.else_body
97
98 return False # NO DEFAULT BLOCK - Different than None
99
100
101def ShouldSkipPyFile(node: MypyFile) -> bool:
102 # Skip some stdlib stuff. A lot of it is brought in by 'import
103 # typing'. These module are special; their contents are currently all
104 # built-in primitives.
105 return node.fullname in ('__future__', 'sys', 'types', 'typing', 'abc',
106 '_ast', 'ast', '_weakrefset', 'collections',
107 'cStringIO', 're', 'builtins')
108
109
110def IsStr(t: Type):
111 """Helper to check if a type is a string."""
112 return isinstance(t, Instance) and t.type.fullname == 'builtins.str'