<commit_before>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Loyc;
using Loyc.MiniTest;

namespace LeMP.Tests
{
	[TestFixture]
	public class TestReplaceAndDefineMacros : MacroTesterBase
	{
		[Test]
		public void TestReplace_basics()
		{
			// Simple cases
			using (MessageSink.SetDefault(_msgHolder)) {
				TestLes(@"replace (nothing => nobody) {nowhere;}", "nowhere;");
				Assert.IsTrue(_msgHolder.List.Count > 0, "expected warning about 'no replacements'");
			}
			TestLes(@"replace (a => b) {a;}", "b;");
			TestLes(@"replace (7 => seven) {x = 7;}", "x = seven;");
			TestLes(@"replace (7() => ""seven"") {x = 7() + 7;}", @"x = ""seven"" + 7;");
			TestLes(@"replace (a => b) {@[Hello] a; a(a);}", "@[Hello] b; b(b);");
			TestLes("replace (MS => MessageSink; C => Current; W => Write; S => Severity; B => \"Bam!\")\n" +
					"    { MS.C.W(S.Error, @null, B); }",
					@"MessageSink.Current.Write(Severity.Error, @null, ""Bam!"");");
			TestLes(@"replace (Write => Store; Console.Write => Console.Write) " +
					@"{ Write(x); Console.Write(x); }", "Store(x); Console.Write(x);");

			// Swap
			TestLes("replace (foo => bar; bar => foo) {foo() = bar;}", "bar() = foo;");
			TestLes("replace (a => 'a'; 'a' => A; A => a) {'a' = A - a;}", "A = a - 'a';");

			// Captures
			TestBoth("replace (input($capture) => output($capture)) { var i = 21; input(i * 2); };",
					 "replace (input($capture) => output($capture)) { var i = 21; input(i * 2); }",
					 "var i = 21; output(i * 2);");
		}

		[Test]
		public void TestReplace_params()
		{
			TestEcs("replace({{ $(params before); on_exit { $(params command); } $(params after); }} =>\n" +
					"        {{ $before;          try  { $after; } finally { $command; }         }}) \n" +
					"{{ var foo = new Foo(); on_exit { foo.Dispose(); } Combobulate(foo); return foo; }}",
					" { var foo = new Foo(); try { Combobulate(foo); return foo; } finally { foo.Dispose(); } }");

			TestLes("replace ($($format; $(..args)) => String.Format($format, $(..args)))\n" +
					@"   { MessageBox.Show($(""I hate {0}""; noun)); }",
					@"MessageBox.Show(String.Format(""I hate {0}"", noun));");
			TestLes("replace ($($format; $(..args)) => String.Format($format, $(..args)))\n" +
					@"   { MessageBox.Show($(""I hate {0}ing {1}s""; verb; noun), $(""FYI"";)); }",
					@"MessageBox.Show(String.Format(""I hate {0}ing {1}s"", verb, noun), String.Format(""FYI""));");
		}

		[Test(Fails = "Not Implemented")]
		public void TestReplace_match_attributes()
		{
			// [foo] a([attr] Foo) `MatchesPattern` 
			// [`%`, bar] a([$attr] $foo)

			// ([foo] F([x] X, [y] Y, [a1(...), a2(...)] Z) `MatchesPattern`
			//        F(X, $Y, $(params P), [$A, a1($(params args))] $Z)) == false cuz [x] is unmatched

			// TODO
		}

		[Test]
		public void TestReplaceInTokenTree()
		{
			// Subtlety: the test fails if there is no newline after `replace(...) {`.
			// Without the newline, %appendStatement is attached to foo(), which the test does not expect.
			TestLes("replace (foo => bar; bar => foo) {\n  foo(@{ foo(*****) }); }", "bar(@{ bar(*****) });");
			TestLes("replace (foo => bar; bar => foo) {\n  foo(@{ foo(%bar%) }); }", "bar(@{ bar(%foo%) });");
			TestLes("replace (foo => bar; bar => foo) {\n  foo(@{ ***(%bar%) }); }", "bar(@{ ***(%foo%) });");
			TestLes("unroll ((A; B) `in` ((Eh; Bee); ('a'; \"b\"); (1; 2.0))) {\n  foo(B - A, @{A + B}); }",
				@"foo(Bee - Eh,  TokenTree""Eh '+ Bee"");
				  foo(""b"" - 'a', TokenTree""'a' '+ \""b\"""");
				  foo(2.0 - 1, TokenTree""1 '+ 2.0"");");
		}

		[Test]
		public void TestReplace_advanced()
		{
			// Nested replacements
			TestEcs(@"replace(X => Y, X($(params p)) => X($p)) { X = X(X, Y); }",
					@"Y = X(Y, Y);");
			// Note: $a * $b doesn't work because it is seen as a variable decl
			TestEcs(@"replace(($a + $b + $c) => Add($a, $b, $c), ($a`'*`$b) => Mul($a, $b))
			          { var y = 2*x*2 + 3*x + 4; }",
					@"var y = Add(Mul(Mul(2, x), 2), Mul(3, x), 4);");

			//TestEcs(@"replace(TO=>DO) {}", @"");
		}

		[Test]
		public void TestReplace_RemainingNodes()
		{
			TestEcs(@"{ replace(C => Console, WL => WriteLine); string name = ""Bob""; C.WL(""Hi ""+name); }",
					@"{ string name = ""Bob""; Console.WriteLine(""Hi ""+name); }");
		}

		[Test]
		public void TestDefineEverything()
		{
			var holder = new MessageHolder();
			using (MessageSink.SetDefault(holder)) // suppress errors
			{
				TestEcs(@"define ($anything) { $anything + 1; }
					X();",
					@"define ($anything) { $anything + 1; }
					X();");
				Assert.AreEqual(1, holder.List.Count(msg => 
					msg.Format.EndsWith("Defining a macro that could match everything is not allowed.")));
			}
		}

		[Test]
		public void TestDefineFn()
		{
			TestLes(@"define WL() { Console.WriteLine(); }; WL(); WL();",
					@"Console.WriteLine(); Console.WriteLine();");
			TestEcs(@"define NL() => Console.WriteLine(); NL(); NL();",
					@"Console.WriteLine(); Console.WriteLine();");
			TestEcs(@"define Methods($T) { void F($T arg) {} void G($T arg) {} } Methods(int); Methods(List<int>);",
					@"void F(int arg) {} void G(int arg) {} void F(List<int> arg) {} void G(List<int> arg) {}");
			TestEcs(@"define WL($format, $(..args)) => Console.WriteLine($format, $(..args)); WL(1, 2, 3);",
					@"Console.WriteLine(1, 2, 3);");
			TestLes(@"define WL($format, $(..args)) { Console.WriteLine($format, $(..args)); }; WL(1, 2, 3);",
					@"Console.WriteLine(1, 2, 3);");
			TestEcs(@"[Passive] define operator=(Foo[$index], $value) => Foo.SetAt($index, $value); x = Foo[y] = z;",
					@"x = Foo.SetAt(y, z);");
			// Test warnings about `$`
			using (MessageSink.SetDefault(new SeverityMessageFilter(_msgHolder, Severity.DebugDetail))) {
				_msgHolder.List.Clear();
				TestEcs(@"define Foo(w, $x, y, $z) => (x, $y);", @"");
				Assert.AreEqual(2, _msgHolder.List.Count);
				TestEcs(@"define Foo(a, b) => (a, b);", @"");
				Assert.AreEqual(4, _msgHolder.List.Count);
				Assert.IsTrue(_msgHolder.List.All(msg => msg.Severity == Severity.Warning));
				_msgHolder.WriteListTo(TraceMessageSink.Value);
			}
		}

		[Test]
		public void TestDefineIdentifier()
		{
			TestEcs(@"define WL { Console.WriteLine; } WL(122);",
					@"Console.WriteLine(122);");
			TestEcs(@"define WL => Console.WriteLine; WL(123);",
					@"Console.WriteLine(123);");
			TestLes(@"define WL { Console.WriteLine; }; WL(124);",
					@"Console.WriteLine(124);");
			TestLes(@"#define WL { Console.WriteLine; }; WL(125);",
					@"Console.WriteLine(125);");
		}

		[Test]
		public void TestDefineLiterals()
		{
			TestEcs(@"define ('x') { 'X'; }
					Console.WriteLine('x');",
					@"Console.WriteLine('X');");
			TestEcs(@"define (123) { 456; }
					Console.WriteLine(123 + 456 + 789);",
					@"Console.WriteLine(456 + 456 + 789);");
		}

		[Test]
		public void TestDefineComplexCall()
		{
			TestEcs(@"define ($x << $y) { $x.Append($y); }
					sb << ""Hello"";",
					@"sb.Append(""Hello"");");
			TestEcs(@"define (Foo()()()) { Bar(); }
					define (Invalid.$F($X)) { Valid.$F($X); }
					x = Foo()()()(x);
					Invalid.Foo(x);",<commit_msg>I meant to add this test 3 commits ago<commit_after>using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Loyc;
using Loyc.MiniTest;

namespace LeMP.Tests
{
	[TestFixture]
	public class TestReplaceAndDefineMacros : MacroTesterBase
	{
		[Test]
		public void TestReplace_basics()
		{
			// Simple cases
			using (MessageSink.SetDefault(_msgHolder)) {
				TestLes(@"replace (nothing => nobody) {nowhere;}", "nowhere;");
				Assert.IsTrue(_msgHolder.List.Count > 0, "expected warning about 'no replacements'");
			}
			TestLes(@"replace (a => b) {a;}", "b;");
			TestLes(@"replace (7 => seven) {x = 7;}", "x = seven;");
			TestLes(@"replace (7() => ""seven"") {x = 7() + 7;}", @"x = ""seven"" + 7;");
			TestLes(@"replace (a => b) {@[Hello] a; a(a);}", "@[Hello] b; b(b);");
			TestLes("replace (MS => MessageSink; C => Current; W => Write; S => Severity; B => \"Bam!\")\n" +
					"    { MS.C.W(S.Error, @null, B); }",
					@"MessageSink.Current.Write(Severity.Error, @null, ""Bam!"");");
			TestLes(@"replace (Write => Store; Console.Write => Console.Write) " +
					@"{ Write(x); Console.Write(x); }", "Store(x); Console.Write(x);");

			// Swap
			TestLes("replace (foo => bar; bar => foo) {foo() = bar;}", "bar() = foo;");
			TestLes("replace (a => 'a'; 'a' => A; A => a) {'a' = A - a;}", "A = a - 'a';");

			// Captures
			TestBoth("replace (input($capture) => output($capture)) { var i = 21; input(i * 2); };",
					 "replace (input($capture) => output($capture)) { var i = 21; input(i * 2); }",
					 "var i = 21; output(i * 2);");
		}

		[Test]
		public void TestReplace_params()
		{
			TestEcs("replace({{ $(params before); on_exit { $(params command); } $(params after); }} =>\n" +
					"        {{ $before;          try  { $after; } finally { $command; }         }}) \n" +
					"{{ var foo = new Foo(); on_exit { foo.Dispose(); } Combobulate(foo); return foo; }}",
					" { var foo = new Foo(); try { Combobulate(foo); return foo; } finally { foo.Dispose(); } }");

			TestLes("replace ($($format; $(..args)) => String.Format($format, $(..args)))\n" +
					@"   { MessageBox.Show($(""I hate {0}""; noun)); }",
					@"MessageBox.Show(String.Format(""I hate {0}"", noun));");
			TestLes("replace ($($format; $(..args)) => String.Format($format, $(..args)))\n" +
					@"   { MessageBox.Show($(""I hate {0}ing {1}s""; verb; noun), $(""FYI"";)); }",
					@"MessageBox.Show(String.Format(""I hate {0}ing {1}s"", verb, noun), String.Format(""FYI""));");
		}

		[Test(Fails = "Not Implemented")]
		public void TestReplace_match_attributes()
		{
			// [foo] a([attr] Foo) `MatchesPattern` 
			// [`%`, bar] a([$attr] $foo)

			// ([foo] F([x] X, [y] Y, [a1(...), a2(...)] Z) `MatchesPattern`
			//        F(X, $Y, $(params P), [$A, a1($(params args))] $Z)) == false cuz [x] is unmatched

			// TODO
		}

		[Test]
		public void TestReplaceInTokenTree()
		{
			// Subtlety: the test fails if there is no newline after `replace(...) {`.
			// Without the newline, %appendStatement is attached to foo(), which the test does not expect.
			TestLes("replace (foo => bar; bar => foo) {\n  foo(@{ foo(*****) }); }", "bar(@{ bar(*****) });");
			TestLes("replace (foo => bar; bar => foo) {\n  foo(@{ foo(%bar%) }); }", "bar(@{ bar(%foo%) });");
			TestLes("replace (foo => bar; bar => foo) {\n  foo(@{ ***(%bar%) }); }", "bar(@{ ***(%foo%) });");
			TestLes("unroll ((A; B) `in` ((Eh; Bee); ('a'; \"b\"); (1; 2.0))) {\n  foo(B - A, @{A + B}); }",
				@"foo(Bee - Eh,  TokenTree""Eh '+ Bee"");
				  foo(""b"" - 'a', TokenTree""'a' '+ \""b\"""");
				  foo(2.0 - 1, TokenTree""1 '+ 2.0"");");
		}

		[Test]
		public void TestReplace_advanced()
		{
			// Nested replacements
			TestEcs(@"replace(X => Y, X($(params p)) => X($p)) { X = X(X, Y); }",
					@"Y = X(Y, Y);");
			// Note: $a * $b doesn't work because it is seen as a variable decl
			TestEcs(@"replace(($a + $b + $c) => Add($a, $b, $c), ($a`'*`$b) => Mul($a, $b))
			          { var y = 2*x*2 + 3*x + 4; }",
					@"var y = Add(Mul(Mul(2, x), 2), Mul(3, x), 4);");

			//TestEcs(@"replace(TO=>DO) {}", @"");
		}

		[Test]
		public void TestReplace_RemainingNodes()
		{
			TestEcs(@"{ replace(C => Console, WL => WriteLine); string name = ""Bob""; C.WL(""Hi ""+name); }",
					@"{ string name = ""Bob""; Console.WriteLine(""Hi ""+name); }");
		}

		[Test]
		public void TestDefineEverything()
		{
			var holder = new MessageHolder();
			using (MessageSink.SetDefault(holder)) // suppress errors
			{
				TestEcs(@"define ($anything) { $anything + 1; }
					X();",
					@"define ($anything) { $anything + 1; }
					X();");
				Assert.AreEqual(1, holder.List.Count(msg => 
					msg.Format.EndsWith("Defining a macro that could match everything is not allowed.")));
			}
		}

		[Test]
		public void TestDefineFn()
		{
			TestLes(@"define WL() { Console.WriteLine(); }; WL(); WL();",
					@"Console.WriteLine(); Console.WriteLine();");
			TestEcs(@"define NL() => Console.WriteLine(); NL(); NL();",
					@"Console.WriteLine(); Console.WriteLine();");
			TestEcs(@"define Methods($T) { void F($T arg) {} void G($T arg) {} } Methods(int); Methods(List<int>);",
					@"void F(int arg) {} void G(int arg) {} void F(List<int> arg) {} void G(List<int> arg) {}");
			TestEcs(@"define WL($format, $(..args)) => Console.WriteLine($format, $(..args)); WL(1, 2, 3);",
					@"Console.WriteLine(1, 2, 3);");
			TestLes(@"define WL($format, $(..args)) { Console.WriteLine($format, $(..args)); }; WL(1, 2, 3);",
					@"Console.WriteLine(1, 2, 3);");
			TestEcs(@"[Passive] define operator=(Foo[$index], $value) => Foo.SetAt($index, $value); x = Foo[y] = z;",
					@"x = Foo.SetAt(y, z);");
			// Test warnings about `$`
			using (MessageSink.SetDefault(new SeverityMessageFilter(_msgHolder, Severity.DebugDetail))) {
				_msgHolder.List.Clear();
				TestEcs(@"define Foo(w, $x, y, $z) => (x, $y);", @"");
				Assert.AreEqual(2, _msgHolder.List.Count);
				TestEcs(@"define Foo(a, b) => (a, b);", @"");
				Assert.AreEqual(4, _msgHolder.List.Count);
				Assert.IsTrue(_msgHolder.List.All(msg => msg.Severity == Severity.Warning));
				_msgHolder.WriteListTo(TraceMessageSink.Value);
			}
		}

		[Test]
		public void TestDefineIdentifier()
		{
			TestEcs(@"define WL { Console.WriteLine; } WL(122);",
					@"Console.WriteLine(122);");
			TestEcs(@"define WL => Console.WriteLine; WL(123);",
					@"Console.WriteLine(123);");
			TestLes(@"define WL { Console.WriteLine; }; WL(124);",
					@"Console.WriteLine(124);");
			TestLes(@"#define WL { Console.WriteLine; }; WL(125);",
					@"Console.WriteLine(125);");
		}

		[Test]
		public void TestDefineLiterals()
		{
			TestEcs(@"define ('x') { 'X'; }
					Console.WriteLine('x');",
					@"Console.WriteLine('X');");
			TestEcs(@"define (123) { 456; }
					Console.WriteLine(123 + 456 + 789);",
					@"Console.WriteLine(456 + 456 + 789);");
		}

		[Test]
		public void TestDefineIgnoresBracesAroundPattern()
		{
			TestEcs(@"define ({ 'x'; }) { 'X'; }
					Console.WriteLine('x');",
					@"Console.WriteLine('X');");
			TestLes(@"define ({ WL(); }) { Console.WriteLine(); }; WL(); WL();",
					@"Console.WriteLine(); Console.WriteLine();");
		}

		[Test]
		public void TestDefineComplexCall()
		{
			TestEcs(@"define ($x << $y) { $x.Append($y); }
					sb << ""Hello"";",
					@"sb.Append(""Hello"");");
			TestEcs(@"define (Foo()()()) { Bar(); }
					define (Invalid.$F($X)) { Valid.$F($X); }
					x = Foo()()()(x);
					Invalid.Foo(x);",
{'fraction_non_alphanumeric': 0.256674215908311, 'fraction_numerical': 0.014755335941253174, 'mean_word_length': 3.4836923076923076, 'pattern_counts': {'":': 0, '<': 17, '<?xml version=': 0, '>': 97, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '5619420', 'n_tokens_mistral': 6398, 'n_tokens_neox': 5259, 'n_words': 1227}