commit d2ee68f22a5c69ccdabe7481bae71699f6653a95 Author: tianfenghan Date: Thu Dec 28 12:38:18 2023 +0800 init diff --git a/cases/mixed.py b/cases/mixed.py new file mode 100644 index 00000000..38141ce8 --- /dev/null +++ b/cases/mixed.py @@ -0,0 +1,407 @@ +# Python中单行注释用#表示,#之后同行字符全部认为被注释 + +""" 与之对应的是多行注释 + 用三个双引号表示,这两段双引号当中的内容都会被视作是注释 +""" + +values = [] +kv = {'hello': 'world'} + +# 获得一个整数 +values[0] = 3 +# 获得一个浮点数 +values[1] = 10.0 + +c = 1 + 1 # => 2 +d = 8 - 1 # => 7 +e = 10 * 2 # => 20 +f = 35 / 5 # => 7.0 + +g = 5 // 3 # => 1 +h = -5 // 3 # => -2 +j = 5.5 // 3.0 # => 1.0 # works on floats too +k = -5.0 // 3.0 # => -2.0 + +# Modulo operation +values[10] = 7 % 3 # => 1 + +# Exponentiation (x**y, x to the yth power) +values[11] = 2 ** 3 # => 8 + +# Enforce precedence with parentheses +values[12] = 1 + 3 * 2 # => 7 +values[13] = (1 + 3) * 2 # => 8 + +_ = True # => True +_ = False # => False + +_ = not True # => False +_ = not False # => True + +# Boolean Operators +# Note "and" and "or" are case-sensitive +_ = True and False # => False +_ = False or True # => True + +_ = True + True # => 2 +_ = True * 8 # => 8 +_ = False - 5 # => -5 + +_ = 0 == False # => True +_ = 1 == True # => True +_ = 2 == True # => False +_ = -5 != False # => True + +_ = bool(0) # => False +_ = bool(4) # => True +_ = bool(-6) # => True +_ = 0 and 2 # => 0 +_ = -5 or 0 # => -5 + +# Equality is == +_ = 1 == 1 # => True +_ = 2 == 1 # => False + +# Inequality is != +_ = 1 != 1 # => False +_ = 2 != 1 # => True + +# More comparisons +_ = 1 < 10 # => True +_ = 1 > 10 # => False +_ = 2 <= 2 # => True +_ = 2 >= 2 # => True + +# Seeing whether a value is in a range +_ = 1 < 2 and 2 < 3 # => True +_ = 2 < 3 and 3 < 2 # => False +# Chaining makes this look nicer +_ = 1 < 2 < 3 # => True +_ = 2 < 3 < 2 # => False + +_ = a = [1, 2, 3, 4] # Point a at a new list, [1, 2, 3, 4] +_ = b = a # Point b at what a is pointing to +_ = b is a # => True, a and b refer to the same object +_ = b == a # => True, a's and b's objects are equal +_ = b = [1, 2, 3, 4] # Point b at a new list, [1, 2, 3, 4] +_ = b is a # => False, a and b do not refer to the same object +_ = b == a # => True, a's and b's objects are equal + +# Strings are created with " or ' +_ = "This is a string." +_ = 'This is also a string.' + +# Strings can be added too! But try not to do this. +_ = "Hello " + "world!" # => "Hello world!" +# String literals (but not variables) can be concatenated without using '+' +_ = "Hello " "world!" # => "Hello world!" + +# A string can be treated like a list of characters +_ = "This is a string"[0] # => 'T' + +# You can find the length of a string +_ = len("This is a string") # => 16 + +# You can also format using f-strings or formatted string literals (in Python 3.6+) +name = "Reiko" +_ = f"She said her name is {name}." # => "She said her name is Reiko" +# You can basically put any Python statement inside the braces and it will be output in the string. +_ = f"{name} is {len(name)} characters long." # => "Reiko is 5 characters long." + +# None is an object +_ = None # => None + +# Don't use the equality "==" symbol to compare objects to None +# Use "is" instead. This checks for equality of object identity. +_ = "etc" is None # => False +_ = None is None # => True + +# None, 0, and empty strings/lists/dicts/tuples all evaluate to False. +# All other values are True +_ = bool(None) # => False +_ = bool(0) # => False +_ = bool("") # => False +_ = bool([]) # => False +_ = bool({}) # => False +_ = bool(()) # => False + +# Python has a print function +print("I'm Python. Nice to meet you!") # => I'm Python. Nice to meet you! + +# By default the print function also prints out a newline at the end. +# Use the optional argument end to change the end string. +print("Hello, World", end="!") # => Hello, World! + +# Simple way to get input data from console +input_string_var = input("Enter some data: ") # Returns the data as a string +# Note: In earlier versions of Python, input() method was named as raw_input() + +# There are no declarations, only assignments. +# Convention is to use lower_case_with_underscores +some_var = 5 + +# Accessing a previously unassigned variable is an exception. +# See Control Flow to learn more about exception handling. + +# if can be used as an expression +# Equivalent of C's '?:' ternary operator +_ = "yahoo!" if 3 > 2 else 2 # => "yahoo!" + + +def test(): + if 3 > 2: + return 'yahoo' + else: + return 2 + + +# Lists store sequences +li = [] +# You can start with a prefilled list +other_li = [4, 5, 6] + +# Add stuff to the end of a list with append +li.append(1) # li is now [1] +li.append(2) # li is now [1, 2] +li.append(4) # li is now [1, 2, 4] +li.append(3) # li is now [1, 2, 4, 3] +# Remove from the end with pop +li.pop() # => 3 and li is now [1, 2, 4] +# Let's put it back +li.append(3) # li is now [1, 2, 4, 3] again. + +# Access a list like you would any array +_ = li[0] # => 1 +# Look at the last element +_ = li[-1] # => 3 + +# Looking out of bounds is an IndexError +_ = li[4] # Raises an IndexError + +# You can look at ranges with slice syntax. +# The start index is included, the end index is not +# (It's a closed/open range for you mathy types.) +_ = li[1:3] # Return list from index 1 to 3 => [2, 4] +_ = li[2:] # Return list starting from index 2 => [4, 3] +_ = li[:3] # Return list from beginning until index 3 => [1, 2, 4] +_ = li[::2] # Return list selecting every second entry => [1, 4] +_ = li[::-1] # Return list in reverse order => [3, 4, 2, 1] +# Use any combination of these to make advanced slices +# li[start:end:step] + +# Make a one layer deep copy using slices +li2 = li[:] # => li2 = [1, 2, 4, 3] but (li2 is li) will result in false. + +# Remove arbitrary elements from a list with "del" +del li[2] # li is now [1, 2, 3] + +# Remove first occurrence of a value +li.remove(2) # li is now [1, 3] +li.remove(2) # Raises a ValueError as 2 is not in the list + +# Insert an element at a specific index +li.insert(1, 2) # li is now [1, 2, 3] again + +# Get the index of the first item found matching the argument +li.index(2) # => 1 +li.index(4) # Raises a ValueError as 4 is not in the list + +# Tuples are like lists but are immutable. +tup = (1, 2, 3) +tup[0] # => 1 +tup[0] = 3 # Raises a TypeError + +type((1)) # => +type((1,)) # => +type(()) # => + +_ = len(tup) # => 3 +_ = tup + (4, 5, 6) # => (1, 2, 3, 4, 5, 6) +_ = tup[:2] # => (1, 2) +_ = 2 in tup # => True + +# You can unpack tuples (or lists) into variables +a, b, c = (1, 2, 3) # a is now 1, b is now 2 and c is now 3 +# You can also do extended unpacking +# Tuples are created by default if you leave out the parentheses +d, e, f = 4, 5, 6 # tuple 4, 5, 6 is unpacked into variables d, e and f +# respectively such that d = 4, e = 5 and f = 6 +# Now look how easy it is to swap two values +e, d = d, e # d is now 5 and e is now 4 + +# Look up values with [] +invalid_dict = {1: "123"} +_ = invalid_dict["one"] # => 1 +_ = invalid_dict.get('one') # => 1 + +# Here is a prefilled dictionary +filled_dict = {"one": 1, "two": 2, "three": 3} + +# Get all keys as an iterable with "keys()". We need to wrap the call in list() +# to turn it into a list. We'll talk about those later. Note - for Python +# versions <3.7, dictionary key ordering is not guaranteed. Your results might +# not match the example below exactly. However, as of Python 3.7, dictionary +# items maintain the order at which they are inserted into the dictionary. +_ = list(filled_dict.keys()) # => ["three", "two", "one"] in Python <3.7 +_ = list(filled_dict.keys()) # => ["one", "two", "three"] in Python 3.7+ + +# Get all values as an iterable with "values()". Once again we need to wrap it +# in list() to get it out of the iterable. Note - Same as above regarding key +# ordering. +_ = list(filled_dict.values()) # => [3, 2, 1] in Python <3.7 +_ = list(filled_dict.values()) # => [1, 2, 3] in Python 3.7+ + +# Check for existence of keys in a dictionary with "in" +_ = "one" in filled_dict # => True +_ = 1 in filled_dict # => False + +# _ = {'a': 1, **{'b': 2}} # => {'a': 1, 'b': 2} +# _ = {'a': 1, **{'a': 2}} # => {'a': 2} + +# Sets store ... well sets +empty_set = set() +# Initialize a set with a bunch of values. Yeah, it looks a bit like a dict. Sorry. +some_set = {1, 1, 2, 2, 3, 4} # some_set is now {1, 2, 3, 4} + +# Do set intersection with & +# 计算交集 +other_set = {3, 4, 5, 6} +filled_set = {1, 2, 3} +_ = filled_set & other_set # => {3, 4, 5} + +# Do set union with | +# 计算并集 +_ = filled_set | other_set # => {1, 2, 3, 4, 5, 6} + +# Do set difference with - +# 计算差集 +_ = {1, 2, 3, 4} - {2, 3, 5} # => {1, 4} + +# Do set symmetric difference with ^ +# 这个有点特殊,计算对称集,也就是去掉重复元素剩下的内容 +_ = {1, 2, 3, 4} ^ {2, 3, 5} # => {1, 4, 5} + +# Check if set on the left is a superset of set on the right +_ = {1, 2} >= {1, 2, 3} # => False + +# Check if set on the left is a subset of set on the right +_ = {1, 2} <= {1, 2, 3} # => True + +if some_var > 10: + print("some_var is totally bigger than 10.") +elif some_var < 10: # This elif clause is optional. + print("some_var is smaller than 10.") +else: # This is optional too. + print("some_var is indeed 10.") + +for animal in ["dog", "cat", "mouse"]: + # You can use format() to interpolate formatted strings + print("{} is a mammal".format(animal)) + +for i in range(4): + print(i) + +animals = ["dog", "cat", "mouse"] +for i, value in enumerate(animals): + print(i, value) + +x = 0 +while x < 4: + print(x) + x += 1 # Shorthand for x = x + 1 + +# Handle exceptions with a try/except block +try: + # Use "raise" to raise an error + raise IndexError("This is an index error") +except IndexError as e: + pass # Pass is just a no-op. Usually you would do recovery here. +except (TypeError, NameError): + pass # Multiple exceptions can be handled together, if required. +finally: # Execute under all circumstances + print("We can clean up resources here") + + +# Instead of try/finally to cleanup resources you can use a with statement +# 代替使用try/finally语句来关闭资源 +with open("myfile.txt") as f: + for line in f: + print(line) + +# Writing to a file +# 使用with写入文件 +contents = {"aa": 12, "bb": 21} +with open("myfile1.txt", "w+") as file: + file.write(str(contents)) # writes a string to a file + +with open("myfile2.txt", "w+") as file: + file.write(json.dumps(contents)) # writes an object to a file + +# Reading from a file +# 使用with读取文件 +with open('myfile1.txt', "r+") as file: + contents = file.read() # reads a string from a file +print(contents) +# print: {"aa": 12, "bb": 21} + +with open('myfile2.txt', "r+") as file: + contents = json.load(file) # reads a json object from a file +print(contents) +# print: {"aa": 12, "bb": 21} + +# Python offers a fundamental abstraction called the Iterable. +# An iterable is an object that can be treated as a sequence. +# The object returned by the range function, is an iterable. + +filled_dict = {"one": 1, "two": 2, "three": 3} +our_iterable = filled_dict.keys() +print(our_iterable) # => dict_keys(['one', 'two', 'three']). This is an object that implements our Iterable interface. + +# We can loop over it. +for i in our_iterable: + print(i) # Prints one, two, three + +# However we cannot address elements by index. +our_iterable[1] # Raises a TypeError + +# An iterable is an object that knows how to create an iterator. +our_iterator = iter(our_iterable) + +# Our iterator is an object that can remember the state as we traverse through it. +# We get the next object with "next()". +next(our_iterator) # => "one" + +# It maintains state as we iterate. +next(our_iterator) # => "two" +next(our_iterator) # => "three" + +# After the iterator has returned all of its data, it raises a StopIteration exception +next(our_iterator) # Raises StopIteration + +# We can also loop over it, in fact, "for" does this implicitly! +our_iterator = iter(our_iterable) +for i in our_iterator: + print(i) # Prints one, two, three + +# You can grab all the elements of an iterable or iterator by calling list() on it. +list(our_iterable) # => Returns ["one", "two", "three"] +list(our_iterator) # => Returns [] because state is saved + +# Use "def" to create new functions +def add(x, y): + print("x is {} and y is {}".format(x, y)) + return x + y # Return values with a return statement + +# Calling functions with parameters +add(5, 6) # => prints out "x is 5 and y is 6" and returns 11 + +# Another way to call functions is with keyword arguments +add(y=6, x=5) # Keyword arguments can arrive in any order. + +# You can define functions that take a variable number of +# positional arguments +def varargs(*args): + return args + +varargs(1, 2, 3) # => (1, 2, 3) + diff --git a/cases/test.php b/cases/test.php new file mode 100644 index 00000000..cc019a61 --- /dev/null +++ b/cases/test.php @@ -0,0 +1,10 @@ +' + else: + lines[i] = f'
' + else: + if i > 0: + if count % 2 == 1: + line = line.replace("`", "\`") + line = line.replace("<", "<") + line = line.replace(">", ">") + line = line.replace(" ", " ") + line = line.replace("*", "*") + line = line.replace("_", "_") + line = line.replace("-", "-") + line = line.replace(".", ".") + line = line.replace("!", "!") + line = line.replace("(", "(") + line = line.replace(")", ")") + line = line.replace("$", "$") + lines[i] = "
" + line + text = "".join(lines) + return text + + +def predict(input, chatbot, max_length, top_p, temperature, history, past_key_values): + chatbot.append((parse_text(input), "")) + for response, history, past_key_values in model.stream_chat(tokenizer, input, history, + past_key_values=past_key_values, + return_past_key_values=True, + max_length=max_length, top_p=top_p, + temperature=temperature): + chatbot[-1] = (parse_text(input), parse_text(response)) + + yield chatbot, history, past_key_values + + +def reset_user_input(): + return gr.update(value='') + +# +def reset_state(): + return [], [], None + + +with gr.Blocks() as demo: + gr.HTML("""

ChatGLM3-6B

""") + + chatbot = gr.Chatbot() + with gr.Row(): + with gr.Column(scale=4): + with gr.Column(scale=12): + user_input = gr.Textbox(show_label=False, placeholder="Input...", lines=10).style( + container=False) + with gr.Column(min_width=32, scale=1): + submitBtn = gr.Button("Submit", variant="primary") + with gr.Column(scale=1): + emptyBtn = gr.Button("Clear History") + max_length = gr.Slider(0, 32768, value=8192, step=1.0, label="Maximum length", interactive=True) + top_p = gr.Slider(0, 1, value=0.8, step=0.01, label="Top P", interactive=True) + temperature = gr.Slider(0, 1, value=0.6, step=0.01, label="Temperature", interactive=True) + + history = gr.State([]) + past_key_values = gr.State(None) + + submitBtn.click(predict, [user_input, chatbot, max_length, top_p, temperature, history, past_key_values], + [chatbot, history, past_key_values], show_progress=True) + submitBtn.click(reset_user_input, [], [user_input]) + + emptyBtn.click(reset_state, outputs=[chatbot, history, past_key_values], show_progress=True) + +demo.queue().launch(share=False, server_name="127.0.0.1", server_port=8501, inbrowser=True) diff --git a/cases/tmp.py b/cases/tmp.py new file mode 100644 index 00000000..4be1a931 --- /dev/null +++ b/cases/tmp.py @@ -0,0 +1,5 @@ +def varargs(*xxx): + return xxx + + +varargs(1, 2, 3) # => (1, 2, 3) diff --git a/conv.php b/conv.php new file mode 100644 index 00000000..485b7df7 --- /dev/null +++ b/conv.php @@ -0,0 +1,824 @@ +_type != 'Module') { + echo "invalid python module\n"; +} + +function debug($v) +{ + global $translator; + if ($translator->mode == 'cli') { + echo 'Error: Unsupported Python Syntax, Line: ' . $v->lineno . PHP_EOL; + debug_print_backtrace(); + var_dump($v); + } else { + header('Content-Type: application/json'); + echo json_encode($v, JSON_PRETTY_PRINT); + } + die; +} + +function if_empty_debug($if_expr, $v) +{ + if (empty($if_expr)) { + debug($v); + } +} + +function if_not_empty_debug($if_expr, $v) +{ + if (!empty($if_expr)) { + debug($v); + } +} + +class Translator +{ + private array $keywords = ['abs', 'aiter', 'all', 'anext', 'any', 'ascii', 'bin', 'bool', 'breakpoint', 'bytearray', 'bytes', 'callable', 'chr', 'classmethod', 'compile', 'complex', 'copyright', 'credits', 'delattr', 'dict', 'dir', 'divmod', 'enumerate', 'eval', 'exec', 'exit', 'filter', 'float', 'format', 'frozenset', 'getattr', 'globals', 'hasattr', 'hash', 'help', 'hex', 'id', 'input', 'int', 'isinstance', 'issubclass', 'iter', 'len', 'license', 'list', 'locals', 'map', 'max', 'memoryview', 'min', 'next', 'object', 'oct', 'open', 'ord', 'pow', 'print', 'property', 'quit', 'range', 'repr', 'reversed', 'round', 'set', 'setattr', 'slice', 'sorted', 'staticmethod', 'str', 'sum', 'super', 'tuple', 'type', 'vars', 'zip']; + private array $keywordsMap = []; + private int $indentLevel = 0; + private string $indentStr = "\t"; + public string $mode; + private array $definedFunctions = []; + private array $builtinTypes = [ + 'ArithmeticError', + 'AssertionError', + 'AttributeError', + 'BaseException', + 'BaseExceptionGroup', + 'BlockingIOError', + 'BrokenPipeError', + 'BufferError', + 'BytesWarning', + 'ChildProcessError', + 'ConnectionAbortedError', + 'ConnectionError', + 'ConnectionRefusedError', + 'ConnectionResetError', + 'DeprecationWarning', + 'EOFError', + 'Ellipsis', + 'EncodingWarning', + 'EnvironmentError', + 'Exception', 'ExceptionGroup', 'False', + 'FileExistsError', 'FileNotFoundError', 'FloatingPointError', + 'FutureWarning', 'GeneratorExit', 'IOError', 'ImportError', 'ImportWarning', + 'IndentationError', 'IndexError', 'InterruptedError', 'IsADirectoryError', + 'KeyError', 'KeyboardInterrupt', 'LookupError', 'MemoryError', 'ModuleNotFoundError', + 'NameError', 'None', 'NotADirectoryError', 'NotImplemented', 'NotImplementedError', + 'OSError', 'OverflowError', 'PendingDeprecationWarning', 'PermissionError', 'ProcessLookupError', + 'RecursionError', 'ReferenceError', 'ResourceWarning', 'RuntimeError', 'RuntimeWarning', + 'StopAsyncIteration', 'StopIteration', 'SyntaxError', 'SyntaxWarning', + 'SystemError', 'SystemExit', 'TabError', 'TimeoutError', 'True', 'TypeError', + 'UnboundLocalError', 'UnicodeDecodeError', 'UnicodeEncodeError', + 'UnicodeError', 'UnicodeTranslateError', 'UnicodeWarning', 'UserWarning', 'ValueError', 'Warning', 'ZeroDivisionError' + ]; + + function __construct() + { + $this->keywordsMap = array_flip($this->keywords); + $this->builtinTypes = array_flip($this->builtinTypes); + } + + function setMode($mode) + { + $this->mode = $mode; + } + + function setIndent(string $indent) + { + $this->indentStr = $indent; + } + + function prepare($root) + { + foreach ($root->body as $body) { + if ($body->_type == 'FunctionDef') { + $this->definedFunctions[$body->name] = 1; + } + } + } + + function parseAttribute($attr) + { + switch ($attr->_type) { + case 'Name': + return '$' . $attr->id; + case 'Attribute': + return $this->parseAttribute($attr->value) . '->' . $attr->attr; + case 'Constant': + return 'PyCore::str("' . $attr->s . '")'; + case 'Call': + return $this->parseCall($attr); + default: + var_dump(__METHOD__, __LINE__); + debug($attr); + } + } + + function parseFunc($fn) + { + switch ($fn->_type) { + case 'Name': + $id = $fn->id; + if (isset($this->keywordsMap[$id])) { + return 'PyCore::' . $id; + } elseif (isset($this->definedFunctions[$id])) { + return $id; + } elseif (isset($this->builtinTypes[$id])) { + return '$builtins->' . $id; + } else { + return '$' . $id; + } + case 'Attribute': + return $this->parseAttribute($fn->value) . '->' . $fn->attr; + default: + debug($fn); + } + } + + function parseCall($call): string + { + $fn = $this->parseFunc($call->func); + if_empty_debug($fn, $call); + $args = $call->args; + $kwargs = $call->keywords; + + $args_list = []; + + foreach ($args as $arg) { + $args_list[] = $this->parseValue($arg); + } + foreach ($kwargs as $arg) { + $name = $arg->arg; + $args_list[] = $name . ': ' . $this->parseValue($arg->value); + } + + if (empty($args_list)) { + return "$fn()"; + } else { + return "$fn(" . implode(', ', $args_list) . ")"; + } + } + + function parseConstant($value): string + { + return Tool::valueToRepr($value->value); + } + + function parseTuple($tuple) + { + $ids = []; + foreach ($tuple->dims as $dim) { + $ids[] = $this->parseTarget($dim); + } + return '[' . implode(', ', $ids) . ']'; + } + + function parseListComp($listComp) + { + $code = ''; + $this->indentLevel++; + $code .= $this->getIndent() . '$___ = [];' . PHP_EOL; + $recipient = '$___[]'; + $generators = $this->parseGenerators($listComp->generators, $recipient, $captures); + $code .= $this->getIndent() . $generators; + $code .= $this->getIndent() . 'return $___;' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '})(' . implode(',', $captures) . ')'; + return '(function(' . implode(',', $captures) . ') {' . PHP_EOL . $code; + } + + function parseValue($value) + { + switch ($value->_type) { + case 'Call': + return $this->parseCall($value); + case 'Constant': + return $this->parseConstant($value); + case 'Name': + return $this->parseName($value); + case 'List': + return $this->parseList($value); + case 'Dict': + return $this->parseDict($value); + case 'Set': + return $this->parseSet($value); + case 'Tuple': + return $this->parseTuple($value); + case 'ListComp': + return $this->parseListComp($value); + case 'BinOp': + return $this->parseBinOp($value); + case 'JoinedStr': + return $this->parseJoinedStr($value); + case 'Subscript': + return $this->parseSubscript($value); + case 'FormattedValue': + return $this->parseValue($value->value); + case 'UnaryOp': + return $this->parseTarget($value); + case 'BoolOp': + return $this->parseBoolOp($value); + case 'Compare': + return $this->parseTest($value); + case 'IfExp': + return $this->parseIfExp($value); + case 'Yield': + return $this->parseYield($value); + default: + debug($value); + break; + } + } + + function parseSlice($slice, $op = 'Load') + { + if ($slice->_type != 'Constant') { + $_args[] = $slice->lower ? $this->parseTarget($slice->lower) : 'null'; + $_args[] = $slice->upper ? $this->parseTarget($slice->upper) : 'null'; + $_args[] = $slice->step ? $this->parseTarget($slice->step) : 'null'; + $target = 'PyCore::slice(' . implode(', ', $_args) . ')'; + } else { + $target = $this->parseTarget($slice); + } + if ($op == 'Store') { + return $slice->id . '->__setitem__(' . $target . ', $__value)'; + } elseif ($op == 'Del') { + return $slice->id . '->__delitem__(' . $target . ')'; + } else { + return $slice->id . '->__getitem__(' . $target . ')'; + } + } + + function parseImportFrom($node) + { + $module = $node->module; + $imports = []; + foreach ($node->names as $name) { + $type = $name->name; + $as = empty($name->asname) ? $type : $name->asname; + $imports[] = "\$$as = PyCore::import('$module')->$type"; + } + + return implode(';' . PHP_EOL, $imports); + } + + function parseImport($node) + { + $name = $node->names[0]; + $module = $name->name; + $as = empty($name->asname) ? $module : $name->asname; + return "\$$as = PyCore::import('$module')"; + } + + function parseTarget($target) + { + switch ($target->_type) { + case 'Name': + return '$' . $target->id; + case 'UnaryOp': + $operand = $this->parseTarget($target->operand); + switch ($target->op->_type) { + case 'USub': + return '-' . $operand; + case 'Not': + return '!' . $operand; + default: + debug($target); + break; + } + break; + case 'Tuple': + return $this->parseTuple($target); + case 'Subscript': + return $this->parseSubscript($target); + case 'IfExp': + return $this->parseIfExp($target); + case 'Attribute': + return $this->parseAttribute($target); + case 'Constant': + return $this->parseConstant($target); + case 'List': + return $this->parseList($target); + case 'Set': + return $this->parseSet($target); + case 'Call': + return $this->parseCall($target); + case 'BinOp': + return $this->parseBinOp($target); + case 'Compare': + return $this->parseTest($target); + case 'Slice': + return $this->parseSlice($target); + default: + debug($target); + break; + } + } + + function parseIfExp($exp) + { + $test = $this->parseTest($exp->test); + $orelse = $this->parseValue($exp->orelse); + $body = $this->parseValue($exp->body); + return $test . ' ? ' . $body . ' : ' . $orelse; + } + + function parseExpr($node) + { + switch ($node->_type) { + case 'Expr': + if (is_string($node->value->value)) { + return '/** ' . $node->value->value . ' */' . PHP_EOL; + } else { + return $this->parseValue($node->value) . ';'; + } + default: + return $this->parseValue($node->value) . ';'; + } + } + + function parseArguments($args): string + { + $names = []; + foreach ($args->args as $arg) { + $names[] = '$' . $arg->arg; + } + if ($args->vararg) { + $names[] = '...$' . $args->vararg->arg; + } + return implode(', ', $names); + } + + function parseReturn($node) + { + return 'return ' . $this->parseValue($node->value); + } + + function parseTest($test) + { + $left = $this->parseValue($test->left); + $ops = $test->ops; + $comparators = $test->comparators; + + $op = $ops[0]->_type; + $comparator = $comparators[0]; + + switch ($op) { + case 'Is': + case 'Eq': + return $left . ' == ' . $this->parseValue($comparator); + case 'NotEq': + return $left . ' != ' . $this->parseValue($comparator); + case 'In': + return $this->parseValue($comparator) . '->__contains__(' . $left . ')'; + case 'Gt': + return $left . ' > ' . $this->parseValue($comparator); + case 'Lt': + return $left . ' < ' . $this->parseValue($comparator); + case 'GtE': + return $left . ' >= ' . $this->parseValue($comparator); + case 'LtE': + return $left . ' <= ' . $this->parseValue($comparator); + default: + debug($test); + } + } + + function parseIf($node) + { + $expr = $this->parseTest($node->test); + $this->indentLevel++; + $body = $this->parseBody($node->body); + $orelse = empty($node->orelse) ? '' : $this->parseBody($node->orelse); + $this->indentLevel--; + return 'if (' . $expr . ') {' . + PHP_EOL . $body . + PHP_EOL . $this->getIndent() . '}' . + ($orelse ? ' else {' . PHP_EOL . $orelse . PHP_EOL . $this->getIndent() . '}' : '') . + PHP_EOL; + } + + function parseIter($iter) + { + switch ($iter->_type) { + case 'Call': + return $this->parseCall($iter); + case 'List': + return $this->parseList($iter); + case 'Set': + return $this->parseSet($iter); + case 'Dict': + return $this->parseDict($iter); + case 'Name': + return $this->parseName($iter); + default: + debug($iter); + } + } + + function parseSubscript($node) + { + $code = ''; + if ($node->value) { + $code .= $this->parseValue($node->value); + } + if ($node->slice) { + $code .= $this->parseSlice($node->slice, $node->ctx->_type); + } + return $code; + } + + function parseFor($node) + { + $target = $this->parseTarget($node->target); + $iter = $this->parseIter($node->iter); + + $code = '$__iter = PyCore::iter(' . $iter . ');' . PHP_EOL; + $code .= $this->getIndent() . 'while($current = PyCore::next($__iter)) {' . PHP_EOL; + + $this->indentLevel++; + $code .= $this->getIndent() . $target . ' = $current;' . PHP_EOL; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + + return $code . $this->getIndent() . PHP_EOL . $this->getIndent() . '}'; + } + + private function getIndent() + { + return str_repeat($this->indentStr, $this->indentLevel); + } + + function parseFunctionDef($node) + { + $name = $node->name; + $args = $this->parseArguments($node->args); + $fn = PHP_EOL . $this->getIndent() . 'function ' . $name . '(' . $args . ') {' . PHP_EOL; + $this->indentLevel++; + $fn .= $this->parseBody($node->body) . PHP_EOL;; + $this->indentLevel--; + $fn .= $this->getIndent() . '}' . PHP_EOL . PHP_EOL; + + return $fn; + } + + private function addLine($line, array &$lines) + { + if ($this->mode == 'cli') { +// echo $line . PHP_EOL; + } + $lines[] = $line; + } + + function parseBody($tree) + { + $lines = []; + foreach ($tree as $node) { + switch ($node->_type) { + case 'ImportFrom': + $line = $this->parseImportFrom($node) . ';'; + break; + case 'Assign': + $target = $this->parseTarget($node->targets[0]); + $value = $this->parseValue($node->value); + if ($node->targets[0]->_type == 'Subscript') { + $this->addLine('$__value = ' . $value . ';', $lines); + $line = "$target;"; + } else { + $line = "$target = $value;"; + } + break; + case 'AugAssign': + $target = $this->parseTarget($node->target); + $value = $this->parseValue($node->value); + $line = "$target += $value;"; + break; + case 'Import': + $line = $this->parseImport($node) . ';'; + break; + case 'Expr': + $line = $this->parseExpr($node); + break; + case 'FunctionDef': + $line = $this->parseFunctionDef($node); + break; + case 'Return': + $line = $this->parseReturn($node) . ';'; + break; + case 'If': + $line = $this->parseIf($node); + break; + case 'For': + $line = $this->parseFor($node); + break; + case 'While': + $line = $this->parseWhile($node); + break; + case 'Try': + $line = $this->parseTry($node); + break; + case 'Raise': + $line = $this->parseRaise($node) . ';'; + break; + case 'Subscript': + $line = $this->parseSubscript($node) . ';'; + break; + case 'With': + $line = $this->parseWith($node); + break; + case 'Compare': + $line = $this->parseTest($node); + break; + case 'Delete': + $line = $this->parseDelete($node); + break; + default: + debug($node); + $line = ''; + break; + } + $this->addLine($line, $lines); + } + + foreach ($lines as &$line) { + $line = $this->getIndent() . $line; + } + + return implode(PHP_EOL, $lines); + } + + function convert($tree) + { + $this->prepare($tree); + $output = 'parseBody($tree->body); + echo $output; + echo PHP_EOL; + } + + private function parseGenerators($generators, $recipient, &$captures) + { + $code = '$elts = [];' . PHP_EOL; + foreach ($generators as $k => $generator) { + $target = $this->parseTarget($generator->target); + $iter = $generator->iter->id; + $name = '$' . $iter; + $captures[] = $name; + $code .= $this->getIndent() . 'foreach(' . $name . ' as ' . $target . ') {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->getIndent() . '$elts[' . $k . '] = ' . $target . ';' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}' . PHP_EOL; + } + $code .= $this->getIndent() . $recipient . ' = $elts;' . PHP_EOL;; + return $code; + } + + private function parseBinOp($value) + { + $op = $value->op->_type; + $left = $this->parseTarget($value->left); + $right = $this->parseValue($value->right); + switch ($op) { + case 'Mod': + return $left . ' % ' . $right; + case 'Add': + return $left . ' + ' . $right; + case 'Sub': + return $left . ' - ' . $right; + case 'Mult': + return $left . ' * ' . $right; + case 'Div': + return $left . ' / ' . $right; + default: + return '$operator->' . strtolower($op) . '(' . $left . ' , ' . $right . ')'; + } + } + + private function parseJoinedStr($value) + { + $list = []; + foreach ($value->values as $v) { + $list[] = $this->parseValue($v); + } + return implode(' . ', $list); + } + + private function parseList($target) + { + $values = []; + foreach ($target->elts as $e) { + $values[] = $this->parseValue($e); + } + return 'new PyList([' . implode(', ', $values) . '])'; + } + + private function parseYield($value) + { + return 'yield ' . $this->parseValue($value->value) . ';'; + } + + + private function parseWith(mixed $node) + { + $items = $node->items; + $body = $node->body; + $code = ''; + $finally_code = ''; + + $parseItem = function ($call, $target) use (&$code, &$finally_code) { + $call = $this->parseCall($call); + $target = empty($target) ? '$__' : $this->parseTarget($target); + $code .= $target . '__object = ' . $call . ';' . PHP_EOL;; + $code .= $target . ' = ' . $target . '__object->__enter__();' . PHP_EOL; + $this->indentLevel++; + $finally_code .= $this->getIndent() . $target . '__object->__exit__();' . PHP_EOL; + $this->indentLevel--; + }; + + foreach ($items as $k => $item) { + if ($item->context_expr->_type == 'Tuple') { + $context_expr = $item->context_expr->dims; + $optional_vars = $item->optional_vars->dims; + foreach ($context_expr as $k2 => $dim) { + $parseItem($dim, $optional_vars[$k2]); + } + } else { + $parseItem($item->context_expr, $item->optional_vars); + } + } + + $code .= 'try {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($body); + $this->indentLevel--; + $code .= PHP_EOL . '} finally {' . PHP_EOL; + $code .= $finally_code; + $code .= '}' . PHP_EOL; + return $code; + } + + private function parseDict($dict) + { + $code = '[' . PHP_EOL; + foreach ($dict->values as $k => $v) { + if (empty($dict->keys[$k])) { + debug($v); + } + $key = $dict->keys[$k]; + $this->indentLevel++; + $code .= $this->getIndent() . $this->parseTarget($key) . ' => ' . $this->parseTarget($v) . ',' . PHP_EOL; + $this->indentLevel--; + } + return 'new PyDict(' . $code . '])'; + } + + + private function parseBoolOp($value) + { + switch ($value->op->_type) { + case 'And': + return $this->parseAnd($value->values); + case 'Or': + return $this->parseOr($value->values); + default: + debug($value); + break; + } + } + + private function parseAnd($values) + { + $targets = []; + foreach ($values as $v) { + $targets[] = $this->parseTarget($v); + } + return implode(' && ', $targets); + } + + private function parseOr($values) + { + $targets = []; + foreach ($values as $v) { + $targets[] = $this->parseTarget($v); + } + return implode(' || ', $targets); + } + + private function parseDelete(mixed $node) + { + $code = ''; + foreach ($node->targets as $target) { + $code .= $this->getIndent() . $this->parseSubscript($target) . ';' . PHP_EOL; + } + return $code; + } + + private function parseSet($target) + { + $values = []; + foreach ($target->elts as $e) { + $values[] = $this->parseValue($e); + } + return 'new PySet([' . implode(', ', $values) . '])'; + } + + private function parseWhile(mixed $node) + { + $test = $this->parseTest($node->test); + $code = $this->getIndent() . 'while(' . $test . ') {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + return $code . $this->getIndent() . PHP_EOL . $this->getIndent() . '}'; + } + + private function parseTry(mixed $node) + { + $code = 'try {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + $code .= PHP_EOL . '}'; + + if ($node->handlers) { + $code .= ' catch(PyError $e) {' . PHP_EOL; + $this->indentLevel++; + foreach ($node->handlers as $k => $handler) { + if ($handler->type->_type == 'Name') { + $id = $handler->type->id; + $type = isset($this->builtinTypes[$id]) ? '$builtins->' . $id : '$' . $id; + } else { + $types = []; + foreach ($handler->type->dims as $dim) { + $id = $dim->id; + $types[] = isset($this->builtinTypes[$id]) ? '$builtins->' . $id : '$' . $id; + } + $type = 'new PyTuple([' . implode(', ', $types) . '])'; + } + $code .= ($k == 0 ? $this->getIndent() . 'if' : ' elseif') . ' (PyCore::isinstance($e, ' . $type . ')) {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->body); + $this->indentLevel--; + $code .= PHP_EOL . $this->getIndent() . '}'; + } + $code .= ' else {'; + $this->indentLevel++; + $code .= PHP_EOL . $this->getIndent() . 'throw $e;'; + $this->indentLevel--; + $code .= PHP_EOL . $this->getIndent() . '}' . PHP_EOL; + $this->indentLevel--; + $code .= $this->getIndent() . '}'; + } + + if ($node->finalbody) { + $code .= ' finally {' . PHP_EOL; + $this->indentLevel++; + $code .= $this->parseBody($node->finalbody); + $this->indentLevel--; + $code .= PHP_EOL . $this->getIndent() . '}'; + } + + return $code; + } + + private function parseRaise(mixed $node) + { + return 'throw ' . $this->parseCall($node->exc); + } + + private function parseName($value) + { + return '$' . $value->id; + } +} + +error_reporting(E_ERROR); +// web or cli +if (!empty($argv[2])) { + $mode = $argv[2]; +} else { + $mode = 'cli'; +} + +$translator = new Translator(); +$translator->setMode($mode); +$translator->setIndent(' '); +$translator->convert($json); + diff --git a/dump.py b/dump.py new file mode 100644 index 00000000..4b5b2285 --- /dev/null +++ b/dump.py @@ -0,0 +1,9 @@ +import ast +import json +from ast2json import ast2json +import sys + +if __name__ == "__main__": + file_name = sys.argv[1] + tree = ast2json(ast.parse(open(file_name).read())) + print(json.dumps(tree, indent=4)) diff --git a/py2php.php b/py2php.php new file mode 100644 index 00000000..e58f4c49 --- /dev/null +++ b/py2php.php @@ -0,0 +1,4 @@ +