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

177 lines, 92 significant
1"""
2util.py
3"""
4from __future__ import print_function
5
6import sys
7from mypy.nodes import (CallExpr, IfStmt, Block, Expression, MypyFile,
8 MemberExpr, IntExpr, NameExpr, ComparisonExpr)
9from mypy.types import Instance, Type
10
11from typing import Any, Sequence, Optional
12
13# Used by cppgen_pass and const_pass
14
15# mycpp/examples/small_str.py sorta works with this!
16#SMALL_STR = True
17
18SMALL_STR = False
19
20SymbolPath = Sequence[str]
21
22
23def log(msg: str, *args: Any) -> None:
24 if args:
25 msg = msg % args
26 print(msg, file=sys.stderr)
27
28
29def join_name(parts: SymbolPath,
30 strip_package: bool = False,
31 delim: str = '::') -> str:
32 """
33 Join the given name path into a string with the given delimiter.
34 Use strip_package to remove the top-level directory (e.g. `core`, `ysh`)
35 when dealing with C++ namespaces.
36 """
37 if not strip_package:
38 return delim.join(parts)
39
40 if len(parts) > 1:
41 return delim.join(('', ) + parts[1:])
42
43 return parts[0]
44
45
46def split_py_name(name: str) -> SymbolPath:
47 ret = tuple(name.split('.'))
48 if len(ret) and ret[0] == 'mycpp':
49 # Drop the prefix 'mycpp.' if present. This makes names compatible with
50 # the examples that use testpkg.
51 return ret[1:]
52
53 return ret
54
55
56def _collect_cases(module_path: str,
57 if_node: IfStmt,
58 out: list[tuple[Expression, Block]],
59 errors=None) -> Optional[Block] | bool:
60 """
61 The MyPy AST has a recursive structure for if-elif-elif rather than a
62 flat one. It's a bit confusing.
63
64 Appends (expr, block) cases to out param, and returns the default
65 block, which has no expression.
66
67 default block may be None.
68
69 Returns False if there is no default block.
70 """
71 assert isinstance(if_node, IfStmt), if_node
72 assert len(if_node.expr) == 1, if_node.expr
73 assert len(if_node.body) == 1, if_node.body
74
75 expr = if_node.expr[0]
76 body = if_node.body[0]
77
78 if not isinstance(expr, CallExpr):
79 if errors is not None:
80 errors.append((module_path, expr.line,
81 'Expected call like case(x), got %s' % expr))
82 return
83
84 out.append((expr, body))
85
86 if if_node.else_body:
87 first_of_block = if_node.else_body.body[0]
88 # BUG: this is meant for 'elif' only. But it also triggers for
89 #
90 # else:
91 # if 0:
92
93 if isinstance(first_of_block, IfStmt):
94 return _collect_cases(module_path, first_of_block, out, errors)
95 else:
96 # default case - no expression
97 return if_node.else_body
98
99 return False # NO DEFAULT BLOCK - Different than None
100
101
102def ShouldSkipPyFile(node: MypyFile) -> bool:
103 # Skip some stdlib stuff. A lot of it is brought in by 'import
104 # typing'. These module are special; their contents are currently all
105 # built-in primitives.
106 return node.fullname in ('__future__', 'sys', 'types', 'typing', 'abc',
107 '_ast', 'ast', '_weakrefset', 'collections',
108 'cStringIO', 're', 'builtins')
109
110
111def IsStr(t: Type):
112 """Helper to check if a type is a string."""
113 return isinstance(t, Instance) and t.type.fullname == 'builtins.str'
114
115
116def _ShouldSkipIf(stmt: IfStmt) -> bool:
117 cond = stmt.expr[0]
118
119 # Omit anything that looks like if __name__ == ...
120 if (isinstance(cond, ComparisonExpr) and
121 isinstance(cond.operands[0], NameExpr) and
122 cond.operands[0].name == '__name__'):
123 return True
124
125 if isinstance(cond, NameExpr) and cond.name == 'TYPE_CHECKING':
126 # Omit if TYPE_CHECKING blocks. They contain type expressions that
127 # don't type check!
128 return True
129
130 return False
131
132
133def GetSpecialIfCondition(stmt: IfStmt) -> Optional[str]:
134 cond = stmt.expr[0]
135 if isinstance(cond, NameExpr) and cond.name == 'TYPE_CHECKING':
136 return cond.name
137
138 if isinstance(cond, MemberExpr) and cond.name in ('PYTHON', 'CPP'):
139 return cond.name
140
141 return None
142
143
144def ShouldVisitIfExpr(stmt: IfStmt) -> bool:
145 if _ShouldSkipIf(stmt) or GetSpecialIfCondition(stmt) in ('PYTHON', 'CPP'):
146 return False
147
148 cond = stmt.expr[0]
149 if isinstance(cond, IntExpr) and cond.value == 0:
150 return False
151
152 return True
153
154
155def ShouldVisitIfBody(stmt: IfStmt) -> bool:
156 if _ShouldSkipIf(stmt):
157 return False
158
159 cond = stmt.expr[0]
160 if isinstance(cond, MemberExpr) and cond.name == 'PYTHON':
161 return False
162
163 if isinstance(cond, IntExpr) and cond.value == 0:
164 return False
165
166 return True
167
168
169def ShouldVisitElseBody(stmt: IfStmt) -> bool:
170 if _ShouldSkipIf(stmt):
171 return False
172
173 cond = stmt.expr[0]
174 if isinstance(cond, MemberExpr) and cond.name == 'CPP':
175 return False
176
177 return stmt.else_body is not None