From 0242fa81861382f84dd1a33b19000cb32fca5ae8 Mon Sep 17 00:00:00 2001 From: Michael Phelps Date: Mon, 5 Jul 2021 23:01:59 -0400 Subject: [PATCH] Add initial implementation for forelse/whileelse --- README.md | 221 +++++++++++++++++++++++----------- examples/forelse.go | 47 +++++--- examples/forelse.py | 12 ++ pytago/go_ast/core.py | 34 ++++-- pytago/go_ast/transformers.py | 29 ++++- pytago/tests/test_core.py | 5 +- scripts/generate_readme.py | 1 - setup.py | 2 +- 8 files changed, 250 insertions(+), 101 deletions(-) diff --git a/README.md b/README.md index 76887fa..8c6f5a1 100644 --- a/README.md +++ b/README.md @@ -4219,6 +4219,157 @@ func main() { fmt.Println(x, y, z, a, b, c, d) } ``` +### scope +#### Python +```python +import random + + +def main(): + if random.random() > 0.5: + a = 1 + else: + a = 2 + + if random.random() > 0.5: + if random.random() > 0.5: + b = 1 + else: + b = 2 + else: + b = 3 + + def hello_world(): + c = 3 + print(c) + + hello_world() + print(a, b) + + +if __name__ == '__main__': + main() +``` +#### Go +```go +package main + +import ( + "fmt" + "math/rand" + "time" +) + +func init() { + rand.Seed(time.Now().UnixNano()) +} + +func main() { + var a int + var b int + if rand.Float64() > 0.5 { + a = 1 + } else { + a = 2 + } + if rand.Float64() > 0.5 { + if rand.Float64() > 0.5 { + b = 1 + } else { + b = 2 + } + } else { + b = 3 + } + hello_world := func() { + c := 3 + fmt.Println(c) + } + hello_world() + fmt.Println(a, b) +} +``` +### forelse +#### Python +```python +def main(): + for x in range(4): + if x == 5: + break + else: + print("Well of course that didn't happen") + + for x in range(7): + if x == 5: + break + else: + print("H-hey wait!") + + i = 0 + while i < 3: + print("Works with while too") + for x in range(3): + print("BTW don't worry about nested breaks") + break + if i == 10: + break + i += 1 + else: + print("Yeah not likely") + print(i) + + +if __name__ == '__main__': + main() +``` +#### Go +```go +package main + +import "fmt" + +func main() { + var x int + if func() bool { + for x = 0; x < 4; x++ { + if x == 5 { + return false + } + } + return true + }() { + fmt.Println("Well of course that didn't happen") + } + if func() bool { + for x = 0; x < 7; x++ { + if x == 5 { + return false + } + } + return true + }() { + fmt.Println("H-hey wait!") + } + i := 0 + if func() bool { + for i < 3 { + fmt.Println("Works with while too") + for x = 0; x < 3; x++ { + fmt.Println("BTW don't worry about nested breaks") + break + } + if i == 10 { + return false + } + i += 1 + } + return true + }() { + fmt.Println("Yeah not likely") + } + fmt.Println(i) +} +``` ### algomajorityelement #### Python ```python @@ -4442,76 +4593,6 @@ func main() { }()) } ``` -### scope -#### Python -```python -import random - - -def main(): - if random.random() > 0.5: - a = 1 - else: - a = 2 - - if random.random() > 0.5: - if random.random() > 0.5: - b = 1 - else: - b = 2 - else: - b = 3 - - def hello_world(): - c = 3 - print(c) - - hello_world() - print(a, b) - - -if __name__ == '__main__': - main() -``` -#### Go -```go -package main - -import ( - "fmt" - "math/rand" - "time" -) - -func init() { - rand.Seed(time.Now().UnixNano()) -} - -func main() { - var a int - var b int - if rand.Float64() > 0.5 { - a = 1 - } else { - a = 2 - } - if rand.Float64() > 0.5 { - if rand.Float64() > 0.5 { - b = 1 - } else { - b = 2 - } - } else { - b = 3 - } - hello_world := func() { - c := 3 - fmt.Println(c) - } - hello_world() - fmt.Println(a, b) -} -``` ## TODOs diff --git a/examples/forelse.go b/examples/forelse.go index 538f1de..1717db8 100644 --- a/examples/forelse.go +++ b/examples/forelse.go @@ -3,24 +3,43 @@ package main import "fmt" func main() { - broke := false - for x := 0; x < 4; x++ { - if x == 5 { - broke = true - break + var x int + if func() bool { + for x = 0; x < 4; x++ { + if x == 5 { + return false + } } - } - if !broke { + return true + }() { fmt.Println("Well of course that didn't happen") } - broke = false - for x := 0; x < 7; x++ { - if x == 5 { - broke = true - break + if func() bool { + for x = 0; x < 7; x++ { + if x == 5 { + return false + } } - } - if !broke { + return true + }() { fmt.Println("H-hey wait!") } + i := 0 + if func() bool { + for i < 3 { + fmt.Println("Works with while too") + for x = 0; x < 3; x++ { + fmt.Println("BTW don't worry about nested breaks") + break + } + if i == 10 { + return false + } + i += 1 + } + return true + }() { + fmt.Println("Yeah not likely") + } + fmt.Println(i) } diff --git a/examples/forelse.py b/examples/forelse.py index 3647e86..31ed229 100644 --- a/examples/forelse.py +++ b/examples/forelse.py @@ -11,6 +11,18 @@ def main(): else: print("H-hey wait!") + i = 0 + while i < 3: + print("Works with while too") + for x in range(3): + print("BTW don't worry about nested breaks") + break + if i == 10: + break + i += 1 + else: + print("Yeah not likely") + print(i) if __name__ == '__main__': diff --git a/pytago/go_ast/core.py b/pytago/go_ast/core.py index 35db373..c6fb4ef 100644 --- a/pytago/go_ast/core.py +++ b/pytago/go_ast/core.py @@ -436,8 +436,9 @@ class GoAST(ast.AST): _prefix = "ast." - def __init__(self, parents=None, **kwargs): + def __init__(self, parents=None, _py_context=None, **kwargs): super().__init__(**kwargs) + self._py_context = _py_context or {} self.parents = parents or [] for field_name in self._fields: field = getattr(self, field_name, None) @@ -518,9 +519,8 @@ def __repr__(self): class Expr(GoAST): - def __init__(self, *args, _type_help=None, _py_context=None, **kwargs): + def __init__(self, *args, _type_help=None, **kwargs): self._type_help = _type_help - self._py_context = _py_context or {} super().__init__(**kwargs) def __or__(self, Y: 'Expr') -> 'BinaryExpr': @@ -1226,14 +1226,12 @@ def __init__(self, Kind: ObjKind = None, Name: str = None, Type: Expr = None, - _py_context: dict = None, **kwargs) -> None: self.Data = Data self.Decl = Decl self.Kind = Kind self.Name = Name self.Type = Type - self._py_context = _py_context or {} super().__init__(**kwargs) @@ -2442,15 +2440,22 @@ def from_While(cls, node: ast.While): cond = build_expr_list([node.test])[0] match cond: case Ident(Name="true"): - return cls(Body=body) + loop = cls(Body=body) case BasicLit(Value=x) if not json.loads(x): - return cls(Body=body, Cond=Ident.from_str("false")) + loop = cls(Body=body, Cond=Ident.from_str("false")) case BasicLit(Kind=x) if x in [token.INT, token.STRING, token.FLOAT]: - return cls(Body=body) + loop = cls(Body=body) case Ident(Name="false") | Ident(Name="nil"): - return cls(Body=body, Cond=Ident.from_str("false")) - - return cls(Body=body, Cond=cond) + loop = cls(Body=body, Cond=Ident.from_str("false")) + case _: + loop = cls(Body=body, Cond=cond) + if node.orelse: + loop._py_context["forelse"] = True + # The breaks of this loop can be replaced with a return False by a transformer + return IfStmt(Body=BlockStmt(List=build_stmt_list(node.orelse)), + Cond=FuncLit(Body=BlockStmt(List=[loop, Ident("true").return_()]), + Type=FuncType(Results=FieldList(List=[Field(Type=GoBasicType.BOOL.ident)]))).call()) + return loop class FuncType(Expr): @@ -3107,6 +3112,13 @@ def from_For(cls, node: ast.For, **kwargs): key = Ident.from_str("_") value = build_expr_list([node.target])[0] x = build_expr_list([node.iter])[0] + if node.orelse: + for_body = cls(Body=body, Key=key, Tok=tok, Value=value, X=x, _py_context={**kwargs.get("_py_context", {}), "forelse": True}, + **kwargs) + # The breaks of this loop can be replaced with a return False by a transformer + return IfStmt(Body=BlockStmt(List=build_stmt_list(node.orelse)), + Cond=FuncLit(Body=BlockStmt(List=[for_body, Ident("true").return_()]), + Type=FuncType(Results=FieldList(List=[Field(Type=GoBasicType.BOOL.ident)]))).call()) return cls(Body=body, Key=key, Tok=tok, Value=value, X=x, **kwargs) @classmethod diff --git a/pytago/go_ast/transformers.py b/pytago/go_ast/transformers.py index 5d82c18..eb3cbf3 100644 --- a/pytago/go_ast/transformers.py +++ b/pytago/go_ast/transformers.py @@ -10,7 +10,7 @@ ast_snippets, MapType, ValueSpec, Expr, BadStmt, SendStmt, len_ # Shortcuts from pytago.go_ast.core import _find_nodes, GoAST, ChanType, StructType, InterfaceType, BadExpr, OP_COMPLIMENTS, \ - GoStmt, TypeSwitchStmt, StarExpr, GenDecl, TypeAssertExpr, DeclStmt + GoStmt, TypeSwitchStmt, StarExpr, GenDecl, TypeAssertExpr, DeclStmt, BranchStmt v = Ident.from_str @@ -680,7 +680,7 @@ def visit_RangeStmt(self, node: RangeStmt): case _: post = AssignStmt(Lhs=[node.Value], Rhs=[step], Tok=token.ADD_ASSIGN) cond = BinaryExpr(X=node.Value, Op=token.GTR if flipped else token.LSS, Y=stop) - return ForStmt(Body=node.Body, Cond=cond, Init=init, Post=post) + return ForStmt(Body=node.Body, Cond=cond, Init=init, Post=post, _py_context=node._py_context) return node @@ -1938,6 +1938,30 @@ def visit_FuncDecl_or_FuncLit(self, node: FuncDecl): return node +class ForElseBreaksReturnFalse(BaseTransformer): + def __init__(self): + super().__init__() + self.breaks_return_false = False + + def visit_Loop(self, node): + breaks_return_false = self.breaks_return_false + self.breaks_return_false = getattr(node, "_py_context", {}).get("forelse", False) + self.generic_visit(node) + self.breaks_return_false = breaks_return_false + return node + + def visit_RangeStmt(self, node: RangeStmt): + return self.visit_Loop(node) + + def visit_ForStmt(self, node: ForStmt): + return self.visit_Loop(node) + + def visit_BranchStmt(self, node: BranchStmt): + if node.Tok == token.BREAK and self.breaks_return_false: + return Ident("false").return_() + return node + + ALL_TRANSFORMS = [ #### STAGE 0 #### InsertUniqueInitializers, @@ -1985,6 +2009,7 @@ def visit_FuncDecl_or_FuncLit(self, node: FuncDecl): TypeSwitchStatementsRedeclareWithType, RemoveUnnecessaryFunctionLiterals, RemoveConflictingImports, + ForElseBreaksReturnFalse, RemoveGoCallReturns, # Needs to be below scoping functions or they'll just get added back RemoveBadStmt, # Should be last as these are used for scoping MergeAdjacentInits, diff --git a/pytago/tests/test_core.py b/pytago/tests/test_core.py index 600db6c..7d5d85d 100644 --- a/pytago/tests/test_core.py +++ b/pytago/tests/test_core.py @@ -226,6 +226,9 @@ def test_stringmultiply(self): def test_scope(self): self.assert_examples_match("scope") + def test_forelse(self): + self.assert_examples_match("forelse") + # Algorithms def test_algomajorityelement(self): self.assert_examples_match("algomajorityelement") @@ -238,8 +241,6 @@ def test_algointersection(self): # In development - # def test_forelse(self): - # self.assert_examples_match("forelse") # # def test_iterunpacking(self): # self.assert_examples_match("iterunpacking") diff --git a/scripts/generate_readme.py b/scripts/generate_readme.py index 038a33e..b6ad650 100644 --- a/scripts/generate_readme.py +++ b/scripts/generate_readme.py @@ -10,7 +10,6 @@ TEST_FILE_PATH = rel + "../pytago/tests/test_core.py" DISABLED_EXAMPLES = { - "forelse", "iterunpacking", "dunders", "pop", diff --git a/setup.py b/setup.py index 8b950e7..e3bf8f6 100644 --- a/setup.py +++ b/setup.py @@ -5,7 +5,7 @@ setup( name='pytago', - version='0.0.11', + version='0.0.12', packages=['pytago', 'pytago.go_ast'], url='https://github.com/nottheswimmer/pytago', license='',