dsl_thumbCode Generation is a big trend from Code Snippet Generators to Full-Blown Software Factories. Our idea here is to use Domain Specific Language to generate Code Snippets like Visual Studio can do but more flexibly and more agnostically (you are not stuck with .NET languages or obliged to buy Visual Studio). Though our example will generate C# code, you can use the very same structure to generate any other languages (Java, PHP, TextUML, …) just by changing the code-template variables.

Before diving into this tutorial, I invite you to read these 2 InfoQ articles:
Why DSL instead of Framework by Martin Fowler

Language-oriented programming : an evolutionary step beyond object-oriented programming?

In previous installment, “how to create your own domain specific language”, you have learnt to parse an english sentence in Rebol:


phrase: "Create a C# class with Id, First Name, Last Name, Birth Date"
rule: ["Create a " copy Language to " class" thru " with " copy attributes-list to end]
parse phrase rule
Language
attributes-list

Note that thru is not really necessary in this case, we just use it to show you how to skip some words while parsing.

The Language Word (Rebol’s word is like variable in other languages) will contain “C#” and attributes-list will contain “Id, First Name, Last-Name, Birth-Date” as Rebol’s console shows (you can just copy and paste the code above instead of re-typing it):

Now change the rule to:


rule: ["Create a " copy Language to " class" thru " with " copy attributes-list to end (
print ["code-generation in" Language "implementation coming soon ..."])]

Within a parse block, Rebol will interpret every words between “(” and “)” as code block to evaluate like in


(print ["code-generation in" Language "implementation coming soon ..."])

Rebol’s console will then print “code-generation in C# implementation coming soon …” (note that the print function can do concatenation of a block of words and automatically adds a space between 2 words; if you don’t need space, use prin function instead):

Let’s create a generate-code function in Rebol:


generate-code: func[Language][print ["Code Generation in" Language "implementation coming soon..."]]
generate-code "C#"

and call it from the rule:


rule: ["Create a " copy Language to " class" thru " with " copy attributes-list to end (
generate-code)]

We can now fully implement our code-generation for all the languages, for example we will implement the C# language by taking inspiration from the Csharpfriends online generator:


generate-code: func[][
    attributes: parse/all attributes-list ","
    switch Language [
        "C#" [generate-code-csharp attributes]
    ] /default [print ["Language" Language "not yet implemented"]]
]

generate-code-csharp: func[attributes][
    namespace: if/else ((answer: ask "namespace (ex.:MyApp): ") = "") ["MyApp"][answer]
    class-name: if/else ((answer: ask "class-name (ex.: Cperson): ") = "") ["CPerson"][answer]
    private-prefix: "_"
    code-template: {using System;

// Generated by http://www.reboltutorial.com
// Template based on http://www.csharpfriends.com/demos/csharp_class_generator.aspx

namespace <%namespace%>
{
    public class <%class-name%>
    {
        // private members
        <%private-members%>

        // empty constructor
        public <%class-name%> ()
        {
        }

        // full constructor
        public <%class-name%> (<%private-constructor-arguments-list%>)
        {
            <%private-constructor-body%>
        }

       // public accessors
        <%public-accessors%>
    }
}
}; end of C# class template

private-members-template: {<%attribute-type%> <%private-prefix%><%attribute-name%>;}

private-constructor-arguments-list-template: {<%attribute-type%> <%attribute-name%>}
private-constructor-body-template: {this.<%private-prefix%><%attribute-name%> = <%attribute-name%>;}
public-accessors-template: {public <%attribute-type%> <%attribute-name%> {
            get { return <%private-prefix%><%attribute-name%>;}
            set { <%private-prefix%><%attribute-name%> = value; }
        }

}

    private-members: ""
    private-constructor-arguments-list: ""
    private-constructor-body: ""
    public-accessors: ""

    n: length? attributes
    counter: 0
    foreach attribute attributes [
        counter: counter + 1
        attribute-name: trim/all attribute; remove all whitespaces
        attribute-type: if/else ((answer: ask ["attribute-type for" attribute "(ex. string) : " ]) = "") ["string"][answer]

        if (counter > 1) [
            append private-members "        "
            append private-constructor-body "            "
            append public-accessors "        "
        ]

        append private-members build-markup private-members-template
        append private-constructor-arguments-list build-markup private-constructor-arguments-list-template
        append private-constructor-body build-markup private-constructor-body-template
        append public-accessors build-markup public-accessors-template

        if (counter < n) [
            append private-members newline
            append private-constructor-arguments-list ", "
            append private-constructor-body newline
        ]
    ]
    source-code: build-markup code-template
    write clipboard:// source-code
    Print "source code copied into clipboard ..."
    input
]

We use the build-markup function already explained here to render the code-template variable.

This is the C# source code generated:


using System;

// Generated by http://www.reboltutorial.com
// Template based on http://www.csharpfriends.com/demos/csharp_class_generator.aspx

namespace MyApp
{
    public class CPerson
    {
        // private members
        string _Id;
        string _FirstName;
        string _LastName;
        string _BirthDate;

        // empty constructor
        public CPerson ()
        {
        }

        // full constructor
        public CPerson (string Id, string FirstName, string LastName, string BirthDate)
        {
            this._Id = Id;
            this._FirstName = FirstName;
            this._LastName = LastName;
            this._BirthDate = BirthDate;
        }

       // public accessors
        public string Id {
            get { return _Id;}
            set { _Id = value; }
        }

        public string FirstName {
            get { return _FirstName;}
            set { _FirstName = value; }
        }

        public string LastName {
            get { return _LastName;}
            set { _LastName = value; }
        }

        public string BirthDate {
            get { return _BirthDate;}
            set { _BirthDate = value; }
        }
    }
}

Copy and paste the whole code above into Rebol Console and test with the usual parse instruction:


parse phrase rule


You can paste the C# code into Snippet Compiler or Visual Studio and test it by adding a Main method:


static void Main () {
  CPerson Person = new CPerson();
  Person.Id = "JD";
  Person.FirstName = "John";
  Person.LastName = "Doe";
  System.Console.WriteLine(Person.FirstName + " " + Person.LastName);
  System.Console.Read();
}

You can test or download the full script below by typing in Rebol Console do http://reboltutorial.com/source/csharp.r:


Rebol[
  title: "C# Code Generator"
  author: "http://reboltutorial.com/blog/create-dsl-2/"
  version: 1.0.0
]

phrase: ask "Enter your phrase: "
Comment [Phrase: "Create a C# class with Id, First Name, Last Name"; is valid]
Comment [Phrase: "Create C# class with Id, First Name, Last Name"; is also valid]

rule: ["Create " ["a" | ] copy Language to " class" thru " with " copy attributes-list to end (generate-code)]

parse phrase rule

generate-code: func[][
    attributes: parse/all attributes-list ","
    switch Language [
        "C#" [generate-code-csharp attributes]
    ] /default [print ["Language" Language "not yet implemented"]]
]

generate-code-csharp: func[attributes][
    namespace: if/else ((answer: ask "namespace (ex.:MyApp): ") = "") ["MyApp"][answer]
    class-name: if/else ((answer: ask "class-name (ex.: Cperson): ") = "") ["CPerson"][answer]
    private-prefix: "_"
    code-template: {using System;

// Generated by http://www.reboltutorial.com
// Template based on http://www.csharpfriends.com/demos/csharp_class_generator.aspx

namespace <%namespace%>
{
    public class <%class-name%>
    {
        // private members
        <%private-members%>

        // empty constructor
        public <%class-name%> ()
        {
        }

        // full constructor
        public <%class-name%> (<%private-constructor-arguments-list%>)
        {
            <%private-constructor-body%>
        }

       // public accessors
        <%public-accessors%>
    }
}
}; end of C# class template

private-members-template: {<%attribute-type%> <%private-prefix%><%attribute-name%>;}

private-constructor-arguments-list-template: {<%attribute-type%> <%attribute-name%>}
private-constructor-body-template: {this.<%private-prefix%><%attribute-name%> = <%attribute-name%>;}
public-accessors-template: {public <%attribute-type%> <%attribute-name%> {
            get { return <%private-prefix%><%attribute-name%>;}
            set { <%private-prefix%><%attribute-name%> = value; }
        }

}

    private-members: ""
    private-constructor-arguments-list: ""
    private-constructor-body: ""
    public-accessors: ""

    n: length? attributes
    counter: 0
    foreach attribute attributes [
        counter: counter + 1
        attribute-name: trim/all attribute; remove all whitespaces
        attribute-type: if/else ((answer: ask ["attribute-type for" attribute "(ex. string) : " ]) = "") ["string"][answer]

        if (counter > 1) [
            append private-members "        "
            append private-constructor-body "            "
            append public-accessors "        "
        ]

        append private-members build-markup private-members-template
        append private-constructor-arguments-list build-markup private-constructor-arguments-list-template
        append private-constructor-body build-markup private-constructor-body-template
        append public-accessors build-markup public-accessors-template

        if (counter < n) [
            append private-members newline
            append private-constructor-arguments-list ", "
            append private-constructor-body newline
        ]
    ]
    source-code: build-markup code-template
    write clipboard:// source-code
    Print "source code copied into clipboard ..."
    input
]

Note that we have introduced a refinement to the rule with the ["a" | ] block. This allows to accept either this kind of phrase:
“Create a C# class with Id, First Name, Last Name”
or
this kind of phrase:
“Create C# class with Id, First Name, Last Name”

C# Code Generator

In conclusion, you have seen how to implement the C# source code generator. You can extend it easily well beyond what the Csharpfriends online tool offers, it’s only up to you. As for other languages, you can use the same exact structure by just changing the code-template variable content.

Last final suggestion: why not wrap your C# code generator with a Csharp protocol to call it anywhere from your browser ;)

  • Bookmark and Share