示例#1
0
    public override async Task GenerateInTranslationCreateClass(ObjectGeneration obj, StructuredStringBuilder sb)
    {
        using (var args = new Function(sb,
                                       $"public static void FillPublic{ModuleNickname}"))
        {
            args.Add($"{obj.Interface(getter: false, internalInterface: true)} item");
            args.Add($"XElement {XElementLine.GetParameterName(obj, Context.Backend)}");
            foreach (var item in MainAPI.ReaderAPI.CustomAPI)
            {
                if (!item.API.TryResolve(obj, TranslationDirection.Reader, Context.Backend, out var line))
                {
                    continue;
                }
                args.Add(line.Result);
            }
            args.Add($"ErrorMaskBuilder? errorMask");
            args.Add($"{nameof(TranslationCrystal)}? translationMask");
        }
        using (sb.CurlyBrace())
        {
            sb.AppendLine("try");
            using (sb.CurlyBrace())
            {
                sb.AppendLine($"foreach (var elem in {XElementLine.GetParameterName(obj, Context.Backend)}.Elements())");
                using (sb.CurlyBrace())
                {
                    using (var args = sb.Call(
                               $"{TranslationCreateClass(obj)}.FillPublicElement{ModuleNickname}"))
                    {
                        args.Add("item: item");
                        args.Add($"{XElementLine.GetParameterName(obj, Context.Backend)}: elem");
                        args.Add("name: elem.Name.LocalName");
                        args.Add("errorMask: errorMask");
                        if (TranslationMaskParameter)
                        {
                            args.Add("translationMask: translationMask");
                        }
                        foreach (var item in MainAPI.ReaderAPI.CustomAPI)
                        {
                            if (!item.API.TryGetPassthrough(obj, TranslationDirection.Reader, Context.Backend, out var passthrough))
                            {
                                continue;
                            }
                            args.Add(passthrough);
                        }
                    }
                }
            }
            sb.AppendLine("catch (Exception ex)");
            sb.AppendLine("when (errorMask != null)");
            using (sb.CurlyBrace())
            {
                sb.AppendLine("errorMask.ReportException(ex);");
            }
        }
        sb.AppendLine();

        FillPublicElement(obj, sb);
        await base.GenerateInTranslationCreateClass(obj, sb);
    }
示例#2
0
        public void FunctionDuplicateElementInImage()
        {
            var f = new Function <int, int>();

            f.Add(new OrderedTuple2 <int, int>(1, 2));
            Assert.ThrowsException <DuplicateElementInFunctionImageException>(
                () => f.Add(new OrderedTuple2 <int, int>(1, 3))
                );
        }
    public void GenerateCopyInRet_Internal(
        StructuredStringBuilder sb,
        ObjectGeneration objGen,
        TypeGeneration typeGen,
        Accessor nodeAccessor,
        Accessor itemAccessor,
        bool ret,
        Accessor errorMaskAccessor,
        Accessor translationMaskAccessor)
    {
        var list = typeGen as ListType;

        if (!XmlMod.TryGetTypeGeneration(list.SubTypeGeneration.GetType(), out var subTransl))
        {
            throw new ArgumentException("Unsupported type generator: " + list.SubTypeGeneration);
        }

        if (ret)
        {
            throw new NotImplementedException();
        }

        MaskGenerationUtility.WrapErrorFieldIndexPush(
            sb: sb,
            toDo: () =>
        {
            using (var args = new Function(
                       sb,
                       $"if ({TranslatorName}<{list.SubTypeGeneration.TypeName(getter: false, needsCovariance: true)}>.Instance.Parse"))
            {
                args.AddPassArg($"{XmlTranslationModule.XElementLine.GetParameterName(objGen, Context.Backend)}");
                args.Add($"enumer: out var {typeGen.Name}Item");
                args.Add($"transl: {subTransl.GetTranslatorInstance(list.SubTypeGeneration, getter: false)}.Parse");
                args.Add("errorMask: errorMask");
                args.Add($"translationMask: {translationMaskAccessor})");
            }
            using (sb.CurlyBrace())
            {
                if (typeGen.Nullable)
                {
                    sb.AppendLine($"{itemAccessor.Access} = {typeGen.Name}Item.ToExtendedList();");
                }
                else
                {
                    sb.AppendLine($"{itemAccessor.Access}.SetTo({typeGen.Name}Item);");
                }
            }
            sb.AppendLine("else");
            using (sb.CurlyBrace())
            {
                list.GenerateClear(sb, itemAccessor);
            }
        },
            errorMaskAccessor: errorMaskAccessor,
            indexAccessor: typeGen.IndexEnumInt);
    }
示例#4
0
        public DALViewModel(IRegionManager regionManager, IXmlCompareUserControl UserControl)
        {
            _regionManager = regionManager;
            m_UserControl  = UserControl;
            CancelCommand  = new DelegateCommand(CancelExecute);
            ToCommand      = new DelegateCommand(ToCommandExecute);
            FromCommand    = new DelegateCommand(FromCommandExecute);
            OkCommand      = new DelegateCommand(OkCommandExecute);
            List <CFunction> lstFunc = m_UserControl.GetFunctions();

            foreach (CFunction tmp in lstFunc)
            {
                Function.Add(tmp);
            }
            m_CurrentFunction = null;
        }
示例#5
0
        /// <summary>
        /// Generates service method code.
        /// </summary>
        /// <param name="serviceMethod">the service method</param>
        public void GenerateServiceMethodCode(Function serviceMethod)
        {
            var body = serviceMethod.Annotations.OfType <FunctionBodyAnnotation>().SingleOrDefault();

            serviceMethod.Annotations.RemoveAll(a => a is MethodCodeAnnotation);
            string methodCode = string.Empty;

            // Generate the method code - CodeDom annotations take preference over FunctionBodyAnnotations (QueryExpressions)
            CodeDomBodyAnnotation codeAnnotation = serviceMethod.Annotations.OfType <CodeDomBodyAnnotation>().SingleOrDefault();

            if (codeAnnotation != default(CodeDomBodyAnnotation))
            {
                methodCode = this.GenerateMethodCode(codeAnnotation.Statements, serviceMethod.Model);
            }
            else if (body != default(FunctionBodyAnnotation))
            {
                methodCode = this.GenerateMethodCode(body.FunctionBody, serviceMethod.Model);
            }

            serviceMethod.Add(new MethodCodeAnnotation {
                Code = methodCode
            });
        }
示例#6
0
        void ReadOptionalObjectInfo(OptionalObjectInfo entity, PublicObjectDescriptor parent)
        {
            if (entity == null)
            {
                return;
            }

            UInt32 address = (UInt32)entity.Address + ImageBase;

            VBStruct.Make <OptionalObjectInfo>(entity, address, true);
            Bytes.MakeNameAnyway((UInt32)address, "OptInf_" + parent.Name);

            if (entity.Controls != null && entity.Controls.Length > 0)
            {
                //address = (UInt32)entity.Address + ImageBase;

                if (entity.Controls.Length == 1)
                {
                    address = (UInt32)entity.Controls[0].Address + ImageBase;
                    VBStruct.Make <VBControl>(entity.Controls[0], address, true);
                    Bytes.MakeNameAnyway((UInt32)address, "Control_" + parent.Name);
                }
                else
                {
                    foreach (VBControl item in entity.Controls)
                    {
                        address = (UInt32)item.Address + ImageBase;
                        VBStruct.Make <VBControl>(item, address, true);
                        Bytes.MakeNameAnyway((UInt32)address, "Control_" + parent.Name + "_" + item.Name2);
                    }
                }
            }

            if (entity.EventLinks != null && entity.EventLinks.Length > 0)
            {
                Int32 i = 1;
                foreach (EventLink2 item in entity.EventLinks)
                {
                    address = (UInt32)item.Address + ImageBase;
                    VBStruct.Make <EventLink2>(item, address, true);

                    // 事件列表命名
                    String name = String.Empty;
                    if (parent.ProcNames != null && parent.ProcNames.Length > i - 1)
                    {
                        name = parent.Name + "_" + parent.ProcNames[i - 1].FriendName;
                    }
                    if (String.IsNullOrEmpty(name))
                    {
                        name = parent.Name + "_" + i.ToString("X2");
                    }
                    i++;
                    Bytes.MakeNameAnyway((UInt32)address, "Event_" + name);

                    // 跳转命名
                    address = (UInt32)item.Jump;
                    Bytes.MakeNameAnyway(address, "j" + name);
                    Bytes.MakeCode(address);

                    // 函数命名
                    if (Bytes.Byte(address) == 0xE9)
                    {
                        // Jump语句,下一个字就是函数起始地址
                        address = Bytes.Dword(address + 1) + address + 5;

                        Function func = Function.FindByAddress(address);
                        if (func == null)
                        {
                            // 如果函数不存在,则创建函数
                            Function.Add(address, Bytes.BadAddress);
                            func = Function.FindByAddress(address);
                        }
                        else
                        {
                            // 函数存在,但是函数的起始地址并不是当前行,表明这个函数分析有错,修改地址
                            if (func.Start != address)
                            {
                                //Function.Delete(func.Start);
                                //Function.Add(func.Start, address - 1);
                                func.End = address - 1;

                                Function.Add(address, Bytes.BadAddress);
                                func = Function.FindByAddress(address);
                            }
                        }

                        if (func == null)
                        {
                            KernelWin.WriteLine("0x{0:X} 创建函数失败!", address);
                        }
                        else
                        {
                            Bytes.MakeLabelAnyway(address, name);
                        }
                    }
                }
            }
        }
示例#7
0
    Node ParseRHS(Node lhs, int minprec)
    {
        if ((op.flags & BuiltinOp.unary_only) != 0)
        {
            Error(op.name + " cannot be used as a binary operator");
        }
        var sop = op;

        Next();

        if (sop.name == "=")
        {
            var ph = lhs.t as PlaceHolder;
            if (ph == null)
            {
                Error("left of = must be a variable");
            }
            return(new Node(sop, ParseExp(minprec), lhs)); // swap order to keep consistent L->R eval
        }
        else if (sop.name == "=>")
        {
            var f = prog.GenFun(funnames);
            funnames.Add(f.name);

            //if(curfun!=null)
            // FIXME: what if an error occurs below? we'd have a half finished function
            curfun.Add(f);
            //else topfun = f;

            var bf = curfun;
            curfun = f;
            f.root = ParseExp(minprec);
            curfun = bf;

            if (lhs.t is BuiltinOp)
            {
                switch (lhs.t.name)
                {
                case "=":
                    var ph = lhs.At(1).t as PlaceHolder;
                    f.args.Add(ph);
                    return(new Node(f, lhs.At(0)));

                case "__tuple":
                    lhs.ForEach(n =>
                    {
                        if (!(n.t is PlaceHolder))
                        {
                            Error("lambda parameters must be identifiers");
                        }
                        f.args.Add(n.t as PlaceHolder);
                    });
                    return(new Node(new FunctionValue {
                        reff = f, name = f.name
                    }));                                                               // fixme: dup
                }
            }
            else if (lhs.t is PlaceHolder)
            {
                f.args.Add(lhs.t as PlaceHolder);
                return(new Node(new FunctionValue {
                    reff = f, name = f.name
                }));
            }
            Error("left of => must be a variable/tuple or assignment");
        }
        else if (sop.name == "|>")
        {
            return(new Node(sop, ParseExp(minprec), lhs));   // same order as __apply
        }

        return(new Node(sop, lhs, ParseExp(minprec)));
    }
示例#8
0
    protected virtual void FillPublicElement(ObjectGeneration obj, StructuredStringBuilder sb)
    {
        using (var args = new Function(sb,
                                       $"public static void FillPublicElement{ModuleNickname}"))
        {
            args.Add($"{obj.Interface(getter: false)} item");
            args.Add($"XElement {XElementLine.GetParameterName(obj, Context.Backend)}");
            args.Add("string name");
            args.Add($"ErrorMaskBuilder? errorMask");
            args.Add($"{nameof(TranslationCrystal)}? translationMask");
        }
        using (sb.CurlyBrace())
        {
            sb.AppendLine("switch (name)");
            using (sb.CurlyBrace())
            {
                foreach (var field in obj.IterateFields())
                {
                    if (field.Derivative)
                    {
                        continue;
                    }
                    if (field.ReadOnly)
                    {
                        continue;
                    }
                    if (!TryGetTypeGeneration(field.GetType(), out var generator))
                    {
                        throw new ArgumentException("Unsupported type generator: " + field);
                    }

                    sb.AppendLine($"case \"{field.Name}\":");
                    using (sb.IncreaseDepth())
                    {
                        if (generator.ShouldGenerateCopyIn(field))
                        {
                            List <string> conditions = new List <string>();
                            if (TranslationMaskParameter)
                            {
                                conditions.Add(field.GetTranslationIfAccessor("translationMask"));
                            }
                            if (conditions.Count > 0)
                            {
                                using (var args = sb.If(ands: true))
                                {
                                    foreach (var item in conditions)
                                    {
                                        args.Add(item);
                                    }
                                }
                            }
                            using (sb.CurlyBrace(doIt: conditions.Count > 0))
                            {
                                generator.GenerateCopyIn(
                                    sb: sb,
                                    objGen: obj,
                                    typeGen: field,
                                    nodeAccessor: XElementLine.GetParameterName(obj, Context.Backend).Result,
                                    itemAccessor: Accessor.FromType(field, "item"),
                                    translationMaskAccessor: "translationMask",
                                    errorMaskAccessor: $"errorMask");
                            }
                        }
                        sb.AppendLine("break;");
                    }
                }

                sb.AppendLine("default:");
                using (sb.IncreaseDepth())
                {
                    if (obj.HasLoquiBaseObject)
                    {
                        using (var args = sb.Call(
                                   $"{obj.BaseClass.CommonClassName(LoquiInterfaceType.ISetter)}.FillPublicElement{ModuleNickname}{obj.GetBaseMask_GenericTypes(MaskType.Error)}"))
                        {
                            args.Add("item: item");
                            args.Add($"{XElementLine.GetParameterName(obj, Context.Backend)}: {XElementLine.GetParameterName(obj, Context.Backend)}");
                            args.Add("name: name");
                            args.Add("errorMask: errorMask");
                            if (TranslationMaskParameter)
                            {
                                args.Add($"translationMask: translationMask");
                            }
                        }
                    }
                    sb.AppendLine("break;");
                }
            }
        }
        sb.AppendLine();
    }
示例#9
0
    public virtual void GenerateWriteToNode(ObjectGeneration obj, StructuredStringBuilder sb)
    {
        using (var args = new Function(sb,
                                       $"public static void WriteToNode{ModuleNickname}{obj.GetGenericTypes(MaskType.Normal)}"))
        {
            args.Add($"{obj.Interface(internalInterface: true, getter: true)} item");
            args.Add($"XElement {XElementLine.GetParameterName(obj, Context.Backend)}");
            args.Add($"ErrorMaskBuilder? errorMask");
            args.Add($"{nameof(TranslationCrystal)}? translationMask");
        }
        using (sb.CurlyBrace())
        {
            if (obj.HasLoquiBaseObject)
            {
                using (var args = sb.Call(
                           $"{TranslationWriteClass(obj.BaseClass)}.WriteToNode{ModuleNickname}"))
                {
                    args.Add($"item: item");
                    args.Add($"{XElementLine.GetParameterName(obj, Context.Backend)}: {XElementLine.GetParameterName(obj, Context.Backend)}");
                    args.Add($"errorMask: errorMask");
                    args.Add($"translationMask: translationMask");
                }
            }
            foreach (var field in obj.IterateFieldIndices())
            {
                if (!TryGetTypeGeneration(field.Field.GetType(), out var generator))
                {
                    throw new ArgumentException("Unsupported type generator: " + field.Field);
                }

                if (!generator.ShouldGenerateWrite(field.Field))
                {
                    continue;
                }

                List <string> conditions = new List <string>();
                if (field.Field.Nullable)
                {
                    conditions.Add($"{field.Field.NullableAccessor(getter: true, accessor: Accessor.FromType(field.Field, "item"))}");
                }
                if (TranslationMaskParameter)
                {
                    conditions.Add(field.Field.GetTranslationIfAccessor("translationMask"));
                }
                if (conditions.Count > 0)
                {
                    using (var args = sb.If(ands: true))
                    {
                        foreach (var item in conditions)
                        {
                            args.Add(item);
                        }
                    }
                }
                using (sb.CurlyBrace(doIt: conditions.Count > 0))
                {
                    var maskType = Gen.MaskModule.GetMaskModule(field.Field.GetType()).GetErrorMaskTypeStr(field.Field);
                    generator.GenerateWrite(
                        sb: sb,
                        objGen: obj,
                        typeGen: field.Field,
                        writerAccessor: $"{XElementLine.GetParameterName(obj, Context.Backend)}",
                        itemAccessor: Accessor.FromType(field.Field, "item"),
                        errorMaskAccessor: $"errorMask",
                        translationMaskAccessor: "translationMask",
                        nameAccessor: $"nameof(item.{field.Field.Name})");
                }
            }
        }
        sb.AppendLine();
    }
示例#10
0
    internal string Restructor()
    {
        if (CodeProblems())
        {
            return("Cannot restruct code while there are errors / edits.");
        }

        bool cull_unused_args = false;

        if (cull_unused_args)
        {
            // If by means of editing a function argument (or free variable) has become unused, it
            // can be removed. This also removes all caller argument values, which is contentious,
            // since it may be code the programmer cares about, so ideally should be a separate pass
            // from Restructor where it shows you graphically in the editor what is dead code.
            // Until we have that, we just delete it, to ensure the Restructor algoritm is maximally
            // efficient.
            // FIXME: also, this must not remove parameters needed for the function-value arguments
            // to e.g. game().
            for (;;)
            {
                bool modified = false;
                mf.ForAllFuncs(f =>
                {
                    f.freevars.RemoveAll(ph => !f.ExistsNode(n => n.t == ph));
                    // Have to use a regular loop to ensure element is removed within iteration.
                    for (int i = 0; i < f.args.Count; i++)
                    {
                        var ph = f.args[i];
                        if (!f.ExistsNode(n => n.t == ph))
                        {
                            // FIXME: this also removes code with side effects, which is clearly NOT
                            // ok. A side effect argument should be pulled out of the argument list
                            // and made into a seperate statement.
                            f.args.RemoveAt(i);
                            mf.ForAllNodes(n =>
                            {
                                if (n.t == f)
                                {
                                    n.Remove(i);
                                    modified = true;
                                }
                            });
                            i--;
                        }
                    }
                });
                if (!modified)
                {
                    break;
                }
            }
        }

        for (; ;)
        {
            Debug.WriteLine("Starting Pass");

            // Iterate thru all code, and add every possible TwoNode you find into a map from each
            // possible kind of TwoNode to a list of occurrences. (collecting structurally
            // equivalent copies in the code).
            // For example, if the code is `(1+2)*(1+3)`, then parent `+` with child `1` (at index
            // 0) will be the highest occurring TwoNode.

            var rd = new Dictionary <TwoNode, SameNodeSet>(new TwoNodeEqualityComparer());

            mf.ForAllRoots((n, f) => n.Register(rd, f));

            // Add all map items where the list has at least 2 items to an overal list, and sort
            // that by occurrences.

            var lsns = new List <SameNodeSet>();
            foreach (KeyValuePair <TwoNode, SameNodeSet> kvp in rd)
            {
                if (kvp.Value.l.Count > 1)
                {
                    lsns.Add(kvp.Value);
                }
            }

            lsns.Sort(new SameNodeSetOrderComparer());

            lsns.RemoveAll(sns =>
            {
                // Any node may only participate in one TwoNode, since a refactor is destructive.
                // Combinations that didn't get to participate can be picked up by next passes.
                sns.l.RemoveAll(tn =>
                {
                    bool used = tn.a.refactorused || tn.b.refactorused;
                    // have to do this while checking, because of add(add(add(...))) situations
                    tn.a.refactorused = tn.b.refactorused = true;
                    return(used);
                });
                return(sns.l.Count <= 1);
            });

            // Nothing to refactor, we're done!

            if (lsns.Count == 0)
            {
                break;
            }

            lsns.Sort(new SameNodeSetOrderComparer());

            // Go through the overal list in order of number of occurrences:

            foreach (var sns in lsns)
            {
                var a   = sns.l[0].a;
                var b   = sns.l[0].b;
                var pos = sns.l[0].pos;

                // Create a new function that will have the TwoNode as body. Generate placeholders
                // for all children of the parent that are not the given child, and all children of
                // the child.
                // So here the function would be: `f(x) = 1 + x`
                var f = GenFun();
                f.root = new Node(a.t);

                // Find the lowest common ancestor function that dominates all occurrences, so
                // function is defined as local as possible.Make that the parent function. (this
                // could be skipped if all functions were global).
                Function parent = null;
                foreach (var tn in sns.l)
                {
                    parent = tn.domf.LCA(parent);
                }

                Debug.WriteLine("Make Function " + f.name + " : " + sns.l.Count + " => " +
                                parent.name);

                parent.Add(f);

                for (var i = 0; i < a.Arity(); i++)
                {
                    if (i == pos)
                    {
                        var nb = new Node(b.t);
                        b.ForEach(n => nb.Add(GenVar(f)));
                        f.root.Add(nb);
                    }
                    else
                    {
                        f.root.Add(GenVar(f));
                    }
                }

                // if the child was already a placeholder, it becomes a free variable of the new
                // function, similarly if either node is a function, then its free vars are added
                // to the new function.
                b.IfPlaceHolder(ph => f.freevars.Add(ph));
                a.IfFunction(of => f.AddFreeVarsFrom(of));
                b.IfFunction(of => f.AddFreeVarsFrom(of));

                // if the child wasn't the last child: (because if it is the last child, the order
                // of evaluation vs the other children doesn't change, so no special action is
                // needed!)
                if (pos + 1 < a.Arity())
                {
                    // Find all side-effects in all children of the child. This takes the entire
                    // call graph into account from this node.
                    var bdb = new Dictionary <NodeType, SideEffect>();
                    b.t.CollectSideEffects(bdb, b);

                    // If there are any side - effecs, for each following child, for each of its
                    // occurrences, check if the two children are order dependent.
                    // E.g. `f(a = 1, a)` or `f(a, a = 1)` are order dependent, and we have to
                    // ensure to retain the evaluation order. `f(a = 1, b)` is not order dependent.
                    if (bdb.Count > 0)
                    {
                        for (int p = pos + 1; p < a.Arity(); p++)
                        {
                            foreach (var tn in sns.l)
                            {
                                // TODO: could instead compare against b's dict rather than create
                                var adb = new Dictionary <NodeType, SideEffect>();
                                tn.a.At(p).CollectSideEffects(adb);
                                if (adb.Count > 0)
                                {
                                    foreach (var bkvp in bdb)
                                    {
                                        SideEffect ase;
                                        if (adb.TryGetValue(bkvp.Key, out ase))
                                        {
                                            var bse = bkvp.Value;
                                            if (ase.write || bse.write)
                                            {
                                                // Found an order-dependent occurrence.
                                                // TODO: should really allow for non-se callers to call a
                                                // pure version of function
                                                goto have_se;
                                            }
                                        }
                                    }
                                }
                            }
                            continue;
have_se:
                            foreach (var tn in sns.l)
                            {
                                Function nof = GenFun();
                                parent.Add(nof);
                                nof.root = tn.a.At(p);
                                tn.a.Remove(p);
                                tn.a.Insert(p, new Node(
                                                new FunctionValue {
                                    reff = nof, name = nof.name
                                }));
                                // FIXME: free vars?
                            }
                            var apply = new Node(uniques.applyop, f.root.At(p));
                            f.root.Remove(p);
                            f.root.Insert(p, apply);
                        }
                    }
                }

                f.root.Sanity();

                // Now replace all occurrences with our new function, so our expression becomes
                // `f(2) * f(3)`.

                foreach (var tn in sns.l)
                {
                    Debug.Assert(tn.a.Arity() + tn.b.Arity() - 1 == f.args.Count);
                    Debug.Assert(tn.a.At(pos) == tn.b);

                    tn.a.t = f;
                    tn.a.Remove(pos);

                    tn.b.ForEach((n, j) => tn.a.Insert(pos + j, n));
                    tn.a.Sanity();
                }

                // If any arguments are equal accross all occurrences, merge those arguments.
                f.MergeEqualArgs(sns.l);
            }

            // Inline all functions that have just 1 caller. This is essential, because the above
            // algorithm generates a ton of mini-functions whose body contains just one node, so
            // this inlining is responsible for making those back into the largest possible
            // functions, and shifting the boundaries of abstractions.
            // Going down to one caller is a natural consequence of the above algorithm whenever a
            // function call with multiple occurrences becomes the given child of a new function.
            // Together, these parts of the algorithm create the "emergent" behavior of finding
            // optimal function boundaries regardless of the code structure.
            InlineAll();
        }

        InlineAll();
        Validate();     // should not be needed, just incase restructor trampled on some types

        return("Restructed.");
    }