Esempio n. 1
0
        protected void Compile(IEnumerable <string> sources, IMetadataImporter metadataImporter = null, INamer namer = null, IRuntimeLibrary runtimeLibrary = null, IErrorReporter errorReporter = null, Action <IMethod, JsFunctionDefinitionExpression, MethodCompiler> methodCompiled = null, IList <string> defineConstants = null, bool allowUserDefinedStructs = true, IEnumerable <IAssemblyReference> references = null)
        {
            var  sourceFiles          = sources.Select((s, i) => new MockSourceFile("File" + i + ".cs", s)).ToList();
            bool defaultErrorHandling = false;

            if (errorReporter == null)
            {
                defaultErrorHandling = true;
                errorReporter        = new MockErrorReporter(true);
            }

            var compiler = new Saltarelle.Compiler.Compiler.Compiler(metadataImporter ?? new MockMetadataImporter(), namer ?? new MockNamer(), runtimeLibrary ?? new MockRuntimeLibrary(), errorReporter);

            compiler.AllowUserDefinedStructs = allowUserDefinedStructs;
            if (methodCompiled != null)
            {
                compiler.MethodCompiled += methodCompiled;
            }

            var c = PreparedCompilation.CreateCompilation(sourceFiles, references ?? new[] { Common.Mscorlib }, defineConstants);

            CompiledTypes = compiler.Compile(c).AsReadOnly();
            if (defaultErrorHandling)
            {
                ((MockErrorReporter)errorReporter).AllMessages.Should().BeEmpty("Compile should not generate errors");
            }
        }
        internal Tuple <string, ICompilation, IMetadataImporter, MockErrorReporter> Compile(string source, bool includeLinq = false, bool expectErrors = false)
        {
            var sourceFile = new MockSourceFile("file.cs", source);
            var md         = new MetadataImporter.ScriptSharpMetadataImporter(false);
            var n          = new DefaultNamer();
            var er         = new MockErrorReporter(!expectErrors);
            PreparedCompilation compilation = null;
            var rtl      = new ScriptSharpRuntimeLibrary(md, er, n.GetTypeParameterName, tr => { var t = tr.Resolve(compilation.Compilation).GetDefinition(); return(new JsTypeReferenceExpression(t.ParentAssembly, md.GetTypeSemantics(t).Name)); });
            var compiler = new Compiler.Compiler(md, n, rtl, er, allowUserDefinedStructs: false);

            var references = includeLinq ? new[] { Common.Mscorlib, Common.Linq } : new[] { Common.Mscorlib };

            compilation = compiler.CreateCompilation(new[] { sourceFile }, references, null);
            var compiledTypes = compiler.Compile(compilation);

            if (expectErrors)
            {
                Assert.That(er.AllMessages, Is.Not.Empty, "Compile should have generated errors");
                return(Tuple.Create((string)null, compilation.Compilation, (IMetadataImporter)md, er));
            }

            er.AllMessagesText.Should().BeEmpty("Compile should not generate errors");

            var js = new OOPEmulator.ScriptSharpOOPEmulator(compilation.Compilation, md, rtl, er).Rewrite(compiledTypes, compilation.Compilation);

            js = new GlobalNamespaceReferenceImporter().ImportReferences(js);

            string script = string.Join("", js.Select(s => OutputFormatter.Format(s, allowIntermediates: false)));

            if (Output == OutputType.GeneratedScript)
            {
                Console.WriteLine(script);
            }
            return(Tuple.Create(script, compilation.Compilation, (IMetadataImporter)md, er));
        }
        private IList <JsType> Compile(string program)
        {
            var compilation = PreparedCompilation.CreateCompilation("X", new[] { new MockSourceFile("file.cs", program) }, new[] { Common.Mscorlib }, new string[0]);
            var compiler    = new Compiler.Compiler(new MockMetadataImporter(), new MockNamer(), new MockRuntimeLibrary(), new MockErrorReporter());

            return(compiler.Compile(compilation).ToList());
        }
Esempio n. 4
0
        public static Tuple <string, MockErrorReporter> Compile(string source, bool expectErrors = false)
        {
            var sourceFile  = new MockSourceFile("file.cs", source);
            var er          = new MockErrorReporter(!expectErrors);
            var n           = new Namer();
            var references  = new[] { Files.Mscorlib };
            var compilation = PreparedCompilation.CreateCompilation("x", new[] { sourceFile }, references, null);;
            var s           = new AttributeStore(compilation.Compilation, er);
            var md          = new MetadataImporter(er, compilation.Compilation, s, new CompilerOptions());
            var rtl         = new RuntimeLibrary(md, er, compilation.Compilation, n, s);
            var l           = new MockLinker();

            md.Prepare(compilation.Compilation.GetAllTypeDefinitions());
            var compiler = new Compiler(md, n, rtl, er);

            var compiledTypes = compiler.Compile(compilation).ToList();

            if (expectErrors)
            {
                Assert.That(er.AllMessages, Is.Not.Empty, "Compile should have generated errors");
                return(Tuple.Create((string)null, er));
            }

            Assert.That(er.AllMessages, Is.Empty, "Compile should not generate errors");

            var js = new OOPEmulatorInvoker(new OOPEmulator(compilation.Compilation, md, rtl, n, l, s, er), md, er).Process(compiledTypes, null);

            js = new Linker(md, n, s, compilation.Compilation).Process(js);

            string script = OutputFormatter.Format(js, allowIntermediates: false);

            return(Tuple.Create(script, er));
        }
        private static ScriptCompilationResult BuildScriptCompilationResult(AsmDetail detailsToUseForTarget, EmitResult emitResult,
                                                                            PreparedCompilation compilationPrep)
        {
            var result = new ScriptCompilationResult {
                CompilationResult = emitResult
            };

            if (emitResult.Diagnostics.Count(d => d.Severity == DiagnosticSeverity.Error) == 0)
            {
                result.IsCompiled       = true;
                result.AssemblyFilePath = detailsToUseForTarget.AsmPath;
                result.PdbFilePath      = detailsToUseForTarget.PdbPath;
                result.FoundNamespaces.AddRange(compilationPrep.Usings);
                result.FoundMetadataReferences.AddRange(compilationPrep.MetadataReferences);
            }

            return(result);
        }
        protected Tuple <ICompilation, IOOPEmulator, List <JsType> > Compile(string source, IEnumerable <IAssemblyResource> resources = null, IErrorReporter errorReporter = null)
        {
            errorReporter = errorReporter ?? new MockErrorReporter(true);
            var sourceFile  = new MockSourceFile("file.cs", source);
            var n           = new Namer();
            var references  = new[] { Files.Mscorlib };
            var compilation = PreparedCompilation.CreateCompilation("x", new[] { sourceFile }, references, null, resources);
            var s           = new AttributeStore(compilation.Compilation, errorReporter);

            RunAutomaticMetadataAttributeAppliers(s, compilation.Compilation);
            s.RunAttributeCode();
            var md  = new MetadataImporter(errorReporter, compilation.Compilation, s, new CompilerOptions());
            var rtl = new RuntimeLibrary(md, errorReporter, compilation.Compilation, n, s);

            md.Prepare(compilation.Compilation.GetAllTypeDefinitions());
            var compiler      = new Compiler(md, n, rtl, errorReporter);
            var compiledTypes = compiler.Compile(compilation).ToList();

            return(Tuple.Create(compilation.Compilation, (IOOPEmulator) new OOPEmulator(compilation.Compilation, md, rtl, n, new MockLinker(), s, errorReporter), compiledTypes));
        }
Esempio n. 7
0
        private Tuple <JsClass, MockErrorReporter> Compile(string source, bool expectErrors = false)
        {
            var sourceFile  = new MockSourceFile("file.cs", source);
            var er          = new MockErrorReporter(!expectErrors);
            var n           = new Namer();
            var references  = new[] { Mscorlib, QUnit };
            var compilation = PreparedCompilation.CreateCompilation("Test", new[] { sourceFile }, references, null);
            var s           = new AttributeStore(compilation.Compilation, er);

            s.RunAttributeCode();
            var md  = new MetadataImporter(er, compilation.Compilation, s, new CompilerOptions());
            var rtl = new RuntimeLibrary(md, er, compilation.Compilation, n, s);

            md.Prepare(compilation.Compilation.GetAllTypeDefinitions());
            var compiler = new Compiler(md, n, rtl, er);

            var result = compiler.Compile(compilation).ToList();

            Assert.That(result, Has.Count.EqualTo(1), "Should compile exactly one type");
            Assert.That(er.AllMessages, Is.Empty, "Compile should not generate errors");

            result = new TestRewriter(er, rtl, s).Rewrite(result).ToList();
            Assert.That(result, Has.Count.EqualTo(1), "Should have one type after rewrite");
            Assert.That(result[0], Is.InstanceOf <JsClass>(), "Compiled type should be a class after rewrite");

            if (expectErrors)
            {
                Assert.That(er.AllMessages, Is.Not.Empty);
            }
            else
            {
                Assert.That(er.AllMessages, Is.Empty);
            }

            return(Tuple.Create((JsClass)result[0], er));
        }
        protected string Process(string source, string[] typeNames = null, string entryPoint = null, IErrorReporter errorReporter = null)
        {
            bool assertNoErrors = errorReporter == null;

            errorReporter = errorReporter ?? new MockErrorReporter(true);
            var sourceFile  = new MockSourceFile("file.cs", source);
            var n           = new Namer();
            var references  = new[] { Files.Mscorlib };
            var compilation = PreparedCompilation.CreateCompilation(new[] { sourceFile }, references, null);;
            var md          = new MetadataImporter(errorReporter, compilation.Compilation, new CompilerOptions());
            var rtl         = new RuntimeLibrary(md, errorReporter, compilation.Compilation, n);

            md.Prepare(compilation.Compilation.GetAllTypeDefinitions());
            var     compiler      = new Compiler(md, n, rtl, errorReporter);
            var     compiledTypes = compiler.Compile(compilation).ToList();
            var     obj           = new OOPEmulator(compilation.Compilation, md, rtl, n, errorReporter);
            IMethod ep;

            if (entryPoint != null)
            {
                var type = compiledTypes.Single(c => c.CSharpTypeDefinition.FullName == entryPoint.Substring(0, entryPoint.IndexOf('.')));
                ep = type.CSharpTypeDefinition.Methods.Single(m => m.FullName == entryPoint);
            }
            else
            {
                ep = null;
            }
            var rewritten = obj.Process(compiledTypes.Where(t => typeNames == null || typeNames.Contains(t.CSharpTypeDefinition.FullName)), ep);

            if (assertNoErrors)
            {
                Assert.That(((MockErrorReporter)errorReporter).AllMessages, Is.Empty, "Should not have errors");
            }

            return(string.Join("", rewritten.Select(s => OutputFormatter.Format(s, allowIntermediates: true))));
        }
            private IMethod FindEntryPoint(CompilerOptions options, ErrorReporterWrapper er, PreparedCompilation compilation)
            {
                if (options.HasEntryPoint)
                {
                    List <IMethod> candidates;
                    if (!string.IsNullOrEmpty(options.EntryPointClass))
                    {
                        var t = compilation.Compilation.MainAssembly.GetTypeDefinition(new FullTypeName(options.EntryPointClass));
                        if (t == null)
                        {
                            er.Region = DomRegion.Empty;
                            er.Message(Messages._7950, "Could not find the entry point class " + options.EntryPointClass + ".");
                            return(null);
                        }
                        candidates = t.Methods.Where(IsEntryPointCandidate).ToList();
                    }
                    else
                    {
                        candidates =
                            compilation.Compilation.MainAssembly.GetAllTypeDefinitions()
                            .SelectMany(t => t.Methods)
                            .Where(IsEntryPointCandidate)
                            .ToList();
                    }
                    if (candidates.Count != 1)
                    {
                        er.Region = DomRegion.Empty;
                        er.Message(Messages._7950, "Could not find a unique entry point.");
                        return(null);
                    }
                    return(candidates[0]);
                }

                return(null);
            }
            public bool Compile(CompilerOptions options, ErrorReporterWrapper er)
            {
                string intermediateAssemblyFile = Path.GetTempFileName(), intermediateDocFile = Path.GetTempFileName();

                try {
                    // Compile the assembly
                    var settings = MapSettings(options, intermediateAssemblyFile, intermediateDocFile, er);
                    if (er.HasErrors)
                    {
                        return(false);
                    }

                    if (!options.AlreadyCompiled)
                    {
                        // Compile the assembly
                        var ctx = new CompilerContext(settings, new ConvertingReportPrinter(er));
                        var d   = new Mono.CSharp.Driver(ctx);
                        d.Compile();
                        if (er.HasErrors)
                        {
                            return(false);
                        }
                    }

                    var references = LoadReferences(settings.AssemblyReferences, er);
                    if (references == null)
                    {
                        return(false);
                    }

                    PreparedCompilation compilation = PreparedCompilation.CreateCompilation(options.SourceFiles.Select(f => new SimpleSourceFile(f, settings.Encoding)), references.Select(r => r.Item1), options.DefineConstants);

                    IMethod entryPoint = FindEntryPoint(options, er, compilation);

                    var container = new WindsorContainer();
                    foreach (var plugin in TopologicalSortPlugins(references).Reverse())
                    {
                        RegisterPlugin(container, plugin);
                    }

                    // Compile the script
                    container.Register(Component.For <IErrorReporter>().Instance(er),
                                       Component.For <CompilerOptions>().Instance(options),
                                       Component.For <ICompilation>().Instance(compilation.Compilation),
                                       Component.For <ICompiler>().ImplementedBy <Compiler.Compiler>()
                                       );

                    container.Resolve <IMetadataImporter>().Prepare(compilation.Compilation.GetAllTypeDefinitions());

                    var compiledTypes = container.Resolve <ICompiler>().Compile(compilation);

                    foreach (var rewriter in container.ResolveAll <IJSTypeSystemRewriter>())
                    {
                        compiledTypes = rewriter.Rewrite(compiledTypes);
                    }

                    var js = container.Resolve <IOOPEmulator>().Process(compiledTypes, entryPoint);
                    js = container.Resolve <ILinker>().Process(js);

                    if (er.HasErrors)
                    {
                        return(false);
                    }

                    string outputAssemblyPath = !string.IsNullOrEmpty(options.OutputAssemblyPath) ? options.OutputAssemblyPath : Path.ChangeExtension(options.SourceFiles[0], ".dll");
                    string outputScriptPath   = !string.IsNullOrEmpty(options.OutputScriptPath)   ? options.OutputScriptPath   : Path.ChangeExtension(options.SourceFiles[0], ".js");

                    if (!options.AlreadyCompiled)
                    {
                        try {
                            File.Copy(intermediateAssemblyFile, outputAssemblyPath, true);
                        }
                        catch (IOException ex) {
                            er.Region = DomRegion.Empty;
                            er.Message(Messages._7950, ex.Message);
                            return(false);
                        }
                        if (!string.IsNullOrEmpty(options.DocumentationFile))
                        {
                            try {
                                File.Copy(intermediateDocFile, options.DocumentationFile, true);
                            }
                            catch (IOException ex) {
                                er.Region = DomRegion.Empty;
                                er.Message(Messages._7952, ex.Message);
                                return(false);
                            }
                        }
                    }

                    if (options.MinimizeScript)
                    {
                        js = ((JsBlockStatement)Minifier.Process(new JsBlockStatement(js))).Statements;
                    }

                    string script = string.Join("", js.Select(s => options.MinimizeScript ? OutputFormatter.FormatMinified(s) : OutputFormatter.Format(s)));
                    try {
                        File.WriteAllText(outputScriptPath, script, settings.Encoding);
                    }
                    catch (IOException ex) {
                        er.Region = DomRegion.Empty;
                        er.Message(Messages._7951, ex.Message);
                        return(false);
                    }
                    return(true);
                }
                catch (Exception ex) {
                    er.Region = DomRegion.Empty;
                    er.InternalError(ex.ToString());
                    return(false);
                }
                finally {
                    if (!options.AlreadyCompiled)
                    {
                        try { File.Delete(intermediateAssemblyFile); } catch {}
                        try { File.Delete(intermediateDocFile); } catch {}
                    }
                }
            }
Esempio n. 11
0
            public bool Compile(CompilerOptions options, ErrorReporterWrapper er)
            {
                string intermediateAssemblyFile = Path.GetTempFileName(), intermediateDocFile = Path.GetTempFileName();

                try {
                    // Compile the assembly
                    var settings = MapSettings(options, intermediateAssemblyFile, intermediateDocFile, er);
                    if (er.HasErrors)
                    {
                        return(false);
                    }

                    if (!options.AlreadyCompiled)
                    {
                        // Compile the assembly
                        var ctx = new CompilerContext(settings, new ConvertingReportPrinter(er));
                        var d   = new Mono.CSharp.Driver(ctx);
                        d.Compile();
                        if (er.HasErrors)
                        {
                            return(false);
                        }
                    }

                    // Compile the script
                    var md = new MetadataImporter.ScriptSharpMetadataImporter(options.MinimizeScript);
                    var n  = new DefaultNamer();
                    PreparedCompilation compilation = null;
                    var rtl      = new ScriptSharpRuntimeLibrary(md, er, n.GetTypeParameterName, tr => new JsTypeReferenceExpression(tr.Resolve(compilation.Compilation).GetDefinition()));
                    var compiler = new Compiler.Compiler(md, n, rtl, er, allowUserDefinedStructs: options.References.Count == 0 /* We allow user-defined structs in mscorlib only, which can be identified by the fact that it has no references*/);

                    var references = LoadReferences(settings.AssemblyReferences, er);
                    if (references == null)
                    {
                        return(false);
                    }

                    compilation = compiler.CreateCompilation(options.SourceFiles.Select(f => new SimpleSourceFile(f, settings.Encoding)), references, options.DefineConstants);
                    var compiledTypes = compiler.Compile(compilation);

                    IMethod entryPoint = null;
                    if (options.HasEntryPoint)
                    {
                        List <IMethod> candidates;
                        if (!string.IsNullOrEmpty(options.EntryPointClass))
                        {
                            var t = compilation.Compilation.MainAssembly.GetTypeDefinition(new FullTypeName(options.EntryPointClass));
                            if (t == null)
                            {
                                er.Region = DomRegion.Empty;
                                er.Message(7950, "Could not find the entry point class " + options.EntryPointClass + ".");
                                return(false);
                            }
                            candidates = t.Methods.Where(IsEntryPointCandidate).ToList();
                        }
                        else
                        {
                            candidates = compilation.Compilation.MainAssembly.GetAllTypeDefinitions().SelectMany(t => t.Methods).Where(IsEntryPointCandidate).ToList();
                        }
                        if (candidates.Count != 1)
                        {
                            er.Region = DomRegion.Empty;
                            er.Message(7950, "Could not find a unique entry point.");
                            return(false);
                        }
                        entryPoint = candidates[0];
                    }

                    var js = new ScriptSharpOOPEmulator(compilation.Compilation, md, rtl, n, er).Process(compiledTypes, compilation.Compilation, entryPoint);
                    js = new DefaultLinker(md, n).Process(js, compilation.Compilation.MainAssembly);

                    if (er.HasErrors)
                    {
                        return(false);
                    }

                    string outputAssemblyPath = !string.IsNullOrEmpty(options.OutputAssemblyPath) ? options.OutputAssemblyPath : Path.ChangeExtension(options.SourceFiles[0], ".dll");
                    string outputScriptPath   = !string.IsNullOrEmpty(options.OutputScriptPath)   ? options.OutputScriptPath   : Path.ChangeExtension(options.SourceFiles[0], ".js");

                    if (!options.AlreadyCompiled)
                    {
                        try {
                            File.Copy(intermediateAssemblyFile, outputAssemblyPath, true);
                        }
                        catch (IOException ex) {
                            er.Region = DomRegion.Empty;
                            er.Message(7950, ex.Message);
                            return(false);
                        }
                        if (!string.IsNullOrEmpty(options.DocumentationFile))
                        {
                            try {
                                File.Copy(intermediateDocFile, options.DocumentationFile, true);
                            }
                            catch (IOException ex) {
                                er.Region = DomRegion.Empty;
                                er.Message(7952, ex.Message);
                                return(false);
                            }
                        }
                    }

                    if (options.MinimizeScript)
                    {
                        js = ((JsBlockStatement)Minifier.Process(new JsBlockStatement(js))).Statements;
                    }

                    string script = string.Join("", js.Select(s => options.MinimizeScript ? OutputFormatter.FormatMinified(s) : OutputFormatter.Format(s)));
                    try {
                        File.WriteAllText(outputScriptPath, script, settings.Encoding);
                    }
                    catch (IOException ex) {
                        er.Region = DomRegion.Empty;
                        er.Message(7951, ex.Message);
                        return(false);
                    }
                    return(true);
                }
                catch (Exception ex) {
                    er.Region = DomRegion.Empty;
                    er.InternalError(ex.ToString());
                    return(false);
                }
                finally {
                    if (!options.AlreadyCompiled)
                    {
                        try { File.Delete(intermediateAssemblyFile); } catch {}
                        try { File.Delete(intermediateDocFile); } catch {}
                    }
                }
            }
            public bool Compile(CompilerOptions options, ErrorReporterWrapper er)
            {
                string intermediateAssemblyFile = Path.GetTempFileName(), intermediateDocFile = Path.GetTempFileName();

                try {
                    // Compile the assembly
                    var settings = MapSettings(options, intermediateAssemblyFile, intermediateDocFile, er);
                    if (er.HasErrors)
                    {
                        return(false);
                    }

                    if (!options.AlreadyCompiled)
                    {
                        // Compile the assembly
                        var ctx = new CompilerContext(settings, new ConvertingReportPrinter(er));
                        var d   = new Mono.CSharp.Driver(ctx);
                        d.Compile();
                        if (er.HasErrors)
                        {
                            return(false);
                        }
                    }

                    // Compile the script
                    var md = new MetadataImporter.ScriptSharpMetadataImporter(options.MinimizeScript);
                    var n  = new DefaultNamer();
                    PreparedCompilation compilation = null;
                    var rtl      = new ScriptSharpRuntimeLibrary(md, er, n.GetTypeParameterName, tr => { var t = tr.Resolve(compilation.Compilation).GetDefinition(); return(new JsTypeReferenceExpression(t.ParentAssembly, md.GetTypeSemantics(t).Name)); });
                    var compiler = new Compiler.Compiler(md, n, rtl, er, allowUserDefinedStructs: options.References.Count == 0 /* We allow user-defined structs in mscorlib only, which can be identified by the fact that it has no references*/);

                    var references = LoadReferences(settings.AssemblyReferences, er);
                    if (references == null)
                    {
                        return(false);
                    }

                    compilation = compiler.CreateCompilation(options.SourceFiles.Select(f => new SimpleSourceFile(f, settings.Encoding)), references, options.DefineConstants);
                    var compiledTypes = compiler.Compile(compilation);

                    var js = new ScriptSharpOOPEmulator(compilation.Compilation, md, rtl, er).Rewrite(compiledTypes, compilation.Compilation);
                    js = new GlobalNamespaceReferenceImporter().ImportReferences(js);

                    if (er.HasErrors)
                    {
                        return(false);
                    }

                    string outputAssemblyPath = !string.IsNullOrEmpty(options.OutputAssemblyPath) ? options.OutputAssemblyPath : Path.ChangeExtension(options.SourceFiles[0], ".dll");
                    string outputScriptPath   = !string.IsNullOrEmpty(options.OutputScriptPath)   ? options.OutputScriptPath   : Path.ChangeExtension(options.SourceFiles[0], ".js");

                    if (!options.AlreadyCompiled)
                    {
                        try {
                            File.Copy(intermediateAssemblyFile, outputAssemblyPath, true);
                        }
                        catch (IOException ex) {
                            er.Region = DomRegion.Empty;
                            er.Message(7950, ex.Message);
                            return(false);
                        }
                        if (!string.IsNullOrEmpty(options.DocumentationFile))
                        {
                            try {
                                File.Copy(intermediateDocFile, options.DocumentationFile, true);
                            }
                            catch (IOException ex) {
                                er.Region = DomRegion.Empty;
                                er.Message(7952, ex.Message);
                                return(false);
                            }
                        }
                    }

                    string script = string.Join("", js.Select(s => options.MinimizeScript ? OutputFormatter.FormatMinified(Minifier.Process(s)) : OutputFormatter.Format(s)));
                    try {
                        File.WriteAllText(outputScriptPath, script, settings.Encoding);
                    }
                    catch (IOException ex) {
                        er.Region = DomRegion.Empty;
                        er.Message(7951, ex.Message);
                        return(false);
                    }
                    return(true);
                }
                catch (Exception ex) {
                    er.Region = DomRegion.Empty;
                    er.InternalError(ex.ToString());
                    return(false);
                }
                finally {
                    if (!options.AlreadyCompiled)
                    {
                        try { File.Delete(intermediateAssemblyFile); } catch {}
                        try { File.Delete(intermediateDocFile); } catch {}
                    }
                }
            }