My favorites | Sign in
Project Home Downloads Wiki Issues Source
Search
for
Create_your_own_DSL  
One-sentence summary of this page.
Updated Jun 22, 2011 by alexis.f...@gmail.com

Introduction

Using compile time introspection infrastructure it's possible to design and implement your own textual Domain Specific Languages directly in Zoe that can be used from Meta D++ source code. Following code shows a DSL to write Windows Forms user interfaces:

namespace T{

  // DSL to build GUIs
  // defines a window with a button that close the window
  // when the button is clicked
  GUI::Window(MiVentana3){
    Text = "A window with a LayerD DSL for Windows Forms";
    Controls{
      Button(CSalir){
        Text="Exit"; AutoSize=true;
        Click {
          this.Close(); 
        };
      };
    };
  };

  // can be directly mixed with other Meta D++ code
  class Run{
  public:
    static void Main(string^[] args){
      Application::EnableVisualStyles();
      Application::Run(new MiVentana2());
    }
  }

}

To define your own DSL you use compile time infrastructure from Zoe, and the ability of Meta D++ to take expressions and blocks of code as arguments of compile time functions. Additionally the is a special method call that allows additional freedom in defining your DSL.

The code to implement that Windows Form DSL is like this:

/*

  Meta D++ sample.

  Implementation of DSL for .NET Windows Forms.

  Visit http://layerd.net and http://layerd.blogspot.com to get updates of LayerD SDK and documentation.
  
*/
import "System", "platform=DotNET", "ns=DotNET", "assembly=mscorlib";
using DotNET::System;
using DotNET::System::IO;
using DotNET::System::Collections;
using DotNET::LayerD::CodeDOM;
using DotNET::LayerD::ZOECompiler;

namespace Zoe::DotNET::Utils{

  //
  // Classfactory that implements a DSL for Windows Forms in .NET.
  //
  public factory class GUI{
    static void RenderControl(XplNode^ controlDef, XplFunction^ constructor, XplExpression^ containerExp, XplClass^ containerClass)
    {
      if(controlDef.get_TypeName()!=CodeDOMTypes::XplExpression)return;

      XplExpression^ leftExp = null;
      XplExpression^ rigthExp = null;
      XplExpression^ resExp = null;
      XplType^ claseControl = new XplType();
      claseControl.set_typename(controlDef.FindNode("/n").get_StringValue());
      XplExpression^ nombreControl = (XplExpression^)controlDef.FindNode("/e/n");
      XplNodeList^ blockInfo = ((XplFunctioncall^)controlDef.get_Content()).get_bk().Children();
      
      // Declare field
      XplField^ fieldDecl = (XplField^)writecode{%
        $claseControl^ nombreControl = new $claseControl();
      %}.Children().FirstNode();
      fieldDecl.set_name(nombreControl.FindNode("/n").get_StringValue());
      containerClass.Children().InsertAtEnd(fieldDecl);
      
      // Add item to container
      resExp = writecode( $containerExp.Controls.Add($nombreControl) );
      constructor.get_FunctionBody().Children().InsertAtEnd(resExp);

      for(XplNode^ node in blockInfo)
      {
        if (node.get_Content() == null) continue;

        if (node.get_Content().get_TypeName() == CodeDOMTypes::XplAssing)
        {
          // It's a property
          leftExp = ((XplAssing^)node.get_Content()).get_l();
          rigthExp = ((XplAssing^)node.get_Content()).get_r();
          resExp = writecode( $nombreControl.$leftExp = $rigthExp );
          constructor.get_FunctionBody().Children().InsertAtEnd(resExp);
        }
        else 
        {
          if (node.get_ElementName() == "e" && node.get_Content().get_ElementName() == "fc" &&
              node.FindNode("/n").get_StringValue() == "Controls")
          {
            XplNodeList^ list = ((XplFunctioncall^)node.get_Content()).get_bk().Children();
            for(XplNode^ node2 in list)
            {
              RenderControl(node2, constructor, writecode( $nombreControl ), containerClass);
            }
          }
          else {
            if (node.get_ElementName() == "e" && node.get_Content().get_ElementName() == "fc")
            {
              // it's an event for the control 
              XplFunctionBody^ eventLogic = ((XplFunctioncall^)node.get_Content()).get_bk();
              XplExpression^ eventName = ((XplFunctioncall^)node.get_Content()).get_l();
              XplFunction^ eventFunction = (XplFunction^)writecode{%
                void eventFunc(object^ sender, EventArgs^ e)
                {
                  $eventLogic;
                }
              %}.Children().FirstNode();
              eventFunction.set_name(fieldDecl.get_name() + "_" + eventName.get_Content().get_StringValue());
              containerClass.Children().InsertAtEnd(eventFunction);
              XplExpression^ addEventFuncName = null;
              addEventFuncName = writecode( id );
              addEventFuncName.get_Content().set_Value("add_" + eventName.get_Content().get_StringValue());
              XplExpression^ addEvent = null;
              eventName.get_Content().set_Value(eventFunction.get_name());
              addEvent = writecode( $nombreControl.$addEventFuncName(new EventHandler(&this.$eventName)) );
              constructor.get_FunctionBody().Children().InsertAtEnd(addEvent);
            }
          }
        }
      }
    }
    
  public:
    static exp void Window(block info)
    {
      return Window(new XplIName(), info);
    }
    static exp void Window(iname void className, block info)
    {
      // get simple name
      className.Identifier = ZoeHelper::GetSimpleNameFromQualified(className.Identifier);
      
      XplExpression^ thisExp = null;
      thisExp = writecode( this );
      XplClass^ claseVentana = writecode{
        private class $className inherits Form{
        public:
          // Create the class for main window
          $className() { }
        }
      };
      
      for(XplNode^ node in info.Children())
      {
        XplExpression^ leftExp = null;
        XplExpression^ rigthExp = null;
        XplExpression^ resExp = null;
        if (node.get_ElementName() == "e" && node.get_Content().get_ElementName() == "a")
        {
          // it's a property
          leftExp = ((XplAssing^)node.get_Content()).get_l();
          rigthExp = ((XplAssing^)node.get_Content()).get_r();
          resExp = writecode( this.$leftExp = $rigthExp );
          constructor.get_FunctionBody().Children().InsertAtEnd(resExp);
        }
        if (node.get_ElementName() == "e" && node.get_Content().get_ElementName() == "fc" && 
            node.FindNode("/n").get_StringValue() == "Controls")
        {
          // process child controls
          XplNodeList^ list = ((XplFunctioncall^)node.get_Content()).get_bk().Children();
          for(XplNode^ node2 in list)
          {
            RenderControl(node2, constructor, thisExp, claseVentana);
          }
        }
      }
      
      // if class already exists, add members to existing class. Otherwise, add the new class
      XplClass^ backClass = (XplClass^)context.CurrentNamespace.FindNode("/@XplClass[name='" + claseVentana.get_name() + "']");
      if (backClass != null)
      {
        backClass.Children().InsertAtEnd( claseVentana.Children() );
      }
      else
      {
        context.CurrentNamespace.Children().InsertAtEnd(claseVentana);
      }
      // return null to remove compile time function call
      return null;
    }
  }
  
}

More complex DSL can be created using complex function calls

       str = Markup::XML(''
        table(id = "miClase", background = "black", comment = strA + "-" + strB):
          tr: td["Nombre"] td["Apellido"] ;
          tr: td[strA] td[strB] ;
          tr: td[strB] td[strA] ;
          tr: td["Juan"] td["Gonzalez"] ;
          tr: td["Gustavo"] td["Lopez"] ;
          tr: td["Daniel"] td["Perez"] ;
          tr: td["UTN"] td["FRC"] ;
        ; 
      '');  

      // complex function calls use a special syntax "foo('' arguments here '')"
      // where arguments is a sequence of expressions list and punctuators ; or : 
      // like: arg1 + arg2, arg3 : : : ; ; ; arg2: arg2(1,2) ;
      // because expressions can take normal function call form like "foo(arg1){blockArg;}"
      // it's possible to design complex DSL that are constructed from syntax primitives of
      // Meta D++ language

      var result = SQL::Query(''
        Select( name, address ), 
        From ( customers ), 
        Where {
           name="A*" && address="C*" ;
        }
      '');

      // because, all DSL are implemented by transformation and analysis done at 
      // compile time by ordinary classes, you can combine any number of DSL
      GUI::Window(QueryWindow){
        Text = "Results of query here";
        Width = 800; Height = 600;
        Controls{
          Label(queryResult){
            Top = 10; Left = 10;
            Width = 780; Height = 500;
          };
          Button(run){
            Top = 520; Left = 320;
            Text = "Query"; AutoSize = true;
            Click {
               Query^ result = SQL::Query(''
                 Select( name, address ), 
                 From ( customers ), 
                 Where {
                    name="A*" && address="C*" ;
                 }
               '');
               this.queryResult.Text = result.ToString();
            };
          };
        };
      };

Textual DSL are very powerful, allowing a perfect integration with standard engineering tools for version control, code revision, etc. With the flexibility of running and checking DSL at compile time it's possible to follow useful programming paradigms like language oriented programming.


Sign in to add a comment
Powered by Google Project Hosting