Guest User

Untitled

a guest
Jul 15th, 2018
71
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
text 10.69 KB | None | 0 0
  1. import dis
  2. from types import CodeType
  3.  
  4.  
  5. def _patch_code(code: CodeType, **kwargs):
  6. """Create a new CodeType object with modified attributes."""
  7.  
  8. code_attrs = {}
  9.  
  10. # Collect the original CodeType attributes
  11. for attr in dir(code):
  12. if "__" not in attr:
  13. code_attrs[attr] = getattr(code, attr)
  14.  
  15. # Patch the new attributes over the original ones
  16. code_attrs.update(kwargs)
  17.  
  18. new_object = CodeType(
  19. code_attrs["co_argcount"],
  20. code_attrs["co_kwonlyargcount"],
  21. code_attrs["co_nlocals"],
  22. code_attrs["co_stacksize"],
  23. code_attrs["co_flags"],
  24. code_attrs["co_code"],
  25. code_attrs["co_consts"],
  26. code_attrs["co_names"],
  27. code_attrs["co_varnames"],
  28. code_attrs["co_filename"],
  29. code_attrs["co_name"],
  30. code_attrs["co_firstlineno"],
  31. code_attrs["co_lnotab"]
  32. )
  33.  
  34. return new_object
  35.  
  36.  
  37. def _assemble(*instructions):
  38. """
  39. Assemble CPython bytecode into a byte-string given
  40. any amount of two-tuples containing: (op_name, arg_value)
  41. """
  42.  
  43. code = ""
  44.  
  45. for op_name, arg in instructions:
  46. # Some instructions don't take arguments, so just null it.
  47. if arg is None:
  48. arg = 0
  49.  
  50. # Find the opcode so we can create the two-byte instruction
  51. # from the opcode itself and the argument number.
  52. op_code = dis.opmap[op_name]
  53. code += chr(op_code) + chr(arg)
  54.  
  55. # We can't use `str.encode()` here because some opcodes are
  56. # greater than 128 (such as CALL_FUNCTION -> 131) so they wouldn't
  57. # be encoded to ASCII, and UTF-8 would obviously yield inconsistent
  58. # results due to the possibility of multi-byte characters.
  59. return bytes(ord(char) for char in code)
  60.  
  61.  
  62. def _safe_search(tup, *items):
  63. """Search a tuple, or add the item if it's not present."""
  64.  
  65. indices = []
  66.  
  67. for item in items:
  68. # Create a new tuple with the item if it's not already present
  69. if item not in tup:
  70. tup = *tup, item
  71.  
  72. indices.append(tup.index(item))
  73.  
  74. return (*tup, *indices)
  75.  
  76.  
  77. def _exec_code(code: CodeType, *args, **kwargs):
  78. """Execute a CodeType object with args and kwargs."""
  79.  
  80. # We re-assign the bytecode of this empty
  81. # function with the CodeType object so that
  82. # it can be executed in a normal manner.
  83. util = lambda *args, **kwargs: None
  84. util.__code__ = code
  85.  
  86. return util(*args, **kwargs)
  87.  
  88.  
  89. def cpp_stdio(func):
  90. """
  91. Modifies the bytecode of a function so that C++ style `cin` and
  92. `cout` calls are possible in place of `input` and `print`, then
  93. executes it.
  94. Example:
  95. >>> @cpp_stdio
  96. ... def hello_name():
  97. ... cout << "Please enter your name: ";
  98. ... cin >> name;
  99. ...
  100. ... cout << "Hello, " << name << "!" << endl;
  101. ...
  102. Note: I will hurt you if you unironically use this. :D
  103. """
  104.  
  105. def _patch_cin(code: CodeType):
  106. """Patch instances of C++ style `cin` in the function."""
  107.  
  108. # The attributes themselves are read-only so we just
  109. # have to patch these copies onto the original object.
  110. nlocals = code.co_nlocals
  111. varnames = code.co_varnames
  112. consts = code.co_consts
  113. new_code = code.co_code
  114.  
  115. # We need cin_num and input_num for instruction args.
  116. *names, cin_num, input_num = _safe_search(
  117. code.co_names,
  118. "cin", "input"
  119. )
  120.  
  121. # This will be used to find where `cin` is called.
  122. cin_start = _assemble(
  123. ("LOAD_GLOBAL", cin_num)
  124. )
  125.  
  126. # This list will contain all of the implicitly-declared
  127. # variables. This means we can declare them locally from
  128. # the `cin` call alone, which is the 'pythonic' twist. :P
  129. imp_decl = []
  130.  
  131. start_pos = 0
  132. while True:
  133. # Attempt to find another `cin` in the function,
  134. # and stop looking if one couldn't be found.
  135. start_pos = new_code.find(cin_start, start_pos)
  136. if start_pos < 0:
  137. break
  138.  
  139. # `cin` calls are 4 instructions x 2 bytes = 8 bytes
  140. end_pos = start_pos + 8
  141. cin_call = new_code[start_pos:end_pos]
  142.  
  143. # The third byte is the local arg number
  144. # of the variable that is being changed.
  145. store_num = cin_call[3]
  146.  
  147. # Define the variable in the function's local
  148. # scope if it hasn't yet been declared locally.
  149. if cin_call[2] == dis.opmap["LOAD_GLOBAL"]:
  150. *consts, none_num = _safe_search(
  151. consts,
  152. None
  153. )
  154.  
  155. # We'll need to keep track of this to replace it
  156. # if the variable is used later in the function.
  157. prev_store_num = store_num
  158.  
  159. # Add the variable to the local scope declarations
  160. *varnames, store_num = _safe_search(
  161. varnames,
  162. names[store_num]
  163. )
  164.  
  165. nlocals += 1
  166. imp_decl.append(
  167. (prev_store_num, store_num)
  168. )
  169.  
  170. # This is the `var = input()` bytecode. It directly
  171. # replaces the `cin` calls, so that `cin` doesn't even
  172. # need to be defined at all for this to work. Snazzy!
  173. changed_code = _assemble(
  174. ("LOAD_GLOBAL", input_num),
  175. ("CALL_FUNCTION", 0),
  176. ("STORE_FAST", store_num)
  177. )
  178.  
  179. new_code = new_code[:start_pos] + changed_code + new_code[end_pos:]
  180.  
  181. # Stop the intepreter from treating implicity-declared
  182. # local variables as potentially undefined global variables.
  183. for prev_store, new_store in imp_decl:
  184. # The global-loading bytecode
  185. wrong_decl = _assemble(
  186. ("LOAD_GLOBAL", prev_store),
  187. )
  188.  
  189. # The (correct) local-loading bytecode
  190. new_decl = _assemble(
  191. ("LOAD_FAST", new_store),
  192. )
  193.  
  194. new_code = new_code.replace(wrong_decl, new_decl)
  195.  
  196. return _patch_code(code,
  197. co_code=new_code,
  198. co_names=tuple(names),
  199. co_consts=tuple(consts),
  200. co_varnames=tuple(varnames),
  201. co_nlocals=nlocals
  202. )
  203.  
  204. def _patch_cout(code: CodeType):
  205. """
  206. Patch instances of C++ style `cout` in the function.
  207.  
  208. Note: I'm very aware that one can simply make a custom class
  209. which overrides the __lshift__ magic method and does a bunch
  210. of fancy stuff, and that's what I had originally.
  211. This is more fun though :D
  212. """
  213.  
  214. # We need to patch these over the read-only attributes of `code`
  215. new_code = code.co_code
  216. consts = code.co_consts
  217. names = code.co_names
  218.  
  219. # Find the const arg numbers for the two bools,
  220. # newline and empty strings, and the `print` kwargs.
  221. *consts, false_num, true_num, newln_num, empty_str, print_kws = _safe_search(
  222. consts,
  223. False, True, "\n", "", ("sep", "end", "flush")
  224. )
  225.  
  226. # Find the global arg numbers of `count`, `endl` and `print`
  227. *names, cout_num, endl_num, print_num = _safe_search(
  228. names,
  229. "cout", "endl", "print"
  230. )
  231.  
  232. # `cout` calls will always begin with this instruction
  233. cout_start = _assemble(
  234. ("LOAD_GLOBAL", cout_num)
  235. )
  236.  
  237. # Each value is separated by a `<<`, so we'll use this.
  238. separator = _assemble(
  239. ("BINARY_LSHIFT", None)
  240. )
  241.  
  242. # `cout` calls always end with this instruction
  243. cout_end = _assemble(
  244. ("POP_TOP", None)
  245. )
  246.  
  247. # And this is what `endl` will appear as.
  248. endl_value = _assemble(
  249. ("LOAD_GLOBAL", endl_num)
  250. )
  251.  
  252. start_pos = 0
  253. while True:
  254. # Find the boundaries and the bytecode of the `cout` call
  255. start_pos = new_code.find(cout_start, start_pos)
  256. if start_pos < 0:
  257. break
  258.  
  259. end_pos = new_code.find(cout_end, start_pos)
  260. cout_call = new_code[start_pos:end_pos]
  261.  
  262. # Cut off the `cout` part, and remove the `<<` separators.
  263. out_values = cout_call[2:].replace(separator, b"")
  264.  
  265. # Push the print function onto the stack
  266. changed_code = _assemble(
  267. ("LOAD_GLOBAL", print_num)
  268. )
  269.  
  270. # Add each value to be printed from the cout call
  271. changed_code += out_values
  272. if out_values.endswith(endl_value):
  273. changed_code = changed_code[:-2]
  274.  
  275. # `cout` typically doesn't have a separator.
  276. changed_code += _assemble(
  277. ("LOAD_CONST", empty_str) # sep=''
  278. )
  279.  
  280. # Each argument occupies 2 bytes, therefore we can
  281. # just divide the size by 2 to get the amount of them.
  282. args_length = len(out_values) // 2
  283.  
  284. if out_values.endswith(endl_value):
  285. # `endl` follows `print` defaults, but all print
  286. # calls will have the 3 kwargs for the sake
  287. # of consistency (and I'm lazy!)
  288. changed_code +=_assemble(
  289. ("LOAD_CONST", newln_num), # end='\n'
  290. ("LOAD_CONST", true_num), # flush=True
  291. )
  292.  
  293. # Account for the stripped off `endl` value
  294. args_length -= 1
  295. else:
  296. changed_code += _assemble(
  297. ("LOAD_CONST", empty_str), # end=''
  298. ("LOAD_CONST", false_num), # flush=False
  299. )
  300.  
  301. # Loads the kwarg names and call the function
  302. changed_code += _assemble(
  303. ("LOAD_CONST", print_kws),
  304. ("CALL_FUNCTION_KW", 3 + args_length)
  305. )
  306.  
  307. new_code = new_code[:start_pos] + changed_code + new_code[end_pos:]
  308.  
  309. return _patch_code(code,
  310. co_code=new_code,
  311. co_consts=tuple(consts),
  312. co_names=tuple(names)
  313. )
  314.  
  315. def wrapper(*args, **kwargs):
  316. code = func.__code__
  317.  
  318. # We only need to patch `cin` and `count` if they're used.
  319. if "cin" in code.co_names:
  320. code = _patch_cin(code)
  321.  
  322. if "cout" in code.co_names:
  323. code = _patch_cout(code)
  324.  
  325. # Finally, execute the patched code as if nothing has happened :P
  326. _exec_code(code, *args, **kwargs)
  327.  
  328. return wrapper
  329.  
  330.  
  331. if __name__ == '__main__':
  332.  
  333. @cpp_stdio
  334. def addition():
  335. cout << "Enter a number: ";
  336. cin >> x;
  337.  
  338. cout << "And another: ";
  339. cin >> y;
  340.  
  341. result = int(x) + int(y)
  342. cout << x << " + " << y << " = " << result << endl;
  343.  
  344. addition()
Add Comment
Please, Sign In to add comment