diff --git a/Coder.Test/Languages/EntryPointPlacementTests.cs b/Coder.Test/Languages/EntryPointPlacementTests.cs
new file mode 100644
index 0000000..4bbb7d4
--- /dev/null
+++ b/Coder.Test/Languages/EntryPointPlacementTests.cs
@@ -0,0 +1,293 @@
+// Copyright (c) 2023-2026 ktsu-dev contributors
+
+namespace ktsu.Coder.Test.Languages;
+
+using ktsu.Coder.Ast;
+using ktsu.Coder.Languages;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+///
+/// Tests that an entry point declared at file scope, in a namespace or in a type is written where
+/// each language's toolchain looks for it, rather than where the document happened to put it (#128).
+///
+[TestClass]
+public class EntryPointPlacementTests
+{
+ ///
+ /// The exit code the programs below return, which is what proves the body ran.
+ ///
+ private const int Expected = 3;
+
+ ///
+ /// The project the C# program is built as.
+ ///
+ private const string Project = """
+
+
+ Exe
+ net10.0
+ disable
+ disable
+
+
+ """;
+
+ ///
+ /// Tests that C# puts an entry point outside a type into a static class, since C# declares a method
+ /// nowhere else.
+ ///
+ /// Where the document declared the entry point.
+ [TestMethod]
+ [DataRow("file")]
+ [DataRow("namespace")]
+ public void CSharp_WrapsAnEntryPointOutsideATypeInAStaticClass(string placement)
+ {
+ string code = new CSharpGenerator().Generate(Program(placement, withHelper: false));
+
+ int holder = code.IndexOf("static class", StringComparison.Ordinal);
+ int main = code.IndexOf("static int Main", StringComparison.Ordinal);
+ Assert.IsTrue(holder >= 0 && holder < main, code);
+ }
+
+ ///
+ /// Tests that every language other than C# writes an entry point declared in a type at file scope,
+ /// after the type, rather than inside it or not at all.
+ ///
+ /// The generator's display name.
+ /// How the entry point starts, at the start of a line.
+ [TestMethod]
+ [DataRow("C", "int main(")]
+ [DataRow("C++", "int main(")]
+ [DataRow("Rust", "fn main(")]
+ [DataRow("Go", "func main(")]
+ [DataRow("Python", "def main(")]
+ [DataRow("JavaScript", "function main(")]
+ public void AnEntryPointInATypeIsWrittenAtFileScope(string language, string signature)
+ {
+ string code = GeneratorFor(language).Generate(Program("type", withHelper: false));
+
+ int type = code.IndexOf("Program", StringComparison.Ordinal);
+ int main = code.IndexOf($"\n{signature}", StringComparison.Ordinal);
+ Assert.IsTrue(type >= 0 && main > type, code);
+ }
+
+ ///
+ /// Tests that C++ and Rust write an entry point declared in a namespace at file scope, where it is
+ /// found, and keep the names declared beside it in reach of its body.
+ ///
+ /// The generator's display name.
+ /// How the entry point starts, at the start of a line.
+ /// What brings the namespace's names into the body.
+ [TestMethod]
+ [DataRow("C++", "int main(", "using namespace App;")]
+ [DataRow("Rust", "fn run(", "use self::App::*;")]
+ public void AnEntryPointInANamespaceIsWrittenAtFileScope(string language, string signature, string import)
+ {
+ string code = GeneratorFor(language).Generate(Program("namespace", withHelper: true));
+
+ Assert.IsTrue(code.Contains($"\n{signature}", StringComparison.Ordinal), code);
+ StringAssert.Contains(code, import, StringComparison.Ordinal, code);
+ }
+
+ ///
+ /// Tests that a program whose entry point is declared in a namespace, beside the function it calls,
+ /// compiles, links and runs in every language with a compiler.
+ ///
+ /// The generator's display name.
+ [TestMethod]
+ [DataRow("C")]
+ [DataRow("C++")]
+ [DataRow("Rust")]
+ [DataRow("Go")]
+ [DataRow("C#")]
+ public void AnEntryPointInANamespace_CompilesLinksAndRuns(string language) =>
+ CompileAndRun(language, Program("namespace", withHelper: true));
+
+ ///
+ /// Tests that a program whose entry point is declared in a type compiles, links and runs in every
+ /// language with a compiler.
+ ///
+ /// The generator's display name.
+ [TestMethod]
+ [DataRow("C")]
+ [DataRow("C++")]
+ [DataRow("Rust")]
+ [DataRow("Go")]
+ [DataRow("C#")]
+ public void AnEntryPointInAType_CompilesLinksAndRuns(string language) =>
+ CompileAndRun(language, Program("type", withHelper: false));
+
+ ///
+ /// Tests that a type generated on its own, rather than inside a file, still has the entry point it
+ /// holds written after it at file scope.
+ ///
+ /// The generator's display name.
+ /// How the entry point starts, at the start of a line.
+ [TestMethod]
+ [DataRow("C", "int main(")]
+ [DataRow("C++", "int main(")]
+ [DataRow("Rust", "fn main(")]
+ [DataRow("Go", "func main(")]
+ [DataRow("Python", "def main(")]
+ [DataRow("JavaScript", "function main(")]
+ public void AnEntryPointInATypeGeneratedOnItsOwnIsWrittenAfterIt(string language, string signature)
+ {
+ AstNode type = Program("type", withHelper: false).Members[0];
+
+ string code = GeneratorFor(language).Generate(type);
+
+ int declared = code.IndexOf("Program", StringComparison.Ordinal);
+ int main = code.IndexOf($"\n{signature}", StringComparison.Ordinal);
+ Assert.IsTrue(declared >= 0 && main > declared, code);
+ Assert.AreEqual(main, code.LastIndexOf($"\n{signature}", StringComparison.Ordinal), $"The entry point was written twice:{Environment.NewLine}{code}");
+ }
+
+ ///
+ /// Tests that a namespace generated on its own, rather than inside a file, has its entry point
+ /// written after it at file scope, with the names declared beside it brought back into the body.
+ ///
+ /// The generator's display name.
+ /// How the entry point starts, at the start of a line.
+ /// What brings the namespace's names into the body.
+ [TestMethod]
+ [DataRow("C++", "int main(", "using namespace App;")]
+ [DataRow("Rust", "fn run(", "use self::App::*;")]
+ public void AnEntryPointInANamespaceGeneratedOnItsOwnIsWrittenAfterIt(string language, string signature, string import)
+ {
+ AstNode container = Program("namespace", withHelper: true).Members[0];
+
+ string code = GeneratorFor(language).Generate(container);
+
+ int helper = code.IndexOf("helper", StringComparison.Ordinal);
+ int main = code.IndexOf($"\n{signature}", StringComparison.Ordinal);
+ Assert.IsTrue(helper >= 0 && main > helper, code);
+ Assert.IsTrue(code.IndexOf(import, StringComparison.Ordinal) > main, code);
+ }
+
+ ///
+ /// Tests that a type which loses its entry point to file scope keeps its annotations, which the
+ /// copy left behind carries in its place.
+ ///
+ /// The generator's display name.
+ [TestMethod]
+ [DataRow("C")]
+ [DataRow("C++")]
+ [DataRow("Rust")]
+ [DataRow("Go")]
+ [DataRow("Python")]
+ [DataRow("JavaScript")]
+ public void AnAnnotatedTypeKeepsItsAnnotationsWhenItsEntryPointMoves(string language)
+ {
+ SourceFile file = Program("type", withHelper: false);
+ ((ClassDeclaration)file.Members[0]).Annotations.Add(new Annotation("Marked"));
+ LanguageGeneratorBase generator = (LanguageGeneratorBase)GeneratorFor(language);
+ generator.EnforceConservation = true;
+
+ string code = generator.Generate(file);
+
+ StringAssert.Contains(code, "Marked", StringComparison.Ordinal, code);
+ }
+
+ ///
+ /// Builds a program that exits with .
+ ///
+ /// Where the entry point is declared: file, namespace or type.
+ /// Whether the value comes from a function declared beside the entry point.
+ /// The file.
+ private static SourceFile Program(string placement, bool withHelper)
+ {
+ EntryPoint main = new() { ReturnsExitCode = true };
+ Expression value = withHelper ? new CallExpression("helper") : new LiteralExpression(Expected);
+ main.Body.Add(new ReturnStatement(value));
+
+ List members = [];
+ if (withHelper)
+ {
+ FunctionDeclaration helper = new("helper") { ReturnType = "int", Visibility = Visibility.Public, IsStatic = true };
+ helper.Body.Add(new ReturnStatement(new LiteralExpression(Expected)));
+ members.Add(helper);
+ }
+
+ members.Add(main);
+
+ SourceFile file = new("program");
+ switch (placement)
+ {
+ case "namespace":
+ {
+ NamespaceDeclaration container = new("App");
+ members.ForEach(container.Members.Add);
+ file.Members.Add(container);
+ break;
+ }
+
+ case "type":
+ {
+ ClassDeclaration container = new("Program");
+ members.ForEach(container.Members.Add);
+ file.Members.Add(container);
+ break;
+ }
+
+ default:
+ members.ForEach(file.Members.Add);
+ break;
+ }
+
+ return file;
+ }
+
+ private static void CompileAndRun(string language, SourceFile file)
+ {
+ (string[] commands, string versionArgument, string fileName, string build, string run, string runArguments) = ToolchainFor(language);
+ string? compiler = ToolchainHarness.FindOnPath(versionArgument, commands);
+ if (compiler is null)
+ {
+ Assert.Inconclusive($"No {language} toolchain on the path, so nothing was compiled.");
+ return;
+ }
+
+ string code = GeneratorFor(language).Generate(file);
+ ToolchainHarness.InTemporaryDirectory(directory =>
+ {
+ File.WriteAllText(Path.Combine(directory, fileName), code);
+ if (language == "Go")
+ {
+ File.WriteAllText(Path.Combine(directory, "go.mod"), "module program\n\ngo 1.21\n");
+ }
+ else if (language == "C#")
+ {
+ File.WriteAllText(Path.Combine(directory, "program.csproj"), Project);
+ }
+
+ (int exitCode, string output) = ToolchainHarness.Run(compiler, build, directory);
+ Assert.AreEqual(0, exitCode, $"{compiler} rejected the generated source:{Environment.NewLine}{code}{Environment.NewLine}{output}");
+
+ string program = run.Length == 0 ? Path.Combine(directory, "program") : run;
+ (int programExitCode, string programOutput) = ToolchainHarness.Run(program, runArguments, directory);
+ Assert.AreEqual(Expected, programExitCode, $"The entry point did not run:{Environment.NewLine}{code}{Environment.NewLine}{programOutput}");
+ });
+ }
+
+ private static (string[] Commands, string VersionArgument, string FileName, string Build, string Run, string RunArguments) ToolchainFor(string language) => language switch
+ {
+ "C" => (["cc", "gcc", "clang"], "--version", "program.c", "-std=c11 -Wall -Wextra -pedantic -Werror program.c -o program", string.Empty, string.Empty),
+ "C++" => (["c++", "g++", "clang++"], "--version", "program.cpp", "-std=c++20 -Wall -Wextra -pedantic -Werror program.cpp -o program", string.Empty, string.Empty),
+ "Rust" => (["rustc"], "--version", "program.rs", "--edition 2021 -o program program.rs", string.Empty, string.Empty),
+ "Go" => (["go"], "version", "program.go", "build -o program .", string.Empty, string.Empty),
+ "C#" => (["dotnet"], "--version", "Program.cs", "build -nologo -o out", "dotnet", Path.Combine("out", "program.dll")),
+ _ => throw new ArgumentOutOfRangeException(nameof(language), language, null),
+ };
+
+ private static ILanguageGenerator GeneratorFor(string language) => language switch
+ {
+ "C" => new CGenerator(),
+ "C++" => new CppGenerator(),
+ "Rust" => new RustGenerator(),
+ "Go" => new GoGenerator(),
+ "Python" => new PythonGenerator(),
+ "JavaScript" => new JavaScriptGenerator(),
+ "C#" => new CSharpGenerator(),
+ _ => throw new ArgumentOutOfRangeException(nameof(language), language, null),
+ };
+}
diff --git a/Coder/Languages/CSharpGenerator.cs b/Coder/Languages/CSharpGenerator.cs
index a40841b..1b18d84 100644
--- a/Coder/Languages/CSharpGenerator.cs
+++ b/Coder/Languages/CSharpGenerator.cs
@@ -652,8 +652,8 @@ protected override void GenerateFileMembers(SourceFile file, CodeBlocker code)
/// What to call the class, unless a type beside it already has the name.
/// The writer to emit into.
///
- /// C# has no field, function or variable outside a type: one in a namespace fails with CS0116,
- /// and one at file scope with CS0106. Every other target has somewhere to put them, so the AST
+ /// C# has no field, function, variable or entry point outside a type: one in a namespace fails
+ /// with CS0116, and one at file scope with CS0106 (#128). Every other target has somewhere to put them, so the AST
/// allows them, and a static class is what C# has in their place — a reader names
/// Geometry.twice where C++ names Geometry::twice. The class is written where the
/// first of them was declared, and types, enums and nested namespaces stay where they are. Aliases
@@ -765,9 +765,9 @@ private void GenerateMemberHolder(string name, IEnumerable members, Cod
/// Determines whether a member of a namespace or file is one C# can declare only inside a type.
///
/// The member.
- /// True for a field, a function or a variable.
+ /// True for a field, a function, a variable or an entry point.
private static bool IsTypelessMember(AstNode member) =>
- member is FieldDeclaration or FunctionDeclaration or VariableDeclaration;
+ member is FieldDeclaration or FunctionDeclaration or VariableDeclaration or EntryPoint;
///
/// Picks a name for the class holding a scope's members that no type beside it already has.
@@ -1631,7 +1631,8 @@ private void GenerateVariableDeclaration(VariableDeclaration varDecl, CodeBlocke
///
/// Always static, and named with the capital C# gives it. The method is emitted on its own
/// rather than wrapped in a class, because a C# entry point is an ordinary member of whatever
- /// class the document puts it in — including one this generator emits around it.
+ /// class the document puts it in — including the static class this generator writes around one
+ /// declared in a namespace or a file (#128).
///
private void GenerateEntryPoint(EntryPoint entryPoint, CodeBlocker code)
{
diff --git a/Coder/Languages/CppGenerator.cs b/Coder/Languages/CppGenerator.cs
index e03ee4f..642e730 100644
--- a/Coder/Languages/CppGenerator.cs
+++ b/Coder/Languages/CppGenerator.cs
@@ -737,6 +737,13 @@ private void GenerateField(VariableDeclaration field, CodeBlocker code)
EndStatement(code);
}
+ ///
+ ///
+ /// The linker looks for main only at file scope; App::main is an ordinary function
+ /// nothing calls.
+ ///
+ protected override bool EntryPointsLeaveNamespaces => true;
+
///
///
/// C++'s main returns int whether or not the program means to hand back an exit
@@ -765,6 +772,13 @@ protected override void GenerateEntryPoint(EntryPoint entryPoint, CodeBlocker co
using IDisposable types = OpenStaticTypeScope(entryPoint);
using Scope body = new(code);
+ if (NamespacesLeftBy(entryPoint) is [_, ..] namespaces)
+ {
+ // Written at file scope, where the linker looks for it, so the names declared beside it in
+ // its namespace are brought back in (#128).
+ code.WriteLine($"using namespace {string.Join("::", namespaces)};");
+ }
+
if (entryPoint.AcceptsArguments)
{
code.WriteLine("std::vector args(argv + 1, argv + argc);");
diff --git a/Coder/Languages/RustGenerator.cs b/Coder/Languages/RustGenerator.cs
index 73f5e9f..463706e 100644
--- a/Coder/Languages/RustGenerator.cs
+++ b/Coder/Languages/RustGenerator.cs
@@ -1948,6 +1948,7 @@ protected override void GenerateEntryPoint(EntryPoint entryPoint, CodeBlocker co
using (Scope helper = new(code))
{
+ WriteModuleImport(entryPoint, code);
WriteBody(entryPoint.Body, code);
}
@@ -1963,6 +1964,7 @@ protected override void GenerateEntryPoint(EntryPoint entryPoint, CodeBlocker co
using Scope body = new(code);
+ WriteModuleImport(entryPoint, code);
if (entryPoint.AcceptsArguments)
{
code.WriteLine($"let args: Vec = {collect};");
@@ -1971,6 +1973,32 @@ protected override void GenerateEntryPoint(EntryPoint entryPoint, CodeBlocker co
WriteBody(entryPoint.Body, code);
}
+ ///
+ ///
+ /// The compiler looks for main only at the crate root; one in a module is an ordinary
+ /// private function and the crate fails with E0601.
+ ///
+ protected override bool EntryPointsLeaveNamespaces => true;
+
+ ///
+ /// Brings the names of the module an entry point was declared in into its body.
+ ///
+ /// The entry point being written.
+ /// The writer to emit into.
+ ///
+ /// The entry point is written at the crate root, where the compiler looks for it, and the body
+ /// still names what was declared beside it (#128). A use inside the function reaches the
+ /// module without adding anything to the crate root's own names.
+ ///
+ private void WriteModuleImport(EntryPoint entryPoint, CodeBlocker code)
+ {
+ if (NamespacesLeftBy(entryPoint) is [_, ..] modules)
+ {
+ code.WriteLine("#[allow(unused_imports)]");
+ code.WriteLine($"use self::{string.Join("::", modules)}::*;");
+ }
+ }
+
///
/// Spells what a declaration said about who may see it, with a trailing space.
///
diff --git a/Coder/Languages/StandardLanguageGenerator.cs b/Coder/Languages/StandardLanguageGenerator.cs
index b3a22e2..97fb24b 100644
--- a/Coder/Languages/StandardLanguageGenerator.cs
+++ b/Coder/Languages/StandardLanguageGenerator.cs
@@ -4,6 +4,7 @@ namespace ktsu.Coder.Languages;
using System;
using System.Collections.Generic;
+using System.Collections.ObjectModel;
using System.Globalization;
using System.Linq;
using ktsu.Coder.Ast;
@@ -594,6 +595,12 @@ protected sealed override void GenerateInternal(AstNode node, CodeBlocker code)
Ensure.NotNull(node);
Ensure.NotNull(code);
+ if (IsRootHoldingEntryPoint(node))
+ {
+ GenerateWithEntryPointsAfter(node, code);
+ return;
+ }
+
switch (node)
{
case ClassDeclaration classDecl:
@@ -605,7 +612,7 @@ protected sealed override void GenerateInternal(AstNode node, CodeBlocker code)
break;
case SourceFile file:
- GenerateSourceFile(file, code);
+ GenerateSourceFile(WithEntryPointsAtFileScope(file), code);
break;
case NamespaceDeclaration namespaceDecl:
@@ -1050,6 +1057,205 @@ private IEnumerable Separate(PropertyDeclaration property, HashSet
+ /// Gets a value indicating whether an entry point declared in a namespace is written at file scope.
+ ///
+ ///
+ /// True for a language whose toolchain looks for its entry point only at file scope and whose
+ /// namespaces are real scopes: C++ does not link a main declared in a namespace, and Rust
+ /// does not find one declared in a module (#128). The languages that write a namespace's members
+ /// straight into the file have nothing to move.
+ ///
+ protected virtual bool EntryPointsLeaveNamespaces => false;
+
+ ///
+ /// Whether the root being generated is the copy with its entry points taken out.
+ ///
+ private bool generatingView;
+
+ ///
+ /// The namespaces each entry point written at file scope was declared in, outermost first.
+ ///
+ private Dictionary> relocatedEntryPoints = new(ReferenceEqualityComparer.Instance);
+
+ ///
+ /// Gets the namespaces an entry point was declared in before it was moved to file scope.
+ ///
+ /// The entry point being written.
+ /// The namespace names, outermost first, or nothing when it was declared outside one.
+ ///
+ /// The body was written expecting the names declared beside it to be in scope, so a generator
+ /// that moved it out of its namespace brings that namespace's names back into the body.
+ ///
+ protected IReadOnlyList NamespacesLeftBy(EntryPoint entryPoint) =>
+ relocatedEntryPoints.TryGetValue(entryPoint, out IReadOnlyList? path) ? path : [];
+
+ ///
+ /// Determines whether a node is a type or namespace generated on its own with an entry point in it.
+ ///
+ /// The node being dispatched.
+ /// True when its entry points have to be written after it, at file scope.
+ private bool IsRootHoldingEntryPoint(AstNode node) =>
+ !generatingView && ReferenceEquals(node, GenerationRoot) && HoldsEntryPoint(node);
+
+ ///
+ /// Determines whether a type or namespace has an entry point anywhere inside it.
+ ///
+ /// The node to search.
+ /// True when one does.
+ private static bool HoldsEntryPoint(AstNode node) =>
+ node is ClassDeclaration or NamespaceDeclaration && SelfAndDescendants(node).OfType().Any();
+
+ ///
+ /// Returns a file whose entry points all stand where the toolchain looks for them.
+ ///
+ /// The file as the document declared it.
+ /// The file itself when nothing moves, otherwise a copy with the entry points last.
+ ///
+ /// No language here other than C# finds an entry point inside a type: C++ does not link a member
+ /// main, C cannot put a function in a struct, and Rust and Go route a type's members by
+ /// what they are and dropped an entry point without a word. So an entry point in a type, and in a
+ /// language that asks for it one in a namespace too, is written at file scope after everything
+ /// else (#128). The containers are copies holding the document's own members, so the members
+ /// still meet every check by identity.
+ ///
+ private SourceFile WithEntryPointsAtFileScope(SourceFile file)
+ {
+ relocatedEntryPoints = new(ReferenceEqualityComparer.Instance);
+ if (!file.Members.Any(HoldsEntryPoint))
+ {
+ return file;
+ }
+
+ Dictionary> relocated = new(ReferenceEqualityComparer.Instance);
+ List moved = [];
+ List kept = WithoutEntryPoints(file.Members, [], moveHere: false, relocated, moved);
+ relocatedEntryPoints = relocated;
+
+ SourceFile copy = (SourceFile)file.Clone();
+ copy.Members.Clear();
+ foreach (AstNode member in kept.Concat(moved))
+ {
+ copy.Members.Add(member);
+ }
+
+ return copy;
+ }
+
+ ///
+ /// Emits a type or namespace generated on its own, then the entry points it holds at file scope.
+ ///
+ /// The type or namespace being generated.
+ /// The writer to emit into.
+ private void GenerateWithEntryPointsAfter(AstNode root, CodeBlocker code)
+ {
+ Dictionary> relocated = new(ReferenceEqualityComparer.Instance);
+ List moved = [];
+ AstNode view = WithoutEntryPoints([root], [], moveHere: false, relocated, moved)[0];
+ relocatedEntryPoints = relocated;
+
+ generatingView = true;
+ try
+ {
+ GenerateInternal(view, code);
+ }
+ finally
+ {
+ generatingView = false;
+ }
+
+ foreach (EntryPoint entryPoint in moved)
+ {
+ code.NewLine();
+ GenerateInternal(entryPoint, code);
+ }
+ }
+
+ ///
+ /// Copies a run of members, leaving out the entry points that belong at file scope.
+ ///
+ /// The members as declared.
+ /// The namespaces enclosing them, outermost first.
+ /// Whether an entry point standing directly among them is moved.
+ /// Where each moved entry point is recorded with its namespaces.
+ /// The moved entry points, in the order declared.
+ /// The members that stay, with any container that lost an entry point copied.
+ private List WithoutEntryPoints(
+ IEnumerable members,
+ IReadOnlyList namespaces,
+ bool moveHere,
+ Dictionary> relocated,
+ List moved)
+ {
+ List kept = [];
+ foreach (AstNode member in members)
+ {
+ int before = moved.Count;
+ switch (member)
+ {
+ case EntryPoint entryPoint when moveHere:
+ relocated[entryPoint] = namespaces;
+ moved.Add(entryPoint);
+ continue;
+
+ case ClassDeclaration classDecl when HoldsEntryPoint(classDecl):
+ {
+ List inner = WithoutEntryPoints(classDecl.Members, namespaces, moveHere: true, relocated, moved);
+ if (moved.Count == before)
+ {
+ break;
+ }
+
+ ClassDeclaration copy = (ClassDeclaration)classDecl.Clone();
+ for (int index = 0; index < classDecl.Annotations.Count; index++)
+ {
+ RememberAnnotationClone(copy.Annotations[index], classDecl.Annotations[index]);
+ }
+
+ Refill(copy.Members, inner);
+ kept.Add(copy);
+ continue;
+ }
+
+ case NamespaceDeclaration namespaceDecl when HoldsEntryPoint(namespaceDecl):
+ {
+ IReadOnlyList inside = [.. namespaces, .. NamespaceDeclaration.Split(namespaceDecl.Name)];
+ List inner = WithoutEntryPoints(namespaceDecl.Members, inside, EntryPointsLeaveNamespaces, relocated, moved);
+ if (moved.Count == before)
+ {
+ break;
+ }
+
+ NamespaceDeclaration copy = (NamespaceDeclaration)namespaceDecl.Clone();
+ Refill(copy.Members, inner);
+ kept.Add(copy);
+ continue;
+ }
+
+ default:
+ break;
+ }
+
+ kept.Add(member);
+ }
+
+ return kept;
+ }
+
+ ///
+ /// Replaces what a collection holds.
+ ///
+ /// The collection to refill.
+ /// What it should hold.
+ private static void Refill(Collection target, IEnumerable members)
+ {
+ target.Clear();
+ foreach (AstNode member in members)
+ {
+ target.Add(member);
+ }
+ }
+
///
/// Emits the program's entry point, and whatever else the language needs in order to run it.
///