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

167 lines, 84 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 MaybeSkipIfStmt(visitor, stmt: IfStmt) -> bool:
117 """Returns true if the caller should not visit the entire if statement."""
118 cond = stmt.expr[0]
119
120 # Omit anything that looks like if __name__ == ...
121 if (isinstance(cond, ComparisonExpr) and
122 isinstance(cond.operands[0], NameExpr) and
123 cond.operands[0].name == '__name__'):
124 return True
125
126 if isinstance(cond, IntExpr) and cond.value == 0:
127 # But write else: body
128 # Note: this would be invalid at the top level!
129 if stmt.else_body:
130 visitor.accept(stmt.else_body)
131
132 return True
133
134 if isinstance(cond, NameExpr) and cond.name == 'TYPE_CHECKING':
135 # Omit if TYPE_CHECKING blocks. They contain type expressions that
136 # don't type check!
137 return True
138
139 if isinstance(cond, MemberExpr) and cond.name == 'CPP':
140 # just take the if block
141 if hasattr(visitor, 'def_write_ind'):
142 visitor.def_write_ind('// if MYCPP\n')
143 visitor.def_write_ind('')
144
145 for node in stmt.body:
146 visitor.accept(node)
147
148 if hasattr(visitor, 'def_write_ind'):
149 visitor.def_write_ind('// endif MYCPP\n')
150
151 return True
152
153 if isinstance(cond, MemberExpr) and cond.name == 'PYTHON':
154 # only accept the else block
155 if stmt.else_body:
156 if hasattr(visitor, 'def_write_ind'):
157 visitor.def_write_ind('// if not PYTHON\n')
158 visitor.def_write_ind('')
159
160 visitor.accept(stmt.else_body)
161
162 if hasattr(visitor, 'def_write_ind'):
163 visitor.def_write_ind('// endif MYCPP\n')
164
165 return True
166
167 return False