From 77da54b23548c41be5ba50980206667c1cae3c65 Mon Sep 17 00:00:00 2001 From: juliawgraham Date: Sun, 4 Dec 2022 15:11:12 -0500 Subject: [PATCH 01/29] wrote a basic test that calls visit_Match --- src/latexify/codegen/function_codegen.py | 8 ++++++++ src/latexify/codegen/function_codegen_test.py | 19 +++++++++++++++++++ 2 files changed, 27 insertions(+) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 33f8df0..0e68c75 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -536,6 +536,14 @@ def visit_IfExp(self, node: ast.IfExp) -> str: latex += self.visit(node) return latex + r", & \mathrm{otherwise} \end{array} \right." + def visit_Match(self, node: ast.Match) -> str: + print(ast.dump(node)) + return "Match" + + def visit_MatchValue(self, node: ast.MatchValue) -> str: + print(ast.dump(node)) + return "MatchValue" + def _reduce_stop_parameter(self, node: ast.BinOp) -> str: # ast.Constant class is added in Python 3.8 # ast.Num is the relevant node type in previous versions diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 793ab62..51a1b86 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -10,6 +10,25 @@ from latexify import exceptions, test_utils from latexify.codegen import FunctionCodegen, function_codegen +def test_matchvalue() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\mathrm{f}(x) = x" + + + + + + + def test_generic_visit() -> None: class UnknownNode(ast.AST): From c213d0f30e8913176d92afa97616f9510c18e789 Mon Sep 17 00:00:00 2001 From: juliawgraham Date: Sun, 4 Dec 2022 15:22:58 -0500 Subject: [PATCH 02/29] added extra space to publish branch --- src/latexify/codegen/function_codegen_test.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 51a1b86..4d2c620 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -30,6 +30,7 @@ def test_matchvalue() -> None: + def test_generic_visit() -> None: class UnknownNode(ast.AST): pass From 73425a6e4c3db4e0920514097bd601ccef203032 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 4 Dec 2022 16:46:37 -0500 Subject: [PATCH 03/29] implementation for MatchValue --- src/latexify/codegen/function_codegen.py | 26 +++++++-- src/latexify/codegen/function_codegen_test.py | 53 +++++++++++-------- 2 files changed, 53 insertions(+), 26 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 0e68c75..8e69122 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -537,13 +537,29 @@ def visit_IfExp(self, node: ast.IfExp) -> str: return latex + r", & \mathrm{otherwise} \end{array} \right." def visit_Match(self, node: ast.Match) -> str: - print(ast.dump(node)) - return "Match" + """Visit a match node""" + latex = r"\left\{ \begin{array}{ll} " + subject_latex = self.visit(node.subject) + for match_case in node.cases: + true_latex, cond_latex = self.visit(match_case) + if cond_latex: + latex += true_latex + r", & \mathrm{if} \ " + subject_latex + cond_latex + r" \\ " + else: + latex += true_latex + r", & \mathrm{otherwise}" + latex += r"\end{array} \right." + return latex + def visit_match_case(self, node: ast.match_case) -> str: + """Visit a match_case node""" + cond_latex = self.visit(node.pattern) + true_latex = self.visit(node.body[0]) + return true_latex, cond_latex + def visit_MatchValue(self, node: ast.MatchValue) -> str: - print(ast.dump(node)) - return "MatchValue" - + """Visit a MatchValue node""" + latex = self.visit(node.value) + return r" = " + latex + def _reduce_stop_parameter(self, node: ast.BinOp) -> str: # ast.Constant class is added in Python 3.8 # ast.Num is the relevant node type in previous versions diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 4d2c620..4fbc570 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -10,27 +10,6 @@ from latexify import exceptions, test_utils from latexify.codegen import FunctionCodegen, function_codegen -def test_matchvalue() -> None: - tree = ast.parse( - textwrap.dedent( - """ - match x: - case 0: - return 1 - """ - ) - ).body[0] - - assert FunctionCodegen().visit(tree) == r"\mathrm{f}(x) = x" - - - - - - - - - def test_generic_visit() -> None: class UnknownNode(ast.AST): pass @@ -764,3 +743,35 @@ def test_use_set_symbols_compare(code: str, latex: str) -> None: tree = ast.parse(code).body[0].value assert isinstance(tree, ast.Compare) assert function_codegen.FunctionCodegen(use_set_symbols=True).visit(tree) == latex + + +def test_matchvalue() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ \end{array} \right." + + +def test_multiple_matchvalue() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + case 1: + return 2 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ \end{array} \right." + + From adb9a50e96f1ff340556062a84f31f24b3a340e1 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 4 Dec 2022 16:49:40 -0500 Subject: [PATCH 04/29] partial implementation for MatchAs, only implemented for the wildcard case --- src/latexify/codegen/function_codegen.py | 10 ++++++ src/latexify/codegen/function_codegen_test.py | 34 +++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 8e69122..5cda033 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -559,6 +559,16 @@ def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node""" latex = self.visit(node.value) return r" = " + latex + + def visit_MatchAs(self, node: ast.MatchAs) -> str: + """Visit a MatchAs node""" + """If MatchAs is a wildcard, return 'otherwise' case, else throw error""" + if not(node.pattern): + return '' + else: + raise exceptions.LatexifySyntaxError( + "Nonempty as-patterns are not supported in MatchAs nodes." + ) def _reduce_stop_parameter(self, node: ast.BinOp) -> str: # ast.Constant class is added in Python 3.8 diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 4fbc570..ab2d92a 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -775,3 +775,37 @@ def test_multiple_matchvalue() -> None: assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ \end{array} \right." +def test_matchvalue_matchas() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." + + +def test_matchvalue_matchas() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + case [x] as y: + return 2 + """ + ) + ).body[0] + + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Nonempty as-patterns are not supported in MatchAs nodes.", + ): + FunctionCodegen().visit(tree) \ No newline at end of file From f64490e01fd95edba7c324f9cfa31015ba9ca20f Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 4 Dec 2022 17:02:36 -0500 Subject: [PATCH 05/29] changed test names for matchas tests --- src/latexify/codegen/function_codegen_test.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index ab2d92a..85f6a75 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -775,7 +775,7 @@ def test_multiple_matchvalue() -> None: assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ \end{array} \right." -def test_matchvalue_matchas() -> None: +def test_matchvalue_matchas_wildcard() -> None: tree = ast.parse( textwrap.dedent( """ @@ -791,7 +791,7 @@ def test_matchvalue_matchas() -> None: assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." -def test_matchvalue_matchas() -> None: +def test_matchvalue_matchas_nonempty() -> None: tree = ast.parse( textwrap.dedent( """ From 4aa11952dac87c82b3b87b510985b1c53030b5f0 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Tue, 6 Dec 2022 23:13:01 -0500 Subject: [PATCH 06/29] 1. merged visit match_case to visit Match; 2. added wildcards error handling; 3. added more unit tests (with juliawgraham) --- src/latexify/codegen/function_codegen.py | 24 +++--- src/latexify/codegen/function_codegen_test.py | 76 +++++++++++++++++-- 2 files changed, 83 insertions(+), 17 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 5cda033..fd34bd4 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -540,20 +540,24 @@ def visit_Match(self, node: ast.Match) -> str: """Visit a match node""" latex = r"\left\{ \begin{array}{ll} " subject_latex = self.visit(node.subject) - for match_case in node.cases: - true_latex, cond_latex = self.visit(match_case) - if cond_latex: + for i, match_case in enumerate(node.cases): + true_latex = self.visit(match_case.body[0]) + cond_latex = self.visit(match_case.pattern) + + if i < len(node.cases)-1: # no wildcard + if not cond_latex: + raise exceptions.LatexifySyntaxError( + "Match subtrees must contain only one wildcard at the end." + ) latex += true_latex + r", & \mathrm{if} \ " + subject_latex + cond_latex + r" \\ " - else: + else: + if cond_latex: + raise exceptions.LatexifySyntaxError( + "Match subtrees must contain only one wildcard at the end." + ) latex += true_latex + r", & \mathrm{otherwise}" latex += r"\end{array} \right." return latex - - def visit_match_case(self, node: ast.match_case) -> str: - """Visit a match_case node""" - cond_latex = self.visit(node.pattern) - true_latex = self.visit(node.body[0]) - return true_latex, cond_latex def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node""" diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 85f6a75..629e460 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -752,12 +752,13 @@ def test_matchvalue() -> None: match x: case 0: return 1 + case _: + return 2 """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ \end{array} \right." - + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." def test_multiple_matchvalue() -> None: tree = ast.parse( @@ -768,14 +769,51 @@ def test_multiple_matchvalue() -> None: return 1 case 1: return 2 + case _: + return 3 """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ \end{array} \right." + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ {3}, & \mathrm{otherwise}\end{array} \right." + +def test_single_matchvalue_no_wildcards() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + """ + ) + ).body[0] + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) -def test_matchvalue_matchas_wildcard() -> None: +def test_multiple_matchvalue_no_wildcards() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + case 1: + return 2 + """ + ) + ).body[0] + + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) + +def test_multiple_matchas_wildcards() -> None: tree = ast.parse( textwrap.dedent( """ @@ -784,14 +822,38 @@ def test_matchvalue_matchas_wildcard() -> None: return 1 case _: return 2 + case _: + return 3 """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) +def test_matchas_nonempty() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case [x] as y: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Nonempty as-patterns are not supported in MatchAs nodes.", + ): + FunctionCodegen().visit(tree) -def test_matchvalue_matchas_nonempty() -> None: +def test_matchas_nonempty_end() -> None: tree = ast.parse( textwrap.dedent( """ @@ -808,4 +870,4 @@ def test_matchvalue_matchas_nonempty() -> None: exceptions.LatexifySyntaxError, match=r"Nonempty as-patterns are not supported in MatchAs nodes.", ): - FunctionCodegen().visit(tree) \ No newline at end of file + FunctionCodegen().visit(tree) From 607c3595ddc77dbb5d971a43afdcd440571462b0 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Tue, 6 Dec 2022 23:13:01 -0500 Subject: [PATCH 07/29] 1. removed unused i; 2. added error handling for multiple statements in Match node; 3. added corresponding unit tests Co-authored-by: Lucybean-hi Co-authored-by: juliawgraham Co-authored-by: Erica Fu --- src/latexify/codegen/function_codegen.py | 31 +++--- src/latexify/codegen/function_codegen_test.py | 96 +++++++++++++++++-- 2 files changed, 109 insertions(+), 18 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 5cda033..e88a4c9 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -540,25 +540,34 @@ def visit_Match(self, node: ast.Match) -> str: """Visit a match node""" latex = r"\left\{ \begin{array}{ll} " subject_latex = self.visit(node.subject) - for match_case in node.cases: - true_latex, cond_latex = self.visit(match_case) - if cond_latex: + for i, match_case in enumerate(node.cases): + if len(match_case.body) != 1: + raise exceptions.LatexifySyntaxError( + "Multiple statements are not supported in Match nodes." + ) + + true_latex = self.visit(match_case.body[0]) + cond_latex = self.visit(match_case.pattern) + + if i < len(node.cases)-1: # no wildcard + if not cond_latex: + raise exceptions.LatexifySyntaxError( + "Match subtrees must contain only one wildcard at the end." + ) latex += true_latex + r", & \mathrm{if} \ " + subject_latex + cond_latex + r" \\ " - else: + else: + if cond_latex: + raise exceptions.LatexifySyntaxError( + "Match subtrees must contain only one wildcard at the end." + ) latex += true_latex + r", & \mathrm{otherwise}" latex += r"\end{array} \right." return latex - - def visit_match_case(self, node: ast.match_case) -> str: - """Visit a match_case node""" - cond_latex = self.visit(node.pattern) - true_latex = self.visit(node.body[0]) - return true_latex, cond_latex def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node""" latex = self.visit(node.value) - return r" = " + latex + return " = " + latex def visit_MatchAs(self, node: ast.MatchAs) -> str: """Visit a MatchAs node""" diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 85f6a75..144d6db 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -752,12 +752,13 @@ def test_matchvalue() -> None: match x: case 0: return 1 + case _: + return 2 """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ \end{array} \right." - + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." def test_multiple_matchvalue() -> None: tree = ast.parse( @@ -768,14 +769,51 @@ def test_multiple_matchvalue() -> None: return 1 case 1: return 2 + case _: + return 3 """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ \end{array} \right." + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ {3}, & \mathrm{otherwise}\end{array} \right." + +def test_single_matchvalue_no_wildcards() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + """ + ) + ).body[0] + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) -def test_matchvalue_matchas_wildcard() -> None: +def test_multiple_matchvalue_no_wildcards() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + return 1 + case 1: + return 2 + """ + ) + ).body[0] + + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) + +def test_multiple_matchas_wildcards() -> None: tree = ast.parse( textwrap.dedent( """ @@ -784,14 +822,38 @@ def test_matchvalue_matchas_wildcard() -> None: return 1 case _: return 2 + case _: + return 3 """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) +def test_matchas_nonempty() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case [x] as y: + return 1 + case _: + return 2 + """ + ) + ).body[0] -def test_matchvalue_matchas_nonempty() -> None: + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Nonempty as-patterns are not supported in MatchAs nodes.", + ): + FunctionCodegen().visit(tree) + +def test_matchas_nonempty_end() -> None: tree = ast.parse( textwrap.dedent( """ @@ -808,4 +870,24 @@ def test_matchvalue_matchas_nonempty() -> None: exceptions.LatexifySyntaxError, match=r"Nonempty as-patterns are not supported in MatchAs nodes.", ): - FunctionCodegen().visit(tree) \ No newline at end of file + FunctionCodegen().visit(tree) + +def test_matchvalue_mutliple_statements() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0: + x = 5 + return 1 + case 1: + return 2 + """ + ) + ).body[0] + + with pytest.raises( + exceptions.LatexifySyntaxError, + match=r"Multiple statements are not supported in Match nodes.", + ): + FunctionCodegen().visit(tree) \ No newline at end of file From 33f74390f51b7efcedcfd476bc0754c5b41addc0 Mon Sep 17 00:00:00 2001 From: erica-w-fu Date: Wed, 7 Dec 2022 14:38:07 -0500 Subject: [PATCH 08/29] created a function visit_MatchOr, still needs to be implemented --- src/latexify/codegen/function_codegen.py | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index fd34bd4..e03efcb 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -574,6 +574,11 @@ def visit_MatchAs(self, node: ast.MatchAs) -> str: "Nonempty as-patterns are not supported in MatchAs nodes." ) + def visit_MatchOr(self, node: ast.MatchOr) -> str: + """Visit a MatchOr node""" + # still need to implement + return + def _reduce_stop_parameter(self, node: ast.BinOp) -> str: # ast.Constant class is added in Python 3.8 # ast.Num is the relevant node type in previous versions From a57b6a8dcca49a94033e93e254e1eb854590b5c9 Mon Sep 17 00:00:00 2001 From: juliawgraham Date: Thu, 8 Dec 2022 21:36:27 -0500 Subject: [PATCH 09/29] updated matchcase to accept inequalities --- src/latexify/codegen/function_codegen.py | 3 +++ src/latexify/codegen/function_codegen_test.py | 17 ++++++++++++++++- 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index e88a4c9..7c91cc2 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -550,6 +550,9 @@ def visit_Match(self, node: ast.Match) -> str: cond_latex = self.visit(match_case.pattern) if i < len(node.cases)-1: # no wildcard + if (match_case.guard): + cond_latex = self.visit(match_case.guard) + subject_latex = "" # getting 'x' from cond_latex if not cond_latex: raise exceptions.LatexifySyntaxError( "Match subtrees must contain only one wildcard at the end." diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 144d6db..9aba70e 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -890,4 +890,19 @@ def test_matchvalue_mutliple_statements() -> None: exceptions.LatexifySyntaxError, match=r"Multiple statements are not supported in Match nodes.", ): - FunctionCodegen().visit(tree) \ No newline at end of file + FunctionCodegen().visit(tree) + +def test_matchcase_with_guard() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if x>0: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {x > {0}} \\ {2}, & \mathrm{otherwise}\end{array} \right." From e1d90f99bc906da1568e921e97b71bbf2cce8bec Mon Sep 17 00:00:00 2001 From: juliawgraham Date: Thu, 8 Dec 2022 21:55:20 -0500 Subject: [PATCH 10/29] added support for and/or in guard --- src/latexify/codegen/function_codegen.py | 2 +- src/latexify/codegen/function_codegen_test.py | 45 +++++++++++++++++++ 2 files changed, 46 insertions(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 7c91cc2..c1bf68f 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -552,7 +552,7 @@ def visit_Match(self, node: ast.Match) -> str: if i < len(node.cases)-1: # no wildcard if (match_case.guard): cond_latex = self.visit(match_case.guard) - subject_latex = "" # getting 'x' from cond_latex + subject_latex = "" # getting variable from cond_latex if not cond_latex: raise exceptions.LatexifySyntaxError( "Match subtrees must contain only one wildcard at the end." diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 9aba70e..66c554b 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -906,3 +906,48 @@ def test_matchcase_with_guard() -> None: ).body[0] assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {x > {0}} \\ {2}, & \mathrm{otherwise}\end{array} \right." + +def test_matchcase_with_and_guard() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if x>0 and x<=10: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {{x > {0}} \land {x \le {10}}} \\ {2}, & \mathrm{otherwise}\end{array} \right." + +def test_matchcase_with_or_guard() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if x>0 or x<=10: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {{x > {0}} \lor {x \le {10}}} \\ {2}, & \mathrm{otherwise}\end{array} \right." + +def test_matchcase_with_multiple_guards() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if 0 < x <= 10: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {{0} < x \le {10}} \\ {2}, & \mathrm{otherwise}\end{array} \right." From 64a281c9706e8dd56594f339f59d587e62f11b17 Mon Sep 17 00:00:00 2001 From: Ruoxi Yang Date: Thu, 8 Dec 2022 22:34:51 -0500 Subject: [PATCH 11/29] matchor implemented with tests --- src/latexify/codegen/function_codegen.py | 16 +++++++++++----- src/latexify/codegen/function_codegen_test.py | 15 +++++++++++++++ 2 files changed, 26 insertions(+), 5 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index e03efcb..06b0269 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -549,7 +549,7 @@ def visit_Match(self, node: ast.Match) -> str: raise exceptions.LatexifySyntaxError( "Match subtrees must contain only one wildcard at the end." ) - latex += true_latex + r", & \mathrm{if} \ " + subject_latex + cond_latex + r" \\ " + latex += true_latex + r", & \mathrm{if} \ " + cond_latex + r" \\ " else: if cond_latex: raise exceptions.LatexifySyntaxError( @@ -557,12 +557,13 @@ def visit_Match(self, node: ast.Match) -> str: ) latex += true_latex + r", & \mathrm{otherwise}" latex += r"\end{array} \right." - return latex + latex_final = latex.replace("subject_name", subject_latex) + return latex_final def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node""" latex = self.visit(node.value) - return r" = " + latex + return r"subject_name = " + latex def visit_MatchAs(self, node: ast.MatchAs) -> str: """Visit a MatchAs node""" @@ -576,8 +577,13 @@ def visit_MatchAs(self, node: ast.MatchAs) -> str: def visit_MatchOr(self, node: ast.MatchOr) -> str: """Visit a MatchOr node""" - # still need to implement - return + latex = "" + for i, pattern in enumerate(node.patterns): + if (i != 0): + latex += r" \lor " + self.visit(pattern) + else: + latex += self.visit(pattern) + return latex def _reduce_stop_parameter(self, node: ast.BinOp) -> str: # ast.Constant class is added in Python 3.8 diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 629e460..3f08636 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -871,3 +871,18 @@ def test_matchas_nonempty_end() -> None: match=r"Nonempty as-patterns are not supported in MatchAs nodes.", ): FunctionCodegen().visit(tree) + +def test_matchor() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0 | 1: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \lor x = {1} \\ {2}, & \mathrm{otherwise}\end{array} \right." \ No newline at end of file From 2dbcf03bee08d16229020567e7dd49973188b5b7 Mon Sep 17 00:00:00 2001 From: erica-w-fu Date: Thu, 8 Dec 2022 22:59:54 -0500 Subject: [PATCH 12/29] added requirements for python version 3.10 for all test cases with match --- src/latexify/codegen/function_codegen_test.py | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 3f08636..579ff06 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -745,6 +745,7 @@ def test_use_set_symbols_compare(code: str, latex: str) -> None: assert function_codegen.FunctionCodegen(use_set_symbols=True).visit(tree) == latex +@test_utils.require_at_least(10) def test_matchvalue() -> None: tree = ast.parse( textwrap.dedent( @@ -760,6 +761,8 @@ def test_matchvalue() -> None: assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." + +@test_utils.require_at_least(10) def test_multiple_matchvalue() -> None: tree = ast.parse( textwrap.dedent( @@ -777,6 +780,8 @@ def test_multiple_matchvalue() -> None: assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ {3}, & \mathrm{otherwise}\end{array} \right." + +@test_utils.require_at_least(10) def test_single_matchvalue_no_wildcards() -> None: tree = ast.parse( textwrap.dedent( @@ -794,6 +799,8 @@ def test_single_matchvalue_no_wildcards() -> None: ): FunctionCodegen().visit(tree) + +@test_utils.require_at_least(10) def test_multiple_matchvalue_no_wildcards() -> None: tree = ast.parse( textwrap.dedent( @@ -813,6 +820,8 @@ def test_multiple_matchvalue_no_wildcards() -> None: ): FunctionCodegen().visit(tree) + +@test_utils.require_at_least(10) def test_multiple_matchas_wildcards() -> None: tree = ast.parse( textwrap.dedent( @@ -834,6 +843,8 @@ def test_multiple_matchas_wildcards() -> None: ): FunctionCodegen().visit(tree) + +@test_utils.require_at_least(10) def test_matchas_nonempty() -> None: tree = ast.parse( textwrap.dedent( @@ -853,6 +864,8 @@ def test_matchas_nonempty() -> None: ): FunctionCodegen().visit(tree) + +@test_utils.require_at_least(10) def test_matchas_nonempty_end() -> None: tree = ast.parse( textwrap.dedent( @@ -872,6 +885,8 @@ def test_matchas_nonempty_end() -> None: ): FunctionCodegen().visit(tree) + +@test_utils.require_at_least(10) def test_matchor() -> None: tree = ast.parse( textwrap.dedent( From 62a4633755d2c766892ed79bd720fe53ea6036e9 Mon Sep 17 00:00:00 2001 From: juliawgraham Date: Thu, 8 Dec 2022 23:18:38 -0500 Subject: [PATCH 13/29] updated formatting using flake/black Co-authored-by: Yuqi Co-authored-by: Erica Fu Co-authored-by: Lucybean-hi --- src/latexify/codegen/function_codegen.py | 14 +- src/latexify/codegen/function_codegen_test.py | 178 ++++++++++-------- 2 files changed, 103 insertions(+), 89 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 06b0269..2d6355d 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -539,18 +539,18 @@ def visit_IfExp(self, node: ast.IfExp) -> str: def visit_Match(self, node: ast.Match) -> str: """Visit a match node""" latex = r"\left\{ \begin{array}{ll} " - subject_latex = self.visit(node.subject) + subject_latex = self.visit(node.subject) for i, match_case in enumerate(node.cases): true_latex = self.visit(match_case.body[0]) cond_latex = self.visit(match_case.pattern) - if i < len(node.cases)-1: # no wildcard + if i < len(node.cases) - 1: # no wildcard if not cond_latex: raise exceptions.LatexifySyntaxError( "Match subtrees must contain only one wildcard at the end." ) latex += true_latex + r", & \mathrm{if} \ " + cond_latex + r" \\ " - else: + else: if cond_latex: raise exceptions.LatexifySyntaxError( "Match subtrees must contain only one wildcard at the end." @@ -564,12 +564,12 @@ def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node""" latex = self.visit(node.value) return r"subject_name = " + latex - + def visit_MatchAs(self, node: ast.MatchAs) -> str: """Visit a MatchAs node""" """If MatchAs is a wildcard, return 'otherwise' case, else throw error""" - if not(node.pattern): - return '' + if not (node.pattern): + return "" else: raise exceptions.LatexifySyntaxError( "Nonempty as-patterns are not supported in MatchAs nodes." @@ -579,7 +579,7 @@ def visit_MatchOr(self, node: ast.MatchOr) -> str: """Visit a MatchOr node""" latex = "" for i, pattern in enumerate(node.patterns): - if (i != 0): + if i != 0: latex += r" \lor " + self.visit(pattern) else: latex += self.visit(pattern) diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 579ff06..2660ad8 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -10,6 +10,7 @@ from latexify import exceptions, test_utils from latexify.codegen import FunctionCodegen, function_codegen + def test_generic_visit() -> None: class UnknownNode(ast.AST): pass @@ -749,155 +750,168 @@ def test_use_set_symbols_compare(code: str, latex: str) -> None: def test_matchvalue() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0: - return 1 - case _: - return 2 - """ + """ + match x: + case 0: + return 1 + case _: + return 2 + """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{otherwise}\end{array} \right." + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ " + + r"{2}, & \mathrm{otherwise}\end{array} \right." + ) @test_utils.require_at_least(10) def test_multiple_matchvalue() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0: - return 1 - case 1: - return 2 - case _: - return 3 - """ + """ + match x: + case 0: + return 1 + case 1: + return 2 + case _: + return 3 + """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ {2}, & \mathrm{if} \ x = {1} \\ {3}, & \mathrm{otherwise}\end{array} \right." + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \\ " + + r"{2}, & \mathrm{if} \ x = {1} \\ " + + r"{3}, & \mathrm{otherwise}\end{array} \right." + ) @test_utils.require_at_least(10) def test_single_matchvalue_no_wildcards() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0: - return 1 - """ + """ + match x: + case 0: + return 1 + """ ) ).body[0] with pytest.raises( - exceptions.LatexifySyntaxError, - match=r"Match subtrees must contain only one wildcard at the end.", - ): - FunctionCodegen().visit(tree) + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) @test_utils.require_at_least(10) def test_multiple_matchvalue_no_wildcards() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0: - return 1 - case 1: - return 2 - """ + """ + match x: + case 0: + return 1 + case 1: + return 2 + """ ) ).body[0] with pytest.raises( - exceptions.LatexifySyntaxError, - match=r"Match subtrees must contain only one wildcard at the end.", - ): - FunctionCodegen().visit(tree) + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) @test_utils.require_at_least(10) def test_multiple_matchas_wildcards() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0: - return 1 - case _: - return 2 - case _: - return 3 - """ + """ + match x: + case 0: + return 1 + case _: + return 2 + case _: + return 3 + """ ) ).body[0] with pytest.raises( - exceptions.LatexifySyntaxError, - match=r"Match subtrees must contain only one wildcard at the end.", - ): - FunctionCodegen().visit(tree) + exceptions.LatexifySyntaxError, + match=r"Match subtrees must contain only one wildcard at the end.", + ): + FunctionCodegen().visit(tree) @test_utils.require_at_least(10) def test_matchas_nonempty() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case [x] as y: - return 1 - case _: - return 2 - """ + """ + match x: + case [x] as y: + return 1 + case _: + return 2 + """ ) ).body[0] with pytest.raises( - exceptions.LatexifySyntaxError, - match=r"Nonempty as-patterns are not supported in MatchAs nodes.", - ): - FunctionCodegen().visit(tree) + exceptions.LatexifySyntaxError, + match=r"Nonempty as-patterns are not supported in MatchAs nodes.", + ): + FunctionCodegen().visit(tree) @test_utils.require_at_least(10) def test_matchas_nonempty_end() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0: - return 1 - case [x] as y: - return 2 - """ + """ + match x: + case 0: + return 1 + case [x] as y: + return 2 + """ ) ).body[0] with pytest.raises( - exceptions.LatexifySyntaxError, - match=r"Nonempty as-patterns are not supported in MatchAs nodes.", - ): - FunctionCodegen().visit(tree) + exceptions.LatexifySyntaxError, + match=r"Nonempty as-patterns are not supported in MatchAs nodes.", + ): + FunctionCodegen().visit(tree) + - @test_utils.require_at_least(10) def test_matchor() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0 | 1: - return 1 - case _: - return 2 - """ + """ + match x: + case 0 | 1: + return 1 + case _: + return 2 + """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \lor x = {1} \\ {2}, & \mathrm{otherwise}\end{array} \right." \ No newline at end of file + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \lor x = {1} \\" + + r" {2}, & \mathrm{otherwise}\end{array} \right." + ) From 5b6e676ccdccf79119a713269ecbbc541c99d8c6 Mon Sep 17 00:00:00 2001 From: juliawgraham Date: Thu, 8 Dec 2022 23:40:40 -0500 Subject: [PATCH 14/29] updated syntax according to flake/black (after merging match_case guards and matchor) Co-authored-by: Yuqi Co-authored-by: Erica Fu Co-authored-by: Lucybean-hi --- src/latexify/codegen/function_codegen.py | 8 +- src/latexify/codegen/function_codegen_test.py | 130 ++++++++++-------- 2 files changed, 73 insertions(+), 65 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index e0fdcc5..1335ed1 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -548,10 +548,10 @@ def visit_Match(self, node: ast.Match) -> str: true_latex = self.visit(match_case.body[0]) cond_latex = self.visit(match_case.pattern) - if i < len(node.cases)-1: # no wildcard - if (match_case.guard): + if i < len(node.cases) - 1: # no wildcard cases + if match_case.guard: cond_latex = self.visit(match_case.guard) - subject_latex = "" # getting variable from cond_latex + subject_latex = "" # getting variable from cond_latex if not cond_latex: raise exceptions.LatexifySyntaxError( "Match subtrees must contain only one wildcard at the end." @@ -572,7 +572,7 @@ def visit_MatchValue(self, node: ast.MatchValue) -> str: latex = self.visit(node.value) return "subject_name = " + latex - + def visit_MatchAs(self, node: ast.MatchAs) -> str: """Visit a MatchAs node""" """If MatchAs is a wildcard, return 'otherwise' case, else throw error""" diff --git a/src/latexify/codegen/function_codegen_test.py b/src/latexify/codegen/function_codegen_test.py index 064b7be..ab00b6a 100644 --- a/src/latexify/codegen/function_codegen_test.py +++ b/src/latexify/codegen/function_codegen_test.py @@ -831,26 +831,6 @@ def test_multiple_matchvalue_no_wildcards() -> None: FunctionCodegen().visit(tree) -def test_multiple_matchvalue_no_wildcards() -> None: - tree = ast.parse( - textwrap.dedent( - """ - match x: - case 0: - return 1 - case 1: - return 2 - """ - ) - ).body[0] - - with pytest.raises( - exceptions.LatexifySyntaxError, - match=r"Match subtrees must contain only one wildcard at the end.", - ): - FunctionCodegen().visit(tree) - - @test_utils.require_at_least(10) def test_multiple_matchas_wildcards() -> None: tree = ast.parse( @@ -916,17 +896,18 @@ def test_matchas_nonempty_end() -> None: FunctionCodegen().visit(tree) +@test_utils.require_at_least(10) def test_matchvalue_mutliple_statements() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case 0: - x = 5 - return 1 - case 1: - return 2 - """ + """ + match x: + case 0: + x = 5 + return 1 + case 1: + return 2 + """ ) ).body[0] @@ -936,68 +917,95 @@ def test_matchvalue_mutliple_statements() -> None: ): FunctionCodegen().visit(tree) + +@test_utils.require_at_least(10) def test_matchcase_with_guard() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case x if x>0: - return 1 - case _: - return 2 - """ + """ + match x: + case x if x>0: + return 1 + case _: + return 2 + """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {x > {0}} \\ {2}, & \mathrm{otherwise}\end{array} \right." + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} " + + r"{1}, & \mathrm{if} \ {x > {0}} \\ " + + r"{2}, & \mathrm{otherwise}\end{array} \right." + ) + +@test_utils.require_at_least(10) def test_matchcase_with_and_guard() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case x if x>0 and x<=10: - return 1 - case _: - return 2 - """ + """ + match x: + case x if x>0 and x<=10: + return 1 + case _: + return 2 + """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {{x > {0}} \land {x \le {10}}} \\ {2}, & \mathrm{otherwise}\end{array} \right." + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} " + + r"\ {{x > {0}} \land {x \le {10}}} \\ " + + r"{2}, & \mathrm{otherwise}\end{array} \right." + ) + +@test_utils.require_at_least(10) def test_matchcase_with_or_guard() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case x if x>0 or x<=10: - return 1 - case _: - return 2 - """ + """ + match x: + case x if x>0 or x<=10: + return 1 + case _: + return 2 + """ ) ).body[0] - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {{x > {0}} \lor {x \le {10}}} \\ {2}, & \mathrm{otherwise}\end{array} \right." + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, " + + r"& \mathrm{if} \ {{x > {0}} \lor {x \le {10}}} \\ " + + r"{2}, & \mathrm{otherwise}\end{array} \right." + ) + +@test_utils.require_at_least(10) def test_matchcase_with_multiple_guards() -> None: tree = ast.parse( textwrap.dedent( - """ - match x: - case x if 0 < x <= 10: - return 1 - case _: - return 2 - """ + """ + match x: + case x if 0 < x <= 10: + return 1 + case _: + return 2 + """ ) ).body[0] + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, " + + r"& \mathrm{if} \ {{0} < x \le {10}} \\ {2}, " + + r"& \mathrm{otherwise}\end{array} \right." + ) - assert FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ {{0} < x \le {10}} \\ {2}, & \mathrm{otherwise}\end{array} \right." - @test_utils.require_at_least(10) def test_matchor() -> None: tree = ast.parse( From c04769621de4e31d4a9d009e2cf790bed44a536a Mon Sep 17 00:00:00 2001 From: juliawgraham <89917126+juliawgraham@users.noreply.github.com> Date: Sun, 11 Dec 2022 15:21:06 -0500 Subject: [PATCH 15/29] Moved matchor / match guard test cases into function_codegen_match_test --- .../codegen/function_codegen_match_test.py | 109 ++++++++++++++++++ 1 file changed, 109 insertions(+) diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index 87594ec..5dd29cf 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -184,3 +184,112 @@ def test_matchvalue_mutliple_statements() -> None: match=r"^Match cases must contain exactly 1 return statement\.$", ): function_codegen.FunctionCodegen().visit(tree) + + +@test_utils.require_at_least(10) +def test_matchcase_with_guard() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if x>0: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} " + + r"{1}, & \mathrm{if} \ {x > {0}} \\ " + + r"{2}, & \mathrm{otherwise}\end{array} \right." + ) + + +@test_utils.require_at_least(10) +def test_matchcase_with_and_guard() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if x>0 and x<=10: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} " + + r"\ {{x > {0}} \land {x \le {10}}} \\ " + + r"{2}, & \mathrm{otherwise}\end{array} \right." + ) + + +@test_utils.require_at_least(10) +def test_matchcase_with_or_guard() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if x>0 or x<=10: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, " + + r"& \mathrm{if} \ {{x > {0}} \lor {x \le {10}}} \\ " + + r"{2}, & \mathrm{otherwise}\end{array} \right." + ) + + +@test_utils.require_at_least(10) +def test_matchcase_with_multiple_guards() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case x if 0 < x <= 10: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, " + + r"& \mathrm{if} \ {{0} < x \le {10}} \\ {2}, " + + r"& \mathrm{otherwise}\end{array} \right." + ) + + +@test_utils.require_at_least(10) +def test_matchor() -> None: + tree = ast.parse( + textwrap.dedent( + """ + match x: + case 0 | 1: + return 1 + case _: + return 2 + """ + ) + ).body[0] + + assert ( + FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \lor x = {1} \\" + + r" {2}, & \mathrm{otherwise}\end{array} \right." + ) From fc4eca9efb14403e546b1ee51c2d36e077628936 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 11 Dec 2022 15:58:59 -0500 Subject: [PATCH 16/29] updated everything so it's consistent with main --- src/latexify/codegen/function_codegen.py | 105 ++++++++++++++---- .../codegen/function_codegen_match_test.py | 40 +++---- 2 files changed, 101 insertions(+), 44 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 5a86fde..61aa86d 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -139,40 +139,97 @@ def visit_If(self, node: ast.If) -> str: latex += self.visit(current_stmt) return latex + r", & \mathrm{otherwise} \end{array} \right." +######### + +# def visit_Match(self, node: ast.Match) -> str: +# """Visit a Match node""" +# if not ( +# len(node.cases) >= 2 +# and isinstance(node.cases[-1].pattern, ast.MatchAs) +# and node.cases[-1].pattern.name is None +# ): +# raise exceptions.LatexifySyntaxError( +# "Match statement must contain the wildcard." +# ) + +# subject_latex = self._expression_codegen.visit(node.subject) +# case_latexes: list[str] = [] + +# for i, case in enumerate(node.cases): +# if len(case.body) != 1 or not isinstance(case.body[0], ast.Return): +# raise exceptions.LatexifyNotSupportedError( +# "Match cases must contain exactly 1 return statement." +# ) + +# if i < len(node.cases) - 1: +# body_latex = self.visit(case.body[0]) +# cond_latex = self.visit(case.pattern) +# case_latexes.append( +# body_latex + r", & \mathrm{if} \ " + subject_latex + cond_latex +# ) +# else: +# case_latexes.append( +# self.visit(node.cases[-1].body[0]) + r", & \mathrm{otherwise}" +# ) + +# return ( +# r"\left\{ \begin{array}{ll} " +# + r" \\ ".join(case_latexes) +# + r" \end{array} \right." +# ) + +# def visit_MatchValue(self, node: ast.MatchValue) -> str: +# """Visit a MatchValue node""" +# latex = self._expression_codegen.visit(node.value) +# return " = " + latex + +############ def visit_Match(self, node: ast.Match) -> str: """Visit a Match node""" - latex = r"\left\{ \begin{array}{ll} " - subject_latex = self.visit(node.subject) - for i, match_case in enumerate(node.cases): - if len(match_case.body) != 1: - raise exceptions.LatexifySyntaxError( - "Multiple statements are not supported in Match nodes." + if not ( + len(node.cases) >= 2 + and isinstance(node.cases[-1].pattern, ast.MatchAs) + and node.cases[-1].pattern.name is None + ): + raise exceptions.LatexifySyntaxError( + "Match statement must contain the wildcard." + ) + + + subject_latex = self._expression_codegen.visit(node.subject) + case_latexes: list[str] = [] + + for i, case in enumerate(node.cases): + if len(case.body) != 1 or not isinstance(case.body[0], ast.Return): + raise exceptions.LatexifyNotSupportedError( + "Match cases must contain exactly 1 return statement." ) - true_latex = self.visit(match_case.body[0]) - cond_latex = self.visit(match_case.pattern) - if i < len(node.cases) - 1: # no wildcard cases - if match_case.guard: - cond_latex = self.visit(match_case.guard) + if i < len(node.cases) - 1: + body_latex = self.visit(case.body[0]) + cond_latex = self.visit(case.pattern) + if case.guard: + cond_latex = self._expression_codegen.visit(case.guard) subject_latex = "" # getting variable from cond_latex - if not cond_latex: - raise exceptions.LatexifySyntaxError( - "Match subtrees must contain only one wildcard at the end." - ) - latex += true_latex + r", & \mathrm{if} \ " + cond_latex + r" \\ " + + case_latexes.append( + body_latex + r", & \mathrm{if} \ " + cond_latex + ) else: - if cond_latex: - raise exceptions.LatexifySyntaxError( - "Match subtrees must contain only one wildcard at the end." - ) - latex += true_latex + r", & \mathrm{otherwise}" - latex += r"\end{array} \right." - latex_final = latex.replace("subject_name", subject_latex) + case_latexes.append( + self.visit(node.cases[-1].body[0]) + r", & \mathrm{otherwise}" + ) + + latex = (r"\left\{ \begin{array}{ll} " + + r" \\ ".join(case_latexes) + + r" \end{array} \right.") + + latex_final = latex.replace("subject_name", subject_latex) return latex_final def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node""" - latex = self.visit(node.value) + latex = self._expression_codegen.visit(node.value) return "subject_name = " + latex diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index 5dd29cf..8307de6 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -137,8 +137,8 @@ def test_matchas_nonempty() -> None: ).body[0] with pytest.raises( - exceptions.LatexifyNotSupportedError, - match=r"^Unsupported AST: MatchAs$", + exceptions.LatexifySyntaxError, + match=r"^Nonempty as-patterns are not supported in MatchAs nodes.$", ): function_codegen.FunctionCodegen().visit(tree) @@ -201,10 +201,10 @@ def test_matchcase_with_guard() -> None: ).body[0] assert ( - FunctionCodegen().visit(tree) + function_codegen.FunctionCodegen().visit(tree) == r"\left\{ \begin{array}{ll} " - + r"{1}, & \mathrm{if} \ {x > {0}} \\ " - + r"{2}, & \mathrm{otherwise}\end{array} \right." + + r"1, & \mathrm{if} \ x > 0 \\ " + + r"2, & \mathrm{otherwise} \end{array} \right." ) @@ -223,10 +223,10 @@ def test_matchcase_with_and_guard() -> None: ).body[0] assert ( - FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} " - + r"\ {{x > {0}} \land {x \le {10}}} \\ " - + r"{2}, & \mathrm{otherwise}\end{array} \right." + function_codegen.FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} 1, & \mathrm{if} " + + r"\ x > 0 \land x \le 10 \\ " + + r"2, & \mathrm{otherwise} \end{array} \right." ) @@ -245,10 +245,10 @@ def test_matchcase_with_or_guard() -> None: ).body[0] assert ( - FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} {1}, " - + r"& \mathrm{if} \ {{x > {0}} \lor {x \le {10}}} \\ " - + r"{2}, & \mathrm{otherwise}\end{array} \right." + function_codegen.FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} 1, " + + r"& \mathrm{if} \ x > 0 \lor x \le 10 \\ " + + r"2, & \mathrm{otherwise} \end{array} \right." ) @@ -267,10 +267,10 @@ def test_matchcase_with_multiple_guards() -> None: ).body[0] assert ( - FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} {1}, " - + r"& \mathrm{if} \ {{0} < x \le {10}} \\ {2}, " - + r"& \mathrm{otherwise}\end{array} \right." + function_codegen.FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} 1, " + + r"& \mathrm{if} \ 0 < x \le 10 \\ 2, " + + r"& \mathrm{otherwise} \end{array} \right." ) @@ -289,7 +289,7 @@ def test_matchor() -> None: ).body[0] assert ( - FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} {1}, & \mathrm{if} \ x = {0} \lor x = {1} \\" - + r" {2}, & \mathrm{otherwise}\end{array} \right." + function_codegen.FunctionCodegen().visit(tree) + == r"\left\{ \begin{array}{ll} 1, & \mathrm{if} \ x = 0 \lor x = 1 \\" + + r" 2, & \mathrm{otherwise} \end{array} \right." ) From 1a1cefe0cb125bdf9adea546b1c8c7f146b36531 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 11 Dec 2022 16:30:54 -0500 Subject: [PATCH 17/29] resolved all comments on our latest PR --- src/latexify/codegen/function_codegen.py | 111 +++--------------- .../codegen/function_codegen_match_test.py | 26 ++-- 2 files changed, 27 insertions(+), 110 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 61aa86d..34683de 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -139,53 +139,8 @@ def visit_If(self, node: ast.If) -> str: latex += self.visit(current_stmt) return latex + r", & \mathrm{otherwise} \end{array} \right." -######### - -# def visit_Match(self, node: ast.Match) -> str: -# """Visit a Match node""" -# if not ( -# len(node.cases) >= 2 -# and isinstance(node.cases[-1].pattern, ast.MatchAs) -# and node.cases[-1].pattern.name is None -# ): -# raise exceptions.LatexifySyntaxError( -# "Match statement must contain the wildcard." -# ) - -# subject_latex = self._expression_codegen.visit(node.subject) -# case_latexes: list[str] = [] - -# for i, case in enumerate(node.cases): -# if len(case.body) != 1 or not isinstance(case.body[0], ast.Return): -# raise exceptions.LatexifyNotSupportedError( -# "Match cases must contain exactly 1 return statement." -# ) - -# if i < len(node.cases) - 1: -# body_latex = self.visit(case.body[0]) -# cond_latex = self.visit(case.pattern) -# case_latexes.append( -# body_latex + r", & \mathrm{if} \ " + subject_latex + cond_latex -# ) -# else: -# case_latexes.append( -# self.visit(node.cases[-1].body[0]) + r", & \mathrm{otherwise}" -# ) - -# return ( -# r"\left\{ \begin{array}{ll} " -# + r" \\ ".join(case_latexes) -# + r" \end{array} \right." -# ) - -# def visit_MatchValue(self, node: ast.MatchValue) -> str: -# """Visit a MatchValue node""" -# latex = self._expression_codegen.visit(node.value) -# return " = " + latex - -############ def visit_Match(self, node: ast.Match) -> str: - """Visit a Match node""" + """Visit a Match node.""" if not ( len(node.cases) >= 2 and isinstance(node.cases[-1].pattern, ast.MatchAs) @@ -208,7 +163,7 @@ def visit_Match(self, node: ast.Match) -> str: if i < len(node.cases) - 1: body_latex = self.visit(case.body[0]) cond_latex = self.visit(case.pattern) - if case.guard: + if case.guard is not None: cond_latex = self._expression_codegen.visit(case.guard) subject_latex = "" # getting variable from cond_latex @@ -228,14 +183,16 @@ def visit_Match(self, node: ast.Match) -> str: return latex_final def visit_MatchValue(self, node: ast.MatchValue) -> str: - """Visit a MatchValue node""" + """Visit a MatchValue node.""" latex = self._expression_codegen.visit(node.value) return "subject_name = " + latex def visit_MatchAs(self, node: ast.MatchAs) -> str: - """Visit a MatchAs node""" - """If MatchAs is a wildcard, return 'otherwise' case, else throw error""" + """ + Visit a MatchAs node. + If MatchAs is a wildcard, return 'otherwise' case, else throw error + """ if not (node.pattern): return "" else: @@ -244,53 +201,13 @@ def visit_MatchAs(self, node: ast.MatchAs) -> str: ) def visit_MatchOr(self, node: ast.MatchOr) -> str: - """Visit a MatchOr node""" - latex = "" + """Visit a MatchOr node.""" + case_latexes: list[str] = [] for i, pattern in enumerate(node.patterns): if i != 0: - latex += r" \lor " + self.visit(pattern) - else: - latex += self.visit(pattern) - return latex - - def _reduce_stop_parameter(self, node: ast.BinOp) -> str: - # ast.Constant class is added in Python 3.8 - # ast.Num is the relevant node type in previous versions - if sys.version_info.minor < 8: - if isinstance(node.right, ast.Num): - if isinstance(node.op, ast.Add): - if node.right.n == 1: - upper = "{" + self.visit(node.left) + "}" - else: - reduced_constant = ast.Num(node.right.n - 1) - new_node = ast.BinOp(node.left, node.op, reduced_constant) - upper = "{" + self.visit(new_node) + "}" - else: - if node.right.n == -1: - upper = "{" + self.visit(node.left) + "}" - else: - reduced_constant = ast.Num(node.right.n + 1) - new_node = ast.BinOp(node.left, node.op, reduced_constant) - upper = "{" + self.visit(new_node) + "}" - else: - upper = "{" + self.visit(node) + "}" - else: - if isinstance(node.right, ast.Constant): - if isinstance(node.op, ast.Add): - if node.right.value == 1: - upper = "{" + self.visit(node.left) + "}" - else: - reduced_constant = ast.Constant(node.right.value - 1) - new_node = ast.BinOp(node.left, node.op, reduced_constant) - upper = "{" + self.visit(new_node) + "}" - else: - if node.right.value == -1: - upper = "{" + self.visit(node.left) + "}" - else: - reduced_constant = ast.Constant(node.right.value + 1) - new_node = ast.BinOp(node.left, node.op, reduced_constant) - upper = "{" + self.visit(new_node) + "}" + case_latexes.append( + r" \lor " + self.visit(pattern) + ) else: - upper = "{" + self.visit(node) + "}" - - return upper + case_latexes.append(self.visit(pattern)) + return "".join(case_latexes) diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index 8307de6..b90780c 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -12,7 +12,7 @@ @test_utils.require_at_least(10) -def test_functiondef_match() -> None: +def test_visit_functiondef_match() -> None: tree = ast.parse( textwrap.dedent( """ @@ -36,7 +36,7 @@ def f(x): @test_utils.require_at_least(10) -def test_matchvalue() -> None: +def test_visit_match() -> None: tree = ast.parse( textwrap.dedent( """ @@ -58,7 +58,7 @@ def test_matchvalue() -> None: @test_utils.require_at_least(10) -def test_multiple_matchvalue() -> None: +def test_visit_multiple_match_cases() -> None: tree = ast.parse( textwrap.dedent( """ @@ -83,7 +83,7 @@ def test_multiple_matchvalue() -> None: @test_utils.require_at_least(10) -def test_single_matchvalue_no_wildcards() -> None: +def test_visit_single_match_case_no_wildcards() -> None: tree = ast.parse( textwrap.dedent( """ @@ -102,7 +102,7 @@ def test_single_matchvalue_no_wildcards() -> None: @test_utils.require_at_least(10) -def test_multiple_matchvalue_no_wildcards() -> None: +def test_visit_multiple_match_cases_no_wildcards() -> None: tree = ast.parse( textwrap.dedent( """ @@ -123,7 +123,7 @@ def test_multiple_matchvalue_no_wildcards() -> None: @test_utils.require_at_least(10) -def test_matchas_nonempty() -> None: +def test_visit_only_wildcard_in_matchas() -> None: tree = ast.parse( textwrap.dedent( """ @@ -144,7 +144,7 @@ def test_matchas_nonempty() -> None: @test_utils.require_at_least(10) -def test_matchvalue_no_return() -> None: +def test_visit_match_case_no_return() -> None: tree = ast.parse( textwrap.dedent( """ @@ -165,7 +165,7 @@ def test_matchvalue_no_return() -> None: @test_utils.require_at_least(10) -def test_matchvalue_mutliple_statements() -> None: +def test_visit_match_case_mutliple_statements() -> None: tree = ast.parse( textwrap.dedent( """ @@ -187,7 +187,7 @@ def test_matchvalue_mutliple_statements() -> None: @test_utils.require_at_least(10) -def test_matchcase_with_guard() -> None: +def test_visit_match_case_with_if() -> None: tree = ast.parse( textwrap.dedent( """ @@ -209,7 +209,7 @@ def test_matchcase_with_guard() -> None: @test_utils.require_at_least(10) -def test_matchcase_with_and_guard() -> None: +def test_visit_match_case_with_if_and() -> None: tree = ast.parse( textwrap.dedent( """ @@ -231,7 +231,7 @@ def test_matchcase_with_and_guard() -> None: @test_utils.require_at_least(10) -def test_matchcase_with_or_guard() -> None: +def test_visit_matchcase_with_if_or() -> None: tree = ast.parse( textwrap.dedent( """ @@ -253,7 +253,7 @@ def test_matchcase_with_or_guard() -> None: @test_utils.require_at_least(10) -def test_matchcase_with_multiple_guards() -> None: +def test_visit_match_case_with_combined_condition() -> None: tree = ast.parse( textwrap.dedent( """ @@ -275,7 +275,7 @@ def test_matchcase_with_multiple_guards() -> None: @test_utils.require_at_least(10) -def test_matchor() -> None: +def test_visit_match_case_or() -> None: tree = ast.parse( textwrap.dedent( """ From befd6dc6e54d229dae83983425d48407f522d489 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 11 Dec 2022 16:36:53 -0500 Subject: [PATCH 18/29] fixed all CI errors, now all tests passed --- src/latexify/codegen/function_codegen.py | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 34683de..9261a5c 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -150,7 +150,6 @@ def visit_Match(self, node: ast.Match) -> str: "Match statement must contain the wildcard." ) - subject_latex = self._expression_codegen.visit(node.subject) case_latexes: list[str] = [] @@ -167,19 +166,19 @@ def visit_Match(self, node: ast.Match) -> str: cond_latex = self._expression_codegen.visit(case.guard) subject_latex = "" # getting variable from cond_latex - case_latexes.append( - body_latex + r", & \mathrm{if} \ " + cond_latex - ) + case_latexes.append(body_latex + r", & \mathrm{if} \ " + cond_latex) else: case_latexes.append( self.visit(node.cases[-1].body[0]) + r", & \mathrm{otherwise}" ) - latex = (r"\left\{ \begin{array}{ll} " + latex = ( + r"\left\{ \begin{array}{ll} " + r" \\ ".join(case_latexes) - + r" \end{array} \right.") - - latex_final = latex.replace("subject_name", subject_latex) + + r" \end{array} \right." + ) + + latex_final = latex.replace("subject_name", subject_latex) return latex_final def visit_MatchValue(self, node: ast.MatchValue) -> str: @@ -205,9 +204,7 @@ def visit_MatchOr(self, node: ast.MatchOr) -> str: case_latexes: list[str] = [] for i, pattern in enumerate(node.patterns): if i != 0: - case_latexes.append( - r" \lor " + self.visit(pattern) - ) + case_latexes.append(r" \lor " + self.visit(pattern)) else: case_latexes.append(self.visit(pattern)) return "".join(case_latexes) From efd60f8177a0bb69c41cb182699a7be7bde9ff7e Mon Sep 17 00:00:00 2001 From: Lucybean-hi Date: Mon, 12 Dec 2022 06:07:53 +0800 Subject: [PATCH 19/29] Update src/latexify/codegen/function_codegen.py Co-authored-by: Zibing Zhang <44979059+ZibingZhang@users.noreply.github.com> --- src/latexify/codegen/function_codegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 9261a5c..c8702ce 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -188,8 +188,8 @@ def visit_MatchValue(self, node: ast.MatchValue) -> str: return "subject_name = " + latex def visit_MatchAs(self, node: ast.MatchAs) -> str: - """ - Visit a MatchAs node. + """Visit a MatchAs node. + If MatchAs is a wildcard, return 'otherwise' case, else throw error """ if not (node.pattern): From 7b672625acbe40ca07b0a915c3017f9ff09030e0 Mon Sep 17 00:00:00 2001 From: Lucybean-hi Date: Mon, 12 Dec 2022 06:08:16 +0800 Subject: [PATCH 20/29] Update src/latexify/codegen/function_codegen.py Co-authored-by: Zibing Zhang <44979059+ZibingZhang@users.noreply.github.com> --- src/latexify/codegen/function_codegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index c8702ce..5f7e939 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -190,7 +190,7 @@ def visit_MatchValue(self, node: ast.MatchValue) -> str: def visit_MatchAs(self, node: ast.MatchAs) -> str: """Visit a MatchAs node. - If MatchAs is a wildcard, return 'otherwise' case, else throw error + If MatchAs is a wildcard, return 'otherwise' case, otherwise throw an error. """ if not (node.pattern): return "" From 33327a3efca9fd379acfd65dbdc72f126deb157c Mon Sep 17 00:00:00 2001 From: Lucybean-hi Date: Mon, 12 Dec 2022 06:08:40 +0800 Subject: [PATCH 21/29] Update src/latexify/codegen/function_codegen.py Co-authored-by: Zibing Zhang <44979059+ZibingZhang@users.noreply.github.com> --- src/latexify/codegen/function_codegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 5f7e939..89ff145 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -192,7 +192,7 @@ def visit_MatchAs(self, node: ast.MatchAs) -> str: If MatchAs is a wildcard, return 'otherwise' case, otherwise throw an error. """ - if not (node.pattern): + if node.pattern is None: return "" else: raise exceptions.LatexifySyntaxError( From 587c50b78983f2d5eda05c326daef9d45755d2d8 Mon Sep 17 00:00:00 2001 From: Lucybean-hi Date: Mon, 12 Dec 2022 06:09:43 +0800 Subject: [PATCH 22/29] Update src/latexify/codegen/function_codegen.py Co-authored-by: Zibing Zhang <44979059+ZibingZhang@users.noreply.github.com> --- src/latexify/codegen/function_codegen.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 89ff145..56abd9a 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -201,7 +201,7 @@ def visit_MatchAs(self, node: ast.MatchAs) -> str: def visit_MatchOr(self, node: ast.MatchOr) -> str: """Visit a MatchOr node.""" - case_latexes: list[str] = [] + case_latexes = [] for i, pattern in enumerate(node.patterns): if i != 0: case_latexes.append(r" \lor " + self.visit(pattern)) From ee8218098002ee9eb99ba23c94f9cc85801f0b04 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 11 Dec 2022 17:43:53 -0500 Subject: [PATCH 23/29] addressed all comments --- src/latexify/codegen/function_codegen.py | 12 ++++---- .../codegen/function_codegen_match_test.py | 30 +++++++++---------- 2 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 56abd9a..fa75c44 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -164,16 +164,15 @@ def visit_Match(self, node: ast.Match) -> str: cond_latex = self.visit(case.pattern) if case.guard is not None: cond_latex = self._expression_codegen.visit(case.guard) - subject_latex = "" # getting variable from cond_latex case_latexes.append(body_latex + r", & \mathrm{if} \ " + cond_latex) else: case_latexes.append( - self.visit(node.cases[-1].body[0]) + r", & \mathrm{otherwise}" + self.visit(case.body[0]) + r", & \mathrm{otherwise}" ) latex = ( - r"\left\{ \begin{array}{ll} " + r"\left\{ \begin{array}{ll}" + r" \\ ".join(case_latexes) + r" \end{array} \right." ) @@ -184,7 +183,6 @@ def visit_Match(self, node: ast.Match) -> str: def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node.""" latex = self._expression_codegen.visit(node.value) - return "subject_name = " + latex def visit_MatchAs(self, node: ast.MatchAs) -> str: @@ -203,8 +201,8 @@ def visit_MatchOr(self, node: ast.MatchOr) -> str: """Visit a MatchOr node.""" case_latexes = [] for i, pattern in enumerate(node.patterns): - if i != 0: - case_latexes.append(r" \lor " + self.visit(pattern)) - else: + if i == 0: case_latexes.append(self.visit(pattern)) + else: + case_latexes.append(r" \lor " + self.visit(pattern)) return "".join(case_latexes) diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index b90780c..9ef3d13 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -28,7 +28,7 @@ def f(x): expected = ( r"f(x) =" r" \left\{ \begin{array}{ll}" - r" 1, & \mathrm{if} \ x = 0 \\" + r"1, & \mathrm{if} \ x = 0 \\" r" 3 x, & \mathrm{otherwise}" r" \end{array} \right." ) @@ -50,7 +50,7 @@ def test_visit_match() -> None: ).body[0] expected = ( r"\left\{ \begin{array}{ll}" - r" 1, & \mathrm{if} \ x = 0 \\" + r"1, & \mathrm{if} \ x = 0 \\" r" 2, & \mathrm{otherwise}" r" \end{array} \right." ) @@ -74,7 +74,7 @@ def test_visit_multiple_match_cases() -> None: ).body[0] expected = ( r"\left\{ \begin{array}{ll}" - r" 1, & \mathrm{if} \ x = 0 \\" + r"1, & \mathrm{if} \ x = 0 \\" r" 2, & \mathrm{if} \ x = 1 \\" r" 3, & \mathrm{otherwise}" r" \end{array} \right." @@ -192,7 +192,7 @@ def test_visit_match_case_with_if() -> None: textwrap.dedent( """ match x: - case x if x>0: + case x if x > 0: return 1 case _: return 2 @@ -202,7 +202,7 @@ def test_visit_match_case_with_if() -> None: assert ( function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} " + == r"\left\{ \begin{array}{ll}" + r"1, & \mathrm{if} \ x > 0 \\ " + r"2, & \mathrm{otherwise} \end{array} \right." ) @@ -224,9 +224,9 @@ def test_visit_match_case_with_if_and() -> None: assert ( function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} 1, & \mathrm{if} " - + r"\ x > 0 \land x \le 10 \\ " - + r"2, & \mathrm{otherwise} \end{array} \right." + == r"\left\{ \begin{array}{ll}1, & \mathrm{if} " + + r"\ x > 0 \land x \le 10 \\" + + r" 2, & \mathrm{otherwise} \end{array} \right." ) @@ -246,9 +246,9 @@ def test_visit_matchcase_with_if_or() -> None: assert ( function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} 1, " - + r"& \mathrm{if} \ x > 0 \lor x \le 10 \\ " - + r"2, & \mathrm{otherwise} \end{array} \right." + == r"\left\{ \begin{array}{ll}1," + + r" & \mathrm{if} \ x > 0 \lor x \le 10 \\" + + r" 2, & \mathrm{otherwise} \end{array} \right." ) @@ -268,9 +268,9 @@ def test_visit_match_case_with_combined_condition() -> None: assert ( function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} 1, " - + r"& \mathrm{if} \ 0 < x \le 10 \\ 2, " - + r"& \mathrm{otherwise} \end{array} \right." + == r"\left\{ \begin{array}{ll}1," + + r" & \mathrm{if} \ 0 < x \le 10 \\ 2," + + r" & \mathrm{otherwise} \end{array} \right." ) @@ -290,6 +290,6 @@ def test_visit_match_case_or() -> None: assert ( function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll} 1, & \mathrm{if} \ x = 0 \lor x = 1 \\" + == r"\left\{ \begin{array}{ll}1, & \mathrm{if} \ x = 0 \lor x = 1 \\" + r" 2, & \mathrm{otherwise} \end{array} \right." ) From 4db2356d088add4d8f8e35ea30bd02ed902aa880 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Sun, 11 Dec 2022 17:47:47 -0500 Subject: [PATCH 24/29] fixed flake errors --- src/latexify/codegen/function_codegen.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index fa75c44..15bf3af 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -186,8 +186,8 @@ def visit_MatchValue(self, node: ast.MatchValue) -> str: return "subject_name = " + latex def visit_MatchAs(self, node: ast.MatchAs) -> str: - """Visit a MatchAs node. - + """ + Visit a MatchAs node. If MatchAs is a wildcard, return 'otherwise' case, otherwise throw an error. """ if node.pattern is None: From ccb0c5d5b78bdaf9825187d45e373db17505c7a0 Mon Sep 17 00:00:00 2001 From: juliawgraham <89917126+juliawgraham@users.noreply.github.com> Date: Sun, 11 Dec 2022 18:03:41 -0500 Subject: [PATCH 25/29] Added spaces to conditionals in tests Co-authored-by: Zibing Zhang <44979059+ZibingZhang@users.noreply.github.com> --- src/latexify/codegen/function_codegen_match_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index 9ef3d13..d2d815f 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -214,7 +214,7 @@ def test_visit_match_case_with_if_and() -> None: textwrap.dedent( """ match x: - case x if x>0 and x<=10: + case x if x > 0 and x <= 10: return 1 case _: return 2 From 2d5807e9def6b07d83d435145fb1a22165dcae95 Mon Sep 17 00:00:00 2001 From: juliawgraham <89917126+juliawgraham@users.noreply.github.com> Date: Sun, 11 Dec 2022 18:04:04 -0500 Subject: [PATCH 26/29] Added more spaces to conditionals in tests Co-authored-by: Zibing Zhang <44979059+ZibingZhang@users.noreply.github.com> --- src/latexify/codegen/function_codegen_match_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index d2d815f..c828ae6 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -236,7 +236,7 @@ def test_visit_matchcase_with_if_or() -> None: textwrap.dedent( """ match x: - case x if x>0 or x<=10: + case x if x > 0 or x <= 10: return 1 case _: return 2 From fd2ddee6de8dcfeb62be00ef48d4a6c54ca7be33 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Wed, 14 Dec 2022 20:05:59 -0500 Subject: [PATCH 27/29] fixed style errors and removed guard --- src/latexify/codegen/function_codegen.py | 33 ++--- .../codegen/function_codegen_match_test.py | 117 +----------------- 2 files changed, 15 insertions(+), 135 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index 15bf3af..e77afe3 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -162,8 +162,8 @@ def visit_Match(self, node: ast.Match) -> str: if i < len(node.cases) - 1: body_latex = self.visit(case.body[0]) cond_latex = self.visit(case.pattern) - if case.guard is not None: - cond_latex = self._expression_codegen.visit(case.guard) + # if case.guard is not None: + # cond_latex = self._expression_codegen.visit(case.guard) case_latexes.append(body_latex + r", & \mathrm{if} \ " + cond_latex) else: @@ -172,7 +172,7 @@ def visit_Match(self, node: ast.Match) -> str: ) latex = ( - r"\left\{ \begin{array}{ll}" + r"\left\{ \begin{array}{ll} " + r" \\ ".join(case_latexes) + r" \end{array} \right." ) @@ -185,24 +185,13 @@ def visit_MatchValue(self, node: ast.MatchValue) -> str: latex = self._expression_codegen.visit(node.value) return "subject_name = " + latex - def visit_MatchAs(self, node: ast.MatchAs) -> str: - """ - Visit a MatchAs node. - If MatchAs is a wildcard, return 'otherwise' case, otherwise throw an error. - """ - if node.pattern is None: - return "" - else: - raise exceptions.LatexifySyntaxError( - "Nonempty as-patterns are not supported in MatchAs nodes." - ) - def visit_MatchOr(self, node: ast.MatchOr) -> str: """Visit a MatchOr node.""" - case_latexes = [] - for i, pattern in enumerate(node.patterns): - if i == 0: - case_latexes.append(self.visit(pattern)) - else: - case_latexes.append(r" \lor " + self.visit(pattern)) - return "".join(case_latexes) + # case_latexes = [] + # for i, pattern in enumerate(node.patterns): + # if i == 0: + # case_latexes.append(self.visit(pattern)) + # else: + # case_latexes.append(r" \lor " + self.visit(pattern)) + # return "".join(case_latexes) + return r" \lor ".join(self.visit(p) for p in node.patterns) diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index c828ae6..4aafd96 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -27,7 +27,7 @@ def f(x): ) expected = ( r"f(x) =" - r" \left\{ \begin{array}{ll}" + r" \left\{ \begin{array}{ll} " r"1, & \mathrm{if} \ x = 0 \\" r" 3 x, & \mathrm{otherwise}" r" \end{array} \right." @@ -49,7 +49,7 @@ def test_visit_match() -> None: ) ).body[0] expected = ( - r"\left\{ \begin{array}{ll}" + r"\left\{ \begin{array}{ll} " r"1, & \mathrm{if} \ x = 0 \\" r" 2, & \mathrm{otherwise}" r" \end{array} \right." @@ -73,7 +73,7 @@ def test_visit_multiple_match_cases() -> None: ) ).body[0] expected = ( - r"\left\{ \begin{array}{ll}" + r"\left\{ \begin{array}{ll} " r"1, & \mathrm{if} \ x = 0 \\" r" 2, & \mathrm{if} \ x = 1 \\" r" 3, & \mathrm{otherwise}" @@ -122,27 +122,6 @@ def test_visit_multiple_match_cases_no_wildcards() -> None: function_codegen.FunctionCodegen().visit(tree) -@test_utils.require_at_least(10) -def test_visit_only_wildcard_in_matchas() -> None: - tree = ast.parse( - textwrap.dedent( - """ - match x: - case [x] as y: - return 1 - case _: - return 2 - """ - ) - ).body[0] - - with pytest.raises( - exceptions.LatexifySyntaxError, - match=r"^Nonempty as-patterns are not supported in MatchAs nodes.$", - ): - function_codegen.FunctionCodegen().visit(tree) - - @test_utils.require_at_least(10) def test_visit_match_case_no_return() -> None: tree = ast.parse( @@ -186,94 +165,6 @@ def test_visit_match_case_mutliple_statements() -> None: function_codegen.FunctionCodegen().visit(tree) -@test_utils.require_at_least(10) -def test_visit_match_case_with_if() -> None: - tree = ast.parse( - textwrap.dedent( - """ - match x: - case x if x > 0: - return 1 - case _: - return 2 - """ - ) - ).body[0] - - assert ( - function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll}" - + r"1, & \mathrm{if} \ x > 0 \\ " - + r"2, & \mathrm{otherwise} \end{array} \right." - ) - - -@test_utils.require_at_least(10) -def test_visit_match_case_with_if_and() -> None: - tree = ast.parse( - textwrap.dedent( - """ - match x: - case x if x > 0 and x <= 10: - return 1 - case _: - return 2 - """ - ) - ).body[0] - - assert ( - function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll}1, & \mathrm{if} " - + r"\ x > 0 \land x \le 10 \\" - + r" 2, & \mathrm{otherwise} \end{array} \right." - ) - - -@test_utils.require_at_least(10) -def test_visit_matchcase_with_if_or() -> None: - tree = ast.parse( - textwrap.dedent( - """ - match x: - case x if x > 0 or x <= 10: - return 1 - case _: - return 2 - """ - ) - ).body[0] - - assert ( - function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll}1," - + r" & \mathrm{if} \ x > 0 \lor x \le 10 \\" - + r" 2, & \mathrm{otherwise} \end{array} \right." - ) - - -@test_utils.require_at_least(10) -def test_visit_match_case_with_combined_condition() -> None: - tree = ast.parse( - textwrap.dedent( - """ - match x: - case x if 0 < x <= 10: - return 1 - case _: - return 2 - """ - ) - ).body[0] - - assert ( - function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll}1," - + r" & \mathrm{if} \ 0 < x \le 10 \\ 2," - + r" & \mathrm{otherwise} \end{array} \right." - ) - - @test_utils.require_at_least(10) def test_visit_match_case_or() -> None: tree = ast.parse( @@ -290,6 +181,6 @@ def test_visit_match_case_or() -> None: assert ( function_codegen.FunctionCodegen().visit(tree) - == r"\left\{ \begin{array}{ll}1, & \mathrm{if} \ x = 0 \lor x = 1 \\" + == r"\left\{ \begin{array}{ll} 1, & \mathrm{if} \ x = 0 \lor x = 1 \\" + r" 2, & \mathrm{otherwise} \end{array} \right." ) From 0daeb72147ba4f7ba8b12d0168b886b6cacb6928 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Wed, 14 Dec 2022 20:20:37 -0500 Subject: [PATCH 28/29] used _match_subject_stack as suggested --- src/latexify/codegen/function_codegen.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/src/latexify/codegen/function_codegen.py b/src/latexify/codegen/function_codegen.py index e77afe3..b0f2424 100644 --- a/src/latexify/codegen/function_codegen.py +++ b/src/latexify/codegen/function_codegen.py @@ -25,6 +25,7 @@ def __init__( use_math_symbols: bool = False, use_signature: bool = True, use_set_symbols: bool = False, + _match_subject_stack: list[str] = [], ) -> None: """Initializer. @@ -34,6 +35,7 @@ def __init__( use_signature: Whether to add the function signature before the expression or not. use_set_symbols: Whether to use set symbols or not. + _match_subject_stack: a stack of subject names that are used in match """ self._expression_codegen = expression_codegen.ExpressionCodegen( use_math_symbols=use_math_symbols, use_set_symbols=use_set_symbols @@ -42,6 +44,7 @@ def __init__( use_math_symbols=use_math_symbols ) self._use_signature = use_signature + self._match_subject_stack = _match_subject_stack def generic_visit(self, node: ast.AST) -> str: raise exceptions.LatexifyNotSupportedError( @@ -141,6 +144,9 @@ def visit_If(self, node: ast.If) -> str: def visit_Match(self, node: ast.Match) -> str: """Visit a Match node.""" + subject_latex = self._expression_codegen.visit(node.subject) + self._match_subject_stack.append(subject_latex) + if not ( len(node.cases) >= 2 and isinstance(node.cases[-1].pattern, ast.MatchAs) @@ -162,8 +168,6 @@ def visit_Match(self, node: ast.Match) -> str: if i < len(node.cases) - 1: body_latex = self.visit(case.body[0]) cond_latex = self.visit(case.pattern) - # if case.guard is not None: - # cond_latex = self._expression_codegen.visit(case.guard) case_latexes.append(body_latex + r", & \mathrm{if} \ " + cond_latex) else: @@ -177,21 +181,14 @@ def visit_Match(self, node: ast.Match) -> str: + r" \end{array} \right." ) - latex_final = latex.replace("subject_name", subject_latex) - return latex_final + self._match_subject_stack.pop() + return latex def visit_MatchValue(self, node: ast.MatchValue) -> str: """Visit a MatchValue node.""" latex = self._expression_codegen.visit(node.value) - return "subject_name = " + latex + return self._match_subject_stack[-1] + " = " + latex def visit_MatchOr(self, node: ast.MatchOr) -> str: """Visit a MatchOr node.""" - # case_latexes = [] - # for i, pattern in enumerate(node.patterns): - # if i == 0: - # case_latexes.append(self.visit(pattern)) - # else: - # case_latexes.append(r" \lor " + self.visit(pattern)) - # return "".join(case_latexes) return r" \lor ".join(self.visit(p) for p in node.patterns) From 2019d4b5fc87bc7ec638ed4c21602cf964610f06 Mon Sep 17 00:00:00 2001 From: Yuqi Gong Date: Wed, 14 Dec 2022 20:33:06 -0500 Subject: [PATCH 29/29] fixed unit tests, adding cdot back --- src/latexify/codegen/function_codegen_match_test.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/latexify/codegen/function_codegen_match_test.py b/src/latexify/codegen/function_codegen_match_test.py index 4aafd96..1bc6d07 100644 --- a/src/latexify/codegen/function_codegen_match_test.py +++ b/src/latexify/codegen/function_codegen_match_test.py @@ -29,7 +29,7 @@ def f(x): r"f(x) =" r" \left\{ \begin{array}{ll} " r"1, & \mathrm{if} \ x = 0 \\" - r" 3 x, & \mathrm{otherwise}" + r" 3 \cdot x, & \mathrm{otherwise}" r" \end{array} \right." ) assert function_codegen.FunctionCodegen().visit(tree) == expected