Composite-oriented Coder

Source: Internet
Author: User

Composite-oriented Coder
Combination-oriented child

 

Combanitor-Oriented is a pattern that recently helped me open the new world door. This is because of haskell. For more information, see monad and ParseC. Finally, several articles by ajoo's predecessors.

Since I officially returned to C # In last September, I have gradually accepted a lot of New paradigm (although the main reason is that the method of using C # in school is too shanzhai ), among them, codegen has a profound impact on me. This codegen is not the codegen in compiler and may be more like the codegen in meta-programming. Abstract: As a step embedded in the building process, you can obtain metadata to generate code.

The specific application scenarios of codegen that I have currently come into use include:
1. RPC-related data unpacking logic, Stub/Skeleton, and Multicast
2. configuration table conversion code
3. Planned and configured visual behavior tree for Code Conversion

From these scenarios, we can see the typical characteristics of this requirement: good performance, easy to call at the upper layer.

Specifically, we will compare this form with some more traditional forms:
The RPC unpack logic directly goes through the function V. S. protobuf.
Codegen into C # code behavior tree V. S. hard decoding script
C # configuration of structure description V. S. meta binary + data binary

It's a bunch of nonsense, and now you can directly go to the topic.

Body

 

First, define a concept, Coder. Of course, this has nothing to do with Coder, which is often caused by some low-end discussions on strings, or Coder in Programmer. Here we regard it as a function, receives a T description structure as a parameter and outputs a string.

For the sake of C #, we define Coder as follows:

    public interface ICoder<in T>    {        string Code(T meta);    }


This is the basic expression of all Coder, and corresponds to it. Any complex code generation program actually generates a string through an abstract data structure.

Based on ICoder, we first start to construct the child from the simplest combination, that is, "0" and "1 ":

    internal class UnitCoder<T> : ICoder<T>    {        readonly string output;        public UnitCoder(string output)        {            this.output = output;        }        public override string Code(T meta)        {            return output;        }    }        internal class ZeroCoder<T> : ICoder<T>    {        private static ZeroCoder<T> instance;        public static ZeroCoder<T> Instance        {            get { return instance ?? (instance = new ZeroCoder<T>()); }        }        public override string Code(T meta)        {            return "";        }    }

 

UnitCoder: returns only one fixed string regardless of the input.
ZeroCoder: returns an empty string regardless of the input.

If there are only two, it seems that nothing can be done. We need a basic Coder that can be customized by us:

    internal class BasicCoder<T> : ICoder<T>    {        private readonly Func<T, string> func;        public BasicCoder(Func<T, string> func)        {            this.func = func;        }        public override string Code(T meta)        {            return func(meta);        }    }


Suppose there is a structure definition:

        class Meta1        {            public string Type;            public string Name;            public string Value;        }


Construct a BasicCoder as follows:

var basicCoder = Generator.GenBasic((Meta1 m) => string.Format(@"{0} {1} = {2}", m.Type, m.Name, m.Value));


In this way, by passing different and specific Meta1 instances to the basicCoder, the Coder produces different code like the real Coder.

Only these three are not enough. We also need to work out a way to combine the two Coder. To be honest, I am very ugly writing this Code. The reason why I have compiled it into a blog is that I hope some predecessors can give some advice. Well, there is obviously bad smell code.
First, we need to transform the most basic ICoder structure:

    public interface ICoder    {        string Code(object meta);    }        public interface ICoder<in T> : ICoder      {        string Code(T meta);    }


In this way, ICoder provides a common Coder interface to facilitate subsequent SequenceCoder. All Coder reuse this logic:

    internal abstract class CoderBase<T> : ICoder<T>    {        private readonly T instance;        public abstract string Code(T meta);        public string Code(object meta)        {            if (meta is T)            {                return Code((T)meta);            }            throw new Exception("...");        }    }

 
Then we started to implement SequenceCoder:

    internal class SequenceCoder<T> : CoderBase<T>    {        readonly ICoder[] coderArr;        readonly Func<T, ICoder[], string> coderJoiner;        public SequenceCoder(ICoder[] coderArr, Func<T, ICoder[], string> coderJoiner)        {            this.coderArr = coderArr;            this.coderJoiner = coderJoiner;        }        public override string Code(T meta)        {            return coderJoiner(meta, coderArr);        }    }

 
My position on SequenceCoder is that a basic component inside the Coder combination subsystem integrates different Coder.
With SequenceCoder, we can add a lot of meaningful things.

Previously we constructed the basicCoder, which is not at the end of the statement ";". Let's construct it. First, some public logics with the prefix and Suffix:

        internal static ICoder<T> WithPostfix<T>(this ICoder<T> coder, string postfix)        {            var coderPostfix = new UnitCoder<T>(postfix);            return new SequenceCoder<T>(new ICoder[] { coder, coderPostfix }, (meta, arr) => string.Join("", coder.Code(meta), coderPostfix.Code(meta)));        }        internal static ICoder<T> WithPrefix<T>(this ICoder<T> coder, string prefix) where        {            var coderPrefix = new UnitCoder<T>(prefix);            return new SequenceCoder<T>(new ICoder[] { coderPrefix, coder }, (meta, arr) => string.Join("", coderPrefix.Code(meta), coder.Code(meta)));        }

 

Then statementCoder:

var statementCoder = basicCoder.WithPostfix(";");


It can also be wrapped in braces:

        public static ICoder<T> Brace<T>(this ICoder<T> coder)        {            return coder.WithPostfix("}").WithPrefix("{");        }

 

var braceStatementCoder = statementCoder.Brace();

 
It can be implemented repeatedly, that is, converting an ICoder <T> to an ICoder <IEnumerable <T>:

    internal class RepeatedCoder<T> : CoderBase<IEnumerable<T>>    {        private readonly ICoder coder;        private readonly string seperator;        private readonly Func<T, bool> predicate;        public RepeatedCoder(ICoder<T> coder, string seperator, Func<T, bool> predicate)        {            this.coder = coder;            this.seperator = seperator;            this.predicate = predicate;        }        public override string Code(IEnumerable<T> meta)        {            bool first = true;            return meta.Where(m=>predicate(m)).Select(m => coder.Code(m)).Aggregate("", (val, cur) =>            {                if (first)                {                    first = false;                    return val + cur;                }                return val + seperator + cur;            });        }    }


For the convenience of writing your own code, you have directly inserted the seperator and predicate logic into it. Please forgive me.

Construct a duplicate Coder:

        public static ICoder<IEnumerable<T>> Many<T>(this ICoder<T> coder, string seperator) where T : class        {            return Generator.GenRepeated(coder, seperator);        }

var repeatedCoder = basicCoder.WithPostfix(";").Many("\n");


In this way, to repeatedCoder an array of Meta1, he will automatically convert each element into a line of code like a coder.

It is not enough for us to return to our needs. Assume that there is A Coder: ICoder <A>. The Coder needs to write A class name based on A field such as name, you need to write a series of field definitions based on another field such as IEnumerable <B>.

The code format we expect to generate:

class XXX{        public t1 aaa = v1;    public t2 bbb = v2;}

 
Assume that the structure of A is defined as follows:

class A{    public string Name;    public IEnumerable<Meta1> Fields;}


In fact, this kind of requirement is also the reason why I made the bad code before, or that sentence, please kindly advise! Continue with the code, CombineCoder:

        public static ICoder<T> GenCombine<T, T1>(ICoder<T> tCoder, ICoder<T1> t1Coder, Func<T, T1> selector)        {            return new SequenceCoder<T>(new ICoder[] { tCoder, t1Coder },                (meta, arr) =>                    string.Format("{0}{1}", tCoder.Code(meta), t1Coder.Code(selector(meta))));        }

 
Reuse the previously constructed repeatedCoder

var coder1 = Generator.GenBasic((A a) => string.Format("class {0}", a.Name)).WithPostfix("\n");var coder2 = repeatedCoder.Brace();


Now we want to combine A-> string coder1 with an IEnumerable <Meta1>-> string coder2 combine to form A-> string classCoder:

var classCoder = Generator.GenCombine(coder1, coder2, a => a.Fields);


Now, we can give classCoder A metadata instance of type A to output the expected string.

Conclusion

 

The main content of this blog post has come to an end. It is true that the Code posted above has a lot of problems in both performance and scalability, but the former is not a key consideration for a codegen program; the latter, as mentioned earlier, there are still many bad tastes of the code, not only SequenceCoder, but also Combine. These two current designs lead to the bad tastes of ICoder and ICoder <T>.

Sequence and Combine are actually the same requirement. If we regard a Coder as a monad, how can we use an understandable concept to represent the operations of monad a and monad B? I have tried to implement bind before, but in any case it is not as convenient as the current Combine, so I came up with the idea of writing this article and hope to be answered by someone else.
Because it is a small text, it does not use so much energy as the previous message queue article. I have been reading haskell and Parsec since February. I plan to write something about parsec and behavior tree. The result is that some things are put on hold .. I can only talk about it later.

In addition to the codegen mentioned in the entire article, this method is not very common in game logic implementation. The first time I saw the behavior tree engine developed by our studio, the intermediate language was translated to a specific language (C #), combine the components in the runtime database to form an entire behavior tree.

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.