Writing Domain Specific
© ASERT 2006-2010




                    Languages (DSLs) using Groovy
                         Dr Paul King, @paulk_asert
                           paulk at asert.com.au
Topics
                    DSL Origins
                    • Groovy Intro
                    • Groovy DSL Features
                    • Groovy DSL Examples
                    • Advanced Topics & Guidelines
© ASERT 2006-2010




                    • More Info




                                                     DSLs 2010 - 2
What is a DSL?...
                      • A domain-specific language is a
                        programming language or executable
                        specification language that offers, through
                        appropriate notations and abstractions,
                        expressive power focused on, and usually
© ASERT 2006-2010




                        restricted to, a particular problem domain
                             – In contrast, general-purpose languages are created to solve
                               problems in many domains
                             – Somewhere between declarative data and a full blown general-
                               purpose programming language (GPL)
                             – AKA: fluent / human interfaces, language oriented
                               programming, problem-oriented languages, little / mini
                               languages, macros, business natural languages
                    Sources:
                    http://en.wikipedia.org/wiki/Domain-specific_language
                    van Deursen, A., Klint, P., Visser, J.: Domain-specific languages: an annotated bibliography. ACM SIGPLAN Notices 35 (2000) 26–36
...What is a DSL?
                      • Advantages:                                                 • Disadvantages:
                      – Domain experts can                                          – Learning cost vs. limited
                        understand, validate, modify,                                 applicability
                        and often even develop DSL                                  – Cost of designing,
                        programs                                                      implementing & maintaining
                      – Somewhat self-documenting                                     DSL as well as the tools/IDEs
                      – Enhance quality,                                            – Attaining proper scope
© ASERT 2006-2010




                        productivity, reliability,                                  – Trade-offs between domain-
                        maintainability, portability                                  specificity and general-
                        and reusability                                               purpose programming
                      – Safety; as long as the                                        language constructs
                        language constructs are safe                                – Efficiency costs
                        any sentence written with                                   – Proliferation of similar non-
                        them can be considered safe                                   standard DSLs, i.e. different
                                                                                      but similar DSLs used within
                                                                                      two insurance companies
                    Source: http://en.wikipedia.org/wiki/Domain-specific_language                            DSLs 2010 - 4
Origins
                    • It has always been a
                      holy grail of computer
                      users to be able to
                      speak directly to our
                      computers
© ASERT 2006-2010




                    • Different languages have pushed the
                      boundaries further than others
                      –   APT for numerically controlled machine tools (1957)
                      –   BNF (1959), COBOL, 4GLs
                      –   LISP & Smalltalk
                      –   Unix "little languages"
                                                                          DSLs 2010 - 5
Origins
                    • It has always been a
                      holy grail of computer
                      users to be able to
                      speak directly to our
                      computers
© ASERT 2006-2010




                    http://dsal.uchicago.edu/reference/gazetteer/pager.html?objectid=DS405.1.I34_V02_298.gif




                                                                                                    DSLs 2010 - 6
Origins: LISP & Smalltalk
                    “In Lisp, you don’t just write your
                    program down toward the language,
                    you also build the language up
                    toward your program”
                    - Paul Graham
© ASERT 2006-2010




                    “When [Smalltalk] is used to
                    describe an application system, the
                    developer extends Smalltalk,
                    creating a domain-specific language
                    by adding a new vocabulary of
                    language elements ...”
                    - Adele Goldberg
                                                          DSLs 2010 - 7
Origins: Minilanguages...




                   Source: The Art of Unix Programming:Taxonomy of languages: http://www.faqs.org/docs/artu/ch08s01.html

# Minilanguage taxonomy                                      %!PS-Adobe-3.0
# Base ellipses                                              %%Creator: groff version 1.20.1
define smallellipse {ellipse width 3.0 height 1.5}           ...
M: ellipse width 3.0 height 1.8 fill 0.2                     597.6 12 72 12 DL(increasing loopiness)297.71 8.2 Q(/etc/passwd)102.67
line from M.n to M.s dashed                                  94.6 Q(.ne)110.715 106.6 Q(wsrc)-.25 E(SNG)195.2 100.6 Q(re)243.8 94.6 Q
D: smallellipse() with .e at M.w + (0.8, 0)                  (ge)-.15 E(xps)-.15 E(Glade)247.26 106.6 Q(m4)306.81 58.6 Q -1(Ya)303.43
line from D.n to D.s dashed                                  70.6 S(cc)1 E(Le)305.5 82.6 Q(x)-.15 E(mak)302.42 94.6 Q(e)-.1 E(XSL)
I: smallellipse() with .w at M.e - (0.8, 0)                  301.16 106.6 Q(T)-.92 E(pic)307.09 118.6 Q(tbl)307.92 130.6 Q(eqn)305.98
                                                             142.6 Q(fetchmail)344.715 82.6 Q -.15(aw)355.345 94.6 S(k).15 E(trof)
# Arrow headings                                             354.84 106.6 Q(f)-.25 E(Postscript)343.875 118.6 Q(dc)412.88 94.6 Q(bc)
arrow from D.w + (0.4, 0.8) to D.e + (-0.4, 0.8)             412.88 106.6 Q(Emacs Lisp)462.53 94.6 Q(Ja)465.395 106.6 Q -.25(va)-.2 G
"flat to structured" "" at last arrow.c                      (Script).25 E(sh)529.075 94.6 Q(tcl)528.52 106.6 Q(Perl)565.065 88.6 Q
...                                                          (Python)558.95 100.6 Q(Ja)564.46 112.6 Q -.25(va)-.2 G 0 Cg EP
# Interpreters                                               %%Trailer
"Emacs Lisp" "JavaScript" at 0.25 between M.e and I.e        end
"sh" "tcl" at 0.55 between M.e and I.e                       %%EOF
"Perl" "Python" "Java" at 0.8 between M.e and I.e
       Input DSL: pic for above picture                                Output "DSL": Postscript for above picture          DSLs 2010 - 8
...Origins: Minilanguages...
import builder.PowerPointBuilder
def name = 'minilanguages'
assert new File("${name}.pic').exists()
"groff -e -p ${name}.pic > ${name}.ps".execute()
"gs -q -sDEVICE='ppmraw' -g2600x3500 -r300x300 -sOutputFile='-' -dBATCH ↵
–dNOPAUSE ${name}.ps | pnmcrop | ppmtogif > ${name}.gif".execute()
def builder = new PowerPointBuilder() // Adapted from Erik Pragt
builder.slideshow(filename: 'DSLsInGroovy_MiniLanguages.ppt') {
    slide(title: 'Origins: Minilanguages...') {
        image(
            origin: [0, 15],
            src: "${name}.gif",
            caption: 'Source: The Art of Unix Programming:Taxonomy...' )
        textbox(
            origin: [5, 100],
            text: new File("${name}.pic").text,
            caption: 'Input DSL: pic for above picture' )
        textbox(
            origin: [115, 100],
            text: new File("${name}.ps").text,
            caption: 'Output "DSL": Postscript for above picture' )
     }
}
                PowerPoint DSL for previous slide                     DSLs 2010 - 9
...Origins: Minilanguages
   Glade                                                        XSLT
   <?xml version="1.0"?>                                         <?xml version="1.0"?>
   <GTK-Interface>                                               <xsl:stylesheetversion="1.0"
   <widget>                                                          xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <class>GtkWindow</class>                                    <xsl:output method="xml"/>
     <name>HelloWindow</name>                                      <xsl:template match="*">
     <border_width>5</border_width>                                  <xsl:element name="{name()}">
     <Signal>                                                          <xsl:for-each select="@*">
       <name>destroy</name>                                              <xsl:element name="{name()}">
       <handler>gtk_main_quit</handler>                                    <xsl:value-of select="."/>
     </Signal>                                                           </xsl:element>
     <title>Hello</title>                                              </xsl:for-each>
     <type>GTK_WINDOW_TOPLEVEL</type>                                  <xsl:apply-templates select="*|text()"/>
     <position>GTK_WIN_POS_NONE</position>                           </xsl:element>
     <allow_shrink>True</allow_shrink>                             </xsl:template>
     <allow_grow>True</allow_grow>                               </xsl:stylesheet>
     <auto_shrink>False</auto_shrink>                                                                        Regex
     <widget>
       <class>GtkButton</class>                                                                              "x.z?z{1,3}y"
       <name>Hello World</name>
       <can_focus>True</can_focus>                                fetchmail
       <label>Hello World</label>
     </widget>                                                    # Poll this site first each cycle.
                                                                  poll pop.provider.net proto pop3
   </widget>
   </GTK-Interface>                                                 user "jsmith" with pass "secret1" is "smith" here
                                                                    user jones with pass "secret2" is "jjones" here with options keep

      SQL                                                         # Poll this site second, unless Lord Voldemort zaps us first.
                                                                  poll billywig.hogwarts.com with proto imap:
    SELECT * FROM TABLE                                             user harry_potter with pass "floo" is harry_potter here
    WHERE NAME LIKE '%SMI'                                        # Poll this site third in the cycle.
    ORDER BY NAME                                                 # Password will be fetched from ~/.netrc
                                                                  poll mailhost.net with proto imap:
                                                                    user esr is esr here
      Troff
      cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps
Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html                                                   DSLs 2010 - 10
And now? The twitterverse says...




                                    DSLs 2010 - 11
...And Now? The twitterverse says




                                    DSLs 2010 - 12
State of the Art for DSLs?
© ASERT 2006-2010




                    Source: http://www.infoq.com/presentations/vanderburg-state-of-dsl-ruby – Gartner's take on technology adoption   DSLs 2010 - 13
"Is it a DSL or an API?" Checklist
  • A ten-question test to help determine whether
    a wad of code represents a DSL or an API:
         1. Have you ever programmed in a language other than Ruby?
            (PHP and HTML don‟t count.) If not, it‟s a DSL.
         2. Is the defining syntactic feature that you’ve cleverly left the
            parentheses off of a list of function arguments?
            If so, it‟s a DSL.
         3. Is the code primarily a list of key-value pairs?
            Welcome to DSL Town, population you!
         4. Does the code require the liberal use of eval() or equivalent?
            DSL, yay!
         5. Have you ever used the phrase “… and it reads just like
            English!” in seriousness? You‟d better get to the hospital;
            you‟re coming down with a case of the DSLs!
         6. ...

Source: http://www.oreillynet.com/onlamp/blog/2007/05/the_is_it_a_dsl_or_an_api_ten.html   DSLs 2010 - 14
DSL usage patterns...
                    • Internal (uses a general purpose language)
                      –   AKA embedded
                      –   Can be a subset or superset
                      –   Can be generated/converted from other "language"
                      –   Numerous mechanisms to make as close to the
                          domain as possible
© ASERT 2006-2010




                    • External (custom language)
                      –   May involve writing your own traditional parser
                      –   Using parser combinators
                      –   Interpreting
                      –   String grepping
                    • Other characteristics
                      – Functional vs OO
                                                                            DSLs 2010 - 15
...DSL usage patterns...
© ASERT 2006-2010




          Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP   DSLs 2010 - 16
...DSL usage patterns
© ASERT 2006-2010




          Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP   DSLs 2010 - 17
DSL usage patterns (Advanced)
© ASERT 2006-2010




          Source: http://www.spinellis.gr/pubs/jrnl/2000-JSS-DSLPatterns/html/dslpat.html
          See: Diomidis Spinellis. Notable design patterns for domain specific languages. Journal of Systems and Software, 56(1):91–99, February 2001.
Topics
                    • DSL origins
                    Groovy Intro
                    • Groovy DSL Features
                    • Groovy DSL Examples
                    • Advanced Topics & Guidelines
© ASERT 2006-2010




                    • More Info




                                                     DSLs 2010 - 19
What is Groovy?
                    • “Groovy is like a super version
                      of Java. It can leverage Java's
                      enterprise capabilities but also
                      has cool productivity features like closures,
                      DSL support, builders and dynamic typing.”
© ASERT 2006-2010




                    Groovy = Java –    boiler plate code
                                  +    optional dynamic typing
                                  +    closures
                                  +    domain specific languages
                                  +    builders
                                  +    metaprogramming
                                                                DSLs 2010 - 20
Groovy Goodies Overview
                    • Fully object oriented
                    • Closures: reusable
                      and assignable
                      pieces of code
                    • Operators can be          • GPath: efficient
                      overloaded
© ASERT 2006-2010




                                                  object navigation
                    • Multimethods              • GroovyBeans
                    • Literal declaration for   • grep and switch
                      lists (arrays), maps,
                      ranges and regular        • Templates, builder,
                      expressions                 swing, Ant, markup,
                                                  XML, SQL, XML-RPC,
                                                  Scriptom, Grails,
                                                  tests, Mocks   DSLs 2010 - 21
Growing Acceptance …
  A slow and steady start but now gaining in
  momentum, maturity and mindshare




Now free
… Growing Acceptance …
© ASERT 2006-2010




                                             DSLs 2010 - 23
… Growing Acceptance …
© ASERT 2006-2010




                    Groovy and Grails downloads:
                    70-90K per month and growing   DSLs 2010 - 24
… Growing Acceptance …
© ASERT 2006-2010




                             Source: http://www.micropoll.com/akira/mpresult/501697-116746




                                                   Source: http://www.grailspodcast.com/
                                                                                        DSLs 2010 - 25
… Growing Acceptance …
© ASERT 2006-2010




                          http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes




                                                              http://www.java.net
                                                                                       DSLs 2010 - 26
… Growing Acceptance …
         What alternative JVM language are you using or intending to use
© ASERT 2006-2010




                           http://www.leonardoborges.com/writings
                                                                    DSLs 2010 - 27
… Growing Acceptance …
© ASERT 2006-2010




                    http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo.com)
                                                                                                                  DSLs 2010 - 28
… Growing Acceptance
© ASERT 2006-2010




                                           DSLs 2010 - 29
The Landscape of JVM Languages

                                                                                                            optional
                                                                                                               static
                                                                                                               types
© ASERT 2006-2010




                                       Dynamic features call
                                       for dynamic types                                   Java bytecode calls
                                                                                               for static types




                    The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.
                                                                                                                  DSLs 2010 - 30
Groovy Starter
                    System.out.println("Hello, World!");   // supports Java syntax
                    println 'Hello, World!'                // but can remove some syntax

                    String name = 'Guillaume'              // Explicit typing/awareness
                    println "$name, I'll get the car."     // Gstring (interpolation)

                    def longer = """${name}, the car
                    is in the next row."""                 // multi-line, implicit type

                    assert 0.5 == 1/2                      // BigDecimal equals()
© ASERT 2006-2010




                    assert 0.1 + 0.2 == 0.3                // and arithmetic

                    def printSize(obj) {                   // implicit/duck typing
                        print obj?.size()                  // safe dereferencing
                    }

                    def pets = ['ant', 'bee', 'cat']       //   native list syntax
                    pets.each { pet ->                     //   closure support
                        assert pet < 'dog'                 //   overloading '<' on String
                    }                                      //   or: for (pet in pets)...




                                                                                            DSLs 2010 - 31
A Better Java...
                    import java.util.List;
                    import java.util.ArrayList;

                    class Erase {
                        private List removeLongerThan(List strings, int length) {   This code
                            List result = new ArrayList();
                            for (int i = 0; i < strings.size(); i++) {              is valid
                                String s = (String) strings.get(i);
                                if (s.length() <= length) {                         Java and
                                }
                                    result.add(s);                                  valid Groovy
                            }
                            return result;
© ASERT 2006-2010




                        }
                        public static void main(String[] args) {
                            List names = new ArrayList();
                            names.add("Ted"); names.add("Fred");
                            names.add("Jed"); names.add("Ned");
                            System.out.println(names);
                            Erase e = new Erase();                                   Based on an
                            List shortNames = e.removeLongerThan(names, 3);
                            System.out.println(shortNames.size());                   example by
                            for (int i = 0; i < shortNames.size(); i++) {            Jim Weirich
                                String s = (String) shortNames.get(i);
                                System.out.println(s);                               & Ted Leung
                            }
                        }
                    }



                                                                                          DSLs 2010 - 32
...A Better Java...
                    import java.util.List;
                    import java.util.ArrayList;

                    class Erase {
                        private List removeLongerThan(List strings, int length) {   Do the
                            List result = new ArrayList();
                            for (int i = 0; i < strings.size(); i++) {              semicolons
                                String s = (String) strings.get(i);
                                if (s.length() <= length) {                         add anything?
                                }
                                    result.add(s);                                  And shouldn‟t
                            }                                                       we us more
                            return result;
© ASERT 2006-2010




                        }                                                           modern list
                        public static void main(String[] args) {
                            List names = new ArrayList();                           notation?
                            names.add("Ted"); names.add("Fred");
                            names.add("Jed"); names.add("Ned");
                                                                                    Why not
                            System.out.println(names);                              import common
                            Erase e = new Erase();
                            List shortNames = e.removeLongerThan(names, 3);         libraries?
                            System.out.println(shortNames.size());
                            for (int i = 0; i < shortNames.size(); i++) {
                                String s = (String) shortNames.get(i);
                                System.out.println(s);
                            }
                        }
                    }



                                                                                           DSLs 2010 - 33
...A Better Java...
                    class Erase {
                        private List removeLongerThan(List strings, int length) {
                            List result = new ArrayList()
                            for (String s in strings) {
                                if (s.length() <= length) {
                                    result.add(s)
                                }
                            }
                            return result
                        }

                        public static void main(String[] args) {
                            List names = new ArrayList()
© ASERT 2006-2010




                            names.add("Ted"); names.add("Fred")
                            names.add("Jed"); names.add("Ned")
                            System.out.println(names)
                            Erase e = new Erase()
                            List shortNames = e.removeLongerThan(names, 3)
                            System.out.println(shortNames.size())
                            for (String s in shortNames) {
                                System.out.println(s)
                            }
                        }
                    }




                                                                                    DSLs 2010 - 34
...A Better Java...
                    class Erase {
                        private List removeLongerThan(List strings, int length) {
                            List result = new ArrayList()
                            for (String s in strings) {
                                if (s.length() <= length) {
                                    result.add(s)                                   Do we need
                                }
                            }                                                       the static types?
                        }
                            return result                                           Must we always
                                                                                    have a main
                        public static void main(String[] args) {
                            List names = new ArrayList()                            method and
© ASERT 2006-2010




                            names.add("Ted"); names.add("Fred")
                            names.add("Jed"); names.add("Ned")                      class definition?
                            System.out.println(names)
                            Erase e = new Erase()
                                                                                    How about
                            List shortNames = e.removeLongerThan(names, 3)          improved
                            System.out.println(shortNames.size())
                            for (String s in shortNames) {                          consistency?
                                System.out.println(s)
                            }
                        }
                    }




                                                                                             DSLs 2010 - 35
...A Better Java...
                    def removeLongerThan(strings, length) {
                        def result = new ArrayList()
                        for (s in strings) {
                            if (s.size() <= length) {
                                result.add(s)
                            }
                        }
                        return result
                    }
© ASERT 2006-2010




                    names = new ArrayList()
                    names.add("Ted")
                    names.add("Fred")
                    names.add("Jed")
                    names.add("Ned")
                    System.out.println(names)
                    shortNames = removeLongerThan(names, 3)
                    System.out.println(shortNames.size())
                    for (s in shortNames) {
                        System.out.println(s)
                    }




                                                              DSLs 2010 - 36
...A Better Java...
                    def removeLongerThan(strings, length) {
                        def result = new ArrayList()
                        for (s in strings) {
                            if (s.size() <= length) {
                                result.add(s)                 Shouldn‟t we
                            }
                        }                                     have special
                        return result                         notation for lists?
                    }
                                                              And special
© ASERT 2006-2010




                    names = new ArrayList()                   facilities for
                    names.add("Ted")
                    names.add("Fred")
                                                              list processing?
                    names.add("Jed")                           Is „return‟
                    names.add("Ned")                          needed at end?
                    System.out.println(names)
                    shortNames = removeLongerThan(names, 3)
                    System.out.println(shortNames.size())
                    for (s in shortNames) {
                        System.out.println(s)
                    }




                                                                             DSLs 2010 - 37
...A Better Java...
                    def removeLongerThan(strings, length) {
                        strings.findAll{ it.size() <= length }
                    }

                    names = ["Ted", "Fred", "Jed", "Ned"]
                    System.out.println(names)
                    shortNames = removeLongerThan(names, 3)
                    System.out.println(shortNames.size())
                    shortNames.each{ System.out.println(s) }
© ASERT 2006-2010




                                                                 DSLs 2010 - 38
...A Better Java...
                    def removeLongerThan(strings, length) {
                        strings.findAll{ it.size() <= length }
                    }
                                                                 Is the method
                    names = ["Ted", "Fred", "Jed", "Ned"]        now needed?
                    System.out.println(names)
                    shortNames = removeLongerThan(names, 3)      Easier ways to
                    System.out.println(shortNames.size())        use common
                    shortNames.each{ System.out.println(s) }
                                                                 methods?
© ASERT 2006-2010




                                                                 Are brackets
                                                                 required here?




                                                                         DSLs 2010 - 39
...A Better Java...
                    names = ["Ted", "Fred", "Jed", "Ned"]
                    println names
                    shortNames = names.findAll{ it.size() <= 3 }
                    println shortNames.size()
                    shortNames.each{ println it }
© ASERT 2006-2010




                                                                   DSLs 2010 - 40
...A Better Java
                    names = ["Ted", "Fred", "Jed", "Ned"]
                    println names
                    shortNames = names.findAll{ it.size() <= 3 }
                    println shortNames.size()
                    shortNames.each{ println it }
© ASERT 2006-2010




                          ["Ted", "Fred", "Jed", "Ned"]
                          3
                          Ted
                          Jed
                          Ned


                                                                   DSLs 2010 - 41
Grapes / Grab
                    // Google Collections example
                    @Grab('com.google.collections:google-collections:1.0')
                    import com.google.common.collect.HashBiMap

                    HashBiMap fruit =
                      [grape:'purple', lemon:'yellow', lime:'green']
                    assert fruit.lemon == 'yellow'
© ASERT 2006-2010




                    assert fruit.inverse().yellow == 'lemon'




                                                                     DSLs 2010 - 42
Better Design Patterns: Immutable...
                    • Java Immutable Class
                      – As per Joshua Bloch                                   // ...
                                                                              @Override
                        Effective Java                                        public boolean equals(Object obj) {
                                                                                  if (this == obj)
                      public final class Punter {                                     return true;
                          private final String first;                             if (obj == null)
                          private final String last;                                  return false;
                                                                                  if (getClass() != obj.getClass())
                         public String getFirst() {                                   return false;
                             return first;                                        Punter other = (Punter) obj;
                         }                                                        if (first == null) {
                                                                                      if (other.first != null)
© ASERT 2006-2010




                         public String getLast() {                                        return false;
                             return last;                                         } else if (!first.equals(other.first))
                         }                                                            return false;
                                                                                  if (last == null) {
                         @Override                                                    if (other.last != null)
                         public int hashCode() {                                          return false;
                             final int prime = 31;                                } else if (!last.equals(other.last))
                             int result = 1;                                          return false;
                             result = prime * result + ((first == null)           return true;
                                 ? 0 : first.hashCode());                     }
                             result = prime * result + ((last == null)
                                 ? 0 : last.hashCode());                      @Override
                             return result;                                   public String toString() {
                         }                                                        return "Punter(first:" + first
                                                                                      + ", last:" + last + ")";
                         public Punter(String first, String last) {           }
                             this.first = first;
                             this.last = last;                            }
                         }
                         // ...


                                                                                                                   DSLs 2010 - 43
...Better Design Patterns: Immutable...
                    • Java Immutable Class                                                            boilerplate
                      – As per Joshua Bloch                                   // ...
                                                                              @Override
                        Effective Java                                        public boolean equals(Object obj) {
                                                                                  if (this == obj)
                      public final class Punter {                                     return true;
                          private final String first;                             if (obj == null)
                          private final String last;                                  return false;
                                                                                  if (getClass() != obj.getClass())
                         public String getFirst() {                                   return false;
                             return first;                                        Punter other = (Punter) obj;
                         }                                                        if (first == null) {
                                                                                      if (other.first != null)
© ASERT 2006-2010




                         public String getLast() {                                        return false;
                             return last;                                         } else if (!first.equals(other.first))
                         }                                                            return false;
                                                                                  if (last == null) {
                         @Override                                                    if (other.last != null)
                         public int hashCode() {                                          return false;
                             final int prime = 31;                                } else if (!last.equals(other.last))
                             int result = 1;                                          return false;
                             result = prime * result + ((first == null)           return true;
                                 ? 0 : first.hashCode());                     }
                             result = prime * result + ((last == null)
                                 ? 0 : last.hashCode());                      @Override
                             return result;                                   public String toString() {
                         }                                                        return "Punter(first:" + first
                                                                                      + ", last:" + last + ")";
                         public Punter(String first, String last) {           }
                             this.first = first;
                             this.last = last;                            }
                         }
                         // ...


                                                                                                                   DSLs 2010 - 44
...Better Design Patterns: Immutable



                          @Immutable class Punter {
                              String first, last
                          }
© ASERT 2006-2010




                                                      DSLs 2010 - 45
Malleable Syntax
                    order to buy 200.shares of GOOG {
                        limitPrice       500
                        allOrNone        false
                        at the value of { qty * unitPrice - 100 }
                    }
                     take 2.pills of chloroquinine after 6.hours
© ASERT 2006-2010




                    def "length of Spock's & his friends' names"() {
                        expect:
                            name.size() == length
                        where:
                            name     | length
                            "Spock" | 5
                            "Kirk"   | 4
                            "Scotty" | 6
                    }
                     Groovy 1.8+                               DSLs 2010 - 46
Topics
                    • DSL origins
                    • Groovy Intro
                    Groovy DSL Features
                    • Groovy DSL Examples
                    • Advanced Topics & Guidelines
© ASERT 2006-2010




                    • More Info




                                                     DSLs 2010 - 47
Groovy DSL Features
© ASERT 2006-2010




                                          DSLs 2010 - 48
Import / Import Static
                    • Imports
                     @Grab('com.google.collections:google-collections:1.0')
                     import com.google.common.collect.HashBiMap as HashMap

                     def m = new HashMap()
                     m.key = 'value'
                     assert m.inverse().value == 'key'
© ASERT 2006-2010




                    • Static Imports
                     import static java.util.Calendar.getInstance as now
                     println now().format('yyyy/MMM/dd')



                                          What Java gives us plus aliases

                                                                      DSLs 2010 - 49
Static Imports...
                               Java Static Imports
                              Discussed later:
                               Builders

                               Closures



                    import groovy.swing.SwingXBuilder
© ASERT 2006-2010




                    import static java.awt.Color.*
                    import static java.lang.Math.*

                    def swing = new SwingXBuilder()
                    def frame = swing.frame(size: [300, 300]) {
                        graph(plots: [
                             [GREEN, {value -> sin(value)}],
                             [BLUE, {value -> cos(value)}],
                             [RED,   {value -> tan(value)}]
                        ])
                    }.show()

                                                                  DSLs 2010 - 50
...Static Imports...
                                  Java gives us this




                    import groovy.swing.SwingXBuilder
© ASERT 2006-2010




                    import static java.awt.Color.*
                    import static java.lang.Math.*

                    def swing = new SwingXBuilder()
                    def frame = swing.frame(size: [300, 300]) {
                        graph(plots: [
                             [GREEN, {value -> sin(value)}],
                             [BLUE, {value -> cos(value)}],
                             [RED,   {value -> tan(value)}]
                        ])
                    }.show()

                                                                  DSLs 2010 - 51
...Static Imports...
                                  Java gives us this




                    import groovy.swing.SwingXBuilder
© ASERT 2006-2010




                    import static java.awt.Color.*
                    import static java.lang.Math.*

                    def swing = new SwingXBuilder()
                    def frame = swing.frame(size: [300, 300]) {
                        graph(plots: [
                             [GREEN, {value -> sin(value)}],
                             [BLUE, {value -> cos(value)}],
                             [RED,   {value -> tan(value)}]
                        ])
                    }.show()

                                                                  DSLs 2010 - 52
...Static Imports...
© ASERT 2006-2010




      Source: http://www.thedoghousediaries.com/?p=1406        DSLs 2010 - 53
...Static Imports

                                   Java doesn‟t give us this!


                    import   groovy.swing.SwingXBuilder
                    import   static java.lang.Math.*
                    import   static java.awt.Color.GREEN as Lime
© ASERT 2006-2010




                    import   static java.awt.Color.BLUE as Sky
                    import   static java.awt.Color.RED   as Maraschino

                    def swing = new SwingXBuilder()
                    def frame = swing.frame(size: [300, 300]) {
                        graph(plots: [
                             [Lime,       {value -> sin(value)}],
                             [Sky,        {value -> cos(value)}],
                             [Maraschino, {value -> tan(value)}]
                        ])
                    }.show()
                                                                         DSLs 2010 - 54
Literal Syntax Conventions
                    • Lists
                      – Special syntax for list literals
                      – Additional common methods (operator overloading)
                              def list = [3, new Date(), 'Jan']
                              assert list + list == list * 2
                    • Maps
© ASERT 2006-2010




                      – Special syntax for map literals
                      – Additional common methods
                              def map = [a: 1, b: 2]
                              assert map['a'] == 1 && map.b == 2
                    • Ranges
                      – Special syntax for various kinds of ranges
                              def letters = 'a'..'z'
                              def numbers = 0..<10
                                                                     DSLs 2010 - 55
Literal Syntax Conventions in DSLs
                    import static java.util.Calendar.getInstance as getNow
                    import static java.util.Calendar.*
                    def discount = [normal:0, silver:5, gold:10]
                    def roomrate = [weekday:150, weekend:95]
                    for (level in ['normal', 'silver', 'gold']) {
                        def multiplier = 1 - discount[level] / 100
                        print 'Your room rate is: '
© ASERT 2006-2010




                        if (now[DAY_OF_WEEK] in MONDAY..FRIDAY)
                             println roomrate.weekday * multiplier
                        else
                             println roomrate.weekend * multiplier
                    }
                                                    Literal list & map syntax
                                                    Ranges
                    Your room rate is: 150         Also
                    Your room rate is: 142.50       Static import aliases

                    Your room rate is: 135.0        BigDecimal arithmetic

                                                                        DSLs 2010 - 56
Compact Syntax...
                    • Java
                    public class BuySharesJava {
                      public static void buyShares(int   qty, String name) {
                        // business logic here ...
                        System.out.println("buying " +   qty
                            + " shares of " + name);
                      }
© ASERT 2006-2010




                      public static void main(String[]   args) {
                        buyShares(3, "BHP");
                      }                                   Script syntax
                                                          Conventions for visibility
                    }
                                                          GDK methods: println


                    • Groovy                              Implicit typing

                                                          GString interpolation

                    def buyShares(qty, name) {            Optional brackets & „;‟

                      // business logic here ...
                      println "buying $qty shares of $name"
                    }
                    buyShares 3, 'BHP'
                                                                             DSLs 2010 - 57
...Compact Syntax...
                    • Java                       Script syntax, Named params, Extra imports
                                                 Conventions for visibility, GDK methods

                    import java.util.Date;       Implicit typing, Multi-line GString
                    import java.util.HashMap;
                                                 Elvis operator, Optional brackets & „;‟
                    import java.util.Map;
                    public class ProcessCustomerJava {
                        public static void printDetails(Map<String, String> cust) {
                            System.out.println("Details as at: " + new Date());
                            String first = cust.get("first");
                            System.out.println("First name: " + first);
© ASERT 2006-2010




                            String last = cust.get("last");
                            System.out.println("Last name: " + (last != null ? last : "unknown"));
                        }
                        public static void main(String[] args) {
                            Map<String, String> details = new HashMap<String, String>();
                            details.put("first", "John");
                            details.put("last", "Smith");
                            printDetails(details);
                        }
                    }                  def printDetails(cust) {
                                           println """Details as at: ${new Date()}
                                       First name: $cust.first
                    • Groovy           Last name: ${cust.last ?: 'unknown'}"""
                                       }
                                       printDetails first: 'John', last: 'Smith'
                                                                                           DSLs 2010 - 58
...Compact Syntax
                    • Java
                    import java.util.Date;

                    public class ProcessStrongCustomerJava {
                        public static void printDetails(CustomerJ cust) {
                            System.out.println("Details as at: " + new Date());
                            String first = cust.getFirst();
                            System.out.println("First name: " + first);

                                                                                                     • Groovy
                            String last = cust.getLast();
                            System.out.println("Last name: " + (last != null ? last : "unknown"));
                        }

                        public static void main(String[] args) {
                            CustomerJ c = new CustomerJ();
                                                                      class CustomerG { String first, last }
© ASERT 2006-2010




                            c.setFirst("John");
                            c.setLast("Smith");                       def printDetails(cust) {
                            printDetails(c);
                        }                                                 println """Details as at: ${new Date()}
                    }
                                                                      First name: $cust.first
                    class CustomerJ {                                 Last name: ${cust.last ?: 'unknown'}"""
                        private String first;
                        private String last;                          }
                        public String getFirst() {
                            return first;
                                                                      printDetails new CustomerG(first: 'John',
                        }                                                                        last: 'Smith')
                        public void setFirst(String first) {
                            this.first = first;
                        }                                                           Plus:
                                                                                     Named params for constructors
                        public String getLast() {
                            return last;
                                                                                     JavaBean conventions
                        }

                        public void setLast(String last) {
                            this.last = last;                                        Leverage Duck Typing
                        }
                    }
                                                                                                                DSLs 2010 - 59
Using With Example
                    letters = ['a', 'b', 'c']
                    range = 'b'..'d'
                    letters.with {
                        add 'd'                 Just normal methods
                                                for ArrayList here
                        remove 'a'
                    }
© ASERT 2006-2010




                    assert letters == range


                    map = [a:10, b:4, c:7]
                    map.with {
                        assert (a + b) / c == 2
                    }
                                                            DSLs 2010 - 60
Closures...
                    • Traditional mainstream languages
                      – Data can be stored in variables, passed around,
                        combined in structured ways to form more complex
                        data; code stays put where it is defined
                    • Languages supporting closures
                      – Data and code can be stored in variables, passed
© ASERT 2006-2010




                        around, combined in structured ways to form more
                        complex algorithms and data; functional coding style
                       doubleNum = { num -> num * 2 }
                       println doubleNum(3) // => 6
                       processThenPrint = { num, closure ->
                           num = closure(num); println "num is $num"
                       }
                       processThenPrint(3, doubleNum)    // => num is 6
                       processThenPrint(10) { it / 2 }   // => num is 5
                                                                       DSLs 2010 - 61
...Closures...

                      int myConst = 4
                      def multiplier = { number -> number * myConst }
                      assert multiplier(10) == 40

                         Name
© ASERT 2006-2010




                      Closure other = { it + myConst }
                      assert other.call(10) == 14



                    def twice = { it * 2 }   // anonymous functions
                    assert twice(10) == 20   // implemented as Closures



                                                                     DSLs 2010 - 62
...Closures...

                      int myConst = 4
                      def multiplier = { number -> number * myConst }
                      assert multiplier(10) == 40

                                                Code
© ASERT 2006-2010




                      Closure other = { it + myConst }
                      assert other.call(10) == 14



                    def twice = { it * 2 }   // anonymous functions
                    assert twice(10) == 20   // implemented as Closures



                                                                     DSLs 2010 - 63
...Closures...
                                             Parameter(s)

                      int myConst = 4
                      def multiplier = { number -> number * myConst }
                      assert multiplier(10) == 40

                                              Default parameter
© ASERT 2006-2010




                      Closure other = { it + myConst }
                      assert other.call(10) == 14



                    def twice = { it * 2 }    // anonymous functions
                    assert twice(10) == 20    // implemented as Closures



                                                                      DSLs 2010 - 64
...Closures...

                      int myConst = 4
                      def multiplier = { number -> number * myConst }
                      assert multiplier(10) == 40


                           Call closure
© ASERT 2006-2010




                      Closure other = { it + myConst }
                      assert other.call(10) == 14

                                             Alternative syntax

                    def twice = { it * 2 }      // anonymous functions
                    assert twice(10) == 20      // implemented as Closures



                                                                        DSLs 2010 - 65
...Closures...

                      int myConst = 4
                      def multiplier = { number -> number * myConst }
                      assert multiplier(10) == 40

                                                   Free variable
© ASERT 2006-2010




                      Closure other = { it + myConst }
                      assert other.call(10) == 14



                    def twice = { it * 2 }   // anonymous functions
                    assert twice(10) == 20   // implemented as Closures



                                                                     DSLs 2010 - 66
...Closures...
                                   Bound to environment/context when called

                      int myConst = 4
                      def multiplier = { number -> number * myConst }
                      assert multiplier(10) == 40
© ASERT 2006-2010




                      Closure other = { it + myConst }
                      assert other.call(10) == 14



                    def twice = { it * 2 }   // anonymous functions
                    assert twice(10) == 20   // implemented as Closures



                                                                          DSLs 2010 - 67
Closures in DSLs...
                                                          Nothing special here.
                    import static java.util.Calendar.*    Just a list of maps.
                    def cart1 = [
                            [qty: 3, unitPrice: 50.0, gift: true,
                             name: 'Groovy in Action Book'],
                            [qty: 1, unitPrice: 40.0, gift: false,
                             name: 'Grails in Action eBook'],
                            [qty: 2, unitPrice: 25.0, gift: true,
© ASERT 2006-2010




                             name: 'Avatar DVD']
                    ]
                    def cart2 = [
                            [qty: 1, unitPrice: 50.0, gift: true,
                             name: 'Groovy in Action Book'],
                            [qty: 2, unitPrice: 30.0, gift: false,
                             name: 'Avatar 3D DVD']
                    ]
                    // ...


                                                                        DSLs 2010 - 68
... Closures in DSLs...
                    // ...
                    def noDiscount = { it.unitPrice * it.qty }
                    def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 }
                    def tenPercentOffGifts = { it.with {
                        unitPrice * qty * (gift ? 0.9 : 1.0)
                    } }
                    def tenPercentOffOverForty = { it.with {
                        unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0)
© ASERT 2006-2010




                    } }
                    def tenPercentOffBooks = { it.with {
                        unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0)
                    } }
                    def tenPercentOffDVDs = { it.with {
                        unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0)
                    } }
                    def buyThreeGetOneFree = { it.with {
                        unitPrice * (qty % 3 + 2 * qty.intdiv(3))
                    } }
                    // ...
                                                 Closure Name
                                                                      DSLs 2010 - 69
... Closures in DSLs...
                    // ...
                    def noDiscount = { it.unitPrice * it.qty }
                    def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 }
                    def tenPercentOffGifts = { it.with {
                        unitPrice * qty * (gift ? 0.9 : 1.0)
                    } }
                    def tenPercentOffOverForty = { it.with {
                        unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0)
© ASERT 2006-2010




                    } }
                    def tenPercentOffBooks = { it.with {
                        unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0)
                    } }
                    def tenPercentOffDVDs = { it.with {
                        unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0)
                    } }
                    def buyThreeGetOneFree = { it.with {
                        unitPrice * (qty % 3 + 2 * qty.intdiv(3))
                    } }
                    // ...
                                                 Closure Code
                                                                      DSLs 2010 - 70
... Closures in DSLs
                    // ...
                    def specials = [noDiscount, tenPercentOffAll,
                        tenPercentOffGifts, tenPercentOffOverForty,
                                                                      1 240.00
                        tenPercentOffBooks, tenPercentOffDVDs,
                        buyThreeGetOneFree]                           2 216.00
                                                                      3 220.00
                    [                                                 4 225.00
                        SUNDAY..SATURDAY,                             5 221.00
© ASERT 2006-2010




                        [cart1, cart2]                                6 235.00
                    ].combinations().each { day, cart ->
                                                                      7 190.00
                        printf "%d %2.2fn", day,
                            cart.sum(specials[day-1])                 1 110.00
                    }                                                 2 99.00
                                                                      3 105.00
                                                                      4 105.00
                                                                      5 105.00
                                                                      6 104.00
                                                                      7 110.00
                                                                       DSLs 2010 - 71
Operator Overloading Example
                    • Java
                    BigDecimal a = new BigDecimal(3.5d);
                    BigDecimal b = new BigDecimal(4.0d);
                    assert a.multiply(b).compareTo(new BigDecimal(14.0d)) == 0;
                    assert a.multiply(b).equals(new BigDecimal(14.0d).setScale(1));

                    • Groovy
© ASERT 2006-2010




                     def c = 3.5, d = 4.0
                     assert c * d == 14.0




                                                                             DSLs 2010 - 72
Groovy Lab Example
                    // require GroovyLab
                    import static org.math.array.Matrix.*
                    import static org.math.plot.Plot.*

                    def A = rand(10,3)       // random Matrix of 10 rows and 3 columns
                    def B = fill(10,3,1.0) // one Matrix of 10 rows and 3 columns
                    def C = A + B           // support for matrix addition with "+" or "-"
                    def D = A - 2.0        // support for number addition with "+" or "-"
                    def E = A * B
© ASERT 2006-2010




                                           // support for matrix multiplication or division
                    def F = rand(3,3)
                    def G = F**(-1)              // support for matrix power (with integers only)
                    println A                   // display Matrix content
                    plot("A",A,"SCATTER") // plot Matrix values as ScatterPlot

                    def M = rand(5,5) + id(5) // Eigenvalues decomposition
                    println "M=n" + M
                    println "V=n" + V(M)
                    println "D=n" + D(M)
                    println "M~n" + (V(M) * D(M) * V(M)**(-1))
                                                                                               DSLs 2010 - 73
Better Control Structures: Switch Poker…
                                                              hand1          hand2

                                                         8C TS KC 9H 4S 7D 2S 5D 3S AC

                    suits = 'SHDC'
                    ranks = '23456789TJQKA'
                    suit = { String card -> suits.indexOf(card[1]) }
                    rank = { String card -> ranks.indexOf(card[0]) }
                    rankSizes = { List cards ->
                        cards.groupBy(rank).collect{ k, v -> v.size() }.sort() }
© ASERT 2006-2010




                    rankValues = { List cards ->
                        cards.collect{ rank(it) }.sort() }
                    // ...


                    println rankSizes(["7S", "7H", "2H", "7D", "AH"])
                    // => [1, 1, 3]




                                                                                   DSLs 2010 - 74
…Better Control Structures: Switch Poker…



                    // ...
                    flush = { List cards -> cards.groupBy(suit).size() == 1 }
                    straight = { def v = rankValues(it); v == v[0]..v[0]+4 }
                    straightFlush = { List cards -> straight(cards) && flush(cards)
© ASERT 2006-2010




                    }
                    fourOfAKind = { List cards -> rankSizes(cards) == [1, 4] }
                    fullHouse = { List cards -> rankSizes(cards) == [2, 3] }
                    threeOfAKind = { List cards -> rankSizes(cards) == [1, 1, 3] }
                    twoPair = { List cards -> rankSizes(cards) == [1, 2, 2] }
                    pair = { List cards -> rankSizes(cards) == [1, 1, 1, 2] }
                    // ...




                                                                               DSLs 2010 - 75
… Better Control Structures: Switch Poker

                          // ...
                          def rankHand(List cards) {
                              switch (cards) {
                                  case straightFlush   :   return   9
                                  case fourOfAKind     :   return   8
                                  case fullHouse       :   return   7
                                  case flush           :   return   6
© ASERT 2006-2010




                                  case straight        :   return   5
                                  case threeOfAKind    :   return   4
                                  case twoPair         :   return   3
                                  case pair            :   return   2
                                  default              :   return   1
                              }
                          }
                          // ...




                                                                        DSLs 2010 - 76
Groovy DSL Features
© ASERT 2006-2010




                                          DSLs 2010 - 77
Builders…
                    • MarkupBuilder

                                                     <html>
                    import groovy.xml.*
                                                       <head>
                    def page = new MarkupBuilder()
                                                         <title>Hello</title>
                    page.html {
                                                       </head>
                      head { title 'Hello' }
                                                       <body>
                      body {
© ASERT 2006-2010




                                                         <ul>
                        ul {
                                                            <li>world 1</li>
                          for (count in 1..5) {
                                                            <li>world 2</li>
                             li "world $count"
                                                            <li>world 3</li>
                    } } } }
                                                            <li>world 4</li>
                                                            <li>world 5</li>
                                                         </ul>
                                                       </body>
                                                     </html>



                                                                         DSLs 2010 - 78
...Builders…
                    • GraphicsBuilder
                     def colors = ['red','darkOrange','blue','darkGreen']
                     (0..3).each { index ->
                       star( cx: 50 + (index*110), cy: 50, or: 40, ir: 15,
                         borderColor: 'black', count: 2+index, fill: colors[index] )
                       star( cx: 50 + (index*110), cy: 140, or: 40, ir: 15,
                         borderColor: 'black‘, count: 7+index, fill: colors[index] )
                     }
© ASERT 2006-2010




                                                                               DSLs 2010 - 79
…Builders
                    new AntBuilder().with {
                      echo(file:'Temp.java', '''
                      class Temp {
                        public static void main(String[] args) {
                           System.out.println("Hello");
                        }
                      }
                      ''')
© ASERT 2006-2010




                      javac(srcdir:'.', includes:'Temp.java', fork:'true')
                      java(classpath:'.', classname:'Temp', fork:'true')
                      echo('Done')
                    }
                    // =>
                    //     [javac] Compiling 1 source file
                    //      [java] Hello
                    //      [echo] Done



                                                                      DSLs 2010 - 80
Metaprogramming
                    • Runtime Metaprogramming
                      –   Categories including DGM
                      –   invokeMethod, methodMissing, getProperty
                      –   GroovyInterceptable
                      –   ExpandoMetaClass
© ASERT 2006-2010




                    • Compile-time Metaprogramming
                      –   AST Macros
                      –   Local and Global flavors based around annotations
                      –   Reduced runtime overhead
                      –   Several built-in annotations:
                           • @Immutable
                           • @Delegate
                           • @Singleton
                                                                        DSLs 2010 - 81
ExpandoMetaClass…

                        import static java.lang.Character.isUpperCase
                        String.metaClass.swapCase = {
                          delegate.collect { ch ->
                            isUpperCase(ch as char) ?
                              ch.toLowerCase() :
                              ch.toUpperCase()
                          }.join()
                        }
© ASERT 2006-2010




                        println new Date().toString().swapCase()
                        // => sUN nOV 09 17:30:06 est 2008


                    List.metaClass.sizeDoubled = {-> delegate.size() * 2 }
                    LinkedList list = []
                    list << 1
                    list << 2
                    assert 4 == list.sizeDoubled()


                                                                        DSLs 2010 - 82
…ExpandoMetaClass
                    class Person {
                        String name
                    }

                    class MortgageLender {
                       def borrowMoney() {
                          "buy house"
                       }
© ASERT 2006-2010




                    }

                    def lender = new MortgageLender()

                    Person.metaClass.buyHouse = lender.&borrowMoney

                    def p = new Person()

                    assert "buy house" == p.buyHouse()



                                                                      DSLs 2010 - 83
Adding your own control structures
• Thread enhancement                                                        ROCK!
                                                                             . 25
  import java.util.concurrent.locks.ReentrantLock                           . 33
  import static System.currentTimeMillis as now                                . 34
  def startTime = now()                                                       . 35
  ReentrantLock.metaClass.withLock = { critical ->                              . 36
      lock()                                                                 .. 131
      try {   critical() }                                                  .. 134
      finally { unlock() }                                                     .. 137
  }
  def lock = new ReentrantLock()
                                                                              .. 138
  def worker = { threadNum ->                                                   .. 139
      4.times { count ->                                                     ... 232
          lock.withLock {                                                   ... 234
              print " " * threadNum                                            ... 237
              print "." * (count + 1)                                         ... 238
              println " ${now() - startTime}"                                   ... 239
          }                                                                  .... 334
          Thread.sleep 100                                                  .... 336
      }
  }
                                                                               .... 337
  5.times { Thread.start worker.curry(it) }                                   .... 338
  println "ROCK!"                                                               .... 339


   Source: http://chrisbroadfoot.id.au/articles/2008/08/06/groovy-threads    DSLs 2010 - 84
EMC DSL…
© ASERT 2006-2010




                               DSLs 2010 - 85
...EMC Neo4j DSL…
                    @GrabResolver(name= 'neo4j-public-repo', root= 'http://m2.neo4j.org')
                    @Grab('org.neo4j:neo4j-kernel:1.1.1')
                    import org.neo4j.kernel.EmbeddedGraphDatabase
                    import org.neo4j.graphdb.*

                    // an enum helper
                    enum MyRelationships implements RelationshipType { knows }

                    // some optional syntactic sugar using EMC DSL
                    Node.metaClass {
© ASERT 2006-2010




                      propertyMissing { String name, val ->
                        delegate.setProperty(name, val) }
                      propertyMissing { String name -> delegate.getProperty(name) }
                      methodMissing { String name, args ->
                        delegate.createRelationshipTo(args[0], MyRelationships."$name") }
                    }

                    Relationship.metaClass {
                      propertyMissing { String name, val ->
                        delegate.setProperty(name, val) }
                      propertyMissing { String name -> delegate.getProperty(name) }
                    }

                    // ...                                                       DSLs 2010 - 86
…EMC Neo4j DSL
                    // ...
                    def graphDb = new EmbeddedGraphDatabase("graphdb")
                    def tx = graphDb.beginTx()
                    def firstNode, secondNode, relationship
                    try {
                      firstNode = graphDb.createNode()
                      secondNode = graphDb.createNode()
                      relationship = firstNode.knows(secondNode)
                      firstNode.message = "Hello,"
                      secondNode.message = "world!"
© ASERT 2006-2010




                      relationship.message = "brave Neo4j"
                      tx.success()
                    } finally {
                      tx.finish()
                      println "$firstNode.message $relationship.message $secondNode.message"
                      // => Hello, brave Neo4j world!
                      graphDb.shutdown()
                    }




                                                                                      DSLs 2010 - 87
Groovy DSL Features
© ASERT 2006-2010




                                          DSLs 2010 - 88
AST Builder
• Numerous approaches, still evolving.
  “From code” approach:

   def result = new AstBuilder().buildFromCode {
       println "Hello World"
   }

• Produces:
   BlockStatement
      -> ReturnStatement
         -> MethodCallExpression
            -> VariableExpression("this")
            -> ConstantExpression("println")
            -> ArgumentListExpression
                -> ConstantExpression("Hello World")
                                                 DSLs 2010 - 89
Type Transformation Example
                    class InventoryItem {
                       def weight, name
                       InventoryItem(Map m) {
                          this.weight = m.weight; this.name = m.name
                       }
                       InventoryItem(weight, name) {
                          this.weight = weight; this.name = name
                       }
                       InventoryItem(String s) {
© ASERT 2006-2010




                          s.find(/weight=(d*)/) { all, w -> this.weight = w }
                          s.find(/name=(.*)/) { all, n -> this.name = n }
                       }
                    }
                    def room = [:]
                    def gold = [weight:50, name:'Gold'] as InventoryItem
                    def emerald = [10, 'Emerald'] as InventoryItem
                    def dagger = ['weight=5, name=Dagger'] as InventoryItem
                    room.contents = [gold, emerald, dagger]
                    room.contents.each{ println it.dump() }
                                                                                 DSLs 2010 - 90
GParsec...
• Miniature parsers
def   isLetter = { ch -> return Character.isLetter(ch) }
def   isDigit = { ch -> return Character.isDigit(ch) }
def   letter = satisfyC(isLetter)
def   digit = satisfyC(isDigit)

• Parser Combinators
 def letterAndDigit = seqC(letter, digit)
 assert letterAndDigit('a123###') == ['a', '1']
 def identifier = seqC(identifierStart,
                       noneOrMoreC(identifierRest))




                                                   DSLs 2010 - 91
...GParsec...
• BNF
   variableDeclarator :
       identifier ( '=' expression )?
   variableDefinitions :
       variableDeclarator ( ',' variableDeclarator)*

• Groovy definitions
def variableDeclarator =
    seqC(identifier, optionalC(seqC(assign, expression)))
def variableDefinitions =
    seqC(variableDeclarator,
         noneOrMoreC(seqC(comma, variableDeclarator)))




                                                  DSLs 2010 - 92
...GParsec
• satisfyC: combinator consumes a single input when its
  predicate succeeds
• altC (alt3C, altCs): is the choice combinator. Given two
  parsers it only looks at its second alternative if the first has
  not consumed any input - regardless of the final value
• seqC (seq3C, seqCs): is the sequencing combinator. It runs
  two parsers in succession and if successful, returns the result
  of the two parsers
• noneOrMoreC, oneOrMoreC: applies a parser zero or more
  times to an input stream. The result from each application of
  the parser are returned in a list
• optionalC: The combinator optionalC may succeed in
  parsing some input. It always returns success.




                                                             DSLs 2010 - 93
Using External Parsers
                    import static com.mdimension.jchronic.Chronic.parse
                    def span = parse("tomorrow at 5pm")
                    println span.endCalendar.format('yyyy-MM-dd HH:mm')
                    // => 2010-05-19 17:00
© ASERT 2006-2010




                                                                    DSLs 2010 - 94
Topics
                    • DSL origins
                    • Groovy Intro
                    • Groovy DSL Features
                    Groovy DSL Examples
                    • Advanced Topics & Guidelines
© ASERT 2006-2010




                    • More Info




                                                     DSLs 2010 - 95
Coin example...

                    enum Coin {
                      penny(1), nickel(5), dime(10), quarter(25)
                      Coin(int value) { this.value = value }
                      int value
                    }
© ASERT 2006-2010




                    import static Coin.*

                    assert 2 * quarter.value +
                           1 * nickel.value +
                           2 * penny.value == 57




                                                             DSLs 2010 - 96
...Coin example...
                    class CoinMath {
                      static multiply(Integer self, Coin c) {
                        self * c.value
                      }
                    }

                    use (CoinMath) {
© ASERT 2006-2010




                      assert 2 * quarter +
                             1 * nickel +
                             2 * penny == 57
                    }
                    // EMC equivalent
                    Integer.metaClass.multiply = {
                      Coin c -> delegate * c.value
                    }
                    assert 2 * quarter + 1 * nickel + 2 * penny == 57
                                                                        DSLs 2010 - 97
...Coin example
                    class CoinValues {
                       static get(Integer self, String name) {
                          self * Coin."${singular(name)}".value
                       }
                       static singular(String val) {
                          val.endsWith('ies') ? val[0..-4] + 'y' : val.endsWith('s') ? val[0..-2] : val
                       }
                    }

                    use (CoinValues) {
                      assert 2.quarters + 1.nickel + 2.pennies == 57
© ASERT 2006-2010




                    }



                    // EMC equivalent
                    Integer.metaClass.getProperty = { String name ->
                       def mp = Integer.metaClass.getMetaProperty(name)
                       if (mp) return mp.getProperty(delegate)
                       def singular = name.endsWith('ies') ? name[0..-4] + 'y' :
                            name.endsWith('s') ? name[0..-2] : name
                       delegate * Coin."$singular".value
                    }

                    assert 2.quarters + 1.nickel + 2.pennies == 57
                                                                                                          DSLs 2010 - 98
Game example...
                    // Trying out the game DSL idea by Sten Anderson from:
                    // http://blogs.citytechinc.com/sanderson/?p=92
                    class GameUtils {
                      static VOWELS = ['a', 'e', 'i', 'o', 'u']
                      static listItems(things) {
                        def result = ''
                        things.eachWithIndex{ thing, index ->
                          if (index > 0) {
© ASERT 2006-2010




                            if (index == things.size() - 1) result += ' and '
                            else if (index < things.size() - 1) result += ', '
                          }
                          result += "${thing.toLowerCase()[0] in VOWELS ? 'an' : 'a'} $thing
                        }
                        result ?: 'nothing'
                      }
                    }
                    import static GameUtils.*
                    ...

                                                                                  DSLs 2010 - 99
...Game example...
                    ...
                    class Room {
                      def description
                      def contents = []
                    }
                    ...
© ASERT 2006-2010




                                                           DSLs 2010 - 100
...Game example...
                    ...
                    class Player {
                      def currentRoom
                      def inventory = []
                      void look() {
                        println "You are in ${currentRoom?.description?:'the void'} 
                    which contains ${listItems(currentRoom?.contents)}"
                      }
© ASERT 2006-2010




                      void inv() {
                        println "You are holding ${listItems(inventory)}"
                      }
                      void take(item) {
                        if (currentRoom?.contents?.remove(item)) {
                          inventory << item
                          println "You took the $item"
                        } else {
                          println "I see no $item here"
                        }
                      }
                      ...                                                       DSLs 2010 - 101
...Game example...
                    ...
                    void drop(item) {
                        if (inventory?.remove(item)) {
                          currentRoom?.contents << item
                          println "You dropped the $item"
                        } else {
                          println "You don't have the $item"
                        }
© ASERT 2006-2010




                      }
                      def propertyMissing(String name) {
                        if (metaClass.respondsTo(this, name)) {
                          this."$name"()
                        }
                        name
                      }
                    }
                    ...


                                                                  DSLs 2010 - 102
...Game example...
                    ...
                    Room plainRoom = new Room(description:'a plain white room',
                                                  contents:['dagger', 'emerald', 'key'])
                    Player player = new Player(currentRoom:plainRoom)
                    player.with {
                      inv
                      look
                      take dagger
© ASERT 2006-2010




                      inv
                      look
                      take emerald
                      inv
                      look
                      take key
                      drop emerald
                      inv
                      look
                    }
                    assert player.inventory == ['dagger', 'key']
                    ...                                                         DSLs 2010 - 103
...Game example
                    ...

                    // now try some error conditions
                    plainRoom.description = null
                    player.with {
                      drop gold
                      take gold
                      drop emerald
© ASERT 2006-2010




                      take emerald
                      take emerald
                      look
                    }




                                                       DSLs 2010 - 104
GEP3 example...
                    Object.metaClass.please =
                      { clos -> clos(delegate) }
                    Object.metaClass.the =
                      { clos -> delegate[1](clos(delegate[0])) }

                    show = { thing -> [thing, { println it }] }
                    square_root = { Math.sqrt(it) }
© ASERT 2006-2010




                    given = { it }

                    given 100 please show the square_root
                    // ==> 10.0




                                                             DSLs 2010 - 105
...GEP3 example...
                    Object.metaClass.of =
                      { delegate[0](delegate[1](it)) }
                    Object.metaClass.the =
                      { clos -> [delegate[0], clos] }

                    show = [{ println it }]
© ASERT 2006-2010




                    square_root = { Math.sqrt(it) }
                    please = { it }

                    please show the square_root of 100
                    // ==> 10.0




                                                         DSLs 2010 - 106
...GEP3 example...

                    show = { println it }
                    square_root = { Math.sqrt(it) }

                    def please(action) {
                        [the: { what ->
                            [of: { n -> action(what(n)) }]
© ASERT 2006-2010




                        }]
                    }

                    please show the square_root of 100
                    // ==> 10.0



                                       Inspiration for this example came from …

                                                                              DSLs 2010 - 107
...GEP3 example...
                    // Japanese DSL using GEP3 rules
                    Object.metaClass.を =
                      Object.metaClass.の =
                       { clos -> clos(delegate) }

                    まず = { it }
                    表示する = { println it }
© ASERT 2006-2010




                    平方根 = { Math.sqrt(it) }

                    まず 100 の 平方根 を 表示する
                    // First, show the square root of 100

                    // => 10.0


                     // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117
                     // http://groovyconsole.appspot.com/edit/241001

                                                                                  DSLs 2010 - 108
...GEP3 example
© ASERT 2006-2010




                    // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117
                    // http://groovyconsole.appspot.com/edit/241001

                                                                                 DSLs 2010 - 109
Medical Prescription DSL…
                    class Drug {
                      String name
                      String toString() { name }
                    }

                    class Measure {
                      Number number
                      String unit, units
                      String toString() { number == 1 ? "1 $unit" : "$number $units" }
© ASERT 2006-2010




                    }

                    class Quantity extends Measure {}
                    class Duration extends Measure {}

                    // ...




                     http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu
                                                                                               DSLs 2010 - 110
…Medical Prescription DSL…
                    // ...
                    // ---- DSL implementation ------------------------------

                    Integer.metaClass.getHour = {-> 1.hours }
                    Integer.metaClass.getPill = {-> 1.pills }
                    Integer.metaClass.getHours = {-> new Duration(number: delegate,
                                                          unit: 'hour', units: 'hours') }
                    Integer.metaClass.getPills = {-> new Quantity(number: delegate,
                                                          unit: 'pill', units: 'pills') }

                    // use the script binding for storing new drugs available as variables
© ASERT 2006-2010




                    binding = new Binding() {
                        def getVariable(String drug) {
                            new Drug(name: drug)
                        }
                    }

                    take = {Quantity quantity ->
                        ['of': {Drug drug ->
                            ['after': {Duration duration ->
                                println "Take $quantity of $drug afer $duration"
                            }]
                        }]
                    }

                    // ...
                                                                                       DSLs 2010 - 111
…Medical Prescription DSL
                    // ...
                    // ---- DSL -------------------------------

                    take 2.pills of chloroquinine after 6.hours
© ASERT 2006-2010




                    http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu
                                                                                              DSLs 2010 - 112
Stock Exchange Order DSL…
                    // ---- A bit of script initialization ---------------------------

                    // use the script binding for silent sentence words like "to", "the"
                    binding = new CustomBinding()
                    // syntax for 200.shares
                    Integer.metaClass.getShares = { -> delegate }

                    // ---- Stock exchange orders DSL --------------------------------

                    order to buy 200.shares of GOOG {
© ASERT 2006-2010




                        limitPrice       500
                        allOrNone        false
                        at the value of { qty * unitPrice - 100 }
                    }

                    order to sell 150.shares of VMW {
                        limitPrice       80
                        allOrNone        true
                        at the value of { qty * unitPrice }
                    }

                    // ...
                             http://groovyconsole.appspot.com/script/226001 by glaforge
                                                                                          DSLs 2010 - 113
… Stock Exchange Order DSL…
                    // ----- Implementation of the DSL --------------------------------

                    enum Action { Buy, Sell }

                    class Order {
                        Security security
                        Integer quantity, limitPrice
                        boolean allOrNone
                        Closure valueCalculation
                        Action action

                       def buy(Integer quantity) {
                           this.quantity = quantity
                           this.action = Action.Buy
                           return this
                       }
© ASERT 2006-2010




                       def sell(Integer quantity) {
                           this.quantity = quantity
                           this.action = Action.Sell
                           return this
                       }

                       def limitPrice(Integer limit) {
                           this.limitPrice = limit
                       }

                       def allOrNone(boolean allOrNone) {
                           this.allOrNone = allOrNone
                       }

                       def at(Closure characteristicsClosure) {
                           return this
                       }

                       def value(Closure valueCalculation) {
                           this.valueCalculation = valueCalculation
                       }

                       // ...                                                             DSLs 2010 - 114
…Stock Exchange Order DSL…
                        // Characteristics of the order: "of GOOG {...}"
                        def of(SecurityAndCharacteristics secAndCharact) {
                            security = secAndCharact.security
                            def c = secAndCharact.characteristics.clone()
                            c.delegationStrategy = Closure.DELEGATE_ONLY
                            c.delegate = this
                            c()
                            // debug print of the resulting order
                            println toString()
                            return this
                        }

                        // Valuation closure: "of { qty, unitPrice -> ... }"
                        def of(Closure valueCalculation) {
© ASERT 2006-2010




                            // in order to be able to define closures like { qty * unitPrice }
                            // without having to explicitly pass the parameters to the closure
                            // we can wrap the closure inside another one
                            // and that closure sets a delegate to the qty and unitPrice variables
                            def wrapped = { qty, unitPrice ->
                                def cloned = valueCalculation.clone()
                                cloned.resolveStrategy = Closure.DELEGATE_ONLY
                                cloned.delegate = [qty: qty, unitPrice: unitPrice]
                                cloned()
                            }
                            return wrapped
                        }

                        String toString() {
                            "$action $quantity shares of $security.name at limit price of $limitPrice"
                        }
                    }

                    // ...
                                                                                                         DSLs 2010 - 115
…Stock Exchange Order DSL
                    // ...

                    class Security {
                        String name
                    }

                    class SecurityAndCharacteristics {
                        Security security
                        Closure characteristics
                    }

                    class CustomBinding extends Binding {
                        def getVariable(String word) {
                            // return System.out when the script requests to write to 'out'
                            if (word == "out") System.out
© ASERT 2006-2010




                             // don't thrown an exception and return null
                             // when a silent sentence word is used,
                             // like "to" and "the" in our DSL
                             null
                        }
                    }

                    // Script helper method for "GOOG {}", "VMW {}", etc.
                    def methodMissing(String name, args) {
                        new SecurityAndCharacteristics(
                            security: new Security(name: name),
                            characteristics: args[0]
                        )
                    }

                    // Script helper method to make "order to" silent
                    // by just creating our current order
                    def order(to) { new Order() }




                                 http://groovyconsole.appspot.com/script/226001 by glaforge
                                                                                              DSLs 2010 - 116
OrderBy example...

                    @Immutable class Person {
                        String first, last
                        int age
                    }

                    def people = [
                        new Person(first:'Ami', last:'Smith', age:20),
                        new Person(first:'Bruce', last:'Jones', age:10),
© ASERT 2006-2010




                        new Person(first:'Ami', last:'Jones', age:15)
                    ]

                    def order1 = new OrderBy()
                    order1.add{ it.first }
                    order1.add{ it.last }
                    order1.add{ it.age }
                    println people.sort(order1)




                                                                           DSLs 2010 - 117
...OrderBy example...

                    def order2 = new OrderBy()
                    order2.with {
                        add{ it.last }
                        add{ it.first }
                        add{ it.age }
                    }
© ASERT 2006-2010




                    println people.sort(order2)




                                                    DSLs 2010 - 118
...OrderBy example...
                    OrderBy.metaClass.firstBy = { Closure closure ->
                        if (delegate.closures) {
                            throw new IllegalStateException("firstBy should only be
                    called on an empty OrderBy")
                        }
                        delegate.add(closure)
                        return delegate
                    }
                    OrderBy.metaClass.thenBy = { Closure closure ->
© ASERT 2006-2010




                        if (!delegate.closures) {
                            throw new IllegalStateException("thenBy should not be
                    called on an empty OrderBy")
                        }
                        delegate.add(closure)
                        return delegate
                    }
                    def order3 = new OrderBy().firstBy { it.age }.
                                                 thenBy { it.first }.
                                                 thenBy { it.last }
                    println people.sort(order3)


                                                                             DSLs 2010 - 119
...OrderBy example
© ASERT 2006-2010




                                         DSLs 2010 - 120
Challenge…
                    From:    customer@acme.org
                    To:      Paul King
                    Subject: Project Request

                    Dear Paul,

                    Could you please create us a small DSL for capturing
                    our business rules. We are thinking some like:

                       price = 3
© ASERT 2006-2010




                       quantity = 10
                       total = price * quantity

                    Perhaps also an ability to check values:

                       assert total == 30


                    Thanks, Happy Customer

                    P.S. Will send a couple of more details in a follow-up email.



                                                                                    DSLs 2010 - 121
…Challenge…
                    From:      customer@acme.org
                    To:        Paul King
                    Subject:   Project Request
                    Date:      This morning

                    Dear Paul,

                    We were thinking a bit more about it, and it would be good to have
                    just a few more features. Hopefully, they won’t have much impact
                    on your existing design and can be added very quickly. It isn’t
© ASERT 2006-2010




                    much, just the ability to have IF, THEN, ELSE like structures, oh
                    yeah and the ability to have loops, and store stuff in files and
                    get stuff from databases and web services if we need.

                    Thanks, Happy Customer

                    P.S. It would be great if you can be finished by this afternoon.
                    We have a customer who would like this feature RSN. Thanks.




                                                                                   DSLs 2010 - 122
…Challenge…

                    def shell = new GroovyShell()
                    shell.evaluate('''
                    price = 3
                    quantity = 10
                    total = price * quantity
                    assert total == 30
                    ''')
© ASERT 2006-2010




                                                    DSLs 2010 - 123
…Challenge…
                    From:      customer@acme.org
                    To:        Paul King
                    Subject:   Project Request
                    Date:      This morning

                    Dear Paul,

                    We were really happy with the DSL engine you provided us with
                    but people started importing all sorts of classes. Can you stop
                    them from doing that? Maybe just java.lang.Math only. Also we
© ASERT 2006-2010




                    decided we don’t like ‚while‛ loops anymore. Leave you to it.

                    Thanks, Happy Customer

                    P.S. Have a beer and crab dinner for me at the conference. Bye.




                                                                                      DSLs 2010 - 124
…Challenge…
                    class CustomerShell {
                        Object evaluate(String text) {
                            try {
                                def loader = new CustomerClassLoader()
                                def clazz = loader.parseClass(text)
                                def script = clazz.newInstance()
                                return script.run()
                            } catch (...) { ... }
                        }
                    }

                    class CustomerClassLoader extends GroovyClassLoader {
                        def createCompilationUnit(CompilerConfiguration config, CodeSource codeSource) {
                            CompilationUnit cu = super.createCompilationUnit(config, codeSource)
© ASERT 2006-2010




                            cu.addPhaseOperation(new CustomerFilteringNodeOperation(), Phases.SEMANTIC_ANALYSIS)
                            return cu
                        }
                    }

                    private class CustomerFilteringNodeOperation extends PrimaryClassNodeOperation {
                       // ...
                       private static final allowedStaticImports = [Math].asImmutable()
                       void visitStaticMethodCallExpression(StaticMethodCallExpression smce) {
                          if (!allowedStaticImports.contains(smce.ownerType.getTypeClass())) {
                              throw new SecurityException("Static method call expressions forbidden in acme shell.")
                          }
                       }
                       void visitWhileLoop(WhileStatement whileStatement) {
                          throw new SecurityException("While statements forbidden in acme shell.")
                       }
                       // ...
                    }


                                                                                                                 DSLs 2010 - 125
…Challenge

                    def shell = new CustomerShell()
                    shell.evaluate('''
                    price = 3
                    quantity = 10
                    total = price * quantity
                    assert total == 30
                    ''')
© ASERT 2006-2010




                                                      DSLs 2010 - 126
EasyB…
                    • Description: BDD, Rspec-like testing library
                    narrative 'segment flown', {
                        as_a 'frequent flyer'
                        i_want 'to accrue rewards points for every segment I fly'
                        so_that 'I can receive free flights for my dedication to the airline'
                    }

                    scenario 'segment flown', {
                        given 'a frequent flyer with a rewards balance of 1500 points'
                        when 'that flyer completes a segment worth 500 points'
© ASERT 2006-2010




                        then 'that flyer has a new rewards balance of 2000 points'
                    }

                    scenario 'segment flown', {
                         given 'a frequent flyer with a rewards balance of 1500 points', {
                             flyer = new FrequentFlyer(1500)
                         }
                         when 'that flyer completes a segment worth 500 points', {
                             flyer.fly(new Segment(500))
                         }
                         then 'that flyer has a new rewards balance of 2000 points', {
                             flyer.pointsBalance.shouldBe 2000
                         }
                     }
                                                                                         DSLs 2010 - 127
…EasyB
                    • Description: BDD, Rspec-like testing library
                    examples "The number #{number}' should be converted to #{romanNumerals}", {
                      number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]
                      romanNumerals = ["I", "II", "III", "IV", "V",
                                       "VI", "VII", "VIII", "IX", "X"]
                    }

                    scenario "Converting #number into the roman numeral #romanNumerals", {
                      given "the number #number", {
                        theNumber = number
© ASERT 2006-2010




                      }
                      when "the system converts this number to the roman numeral equivalent", {
                        theConvertedNumber = RomanNumerals.fromInteger(theNumber)
                      }

                        then "the result should be #romanNumerals", {
                          theConvertedNumber.shouldBe romanNumerals
                        }
                    }




                    Source: http://www.wakaleo.com/blog/285-example-driven-testing-with-easyb
                                                                                                DSLs 2010 - 128
EasyB Preprocessing...
• EasyB supports either of these:
             given "some data", {
                 println '... setting expectations'
             }
             given ("some data") {
                 println '... setting expectations'
             }

• But we would like this:
            given "some data" {
                println '... setting expectations'
            }
                           But also consider Command Expressions (GEP-3) for Groovy1.8+.
Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html
                                                                                            DSLs 2010 - 129
...EasyB Preprocessing...


  String addCommas(text) {
    def pattern =
      ~/(.*)(given|when|then) "([^"]*(.[^"]*)*)" {(.*)/
    def replacement = /$1$2 "$3", {$4/
    (text =~ pattern).replaceAll(replacement)
  }




         Adds the comma in where required before Groovy sees it.




Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html
                                                                                            DSLs 2010 - 130
...EasyB Preprocessing
class SourceModifierParserPlugin extends AntlrParserPlugin {
    Reduction parseCST(SourceUnit sourceUnit, Reader reader)
            throws CompilationFailedException {
        def text = addCommas(reader.text)
        StringReader stringReader = new StringReader(text)
        super.parseCST(sourceUnit, stringReader)
    }
}

def parserPluginFactory = new ParserPluginFactory() {
    ParserPlugin createParserPlugin() {
        new SourceModifierParserPlugin()
    }
}

def conf = new CompilerConfiguration(pluginFactory: parserPluginFactory)
def binding = ...
def shell = new GroovyShell(binding, conf)


       But use with restraint as error messages will be with
       respect to converted source code not original.


Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html
                                                                                            DSLs 2010 - 131
Grails Criteria
                    // Account is a POJO in our domain/model

                    def c = Account.createCriteria()
                    def results = c {
                      like("holderFirstName", "Fred%")
                      and {
                        between("balance", 500, 1000)
                        eq("branch", "London")
© ASERT 2006-2010




                      }
                      maxResults(10)
                      order("holderLastName", "desc")
                    }
                    // source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.2 Criteria




                                                                                          DSLs 2010 - 132
Grails Criteria Example
                    // Book is a POJO in our domain/model

                    def book = Book.findByTitle("The Stand")

                    book = Book.findByTitleLike("Harry Pot%")

                    book = Book.findByReleaseDateBetween( firstDate, secondDate )
© ASERT 2006-2010




                    book = Book.findByReleaseDateGreaterThan( someDate )

                    book = Book.findByTitleLikeOrReleaseDateLessThan(
                         "%Something%", someDate )

                    books = Book.findAllByTitleLikeAndReleaseDateGreaterThan(
                        "%Java%", new Date()-30)
                    // source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.1 Dynamic Finders




                                                                                                     DSLs 2010 - 133
Grails Bean Builder Example
                    bb.beans {
                        marge(Person) {
                            name = "marge"
                            husband = { Person p ->
                                name = "homer"
                                age = 45
                                props = [overweight:true, height:"1.8m"]
                            }
© ASERT 2006-2010




                            children = [bart, lisa]
                        }
                        bart(Person) {
                            name = "Bart"
                            age = 11
                        }
                        lisa(Person) {
                            name = "Lisa"
                            age = 9
                        }
                    }
                    // source: 14. Grails and Spring: 14.3 Runtime Spring with the Beans DSL   DSLs 2010 - 134
GPars...
import static org.gparallelizer.actors.pooledActors.PooledActors.*

// create a new actor that prints out received messages
def console = actor {
    loop {
        react {message ->
            println message
        }
    }
}

// start the actor and send it a message
console.start()
console.send('Message')




                                                                 DSLs 2010 - 135
...GPars
import static org.gparallelizer.dataflow.DataFlow.thread
final def x = new DataFlowVariable()
final def y = new DataFlowVariable()
final def z = new DataFlowVariable()
thread {
    z << ~x + ~y
    println "Result: ${~z}"
}
thread {
    x << 10
}
                              No race-conditions
thread {                      No deadlocks
    y << 5                    No live-locks
}                             Completely deterministic programs
                              BEAUTIFUL code
                                                        DSLs 2010 - 136
Testing DSLs: Spock...
© ASERT 2006-2010




                                             DSLs 2010 - 137
...Testing DSLs: Spock...
                    • Testing framework for Java and Groovy
                    • Highly expressive specification language
                       – No assertion API
                       – No record &
                                        @Speck
                         replay
                                        @RunWith(Sputnik)
                         mocking API    class PublisherSubscriberSpeck {
                       – No               def "events are received by all subscribers"() {
                         superfluous        def pub = new Publisher()
© ASERT 2006-2010




                         annotations        def sub1 = Mock(Subscriber)
                       – Meaningful         def sub2 = Mock(Subscriber)
                         assert error       pub.subscribers << sub1 << sub2
                         messages
                                            when:
                                            pub.send("event")

                                                 then:
                                                 1 * sub1.receive("event")
                                                 1 * sub2.receive("event")
                       – Extensible          }
                       – Compatible      }
                         with JUnit
                         reportingwise
                                                                                       DSLs 2010 - 138
...Testing DSLs: Spock...
                    import com.gargoylesoftware.htmlunit.WebClient
                    import spock.lang.*
                    import org.junit.runner.RunWith

                    @Speck ()
                    @RunWith (Sputnik)
                    class TestSimpBlogSpock {
                      def page, subheadings, para, form, result

                     @Unroll("When #author posts a #category blog with content '#content' it sho
                     def "when creating a new blog entry"() {
© ASERT 2006-2010




                       given:
                         page = new WebClient().getPage('http://localhost:8080/postForm')
                         form = page.getFormByName('post')

                          when:
                            form.getInputByName('title').setValueAttribute("$author was here (and s
                            form.getSelectByName('category').getOptions().find { it.text == categor
                            form.getSelectByName('author').getOptions().find { it.text == author }.
                            form.getTextAreaByName('content').setText(content)
                            result = form.getInputByName('btnPost').click()
                            subheadings = result.getElementsByTagName('h3')
                            para = result.getByXPath('//TABLE//TR/TD/P')[0]
                    ...


                                                                                        DSLs 2010 - 139
... Testing DSLs: Spock
                    ...
                      then:
                        page.titleText == 'Welcome to SimpBlog'
                        result.getElementsByTagName('h1').item(0).textContent.matches("Post.*: $au
                        subheadings.item(1).textContent == "Category: $category"
                        subheadings.item(2).textContent == "Author: $author"

                        and:
                          para.textContent == content                    // Optional use of 'and:'
© ASERT 2006-2010




                        where:
                          author   << ['Bart', 'Homer', 'Lisa']
                          category << ['Home', 'Work', 'Food']
                          content << ['foo', 'bar', 'baz']
                        }
                    }




                                                                                            DSLs 2010 - 140
Testing DSLs: EasyB…
                    • Description: BDD, Rspec-like testing library
                    narrative 'segment flown', {
                        as_a 'frequent flyer'
                        i_want 'to accrue rewards points for every segment I fly'
                        so_that 'I can receive free flights for my dedication to the airline'
                    }

                    scenario 'segment flown', {
                        given 'a frequent flyer with a rewards balance of 1500 points'
                        when 'that flyer completes a segment worth 500 points'
© ASERT 2006-2010




                        then 'that flyer has a new rewards balance of 2000 points'
                    }

                    scenario 'segment flown', {
                         given 'a frequent flyer with a rewards balance of 1500 points', {
                             flyer = new FrequentFlyer(1500)
                         }
                         when 'that flyer completes a segment worth 500 points', {
                             flyer.fly(new Segment(500))
                         }
                         then 'that flyer has a new rewards balance of 2000 points', {
                             flyer.pointsBalance.shouldBe 2000
                         }
                     }
                                                                                         DSLs 2010 - 141
…Testing DSLs: EasyB
                    • When run will be marked as pending
                      – perfect for ATDD

                      scenario "Bart posts a new blog entry", {
                          given "we are on the create blog entry page"
                          when "I have entered 'Bart was here' as the title"
                          and "I have entered 'Cowabunga Dude!' into the content"
                          and "I have selected 'Home' as the category"
© ASERT 2006-2010




                          and "I have selected 'Bart' as the author"
                          and "I click the 'Create Post' button"
                          then "I expect the entry to be posted"
                      }




                                                                                    DSLs 2010 - 142
Testing DSLS: Cucumber...


                    # language: en
                    @newpost
                    Feature: New Blog Post
                      In order to create a new blog entry
                      Bloggers should be able to select their name and category and enter text

                      Scenario: New Posting
© ASERT 2006-2010




                        Given we are on the create blog entry page
                        When I have entered "Bart was here" as the title
                        And I have entered "Cowabunga Dude!" as the content
                        And I have selected "Home" from the "category" dropdown
                        And I have selected "Bart" from the "author" dropdown
                        And I click the 'Create Post' button
                        Then I should see a heading message matching "Post.*: Bart was here.*"




                                                                                          DSLs 2010 - 143
... Testing DSLS: Cucumber...
© ASERT 2006-2010




                                                    DSLs 2010 - 144
...Testing DSLS: Cucumber
                    import com.gargoylesoftware.htmlunit.WebClient
                    this.metaClass.mixin(cuke4duke.GroovyDsl)

                    Given ~/we are on the create blog entry page/, { ->
                        page = new WebClient().getPage('http://localhost:8080/postForm')
                    }

                    When(~/I have entered "(.*)" as the title/) {String title ->
                        form = page.getFormByName('post')
                        form.getInputByName('title').setValueAttribute(title + ' (and so was Cucumber)')
                    }

                    When(~'I have entered "(.*)" as the content') {String content ->
                        form.getTextAreaByName('content').setText(content)
© ASERT 2006-2010




                    }

                    When(~'I have selected "(.*)" from the "(.*)" dropdown') {String option, String name ->
                        form.getSelectByName(name).getOptions().find {
                            it.text == option }.setSelected(true)
                    }

                    When(~"I click the 'Create Post' button") { ->
                        result = form.getInputByName('btnPost').click()
                    }

                    Then(~'I should see a heading message matching "(.*)"') {String pattern ->
                    // ensureThat result.getElementsByTagName('h1').item(0).textContent.matches(pattern)
                        assert result.getElementsByTagName('h1').item(0).textContent.matches(pattern)
                    }




                                                                                                    DSLs 2010 - 145
Einstein‟s Riddle : Prolog
                    % from http://www.baptiste-wicht.com/2010/09/solve-einsteins-riddle-using-prolog

                    % Preliminary definitions
                    persons(0, []) :- !.
                    persons(N, [(_Men,_Color,_Drink,_Smoke,_Animal)|T]) :- N1 is N-1, persons(N1,T).
                    person(1, [H|_], H) :- !.
                    person(N, [_|T], R) :- N1 is N-1, person(N1, T, R).

                    % The Brit lives in a red house
                    hint1([(brit,red,_, _, _)|_]).
                    hint1([_|T]) :- hint1(T).
© ASERT 2006-2010




                    % The Swede keeps dogs as pets
                    hint2([(swede,_,_,_,dog)|_]).
                    hint2([_|T]) :- hint2(T).

                    % The Dane drinks tea
                    hint3([(dane,_,tea,_,_)|_]).
                    hint3([_|T]) :- hint3(T).

                    % The Green house is on the left of the White house
                    hint4([(_,green,_,_,_),(_,white,_,_,_)|_]).
                    hint4([_|T]) :- hint4(T).

                    % The owner of the Green house drinks coffee.
                    hint5([(_,green,coffee,_,_)|_]).
                    hint5([_|T]) :- hint5(T).
                    ...

                                                                                             DSLs 2010 - 146
Einstein‟s Riddle : Polyglot
                    @GrabResolver('http://dev.inf.unideb.hu:8090/archiva/repository/internal')
                    //@Grab('jlog:jlogic-debug:1.3.6')
                    @Grab('org.prolog4j:prolog4j-api:0.2.0')
                    // uncomment one of the next three lines
                    //@Grab('org.prolog4j:prolog4j-jlog:0.2.0')
                    @Grab('org.prolog4j:prolog4j-tuprolog:0.2.0')
                    //@Grab('org.prolog4j:prolog4j-jtrolog:0.2.0')
                    import org.prolog4j.*

                    def p = ProverFactory.prover
                    p.addTheory(new File('/GroovyExamples/tuProlog/src/einstein.pl').text)
                    def sol = p.solve("solution(Persons).")
© ASERT 2006-2010




                    //println sol.solution.get('Persons')     // jlog to avoid converter
                    println sol.get('Persons')                // jtrolog/tuProlog




                                                                                             DSLs 2010 - 147
Einstein‟s Riddle : Polyglot w/ DSL…
                    // define some domain classes and objects
                    enum Pet { dog, cat, bird, fish, horse }
                    enum Color { green, white, red, blue, yellow }
                    enum Smoke { dunhill, blends, pallmall, prince, bluemaster }
                    enum Drink { water, tea, milk, coffee, beer }
                    enum Nationality { Norwegian, Dane, Brit, German, Swede }

                    dogs = dog; birds = bird; cats = cat; horses = horse
                    a = owner = house = the = abode = person = man = is = to =
                        side = next = who = different = 'ignored'
© ASERT 2006-2010




                    // some preliminary definitions
                    p = ProverFactory.prover
                    hintNum = 1

                    p.addTheory('''
                    persons(0, []) :- !.
                    persons(N, [(_Men,_Color,_Drink,_Smoke,_Animal)|T]) :- N1 is N-1, persons(N1,T).
                    person(1, [H|_], H) :- !.
                    person(N, [_|T], R) :- N1 is N-1, person(N1, T, R).
                    ''')




                                                                                             DSLs 2010 - 148
…Einstein‟s Riddle : Polyglot w/ DSL…
                    // define some helper methods (our interface to prolog)
                    def addPairHint(Map m) {
                        def from = m.from?.toString()?.toLowerCase()
                        p.addTheory("""
                        hint$hintNum([(${from ?: '_'},${m.color ?: '_'},${m.drink ?: '_'},${m.smoke ?:
                    '_'},${m.pet ?: '_'})|_]).
                        hint$hintNum([_|T]) :- hint$hintNum(T).
                        """)
                        hintNum++
                    }

                    def addPositionHint(Map m, int pos) {
© ASERT 2006-2010




                        def from = m.from?.toString()?.toLowerCase()
                        p.addTheory("""
                        hint$hintNum(Persons) :- person($pos, Persons, (${from ?: '_'},${m.color ?:
                    '_'},${m.drink ?: '_'},${m.smoke ?: '_'},${m.pet ?: '_'})).
                        """)
                        hintNum++
                    }

                    def addToLeftHint(Map left, Map right) {
                        p.addTheory("""
                        hint$hintNum([(_,$left.color,_,_,_),(_,$right.color,_,_,_)|_]).
                        hint$hintNum([_|T]) :- hint$hintNum(T).
                        """)
                        hintNum++
                    }
                    ...
                                                                                              DSLs 2010 - 149
…Einstein‟s Riddle : Polyglot w/ DSL…
                    // now implement DSL in terms of helper methods
                    def the(Nationality n) {
                      def ctx = [from:n]
                      [
                        drinks: { d -> addPairHint(ctx + [drink:d]) },
                        smokes: { s -> addPairHint(ctx + [smoke:s]) },
                        keeps: { p -> addPairHint(ctx + [pet:p]) },
                        rears: { p -> addPairHint(ctx + [pet:p]) },
                        owns:{ _the -> [first:{ house -> addPositionHint(ctx, 1) }] },
                        has:{ _a ->
© ASERT 2006-2010




                          [pet: { a -> addPairHint(ctx + [pet:a]) }] +
                            Color.values().collectEntries{ c ->
                            [c.toString(), { _dummy -> addPairHint(ctx + [color:c]) } ]
                          }
                        },
                        lives: { _next -> [to: { _the ->
                          Color.values().collectEntries{ c ->
                            [c.toString(), { _dummy -> addNeighbourHint(ctx, [color:c]) } ]
                          }
                        }]}
                      ]
                    }
                    ...


                                                                                      DSLs 2010 - 150
…Einstein‟s Riddle : Polyglot w/ DSL…
                    // now define the DSL
                    the man from the centre house drinks milk
                    the Norwegian owns the first house
                    the Dane drinks tea
                    the German smokes prince
                    the Swede keeps dogs // alternate ending: has a pet dog
                    the Brit has a red house // alternate ending: red abode
                    the owner of the green house drinks coffee
                    the owner of the yellow house smokes dunhill
                    the person known to smoke pallmall rears birds // alt’n8 end: keeps birds
© ASERT 2006-2010




                    the man known to smoke bluemaster drinks beer
                    the green house is on the left side of the white house
                    the man known to smoke blends lives next to the one who keeps cats
                    the man known to keep horses lives next to the man who smokes dunhill
                    the man known to smoke blends lives next to the one who drinks water
                    the Norwegian lives next to the blue house




                                                                                      DSLs 2010 - 151
…Einstein‟s Riddle : Polyglot w/ DSL…
                      // now implement DSL in terms of helper methods
                      def the(Nationality n) {
                        def ctx = [from:n]
                        [
                          drinks: { d -> addPairHint(ctx + [drink:d]) },
                          smokes: { s -> addPairHint(ctx + [smoke:s]) },
                          keeps: { p -> addPairHint(ctx + [pet:p]) },
                          ...
                        ]
                      }
© ASERT 2006-2010




                      ...


                                                   the German smokes prince


                                                   the(German).smokes(prince)

                    n = German
                    ctx = [from: German]
                    [drinks: …,
                     smokes: { s -> addPairHint([from: German, smoke: s]) },
                     keeps: …,
                     …                                  addPairHint([from: German, smoke: prince])
                    ]
                                                                                        DSLs 2010 - 152
…Einstein‟s Riddle : Polyglot w/ DSL…
                    • Some parts of our DSL are automatically
                      statically inferred, e.g. typing „bl‟ and then
                      asking for completion yields:
© ASERT 2006-2010




                    • But other parts are not known, e.g. the
                      word „house‟ in the fragment below:


                                   „house‟ is key for a Map and could be any value
                                                                                     DSLs 2010 - 153
…Einstein‟s Riddle : Polyglot w/ DSL
                    def the(Color c1) {[
                      house: { _is -> [on: { _the -> [left: { _side -> [of: { __the ->
                        Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy ->
                          addToLeftHint([color:c1], [color:c2])
                        }]}
                      }]}]}]}
                    ]}
© ASERT 2006-2010




                    class HousePlaceHolder {
                      def c1, script
                      def house(_is) {
                        [on: { _the -> [left: { _side -> [of: { __the ->
                          Color.values().collectEntries { c2 ->
                            [c2.toString(), { _dummy -> script.addToLeftHint(
                              [color: c1], [color: c2] )}]}
                        }]}]}]
                      }
                    }

                    def the(Color c1) { new HousePlaceHolder(c1:c1, script:this) }



                                         „house‟ is now understood                   DSLs 2010 - 154
Einstein‟s Riddle : Choco w/ DSL…
                    @GrabResolver('http://www.emn.fr/z-info/choco-solver/mvn/repository/')
                    @Grab('choco:choco:2.1.1-SNAPSHOT')
                    import static choco.Choco.*
                    import choco.kernel.model.variables.integer.*

                    def m = new choco.cp.model.CPModel()
                    m.metaClass.plus = { m.addConstraint(it); m }
                    def s = new choco.cp.solver.CPSolver()
                    choco.Choco.metaClass.static.eq = { c, v -> delegate.eq(c, v.ordinal()) }
                    def makeEnumVar(st, arr) { choco.Choco.makeIntVar(st, 0, arr.size()-1,
                    choco.Options.V_ENUM) }
© ASERT 2006-2010




                    pets = new IntegerVariable[num]
                    colors = new IntegerVariable[num]
                    smokes = new IntegerVariable[num]
                    drinks = new IntegerVariable[num]
                    nations = new IntegerVariable[num]

                    (0..<num).each   { i ->
                         pets[i] =   makeEnumVar("pet$i",   pets)
                       colors[i] =   makeEnumVar("color$i", colors)
                       smokes[i] =   makeEnumVar("smoke$i", smokes)
                       drinks[i] =   makeEnumVar("drink$i", drinks)
                      nations[i] =   makeEnumVar("nation$i", nations)
                    }
                    ...

                                                                                     DSLs 2010 - 155
…Einstein‟s Riddle : Choco w/ DSL…
                    // define DSL (simplistic non-refactored version)
                    def neighbours(var1, val1, var2, val2) {
                      and(
                        ifOnlyIf(eq(var1[0], val1), eq(var2[1], val2)),
                        implies(eq(var1[1], val1), or(eq(var2[0], val2), eq(var2[2], val2))),
                        implies(eq(var1[2], val1), or(eq(var2[1], val2), eq(var2[3], val2))),
                        implies(eq(var1[3], val1), or(eq(var2[2], val2), eq(var2[4], val2))),
                        ifOnlyIf(eq(var1[4], val1), eq(var2[3], val2))
                      )
                    }
                    iff = { e1, c1, e2, c2 -> and(*(0..<num).collect{
© ASERT 2006-2010




                      ifOnlyIf(eq(e1[it], c1), eq(e2[it], c2))
                    }) }
                    ...

                    // define the DSL in terms of DSL implementation
                    def the(Nationality n) {
                      def ctx = [nations, n]
                      [
                        drinks:iff.curry(*ctx, drinks),
                        smokes:iff.curry(*ctx, smokes),
                        keeps:iff.curry(*ctx, pets),
                        rears:iff.curry(*ctx, pets),
                        owns:{ _the -> [first:{ house -> eq(nations[first], n)}] },
                    ...

                                                                                      DSLs 2010 - 156
…Einstein‟s Riddle : Choco w/ DSL…
                    // define rules
                    m += all pets are different
                    m += all colors are different
                    m += all smokes are different
                    m += all drinks are different
                    m += all nations are different
                    m += the man from the centre house drinks milk
                    m += the Norwegian owns the first house
                    m += the Dane drinks tea
                    m += the German smokes prince
© ASERT 2006-2010




                    m += the Swede keeps dogs // alternate ending: has a pet dog
                    m += the Brit has a red house // alternate ending: red abode
                    m += the owner of the green house drinks coffee
                    m += the owner of the yellow house smokes dunhill
                    m += the person known to smoke pallmall rears birds // alt end: keeps birds
                    m += the man known to smoke bluemaster drinks beer
                    m += the green house is on the left side of the white house
                    m += the man known to smoke blends lives next to the one who keeps cats
                    m += the man known to keep horses lives next to the man who smokes dunhill
                    m += the man known to smoke blends lives next to the one who drinks water
                    m += the Norwegian lives next to the blue house
                    ...



                                                                                      DSLs 2010 - 157
…Einstein‟s Riddle : Choco w/ DSL
                    def pretty(s, c, arr, i) { c.values().find{
                        it.ordinal() == s.getVar(arr[i])?.value } }

                    // invoke logic solver
                    s.read(m)
                    def more = s.solve()
                    while (more) {
                      for (i in 0..<num) {
                        print   'The '                +   pretty(s,   Nationality, nations, i)
                        print   ' has a pet '         +   pretty(s,   Pet, pets, i)
© ASERT 2006-2010




                        print   ' smokes '            +   pretty(s,   Smoke, smokes, i)
                        print   ' drinks '            +   pretty(s,   Drink, drinks, i)
                        println ' and lives in a '    +   pretty(s,   Color, colors, i) + ' house'
                      }
                      more = s.nextSolution()
                    }

                    • Output:
                    Solving Einstein's Riddle:
                    The Norwegian has a pet cat smokes dunhill drinks water and lives in a yellow house
                    The Dane has a pet horse smokes blends drinks tea and lives in a blue house
                    The Brit has a pet bird smokes pallmall drinks milk and lives in a red house
                    The German has a pet fish smokes prince drinks coffee and lives in a green house
                    The Swede has a pet dog smokes bluemaster drinks beer and lives in a white house

                                                                                               DSLs 2010 - 158
Topics
                    • DSL Origins
                    • Groovy Intro
                    • Groovy DSL Features
                    • Groovy DSL Examples
                    Advanced Topics & Guidelines
© ASERT 2006-2010




                    • More Info




                                                    DSLs 2010 - 159
Guidelines…
• Language and program evolve together.
  Like the border between two warring states,
  the boundary between language and program
  is drawn and redrawn, until eventually it
  comes to rest along the mountains and rivers,
  the natural frontiers of your problem. In the
  end your program will look as if the
  language had been designed for it. And
  when language and program fit one another
  well, you end up with code which is clear,
  small, and efficient.
  – Paul Graham
                                         DSLs 2010 - 160
…Guidelines
  •   Start small, don‟t over engineer
  •   Grow your language iteratively
  •   Play with end users
  •   Let your DSL fly (theirs not yours)
  •   Don‟t expect to get it right first time
  •   Play in a sandbox




http://www.slideshare.net/glaforge/implementing-groovy-domainspecific-languages-s2g-forum-munich-2010
                                                                                          DSLs 2010 - 161
Topics
                    • DSL Origins
                    • Groovy Intro
                    • Groovy DSL Features
                    • Groovy DSL Examples
                    • Advanced Topics and Guidelines
© ASERT 2006-2010




                    More Info




                                                       DSLs 2010 - 162
Further Info…
                    • Compilers : Principles, Techniques,
                      and Tools/ Edition 2, Alfred Aho, Ravi
                      Sethi, Jeffrey Ullman, Monica Lam
                    • Practical API Design : Confessions of
                      a Java Framework Architect, Jaroslav
                      Tulach
© ASERT 2006-2010




                    • Language Implementation Patterns :
                      Create Your Own Domain-Specific and
                      General Programming Languages,
                      Terence Parr



                                                               DSLs 2010 - 163
…Further Info
                    • A model-driven framework for domain
                      specific languages, Martin Karlsch
                    • Design Guidelines for Domain Specific
                      Languages, Gabor Karsai et al
                    • Program Comprehension for Domain-
                      Specific Languages, Pereira et al
© ASERT 2006-2010




                    • Diagrammatic Representations in Domain-
                      Specific Languages, Konstantinos Tourlas
                    • DSLs in Action, Debasish Ghosh
                    • Domain-Specific Languages, Martin Fowler
                    • DSLs in Boo : Domain Specific Languages
                      in .NET, Ayende Rahien


                                                                 DSLs 2010 - 164
GinA 2ed “ReGinA” is coming ...




                                  DSLs 2010 - 165

GroovyDSLs

  • 1.
    Writing Domain Specific ©ASERT 2006-2010 Languages (DSLs) using Groovy Dr Paul King, @paulk_asert paulk at asert.com.au
  • 2.
    Topics DSL Origins • Groovy Intro • Groovy DSL Features • Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 2
  • 3.
    What is aDSL?... • A domain-specific language is a programming language or executable specification language that offers, through appropriate notations and abstractions, expressive power focused on, and usually © ASERT 2006-2010 restricted to, a particular problem domain – In contrast, general-purpose languages are created to solve problems in many domains – Somewhere between declarative data and a full blown general- purpose programming language (GPL) – AKA: fluent / human interfaces, language oriented programming, problem-oriented languages, little / mini languages, macros, business natural languages Sources: http://en.wikipedia.org/wiki/Domain-specific_language van Deursen, A., Klint, P., Visser, J.: Domain-specific languages: an annotated bibliography. ACM SIGPLAN Notices 35 (2000) 26–36
  • 4.
    ...What is aDSL? • Advantages: • Disadvantages: – Domain experts can – Learning cost vs. limited understand, validate, modify, applicability and often even develop DSL – Cost of designing, programs implementing & maintaining – Somewhat self-documenting DSL as well as the tools/IDEs – Enhance quality, – Attaining proper scope © ASERT 2006-2010 productivity, reliability, – Trade-offs between domain- maintainability, portability specificity and general- and reusability purpose programming – Safety; as long as the language constructs language constructs are safe – Efficiency costs any sentence written with – Proliferation of similar non- them can be considered safe standard DSLs, i.e. different but similar DSLs used within two insurance companies Source: http://en.wikipedia.org/wiki/Domain-specific_language DSLs 2010 - 4
  • 5.
    Origins • It has always been a holy grail of computer users to be able to speak directly to our computers © ASERT 2006-2010 • Different languages have pushed the boundaries further than others – APT for numerically controlled machine tools (1957) – BNF (1959), COBOL, 4GLs – LISP & Smalltalk – Unix "little languages" DSLs 2010 - 5
  • 6.
    Origins • It has always been a holy grail of computer users to be able to speak directly to our computers © ASERT 2006-2010 http://dsal.uchicago.edu/reference/gazetteer/pager.html?objectid=DS405.1.I34_V02_298.gif DSLs 2010 - 6
  • 7.
    Origins: LISP &Smalltalk “In Lisp, you don’t just write your program down toward the language, you also build the language up toward your program” - Paul Graham © ASERT 2006-2010 “When [Smalltalk] is used to describe an application system, the developer extends Smalltalk, creating a domain-specific language by adding a new vocabulary of language elements ...” - Adele Goldberg DSLs 2010 - 7
  • 8.
    Origins: Minilanguages... Source: The Art of Unix Programming:Taxonomy of languages: http://www.faqs.org/docs/artu/ch08s01.html # Minilanguage taxonomy %!PS-Adobe-3.0 # Base ellipses %%Creator: groff version 1.20.1 define smallellipse {ellipse width 3.0 height 1.5} ... M: ellipse width 3.0 height 1.8 fill 0.2 597.6 12 72 12 DL(increasing loopiness)297.71 8.2 Q(/etc/passwd)102.67 line from M.n to M.s dashed 94.6 Q(.ne)110.715 106.6 Q(wsrc)-.25 E(SNG)195.2 100.6 Q(re)243.8 94.6 Q D: smallellipse() with .e at M.w + (0.8, 0) (ge)-.15 E(xps)-.15 E(Glade)247.26 106.6 Q(m4)306.81 58.6 Q -1(Ya)303.43 line from D.n to D.s dashed 70.6 S(cc)1 E(Le)305.5 82.6 Q(x)-.15 E(mak)302.42 94.6 Q(e)-.1 E(XSL) I: smallellipse() with .w at M.e - (0.8, 0) 301.16 106.6 Q(T)-.92 E(pic)307.09 118.6 Q(tbl)307.92 130.6 Q(eqn)305.98 142.6 Q(fetchmail)344.715 82.6 Q -.15(aw)355.345 94.6 S(k).15 E(trof) # Arrow headings 354.84 106.6 Q(f)-.25 E(Postscript)343.875 118.6 Q(dc)412.88 94.6 Q(bc) arrow from D.w + (0.4, 0.8) to D.e + (-0.4, 0.8) 412.88 106.6 Q(Emacs Lisp)462.53 94.6 Q(Ja)465.395 106.6 Q -.25(va)-.2 G "flat to structured" "" at last arrow.c (Script).25 E(sh)529.075 94.6 Q(tcl)528.52 106.6 Q(Perl)565.065 88.6 Q ... (Python)558.95 100.6 Q(Ja)564.46 112.6 Q -.25(va)-.2 G 0 Cg EP # Interpreters %%Trailer "Emacs Lisp" "JavaScript" at 0.25 between M.e and I.e end "sh" "tcl" at 0.55 between M.e and I.e %%EOF "Perl" "Python" "Java" at 0.8 between M.e and I.e Input DSL: pic for above picture Output "DSL": Postscript for above picture DSLs 2010 - 8
  • 9.
    ...Origins: Minilanguages... import builder.PowerPointBuilder defname = 'minilanguages' assert new File("${name}.pic').exists() "groff -e -p ${name}.pic > ${name}.ps".execute() "gs -q -sDEVICE='ppmraw' -g2600x3500 -r300x300 -sOutputFile='-' -dBATCH ↵ –dNOPAUSE ${name}.ps | pnmcrop | ppmtogif > ${name}.gif".execute() def builder = new PowerPointBuilder() // Adapted from Erik Pragt builder.slideshow(filename: 'DSLsInGroovy_MiniLanguages.ppt') { slide(title: 'Origins: Minilanguages...') { image( origin: [0, 15], src: "${name}.gif", caption: 'Source: The Art of Unix Programming:Taxonomy...' ) textbox( origin: [5, 100], text: new File("${name}.pic").text, caption: 'Input DSL: pic for above picture' ) textbox( origin: [115, 100], text: new File("${name}.ps").text, caption: 'Output "DSL": Postscript for above picture' ) } } PowerPoint DSL for previous slide DSLs 2010 - 9
  • 10.
    ...Origins: Minilanguages Glade XSLT <?xml version="1.0"?> <?xml version="1.0"?> <GTK-Interface> <xsl:stylesheetversion="1.0" <widget> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <class>GtkWindow</class> <xsl:output method="xml"/> <name>HelloWindow</name> <xsl:template match="*"> <border_width>5</border_width> <xsl:element name="{name()}"> <Signal> <xsl:for-each select="@*"> <name>destroy</name> <xsl:element name="{name()}"> <handler>gtk_main_quit</handler> <xsl:value-of select="."/> </Signal> </xsl:element> <title>Hello</title> </xsl:for-each> <type>GTK_WINDOW_TOPLEVEL</type> <xsl:apply-templates select="*|text()"/> <position>GTK_WIN_POS_NONE</position> </xsl:element> <allow_shrink>True</allow_shrink> </xsl:template> <allow_grow>True</allow_grow> </xsl:stylesheet> <auto_shrink>False</auto_shrink> Regex <widget> <class>GtkButton</class> "x.z?z{1,3}y" <name>Hello World</name> <can_focus>True</can_focus> fetchmail <label>Hello World</label> </widget> # Poll this site first each cycle. poll pop.provider.net proto pop3 </widget> </GTK-Interface> user "jsmith" with pass "secret1" is "smith" here user jones with pass "secret2" is "jjones" here with options keep SQL # Poll this site second, unless Lord Voldemort zaps us first. poll billywig.hogwarts.com with proto imap: SELECT * FROM TABLE user harry_potter with pass "floo" is harry_potter here WHERE NAME LIKE '%SMI' # Poll this site third in the cycle. ORDER BY NAME # Password will be fetched from ~/.netrc poll mailhost.net with proto imap: user esr is esr here Troff cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html DSLs 2010 - 10
  • 11.
    And now? Thetwitterverse says... DSLs 2010 - 11
  • 12.
    ...And Now? Thetwitterverse says DSLs 2010 - 12
  • 13.
    State of theArt for DSLs? © ASERT 2006-2010 Source: http://www.infoq.com/presentations/vanderburg-state-of-dsl-ruby – Gartner's take on technology adoption DSLs 2010 - 13
  • 14.
    "Is it aDSL or an API?" Checklist • A ten-question test to help determine whether a wad of code represents a DSL or an API: 1. Have you ever programmed in a language other than Ruby? (PHP and HTML don‟t count.) If not, it‟s a DSL. 2. Is the defining syntactic feature that you’ve cleverly left the parentheses off of a list of function arguments? If so, it‟s a DSL. 3. Is the code primarily a list of key-value pairs? Welcome to DSL Town, population you! 4. Does the code require the liberal use of eval() or equivalent? DSL, yay! 5. Have you ever used the phrase “… and it reads just like English!” in seriousness? You‟d better get to the hospital; you‟re coming down with a case of the DSLs! 6. ... Source: http://www.oreillynet.com/onlamp/blog/2007/05/the_is_it_a_dsl_or_an_api_ten.html DSLs 2010 - 14
  • 15.
    DSL usage patterns... • Internal (uses a general purpose language) – AKA embedded – Can be a subset or superset – Can be generated/converted from other "language" – Numerous mechanisms to make as close to the domain as possible © ASERT 2006-2010 • External (custom language) – May involve writing your own traditional parser – Using parser combinators – Interpreting – String grepping • Other characteristics – Functional vs OO DSLs 2010 - 15
  • 16.
    ...DSL usage patterns... ©ASERT 2006-2010 Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 16
  • 17.
    ...DSL usage patterns ©ASERT 2006-2010 Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 17
  • 18.
    DSL usage patterns(Advanced) © ASERT 2006-2010 Source: http://www.spinellis.gr/pubs/jrnl/2000-JSS-DSLPatterns/html/dslpat.html See: Diomidis Spinellis. Notable design patterns for domain specific languages. Journal of Systems and Software, 56(1):91–99, February 2001.
  • 19.
    Topics • DSL origins Groovy Intro • Groovy DSL Features • Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 19
  • 20.
    What is Groovy? • “Groovy is like a super version of Java. It can leverage Java's enterprise capabilities but also has cool productivity features like closures, DSL support, builders and dynamic typing.” © ASERT 2006-2010 Groovy = Java – boiler plate code + optional dynamic typing + closures + domain specific languages + builders + metaprogramming DSLs 2010 - 20
  • 21.
    Groovy Goodies Overview • Fully object oriented • Closures: reusable and assignable pieces of code • Operators can be • GPath: efficient overloaded © ASERT 2006-2010 object navigation • Multimethods • GroovyBeans • Literal declaration for • grep and switch lists (arrays), maps, ranges and regular • Templates, builder, expressions swing, Ant, markup, XML, SQL, XML-RPC, Scriptom, Grails, tests, Mocks DSLs 2010 - 21
  • 22.
    Growing Acceptance … A slow and steady start but now gaining in momentum, maturity and mindshare Now free
  • 23.
    … Growing Acceptance… © ASERT 2006-2010 DSLs 2010 - 23
  • 24.
    … Growing Acceptance… © ASERT 2006-2010 Groovy and Grails downloads: 70-90K per month and growing DSLs 2010 - 24
  • 25.
    … Growing Acceptance… © ASERT 2006-2010 Source: http://www.micropoll.com/akira/mpresult/501697-116746 Source: http://www.grailspodcast.com/ DSLs 2010 - 25
  • 26.
    … Growing Acceptance… © ASERT 2006-2010 http://www.jroller.com/scolebourne/entry/devoxx_2008_whiteboard_votes http://www.java.net DSLs 2010 - 26
  • 27.
    … Growing Acceptance… What alternative JVM language are you using or intending to use © ASERT 2006-2010 http://www.leonardoborges.com/writings DSLs 2010 - 27
  • 28.
    … Growing Acceptance… © ASERT 2006-2010 http://it-republik.de/jaxenter/quickvote/results/1/poll/44 (translated using http://babelfish.yahoo.com) DSLs 2010 - 28
  • 29.
    … Growing Acceptance ©ASERT 2006-2010 DSLs 2010 - 29
  • 30.
    The Landscape ofJVM Languages optional static types © ASERT 2006-2010 Dynamic features call for dynamic types Java bytecode calls for static types The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform. DSLs 2010 - 30
  • 31.
    Groovy Starter System.out.println("Hello, World!"); // supports Java syntax println 'Hello, World!' // but can remove some syntax String name = 'Guillaume' // Explicit typing/awareness println "$name, I'll get the car." // Gstring (interpolation) def longer = """${name}, the car is in the next row.""" // multi-line, implicit type assert 0.5 == 1/2 // BigDecimal equals() © ASERT 2006-2010 assert 0.1 + 0.2 == 0.3 // and arithmetic def printSize(obj) { // implicit/duck typing print obj?.size() // safe dereferencing } def pets = ['ant', 'bee', 'cat'] // native list syntax pets.each { pet -> // closure support assert pet < 'dog' // overloading '<' on String } // or: for (pet in pets)... DSLs 2010 - 31
  • 32.
    A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { This code List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { is valid String s = (String) strings.get(i); if (s.length() <= length) { Java and } result.add(s); valid Groovy } return result; © ASERT 2006-2010 } public static void main(String[] args) { List names = new ArrayList(); names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); System.out.println(names); Erase e = new Erase(); Based on an List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); example by for (int i = 0; i < shortNames.size(); i++) { Jim Weirich String s = (String) shortNames.get(i); System.out.println(s); & Ted Leung } } } DSLs 2010 - 32
  • 33.
    ...A Better Java... import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { Do the List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { semicolons String s = (String) strings.get(i); if (s.length() <= length) { add anything? } result.add(s); And shouldn‟t } we us more return result; © ASERT 2006-2010 } modern list public static void main(String[] args) { List names = new ArrayList(); notation? names.add("Ted"); names.add("Fred"); names.add("Jed"); names.add("Ned"); Why not System.out.println(names); import common Erase e = new Erase(); List shortNames = e.removeLongerThan(names, 3); libraries? System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } } DSLs 2010 - 33
  • 34.
    ...A Better Java... class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) } } return result } public static void main(String[] args) { List names = new ArrayList() © ASERT 2006-2010 names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") System.out.println(names) Erase e = new Erase() List shortNames = e.removeLongerThan(names, 3) System.out.println(shortNames.size()) for (String s in shortNames) { System.out.println(s) } } } DSLs 2010 - 34
  • 35.
    ...A Better Java... class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList() for (String s in strings) { if (s.length() <= length) { result.add(s) Do we need } } the static types? } return result Must we always have a main public static void main(String[] args) { List names = new ArrayList() method and © ASERT 2006-2010 names.add("Ted"); names.add("Fred") names.add("Jed"); names.add("Ned") class definition? System.out.println(names) Erase e = new Erase() How about List shortNames = e.removeLongerThan(names, 3) improved System.out.println(shortNames.size()) for (String s in shortNames) { consistency? System.out.println(s) } } } DSLs 2010 - 35
  • 36.
    ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } © ASERT 2006-2010 names = new ArrayList() names.add("Ted") names.add("Fred") names.add("Jed") names.add("Ned") System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } DSLs 2010 - 36
  • 37.
    ...A Better Java... def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) Shouldn‟t we } } have special return result notation for lists? } And special © ASERT 2006-2010 names = new ArrayList() facilities for names.add("Ted") names.add("Fred") list processing? names.add("Jed") Is „return‟ names.add("Ned") needed at end? System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) for (s in shortNames) { System.out.println(s) } DSLs 2010 - 37
  • 38.
    ...A Better Java... def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } names = ["Ted", "Fred", "Jed", "Ned"] System.out.println(names) shortNames = removeLongerThan(names, 3) System.out.println(shortNames.size()) shortNames.each{ System.out.println(s) } © ASERT 2006-2010 DSLs 2010 - 38
  • 39.
    ...A Better Java... def removeLongerThan(strings, length) { strings.findAll{ it.size() <= length } } Is the method names = ["Ted", "Fred", "Jed", "Ned"] now needed? System.out.println(names) shortNames = removeLongerThan(names, 3) Easier ways to System.out.println(shortNames.size()) use common shortNames.each{ System.out.println(s) } methods? © ASERT 2006-2010 Are brackets required here? DSLs 2010 - 39
  • 40.
    ...A Better Java... names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } © ASERT 2006-2010 DSLs 2010 - 40
  • 41.
    ...A Better Java names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it } © ASERT 2006-2010 ["Ted", "Fred", "Jed", "Ned"] 3 Ted Jed Ned DSLs 2010 - 41
  • 42.
    Grapes / Grab // Google Collections example @Grab('com.google.collections:google-collections:1.0') import com.google.common.collect.HashBiMap HashBiMap fruit = [grape:'purple', lemon:'yellow', lime:'green'] assert fruit.lemon == 'yellow' © ASERT 2006-2010 assert fruit.inverse().yellow == 'lemon' DSLs 2010 - 42
  • 43.
    Better Design Patterns:Immutable... • Java Immutable Class – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { if (other.first != null) © ASERT 2006-2010 public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... DSLs 2010 - 43
  • 44.
    ...Better Design Patterns:Immutable... • Java Immutable Class boilerplate – As per Joshua Bloch // ... @Override Effective Java public boolean equals(Object obj) { if (this == obj) public final class Punter { return true; private final String first; if (obj == null) private final String last; return false; if (getClass() != obj.getClass()) public String getFirst() { return false; return first; Punter other = (Punter) obj; } if (first == null) { if (other.first != null) © ASERT 2006-2010 public String getLast() { return false; return last; } else if (!first.equals(other.first)) } return false; if (last == null) { @Override if (other.last != null) public int hashCode() { return false; final int prime = 31; } else if (!last.equals(other.last)) int result = 1; return false; result = prime * result + ((first == null) return true; ? 0 : first.hashCode()); } result = prime * result + ((last == null) ? 0 : last.hashCode()); @Override return result; public String toString() { } return "Punter(first:" + first + ", last:" + last + ")"; public Punter(String first, String last) { } this.first = first; this.last = last; } } // ... DSLs 2010 - 44
  • 45.
    ...Better Design Patterns:Immutable @Immutable class Punter { String first, last } © ASERT 2006-2010 DSLs 2010 - 45
  • 46.
    Malleable Syntax order to buy 200.shares of GOOG { limitPrice 500 allOrNone false at the value of { qty * unitPrice - 100 } } take 2.pills of chloroquinine after 6.hours © ASERT 2006-2010 def "length of Spock's & his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 } Groovy 1.8+ DSLs 2010 - 46
  • 47.
    Topics • DSL origins • Groovy Intro Groovy DSL Features • Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 47
  • 48.
    Groovy DSL Features ©ASERT 2006-2010 DSLs 2010 - 48
  • 49.
    Import / ImportStatic • Imports @Grab('com.google.collections:google-collections:1.0') import com.google.common.collect.HashBiMap as HashMap def m = new HashMap() m.key = 'value' assert m.inverse().value == 'key' © ASERT 2006-2010 • Static Imports import static java.util.Calendar.getInstance as now println now().format('yyyy/MMM/dd') What Java gives us plus aliases DSLs 2010 - 49
  • 50.
    Static Imports...  Java Static Imports Discussed later:  Builders  Closures import groovy.swing.SwingXBuilder © ASERT 2006-2010 import static java.awt.Color.* import static java.lang.Math.* def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [GREEN, {value -> sin(value)}], [BLUE, {value -> cos(value)}], [RED, {value -> tan(value)}] ]) }.show() DSLs 2010 - 50
  • 51.
    ...Static Imports... Java gives us this import groovy.swing.SwingXBuilder © ASERT 2006-2010 import static java.awt.Color.* import static java.lang.Math.* def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [GREEN, {value -> sin(value)}], [BLUE, {value -> cos(value)}], [RED, {value -> tan(value)}] ]) }.show() DSLs 2010 - 51
  • 52.
    ...Static Imports... Java gives us this import groovy.swing.SwingXBuilder © ASERT 2006-2010 import static java.awt.Color.* import static java.lang.Math.* def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [GREEN, {value -> sin(value)}], [BLUE, {value -> cos(value)}], [RED, {value -> tan(value)}] ]) }.show() DSLs 2010 - 52
  • 53.
    ...Static Imports... © ASERT2006-2010 Source: http://www.thedoghousediaries.com/?p=1406 DSLs 2010 - 53
  • 54.
    ...Static Imports Java doesn‟t give us this! import groovy.swing.SwingXBuilder import static java.lang.Math.* import static java.awt.Color.GREEN as Lime © ASERT 2006-2010 import static java.awt.Color.BLUE as Sky import static java.awt.Color.RED as Maraschino def swing = new SwingXBuilder() def frame = swing.frame(size: [300, 300]) { graph(plots: [ [Lime, {value -> sin(value)}], [Sky, {value -> cos(value)}], [Maraschino, {value -> tan(value)}] ]) }.show() DSLs 2010 - 54
  • 55.
    Literal Syntax Conventions • Lists – Special syntax for list literals – Additional common methods (operator overloading) def list = [3, new Date(), 'Jan'] assert list + list == list * 2 • Maps © ASERT 2006-2010 – Special syntax for map literals – Additional common methods def map = [a: 1, b: 2] assert map['a'] == 1 && map.b == 2 • Ranges – Special syntax for various kinds of ranges def letters = 'a'..'z' def numbers = 0..<10 DSLs 2010 - 55
  • 56.
    Literal Syntax Conventionsin DSLs import static java.util.Calendar.getInstance as getNow import static java.util.Calendar.* def discount = [normal:0, silver:5, gold:10] def roomrate = [weekday:150, weekend:95] for (level in ['normal', 'silver', 'gold']) { def multiplier = 1 - discount[level] / 100 print 'Your room rate is: ' © ASERT 2006-2010 if (now[DAY_OF_WEEK] in MONDAY..FRIDAY) println roomrate.weekday * multiplier else println roomrate.weekend * multiplier }  Literal list & map syntax  Ranges Your room rate is: 150 Also Your room rate is: 142.50  Static import aliases Your room rate is: 135.0  BigDecimal arithmetic DSLs 2010 - 56
  • 57.
    Compact Syntax... • Java public class BuySharesJava { public static void buyShares(int qty, String name) { // business logic here ... System.out.println("buying " + qty + " shares of " + name); } © ASERT 2006-2010 public static void main(String[] args) { buyShares(3, "BHP"); }  Script syntax  Conventions for visibility }  GDK methods: println • Groovy  Implicit typing  GString interpolation def buyShares(qty, name) {  Optional brackets & „;‟ // business logic here ... println "buying $qty shares of $name" } buyShares 3, 'BHP' DSLs 2010 - 57
  • 58.
    ...Compact Syntax... • Java  Script syntax, Named params, Extra imports  Conventions for visibility, GDK methods import java.util.Date;  Implicit typing, Multi-line GString import java.util.HashMap;  Elvis operator, Optional brackets & „;‟ import java.util.Map; public class ProcessCustomerJava { public static void printDetails(Map<String, String> cust) { System.out.println("Details as at: " + new Date()); String first = cust.get("first"); System.out.println("First name: " + first); © ASERT 2006-2010 String last = cust.get("last"); System.out.println("Last name: " + (last != null ? last : "unknown")); } public static void main(String[] args) { Map<String, String> details = new HashMap<String, String>(); details.put("first", "John"); details.put("last", "Smith"); printDetails(details); } } def printDetails(cust) { println """Details as at: ${new Date()} First name: $cust.first • Groovy Last name: ${cust.last ?: 'unknown'}""" } printDetails first: 'John', last: 'Smith' DSLs 2010 - 58
  • 59.
    ...Compact Syntax • Java import java.util.Date; public class ProcessStrongCustomerJava { public static void printDetails(CustomerJ cust) { System.out.println("Details as at: " + new Date()); String first = cust.getFirst(); System.out.println("First name: " + first); • Groovy String last = cust.getLast(); System.out.println("Last name: " + (last != null ? last : "unknown")); } public static void main(String[] args) { CustomerJ c = new CustomerJ(); class CustomerG { String first, last } © ASERT 2006-2010 c.setFirst("John"); c.setLast("Smith"); def printDetails(cust) { printDetails(c); } println """Details as at: ${new Date()} } First name: $cust.first class CustomerJ { Last name: ${cust.last ?: 'unknown'}""" private String first; private String last; } public String getFirst() { return first; printDetails new CustomerG(first: 'John', } last: 'Smith') public void setFirst(String first) { this.first = first; } Plus:  Named params for constructors public String getLast() { return last;  JavaBean conventions } public void setLast(String last) { this.last = last;  Leverage Duck Typing } } DSLs 2010 - 59
  • 60.
    Using With Example letters = ['a', 'b', 'c'] range = 'b'..'d' letters.with { add 'd' Just normal methods for ArrayList here remove 'a' } © ASERT 2006-2010 assert letters == range map = [a:10, b:4, c:7] map.with { assert (a + b) / c == 2 } DSLs 2010 - 60
  • 61.
    Closures... • Traditional mainstream languages – Data can be stored in variables, passed around, combined in structured ways to form more complex data; code stays put where it is defined • Languages supporting closures – Data and code can be stored in variables, passed © ASERT 2006-2010 around, combined in structured ways to form more complex algorithms and data; functional coding style doubleNum = { num -> num * 2 } println doubleNum(3) // => 6 processThenPrint = { num, closure -> num = closure(num); println "num is $num" } processThenPrint(3, doubleNum) // => num is 6 processThenPrint(10) { it / 2 } // => num is 5 DSLs 2010 - 61
  • 62.
    ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Name © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 62
  • 63.
    ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Code © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 63
  • 64.
    ...Closures... Parameter(s) int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Default parameter © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 64
  • 65.
    ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Call closure © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 Alternative syntax def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 65
  • 66.
    ...Closures... int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 Free variable © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 66
  • 67.
    ...Closures... Bound to environment/context when called int myConst = 4 def multiplier = { number -> number * myConst } assert multiplier(10) == 40 © ASERT 2006-2010 Closure other = { it + myConst } assert other.call(10) == 14 def twice = { it * 2 } // anonymous functions assert twice(10) == 20 // implemented as Closures DSLs 2010 - 67
  • 68.
    Closures in DSLs... Nothing special here. import static java.util.Calendar.* Just a list of maps. def cart1 = [ [qty: 3, unitPrice: 50.0, gift: true, name: 'Groovy in Action Book'], [qty: 1, unitPrice: 40.0, gift: false, name: 'Grails in Action eBook'], [qty: 2, unitPrice: 25.0, gift: true, © ASERT 2006-2010 name: 'Avatar DVD'] ] def cart2 = [ [qty: 1, unitPrice: 50.0, gift: true, name: 'Groovy in Action Book'], [qty: 2, unitPrice: 30.0, gift: false, name: 'Avatar 3D DVD'] ] // ... DSLs 2010 - 68
  • 69.
    ... Closures inDSLs... // ... def noDiscount = { it.unitPrice * it.qty } def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 } def tenPercentOffGifts = { it.with { unitPrice * qty * (gift ? 0.9 : 1.0) } } def tenPercentOffOverForty = { it.with { unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0) © ASERT 2006-2010 } } def tenPercentOffBooks = { it.with { unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0) } } def tenPercentOffDVDs = { it.with { unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0) } } def buyThreeGetOneFree = { it.with { unitPrice * (qty % 3 + 2 * qty.intdiv(3)) } } // ... Closure Name DSLs 2010 - 69
  • 70.
    ... Closures inDSLs... // ... def noDiscount = { it.unitPrice * it.qty } def tenPercentOffAll = { it.unitPrice * it.qty * 0.9 } def tenPercentOffGifts = { it.with { unitPrice * qty * (gift ? 0.9 : 1.0) } } def tenPercentOffOverForty = { it.with { unitPrice * qty * (unitPrice > 40 ? 0.9 : 1.0) © ASERT 2006-2010 } } def tenPercentOffBooks = { it.with { unitPrice * qty * (name.contains('Book') ? 0.9 : 1.0) } } def tenPercentOffDVDs = { it.with { unitPrice * qty * (name.contains('DVD') ? 0.9 : 1.0) } } def buyThreeGetOneFree = { it.with { unitPrice * (qty % 3 + 2 * qty.intdiv(3)) } } // ... Closure Code DSLs 2010 - 70
  • 71.
    ... Closures inDSLs // ... def specials = [noDiscount, tenPercentOffAll, tenPercentOffGifts, tenPercentOffOverForty, 1 240.00 tenPercentOffBooks, tenPercentOffDVDs, buyThreeGetOneFree] 2 216.00 3 220.00 [ 4 225.00 SUNDAY..SATURDAY, 5 221.00 © ASERT 2006-2010 [cart1, cart2] 6 235.00 ].combinations().each { day, cart -> 7 190.00 printf "%d %2.2fn", day, cart.sum(specials[day-1]) 1 110.00 } 2 99.00 3 105.00 4 105.00 5 105.00 6 104.00 7 110.00 DSLs 2010 - 71
  • 72.
    Operator Overloading Example • Java BigDecimal a = new BigDecimal(3.5d); BigDecimal b = new BigDecimal(4.0d); assert a.multiply(b).compareTo(new BigDecimal(14.0d)) == 0; assert a.multiply(b).equals(new BigDecimal(14.0d).setScale(1)); • Groovy © ASERT 2006-2010 def c = 3.5, d = 4.0 assert c * d == 14.0 DSLs 2010 - 72
  • 73.
    Groovy Lab Example // require GroovyLab import static org.math.array.Matrix.* import static org.math.plot.Plot.* def A = rand(10,3) // random Matrix of 10 rows and 3 columns def B = fill(10,3,1.0) // one Matrix of 10 rows and 3 columns def C = A + B // support for matrix addition with "+" or "-" def D = A - 2.0 // support for number addition with "+" or "-" def E = A * B © ASERT 2006-2010 // support for matrix multiplication or division def F = rand(3,3) def G = F**(-1) // support for matrix power (with integers only) println A // display Matrix content plot("A",A,"SCATTER") // plot Matrix values as ScatterPlot def M = rand(5,5) + id(5) // Eigenvalues decomposition println "M=n" + M println "V=n" + V(M) println "D=n" + D(M) println "M~n" + (V(M) * D(M) * V(M)**(-1)) DSLs 2010 - 73
  • 74.
    Better Control Structures:Switch Poker… hand1 hand2 8C TS KC 9H 4S 7D 2S 5D 3S AC suits = 'SHDC' ranks = '23456789TJQKA' suit = { String card -> suits.indexOf(card[1]) } rank = { String card -> ranks.indexOf(card[0]) } rankSizes = { List cards -> cards.groupBy(rank).collect{ k, v -> v.size() }.sort() } © ASERT 2006-2010 rankValues = { List cards -> cards.collect{ rank(it) }.sort() } // ... println rankSizes(["7S", "7H", "2H", "7D", "AH"]) // => [1, 1, 3] DSLs 2010 - 74
  • 75.
    …Better Control Structures:Switch Poker… // ... flush = { List cards -> cards.groupBy(suit).size() == 1 } straight = { def v = rankValues(it); v == v[0]..v[0]+4 } straightFlush = { List cards -> straight(cards) && flush(cards) © ASERT 2006-2010 } fourOfAKind = { List cards -> rankSizes(cards) == [1, 4] } fullHouse = { List cards -> rankSizes(cards) == [2, 3] } threeOfAKind = { List cards -> rankSizes(cards) == [1, 1, 3] } twoPair = { List cards -> rankSizes(cards) == [1, 2, 2] } pair = { List cards -> rankSizes(cards) == [1, 1, 1, 2] } // ... DSLs 2010 - 75
  • 76.
    … Better ControlStructures: Switch Poker // ... def rankHand(List cards) { switch (cards) { case straightFlush : return 9 case fourOfAKind : return 8 case fullHouse : return 7 case flush : return 6 © ASERT 2006-2010 case straight : return 5 case threeOfAKind : return 4 case twoPair : return 3 case pair : return 2 default : return 1 } } // ... DSLs 2010 - 76
  • 77.
    Groovy DSL Features ©ASERT 2006-2010 DSLs 2010 - 77
  • 78.
    Builders… • MarkupBuilder <html> import groovy.xml.* <head> def page = new MarkupBuilder() <title>Hello</title> page.html { </head> head { title 'Hello' } <body> body { © ASERT 2006-2010 <ul> ul { <li>world 1</li> for (count in 1..5) { <li>world 2</li> li "world $count" <li>world 3</li> } } } } <li>world 4</li> <li>world 5</li> </ul> </body> </html> DSLs 2010 - 78
  • 79.
    ...Builders… • GraphicsBuilder def colors = ['red','darkOrange','blue','darkGreen'] (0..3).each { index -> star( cx: 50 + (index*110), cy: 50, or: 40, ir: 15, borderColor: 'black', count: 2+index, fill: colors[index] ) star( cx: 50 + (index*110), cy: 140, or: 40, ir: 15, borderColor: 'black‘, count: 7+index, fill: colors[index] ) } © ASERT 2006-2010 DSLs 2010 - 79
  • 80.
    …Builders new AntBuilder().with { echo(file:'Temp.java', ''' class Temp { public static void main(String[] args) { System.out.println("Hello"); } } ''') © ASERT 2006-2010 javac(srcdir:'.', includes:'Temp.java', fork:'true') java(classpath:'.', classname:'Temp', fork:'true') echo('Done') } // => // [javac] Compiling 1 source file // [java] Hello // [echo] Done DSLs 2010 - 80
  • 81.
    Metaprogramming • Runtime Metaprogramming – Categories including DGM – invokeMethod, methodMissing, getProperty – GroovyInterceptable – ExpandoMetaClass © ASERT 2006-2010 • Compile-time Metaprogramming – AST Macros – Local and Global flavors based around annotations – Reduced runtime overhead – Several built-in annotations: • @Immutable • @Delegate • @Singleton DSLs 2010 - 81
  • 82.
    ExpandoMetaClass… import static java.lang.Character.isUpperCase String.metaClass.swapCase = { delegate.collect { ch -> isUpperCase(ch as char) ? ch.toLowerCase() : ch.toUpperCase() }.join() } © ASERT 2006-2010 println new Date().toString().swapCase() // => sUN nOV 09 17:30:06 est 2008 List.metaClass.sizeDoubled = {-> delegate.size() * 2 } LinkedList list = [] list << 1 list << 2 assert 4 == list.sizeDoubled() DSLs 2010 - 82
  • 83.
    …ExpandoMetaClass class Person { String name } class MortgageLender { def borrowMoney() { "buy house" } © ASERT 2006-2010 } def lender = new MortgageLender() Person.metaClass.buyHouse = lender.&borrowMoney def p = new Person() assert "buy house" == p.buyHouse() DSLs 2010 - 83
  • 84.
    Adding your owncontrol structures • Thread enhancement ROCK! . 25 import java.util.concurrent.locks.ReentrantLock . 33 import static System.currentTimeMillis as now . 34 def startTime = now() . 35 ReentrantLock.metaClass.withLock = { critical -> . 36 lock() .. 131 try { critical() } .. 134 finally { unlock() } .. 137 } def lock = new ReentrantLock() .. 138 def worker = { threadNum -> .. 139 4.times { count -> ... 232 lock.withLock { ... 234 print " " * threadNum ... 237 print "." * (count + 1) ... 238 println " ${now() - startTime}" ... 239 } .... 334 Thread.sleep 100 .... 336 } } .... 337 5.times { Thread.start worker.curry(it) } .... 338 println "ROCK!" .... 339 Source: http://chrisbroadfoot.id.au/articles/2008/08/06/groovy-threads DSLs 2010 - 84
  • 85.
    EMC DSL… © ASERT2006-2010 DSLs 2010 - 85
  • 86.
    ...EMC Neo4j DSL… @GrabResolver(name= 'neo4j-public-repo', root= 'http://m2.neo4j.org') @Grab('org.neo4j:neo4j-kernel:1.1.1') import org.neo4j.kernel.EmbeddedGraphDatabase import org.neo4j.graphdb.* // an enum helper enum MyRelationships implements RelationshipType { knows } // some optional syntactic sugar using EMC DSL Node.metaClass { © ASERT 2006-2010 propertyMissing { String name, val -> delegate.setProperty(name, val) } propertyMissing { String name -> delegate.getProperty(name) } methodMissing { String name, args -> delegate.createRelationshipTo(args[0], MyRelationships."$name") } } Relationship.metaClass { propertyMissing { String name, val -> delegate.setProperty(name, val) } propertyMissing { String name -> delegate.getProperty(name) } } // ... DSLs 2010 - 86
  • 87.
    …EMC Neo4j DSL // ... def graphDb = new EmbeddedGraphDatabase("graphdb") def tx = graphDb.beginTx() def firstNode, secondNode, relationship try { firstNode = graphDb.createNode() secondNode = graphDb.createNode() relationship = firstNode.knows(secondNode) firstNode.message = "Hello," secondNode.message = "world!" © ASERT 2006-2010 relationship.message = "brave Neo4j" tx.success() } finally { tx.finish() println "$firstNode.message $relationship.message $secondNode.message" // => Hello, brave Neo4j world! graphDb.shutdown() } DSLs 2010 - 87
  • 88.
    Groovy DSL Features ©ASERT 2006-2010 DSLs 2010 - 88
  • 89.
    AST Builder • Numerousapproaches, still evolving. “From code” approach: def result = new AstBuilder().buildFromCode { println "Hello World" } • Produces: BlockStatement -> ReturnStatement -> MethodCallExpression -> VariableExpression("this") -> ConstantExpression("println") -> ArgumentListExpression -> ConstantExpression("Hello World") DSLs 2010 - 89
  • 90.
    Type Transformation Example class InventoryItem { def weight, name InventoryItem(Map m) { this.weight = m.weight; this.name = m.name } InventoryItem(weight, name) { this.weight = weight; this.name = name } InventoryItem(String s) { © ASERT 2006-2010 s.find(/weight=(d*)/) { all, w -> this.weight = w } s.find(/name=(.*)/) { all, n -> this.name = n } } } def room = [:] def gold = [weight:50, name:'Gold'] as InventoryItem def emerald = [10, 'Emerald'] as InventoryItem def dagger = ['weight=5, name=Dagger'] as InventoryItem room.contents = [gold, emerald, dagger] room.contents.each{ println it.dump() } DSLs 2010 - 90
  • 91.
    GParsec... • Miniature parsers def isLetter = { ch -> return Character.isLetter(ch) } def isDigit = { ch -> return Character.isDigit(ch) } def letter = satisfyC(isLetter) def digit = satisfyC(isDigit) • Parser Combinators def letterAndDigit = seqC(letter, digit) assert letterAndDigit('a123###') == ['a', '1'] def identifier = seqC(identifierStart, noneOrMoreC(identifierRest)) DSLs 2010 - 91
  • 92.
    ...GParsec... • BNF variableDeclarator : identifier ( '=' expression )? variableDefinitions : variableDeclarator ( ',' variableDeclarator)* • Groovy definitions def variableDeclarator = seqC(identifier, optionalC(seqC(assign, expression))) def variableDefinitions = seqC(variableDeclarator, noneOrMoreC(seqC(comma, variableDeclarator))) DSLs 2010 - 92
  • 93.
    ...GParsec • satisfyC: combinatorconsumes a single input when its predicate succeeds • altC (alt3C, altCs): is the choice combinator. Given two parsers it only looks at its second alternative if the first has not consumed any input - regardless of the final value • seqC (seq3C, seqCs): is the sequencing combinator. It runs two parsers in succession and if successful, returns the result of the two parsers • noneOrMoreC, oneOrMoreC: applies a parser zero or more times to an input stream. The result from each application of the parser are returned in a list • optionalC: The combinator optionalC may succeed in parsing some input. It always returns success. DSLs 2010 - 93
  • 94.
    Using External Parsers import static com.mdimension.jchronic.Chronic.parse def span = parse("tomorrow at 5pm") println span.endCalendar.format('yyyy-MM-dd HH:mm') // => 2010-05-19 17:00 © ASERT 2006-2010 DSLs 2010 - 94
  • 95.
    Topics • DSL origins • Groovy Intro • Groovy DSL Features Groovy DSL Examples • Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 95
  • 96.
    Coin example... enum Coin { penny(1), nickel(5), dime(10), quarter(25) Coin(int value) { this.value = value } int value } © ASERT 2006-2010 import static Coin.* assert 2 * quarter.value + 1 * nickel.value + 2 * penny.value == 57 DSLs 2010 - 96
  • 97.
    ...Coin example... class CoinMath { static multiply(Integer self, Coin c) { self * c.value } } use (CoinMath) { © ASERT 2006-2010 assert 2 * quarter + 1 * nickel + 2 * penny == 57 } // EMC equivalent Integer.metaClass.multiply = { Coin c -> delegate * c.value } assert 2 * quarter + 1 * nickel + 2 * penny == 57 DSLs 2010 - 97
  • 98.
    ...Coin example class CoinValues { static get(Integer self, String name) { self * Coin."${singular(name)}".value } static singular(String val) { val.endsWith('ies') ? val[0..-4] + 'y' : val.endsWith('s') ? val[0..-2] : val } } use (CoinValues) { assert 2.quarters + 1.nickel + 2.pennies == 57 © ASERT 2006-2010 } // EMC equivalent Integer.metaClass.getProperty = { String name -> def mp = Integer.metaClass.getMetaProperty(name) if (mp) return mp.getProperty(delegate) def singular = name.endsWith('ies') ? name[0..-4] + 'y' : name.endsWith('s') ? name[0..-2] : name delegate * Coin."$singular".value } assert 2.quarters + 1.nickel + 2.pennies == 57 DSLs 2010 - 98
  • 99.
    Game example... // Trying out the game DSL idea by Sten Anderson from: // http://blogs.citytechinc.com/sanderson/?p=92 class GameUtils { static VOWELS = ['a', 'e', 'i', 'o', 'u'] static listItems(things) { def result = '' things.eachWithIndex{ thing, index -> if (index > 0) { © ASERT 2006-2010 if (index == things.size() - 1) result += ' and ' else if (index < things.size() - 1) result += ', ' } result += "${thing.toLowerCase()[0] in VOWELS ? 'an' : 'a'} $thing } result ?: 'nothing' } } import static GameUtils.* ... DSLs 2010 - 99
  • 100.
    ...Game example... ... class Room { def description def contents = [] } ... © ASERT 2006-2010 DSLs 2010 - 100
  • 101.
    ...Game example... ... class Player { def currentRoom def inventory = [] void look() { println "You are in ${currentRoom?.description?:'the void'} which contains ${listItems(currentRoom?.contents)}" } © ASERT 2006-2010 void inv() { println "You are holding ${listItems(inventory)}" } void take(item) { if (currentRoom?.contents?.remove(item)) { inventory << item println "You took the $item" } else { println "I see no $item here" } } ... DSLs 2010 - 101
  • 102.
    ...Game example... ... void drop(item) { if (inventory?.remove(item)) { currentRoom?.contents << item println "You dropped the $item" } else { println "You don't have the $item" } © ASERT 2006-2010 } def propertyMissing(String name) { if (metaClass.respondsTo(this, name)) { this."$name"() } name } } ... DSLs 2010 - 102
  • 103.
    ...Game example... ... Room plainRoom = new Room(description:'a plain white room', contents:['dagger', 'emerald', 'key']) Player player = new Player(currentRoom:plainRoom) player.with { inv look take dagger © ASERT 2006-2010 inv look take emerald inv look take key drop emerald inv look } assert player.inventory == ['dagger', 'key'] ... DSLs 2010 - 103
  • 104.
    ...Game example ... // now try some error conditions plainRoom.description = null player.with { drop gold take gold drop emerald © ASERT 2006-2010 take emerald take emerald look } DSLs 2010 - 104
  • 105.
    GEP3 example... Object.metaClass.please = { clos -> clos(delegate) } Object.metaClass.the = { clos -> delegate[1](clos(delegate[0])) } show = { thing -> [thing, { println it }] } square_root = { Math.sqrt(it) } © ASERT 2006-2010 given = { it } given 100 please show the square_root // ==> 10.0 DSLs 2010 - 105
  • 106.
    ...GEP3 example... Object.metaClass.of = { delegate[0](delegate[1](it)) } Object.metaClass.the = { clos -> [delegate[0], clos] } show = [{ println it }] © ASERT 2006-2010 square_root = { Math.sqrt(it) } please = { it } please show the square_root of 100 // ==> 10.0 DSLs 2010 - 106
  • 107.
    ...GEP3 example... show = { println it } square_root = { Math.sqrt(it) } def please(action) { [the: { what -> [of: { n -> action(what(n)) }] © ASERT 2006-2010 }] } please show the square_root of 100 // ==> 10.0 Inspiration for this example came from … DSLs 2010 - 107
  • 108.
    ...GEP3 example... // Japanese DSL using GEP3 rules Object.metaClass.を = Object.metaClass.の = { clos -> clos(delegate) } まず = { it } 表示する = { println it } © ASERT 2006-2010 平方根 = { Math.sqrt(it) } まず 100 の 平方根 を 表示する // First, show the square root of 100 // => 10.0 // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117 // http://groovyconsole.appspot.com/edit/241001 DSLs 2010 - 108
  • 109.
    ...GEP3 example © ASERT2006-2010 // source: http://d.hatena.ne.jp/uehaj/20100919/1284906117 // http://groovyconsole.appspot.com/edit/241001 DSLs 2010 - 109
  • 110.
    Medical Prescription DSL… class Drug { String name String toString() { name } } class Measure { Number number String unit, units String toString() { number == 1 ? "1 $unit" : "$number $units" } © ASERT 2006-2010 } class Quantity extends Measure {} class Duration extends Measure {} // ... http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu DSLs 2010 - 110
  • 111.
    …Medical Prescription DSL… // ... // ---- DSL implementation ------------------------------ Integer.metaClass.getHour = {-> 1.hours } Integer.metaClass.getPill = {-> 1.pills } Integer.metaClass.getHours = {-> new Duration(number: delegate, unit: 'hour', units: 'hours') } Integer.metaClass.getPills = {-> new Quantity(number: delegate, unit: 'pill', units: 'pills') } // use the script binding for storing new drugs available as variables © ASERT 2006-2010 binding = new Binding() { def getVariable(String drug) { new Drug(name: drug) } } take = {Quantity quantity -> ['of': {Drug drug -> ['after': {Duration duration -> println "Take $quantity of $drug afer $duration" }] }] } // ... DSLs 2010 - 111
  • 112.
    …Medical Prescription DSL // ... // ---- DSL ------------------------------- take 2.pills of chloroquinine after 6.hours © ASERT 2006-2010 http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu DSLs 2010 - 112
  • 113.
    Stock Exchange OrderDSL… // ---- A bit of script initialization --------------------------- // use the script binding for silent sentence words like "to", "the" binding = new CustomBinding() // syntax for 200.shares Integer.metaClass.getShares = { -> delegate } // ---- Stock exchange orders DSL -------------------------------- order to buy 200.shares of GOOG { © ASERT 2006-2010 limitPrice 500 allOrNone false at the value of { qty * unitPrice - 100 } } order to sell 150.shares of VMW { limitPrice 80 allOrNone true at the value of { qty * unitPrice } } // ... http://groovyconsole.appspot.com/script/226001 by glaforge DSLs 2010 - 113
  • 114.
    … Stock ExchangeOrder DSL… // ----- Implementation of the DSL -------------------------------- enum Action { Buy, Sell } class Order { Security security Integer quantity, limitPrice boolean allOrNone Closure valueCalculation Action action def buy(Integer quantity) { this.quantity = quantity this.action = Action.Buy return this } © ASERT 2006-2010 def sell(Integer quantity) { this.quantity = quantity this.action = Action.Sell return this } def limitPrice(Integer limit) { this.limitPrice = limit } def allOrNone(boolean allOrNone) { this.allOrNone = allOrNone } def at(Closure characteristicsClosure) { return this } def value(Closure valueCalculation) { this.valueCalculation = valueCalculation } // ... DSLs 2010 - 114
  • 115.
    …Stock Exchange OrderDSL… // Characteristics of the order: "of GOOG {...}" def of(SecurityAndCharacteristics secAndCharact) { security = secAndCharact.security def c = secAndCharact.characteristics.clone() c.delegationStrategy = Closure.DELEGATE_ONLY c.delegate = this c() // debug print of the resulting order println toString() return this } // Valuation closure: "of { qty, unitPrice -> ... }" def of(Closure valueCalculation) { © ASERT 2006-2010 // in order to be able to define closures like { qty * unitPrice } // without having to explicitly pass the parameters to the closure // we can wrap the closure inside another one // and that closure sets a delegate to the qty and unitPrice variables def wrapped = { qty, unitPrice -> def cloned = valueCalculation.clone() cloned.resolveStrategy = Closure.DELEGATE_ONLY cloned.delegate = [qty: qty, unitPrice: unitPrice] cloned() } return wrapped } String toString() { "$action $quantity shares of $security.name at limit price of $limitPrice" } } // ... DSLs 2010 - 115
  • 116.
    …Stock Exchange OrderDSL // ... class Security { String name } class SecurityAndCharacteristics { Security security Closure characteristics } class CustomBinding extends Binding { def getVariable(String word) { // return System.out when the script requests to write to 'out' if (word == "out") System.out © ASERT 2006-2010 // don't thrown an exception and return null // when a silent sentence word is used, // like "to" and "the" in our DSL null } } // Script helper method for "GOOG {}", "VMW {}", etc. def methodMissing(String name, args) { new SecurityAndCharacteristics( security: new Security(name: name), characteristics: args[0] ) } // Script helper method to make "order to" silent // by just creating our current order def order(to) { new Order() } http://groovyconsole.appspot.com/script/226001 by glaforge DSLs 2010 - 116
  • 117.
    OrderBy example... @Immutable class Person { String first, last int age } def people = [ new Person(first:'Ami', last:'Smith', age:20), new Person(first:'Bruce', last:'Jones', age:10), © ASERT 2006-2010 new Person(first:'Ami', last:'Jones', age:15) ] def order1 = new OrderBy() order1.add{ it.first } order1.add{ it.last } order1.add{ it.age } println people.sort(order1) DSLs 2010 - 117
  • 118.
    ...OrderBy example... def order2 = new OrderBy() order2.with { add{ it.last } add{ it.first } add{ it.age } } © ASERT 2006-2010 println people.sort(order2) DSLs 2010 - 118
  • 119.
    ...OrderBy example... OrderBy.metaClass.firstBy = { Closure closure -> if (delegate.closures) { throw new IllegalStateException("firstBy should only be called on an empty OrderBy") } delegate.add(closure) return delegate } OrderBy.metaClass.thenBy = { Closure closure -> © ASERT 2006-2010 if (!delegate.closures) { throw new IllegalStateException("thenBy should not be called on an empty OrderBy") } delegate.add(closure) return delegate } def order3 = new OrderBy().firstBy { it.age }. thenBy { it.first }. thenBy { it.last } println people.sort(order3) DSLs 2010 - 119
  • 120.
    ...OrderBy example © ASERT2006-2010 DSLs 2010 - 120
  • 121.
    Challenge… From: customer@acme.org To: Paul King Subject: Project Request Dear Paul, Could you please create us a small DSL for capturing our business rules. We are thinking some like: price = 3 © ASERT 2006-2010 quantity = 10 total = price * quantity Perhaps also an ability to check values: assert total == 30 Thanks, Happy Customer P.S. Will send a couple of more details in a follow-up email. DSLs 2010 - 121
  • 122.
    …Challenge… From: customer@acme.org To: Paul King Subject: Project Request Date: This morning Dear Paul, We were thinking a bit more about it, and it would be good to have just a few more features. Hopefully, they won’t have much impact on your existing design and can be added very quickly. It isn’t © ASERT 2006-2010 much, just the ability to have IF, THEN, ELSE like structures, oh yeah and the ability to have loops, and store stuff in files and get stuff from databases and web services if we need. Thanks, Happy Customer P.S. It would be great if you can be finished by this afternoon. We have a customer who would like this feature RSN. Thanks. DSLs 2010 - 122
  • 123.
    …Challenge… def shell = new GroovyShell() shell.evaluate(''' price = 3 quantity = 10 total = price * quantity assert total == 30 ''') © ASERT 2006-2010 DSLs 2010 - 123
  • 124.
    …Challenge… From: customer@acme.org To: Paul King Subject: Project Request Date: This morning Dear Paul, We were really happy with the DSL engine you provided us with but people started importing all sorts of classes. Can you stop them from doing that? Maybe just java.lang.Math only. Also we © ASERT 2006-2010 decided we don’t like ‚while‛ loops anymore. Leave you to it. Thanks, Happy Customer P.S. Have a beer and crab dinner for me at the conference. Bye. DSLs 2010 - 124
  • 125.
    …Challenge… class CustomerShell { Object evaluate(String text) { try { def loader = new CustomerClassLoader() def clazz = loader.parseClass(text) def script = clazz.newInstance() return script.run() } catch (...) { ... } } } class CustomerClassLoader extends GroovyClassLoader { def createCompilationUnit(CompilerConfiguration config, CodeSource codeSource) { CompilationUnit cu = super.createCompilationUnit(config, codeSource) © ASERT 2006-2010 cu.addPhaseOperation(new CustomerFilteringNodeOperation(), Phases.SEMANTIC_ANALYSIS) return cu } } private class CustomerFilteringNodeOperation extends PrimaryClassNodeOperation { // ... private static final allowedStaticImports = [Math].asImmutable() void visitStaticMethodCallExpression(StaticMethodCallExpression smce) { if (!allowedStaticImports.contains(smce.ownerType.getTypeClass())) { throw new SecurityException("Static method call expressions forbidden in acme shell.") } } void visitWhileLoop(WhileStatement whileStatement) { throw new SecurityException("While statements forbidden in acme shell.") } // ... } DSLs 2010 - 125
  • 126.
    …Challenge def shell = new CustomerShell() shell.evaluate(''' price = 3 quantity = 10 total = price * quantity assert total == 30 ''') © ASERT 2006-2010 DSLs 2010 - 126
  • 127.
    EasyB… • Description: BDD, Rspec-like testing library narrative 'segment flown', { as_a 'frequent flyer' i_want 'to accrue rewards points for every segment I fly' so_that 'I can receive free flights for my dedication to the airline' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points' when 'that flyer completes a segment worth 500 points' © ASERT 2006-2010 then 'that flyer has a new rewards balance of 2000 points' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points', { flyer = new FrequentFlyer(1500) } when 'that flyer completes a segment worth 500 points', { flyer.fly(new Segment(500)) } then 'that flyer has a new rewards balance of 2000 points', { flyer.pointsBalance.shouldBe 2000 } } DSLs 2010 - 127
  • 128.
    …EasyB • Description: BDD, Rspec-like testing library examples "The number #{number}' should be converted to #{romanNumerals}", { number = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10] romanNumerals = ["I", "II", "III", "IV", "V", "VI", "VII", "VIII", "IX", "X"] } scenario "Converting #number into the roman numeral #romanNumerals", { given "the number #number", { theNumber = number © ASERT 2006-2010 } when "the system converts this number to the roman numeral equivalent", { theConvertedNumber = RomanNumerals.fromInteger(theNumber) } then "the result should be #romanNumerals", { theConvertedNumber.shouldBe romanNumerals } } Source: http://www.wakaleo.com/blog/285-example-driven-testing-with-easyb DSLs 2010 - 128
  • 129.
    EasyB Preprocessing... • EasyBsupports either of these: given "some data", { println '... setting expectations' } given ("some data") { println '... setting expectations' } • But we would like this: given "some data" { println '... setting expectations' } But also consider Command Expressions (GEP-3) for Groovy1.8+. Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html DSLs 2010 - 129
  • 130.
    ...EasyB Preprocessing... String addCommas(text) { def pattern = ~/(.*)(given|when|then) "([^"]*(.[^"]*)*)" {(.*)/ def replacement = /$1$2 "$3", {$4/ (text =~ pattern).replaceAll(replacement) } Adds the comma in where required before Groovy sees it. Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html DSLs 2010 - 130
  • 131.
    ...EasyB Preprocessing class SourceModifierParserPluginextends AntlrParserPlugin { Reduction parseCST(SourceUnit sourceUnit, Reader reader) throws CompilationFailedException { def text = addCommas(reader.text) StringReader stringReader = new StringReader(text) super.parseCST(sourceUnit, stringReader) } } def parserPluginFactory = new ParserPluginFactory() { ParserPlugin createParserPlugin() { new SourceModifierParserPlugin() } } def conf = new CompilerConfiguration(pluginFactory: parserPluginFactory) def binding = ... def shell = new GroovyShell(binding, conf) But use with restraint as error messages will be with respect to converted source code not original. Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html DSLs 2010 - 131
  • 132.
    Grails Criteria // Account is a POJO in our domain/model def c = Account.createCriteria() def results = c { like("holderFirstName", "Fred%") and { between("balance", 500, 1000) eq("branch", "London") © ASERT 2006-2010 } maxResults(10) order("holderLastName", "desc") } // source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.2 Criteria DSLs 2010 - 132
  • 133.
    Grails Criteria Example // Book is a POJO in our domain/model def book = Book.findByTitle("The Stand") book = Book.findByTitleLike("Harry Pot%") book = Book.findByReleaseDateBetween( firstDate, secondDate ) © ASERT 2006-2010 book = Book.findByReleaseDateGreaterThan( someDate ) book = Book.findByTitleLikeOrReleaseDateLessThan( "%Something%", someDate ) books = Book.findAllByTitleLikeAndReleaseDateGreaterThan( "%Java%", new Date()-30) // source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.1 Dynamic Finders DSLs 2010 - 133
  • 134.
    Grails Bean BuilderExample bb.beans { marge(Person) { name = "marge" husband = { Person p -> name = "homer" age = 45 props = [overweight:true, height:"1.8m"] } © ASERT 2006-2010 children = [bart, lisa] } bart(Person) { name = "Bart" age = 11 } lisa(Person) { name = "Lisa" age = 9 } } // source: 14. Grails and Spring: 14.3 Runtime Spring with the Beans DSL DSLs 2010 - 134
  • 135.
    GPars... import static org.gparallelizer.actors.pooledActors.PooledActors.* //create a new actor that prints out received messages def console = actor { loop { react {message -> println message } } } // start the actor and send it a message console.start() console.send('Message') DSLs 2010 - 135
  • 136.
    ...GPars import static org.gparallelizer.dataflow.DataFlow.thread finaldef x = new DataFlowVariable() final def y = new DataFlowVariable() final def z = new DataFlowVariable() thread { z << ~x + ~y println "Result: ${~z}" } thread { x << 10 } No race-conditions thread { No deadlocks y << 5 No live-locks } Completely deterministic programs BEAUTIFUL code DSLs 2010 - 136
  • 137.
    Testing DSLs: Spock... ©ASERT 2006-2010 DSLs 2010 - 137
  • 138.
    ...Testing DSLs: Spock... • Testing framework for Java and Groovy • Highly expressive specification language – No assertion API – No record & @Speck replay @RunWith(Sputnik) mocking API class PublisherSubscriberSpeck { – No def "events are received by all subscribers"() { superfluous def pub = new Publisher() © ASERT 2006-2010 annotations def sub1 = Mock(Subscriber) – Meaningful def sub2 = Mock(Subscriber) assert error pub.subscribers << sub1 << sub2 messages when: pub.send("event") then: 1 * sub1.receive("event") 1 * sub2.receive("event") – Extensible } – Compatible } with JUnit reportingwise DSLs 2010 - 138
  • 139.
    ...Testing DSLs: Spock... import com.gargoylesoftware.htmlunit.WebClient import spock.lang.* import org.junit.runner.RunWith @Speck () @RunWith (Sputnik) class TestSimpBlogSpock { def page, subheadings, para, form, result @Unroll("When #author posts a #category blog with content '#content' it sho def "when creating a new blog entry"() { © ASERT 2006-2010 given: page = new WebClient().getPage('http://localhost:8080/postForm') form = page.getFormByName('post') when: form.getInputByName('title').setValueAttribute("$author was here (and s form.getSelectByName('category').getOptions().find { it.text == categor form.getSelectByName('author').getOptions().find { it.text == author }. form.getTextAreaByName('content').setText(content) result = form.getInputByName('btnPost').click() subheadings = result.getElementsByTagName('h3') para = result.getByXPath('//TABLE//TR/TD/P')[0] ... DSLs 2010 - 139
  • 140.
    ... Testing DSLs:Spock ... then: page.titleText == 'Welcome to SimpBlog' result.getElementsByTagName('h1').item(0).textContent.matches("Post.*: $au subheadings.item(1).textContent == "Category: $category" subheadings.item(2).textContent == "Author: $author" and: para.textContent == content // Optional use of 'and:' © ASERT 2006-2010 where: author << ['Bart', 'Homer', 'Lisa'] category << ['Home', 'Work', 'Food'] content << ['foo', 'bar', 'baz'] } } DSLs 2010 - 140
  • 141.
    Testing DSLs: EasyB… • Description: BDD, Rspec-like testing library narrative 'segment flown', { as_a 'frequent flyer' i_want 'to accrue rewards points for every segment I fly' so_that 'I can receive free flights for my dedication to the airline' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points' when 'that flyer completes a segment worth 500 points' © ASERT 2006-2010 then 'that flyer has a new rewards balance of 2000 points' } scenario 'segment flown', { given 'a frequent flyer with a rewards balance of 1500 points', { flyer = new FrequentFlyer(1500) } when 'that flyer completes a segment worth 500 points', { flyer.fly(new Segment(500)) } then 'that flyer has a new rewards balance of 2000 points', { flyer.pointsBalance.shouldBe 2000 } } DSLs 2010 - 141
  • 142.
    …Testing DSLs: EasyB • When run will be marked as pending – perfect for ATDD scenario "Bart posts a new blog entry", { given "we are on the create blog entry page" when "I have entered 'Bart was here' as the title" and "I have entered 'Cowabunga Dude!' into the content" and "I have selected 'Home' as the category" © ASERT 2006-2010 and "I have selected 'Bart' as the author" and "I click the 'Create Post' button" then "I expect the entry to be posted" } DSLs 2010 - 142
  • 143.
    Testing DSLS: Cucumber... # language: en @newpost Feature: New Blog Post In order to create a new blog entry Bloggers should be able to select their name and category and enter text Scenario: New Posting © ASERT 2006-2010 Given we are on the create blog entry page When I have entered "Bart was here" as the title And I have entered "Cowabunga Dude!" as the content And I have selected "Home" from the "category" dropdown And I have selected "Bart" from the "author" dropdown And I click the 'Create Post' button Then I should see a heading message matching "Post.*: Bart was here.*" DSLs 2010 - 143
  • 144.
    ... Testing DSLS:Cucumber... © ASERT 2006-2010 DSLs 2010 - 144
  • 145.
    ...Testing DSLS: Cucumber import com.gargoylesoftware.htmlunit.WebClient this.metaClass.mixin(cuke4duke.GroovyDsl) Given ~/we are on the create blog entry page/, { -> page = new WebClient().getPage('http://localhost:8080/postForm') } When(~/I have entered "(.*)" as the title/) {String title -> form = page.getFormByName('post') form.getInputByName('title').setValueAttribute(title + ' (and so was Cucumber)') } When(~'I have entered "(.*)" as the content') {String content -> form.getTextAreaByName('content').setText(content) © ASERT 2006-2010 } When(~'I have selected "(.*)" from the "(.*)" dropdown') {String option, String name -> form.getSelectByName(name).getOptions().find { it.text == option }.setSelected(true) } When(~"I click the 'Create Post' button") { -> result = form.getInputByName('btnPost').click() } Then(~'I should see a heading message matching "(.*)"') {String pattern -> // ensureThat result.getElementsByTagName('h1').item(0).textContent.matches(pattern) assert result.getElementsByTagName('h1').item(0).textContent.matches(pattern) } DSLs 2010 - 145
  • 146.
    Einstein‟s Riddle :Prolog % from http://www.baptiste-wicht.com/2010/09/solve-einsteins-riddle-using-prolog % Preliminary definitions persons(0, []) :- !. persons(N, [(_Men,_Color,_Drink,_Smoke,_Animal)|T]) :- N1 is N-1, persons(N1,T). person(1, [H|_], H) :- !. person(N, [_|T], R) :- N1 is N-1, person(N1, T, R). % The Brit lives in a red house hint1([(brit,red,_, _, _)|_]). hint1([_|T]) :- hint1(T). © ASERT 2006-2010 % The Swede keeps dogs as pets hint2([(swede,_,_,_,dog)|_]). hint2([_|T]) :- hint2(T). % The Dane drinks tea hint3([(dane,_,tea,_,_)|_]). hint3([_|T]) :- hint3(T). % The Green house is on the left of the White house hint4([(_,green,_,_,_),(_,white,_,_,_)|_]). hint4([_|T]) :- hint4(T). % The owner of the Green house drinks coffee. hint5([(_,green,coffee,_,_)|_]). hint5([_|T]) :- hint5(T). ... DSLs 2010 - 146
  • 147.
    Einstein‟s Riddle :Polyglot @GrabResolver('http://dev.inf.unideb.hu:8090/archiva/repository/internal') //@Grab('jlog:jlogic-debug:1.3.6') @Grab('org.prolog4j:prolog4j-api:0.2.0') // uncomment one of the next three lines //@Grab('org.prolog4j:prolog4j-jlog:0.2.0') @Grab('org.prolog4j:prolog4j-tuprolog:0.2.0') //@Grab('org.prolog4j:prolog4j-jtrolog:0.2.0') import org.prolog4j.* def p = ProverFactory.prover p.addTheory(new File('/GroovyExamples/tuProlog/src/einstein.pl').text) def sol = p.solve("solution(Persons).") © ASERT 2006-2010 //println sol.solution.get('Persons') // jlog to avoid converter println sol.get('Persons') // jtrolog/tuProlog DSLs 2010 - 147
  • 148.
    Einstein‟s Riddle :Polyglot w/ DSL… // define some domain classes and objects enum Pet { dog, cat, bird, fish, horse } enum Color { green, white, red, blue, yellow } enum Smoke { dunhill, blends, pallmall, prince, bluemaster } enum Drink { water, tea, milk, coffee, beer } enum Nationality { Norwegian, Dane, Brit, German, Swede } dogs = dog; birds = bird; cats = cat; horses = horse a = owner = house = the = abode = person = man = is = to = side = next = who = different = 'ignored' © ASERT 2006-2010 // some preliminary definitions p = ProverFactory.prover hintNum = 1 p.addTheory(''' persons(0, []) :- !. persons(N, [(_Men,_Color,_Drink,_Smoke,_Animal)|T]) :- N1 is N-1, persons(N1,T). person(1, [H|_], H) :- !. person(N, [_|T], R) :- N1 is N-1, person(N1, T, R). ''') DSLs 2010 - 148
  • 149.
    …Einstein‟s Riddle :Polyglot w/ DSL… // define some helper methods (our interface to prolog) def addPairHint(Map m) { def from = m.from?.toString()?.toLowerCase() p.addTheory(""" hint$hintNum([(${from ?: '_'},${m.color ?: '_'},${m.drink ?: '_'},${m.smoke ?: '_'},${m.pet ?: '_'})|_]). hint$hintNum([_|T]) :- hint$hintNum(T). """) hintNum++ } def addPositionHint(Map m, int pos) { © ASERT 2006-2010 def from = m.from?.toString()?.toLowerCase() p.addTheory(""" hint$hintNum(Persons) :- person($pos, Persons, (${from ?: '_'},${m.color ?: '_'},${m.drink ?: '_'},${m.smoke ?: '_'},${m.pet ?: '_'})). """) hintNum++ } def addToLeftHint(Map left, Map right) { p.addTheory(""" hint$hintNum([(_,$left.color,_,_,_),(_,$right.color,_,_,_)|_]). hint$hintNum([_|T]) :- hint$hintNum(T). """) hintNum++ } ... DSLs 2010 - 149
  • 150.
    …Einstein‟s Riddle :Polyglot w/ DSL… // now implement DSL in terms of helper methods def the(Nationality n) { def ctx = [from:n] [ drinks: { d -> addPairHint(ctx + [drink:d]) }, smokes: { s -> addPairHint(ctx + [smoke:s]) }, keeps: { p -> addPairHint(ctx + [pet:p]) }, rears: { p -> addPairHint(ctx + [pet:p]) }, owns:{ _the -> [first:{ house -> addPositionHint(ctx, 1) }] }, has:{ _a -> © ASERT 2006-2010 [pet: { a -> addPairHint(ctx + [pet:a]) }] + Color.values().collectEntries{ c -> [c.toString(), { _dummy -> addPairHint(ctx + [color:c]) } ] } }, lives: { _next -> [to: { _the -> Color.values().collectEntries{ c -> [c.toString(), { _dummy -> addNeighbourHint(ctx, [color:c]) } ] } }]} ] } ... DSLs 2010 - 150
  • 151.
    …Einstein‟s Riddle :Polyglot w/ DSL… // now define the DSL the man from the centre house drinks milk the Norwegian owns the first house the Dane drinks tea the German smokes prince the Swede keeps dogs // alternate ending: has a pet dog the Brit has a red house // alternate ending: red abode the owner of the green house drinks coffee the owner of the yellow house smokes dunhill the person known to smoke pallmall rears birds // alt’n8 end: keeps birds © ASERT 2006-2010 the man known to smoke bluemaster drinks beer the green house is on the left side of the white house the man known to smoke blends lives next to the one who keeps cats the man known to keep horses lives next to the man who smokes dunhill the man known to smoke blends lives next to the one who drinks water the Norwegian lives next to the blue house DSLs 2010 - 151
  • 152.
    …Einstein‟s Riddle :Polyglot w/ DSL… // now implement DSL in terms of helper methods def the(Nationality n) { def ctx = [from:n] [ drinks: { d -> addPairHint(ctx + [drink:d]) }, smokes: { s -> addPairHint(ctx + [smoke:s]) }, keeps: { p -> addPairHint(ctx + [pet:p]) }, ... ] } © ASERT 2006-2010 ... the German smokes prince the(German).smokes(prince) n = German ctx = [from: German] [drinks: …, smokes: { s -> addPairHint([from: German, smoke: s]) }, keeps: …, … addPairHint([from: German, smoke: prince]) ] DSLs 2010 - 152
  • 153.
    …Einstein‟s Riddle :Polyglot w/ DSL… • Some parts of our DSL are automatically statically inferred, e.g. typing „bl‟ and then asking for completion yields: © ASERT 2006-2010 • But other parts are not known, e.g. the word „house‟ in the fragment below: „house‟ is key for a Map and could be any value DSLs 2010 - 153
  • 154.
    …Einstein‟s Riddle :Polyglot w/ DSL def the(Color c1) {[ house: { _is -> [on: { _the -> [left: { _side -> [of: { __the -> Color.values().collectEntries{ c2 -> [c2.toString(), { _dummy -> addToLeftHint([color:c1], [color:c2]) }]} }]}]}]} ]} © ASERT 2006-2010 class HousePlaceHolder { def c1, script def house(_is) { [on: { _the -> [left: { _side -> [of: { __the -> Color.values().collectEntries { c2 -> [c2.toString(), { _dummy -> script.addToLeftHint( [color: c1], [color: c2] )}]} }]}]}] } } def the(Color c1) { new HousePlaceHolder(c1:c1, script:this) } „house‟ is now understood DSLs 2010 - 154
  • 155.
    Einstein‟s Riddle :Choco w/ DSL… @GrabResolver('http://www.emn.fr/z-info/choco-solver/mvn/repository/') @Grab('choco:choco:2.1.1-SNAPSHOT') import static choco.Choco.* import choco.kernel.model.variables.integer.* def m = new choco.cp.model.CPModel() m.metaClass.plus = { m.addConstraint(it); m } def s = new choco.cp.solver.CPSolver() choco.Choco.metaClass.static.eq = { c, v -> delegate.eq(c, v.ordinal()) } def makeEnumVar(st, arr) { choco.Choco.makeIntVar(st, 0, arr.size()-1, choco.Options.V_ENUM) } © ASERT 2006-2010 pets = new IntegerVariable[num] colors = new IntegerVariable[num] smokes = new IntegerVariable[num] drinks = new IntegerVariable[num] nations = new IntegerVariable[num] (0..<num).each { i -> pets[i] = makeEnumVar("pet$i", pets) colors[i] = makeEnumVar("color$i", colors) smokes[i] = makeEnumVar("smoke$i", smokes) drinks[i] = makeEnumVar("drink$i", drinks) nations[i] = makeEnumVar("nation$i", nations) } ... DSLs 2010 - 155
  • 156.
    …Einstein‟s Riddle :Choco w/ DSL… // define DSL (simplistic non-refactored version) def neighbours(var1, val1, var2, val2) { and( ifOnlyIf(eq(var1[0], val1), eq(var2[1], val2)), implies(eq(var1[1], val1), or(eq(var2[0], val2), eq(var2[2], val2))), implies(eq(var1[2], val1), or(eq(var2[1], val2), eq(var2[3], val2))), implies(eq(var1[3], val1), or(eq(var2[2], val2), eq(var2[4], val2))), ifOnlyIf(eq(var1[4], val1), eq(var2[3], val2)) ) } iff = { e1, c1, e2, c2 -> and(*(0..<num).collect{ © ASERT 2006-2010 ifOnlyIf(eq(e1[it], c1), eq(e2[it], c2)) }) } ... // define the DSL in terms of DSL implementation def the(Nationality n) { def ctx = [nations, n] [ drinks:iff.curry(*ctx, drinks), smokes:iff.curry(*ctx, smokes), keeps:iff.curry(*ctx, pets), rears:iff.curry(*ctx, pets), owns:{ _the -> [first:{ house -> eq(nations[first], n)}] }, ... DSLs 2010 - 156
  • 157.
    …Einstein‟s Riddle :Choco w/ DSL… // define rules m += all pets are different m += all colors are different m += all smokes are different m += all drinks are different m += all nations are different m += the man from the centre house drinks milk m += the Norwegian owns the first house m += the Dane drinks tea m += the German smokes prince © ASERT 2006-2010 m += the Swede keeps dogs // alternate ending: has a pet dog m += the Brit has a red house // alternate ending: red abode m += the owner of the green house drinks coffee m += the owner of the yellow house smokes dunhill m += the person known to smoke pallmall rears birds // alt end: keeps birds m += the man known to smoke bluemaster drinks beer m += the green house is on the left side of the white house m += the man known to smoke blends lives next to the one who keeps cats m += the man known to keep horses lives next to the man who smokes dunhill m += the man known to smoke blends lives next to the one who drinks water m += the Norwegian lives next to the blue house ... DSLs 2010 - 157
  • 158.
    …Einstein‟s Riddle :Choco w/ DSL def pretty(s, c, arr, i) { c.values().find{ it.ordinal() == s.getVar(arr[i])?.value } } // invoke logic solver s.read(m) def more = s.solve() while (more) { for (i in 0..<num) { print 'The ' + pretty(s, Nationality, nations, i) print ' has a pet ' + pretty(s, Pet, pets, i) © ASERT 2006-2010 print ' smokes ' + pretty(s, Smoke, smokes, i) print ' drinks ' + pretty(s, Drink, drinks, i) println ' and lives in a ' + pretty(s, Color, colors, i) + ' house' } more = s.nextSolution() } • Output: Solving Einstein's Riddle: The Norwegian has a pet cat smokes dunhill drinks water and lives in a yellow house The Dane has a pet horse smokes blends drinks tea and lives in a blue house The Brit has a pet bird smokes pallmall drinks milk and lives in a red house The German has a pet fish smokes prince drinks coffee and lives in a green house The Swede has a pet dog smokes bluemaster drinks beer and lives in a white house DSLs 2010 - 158
  • 159.
    Topics • DSL Origins • Groovy Intro • Groovy DSL Features • Groovy DSL Examples Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 159
  • 160.
    Guidelines… • Language andprogram evolve together. Like the border between two warring states, the boundary between language and program is drawn and redrawn, until eventually it comes to rest along the mountains and rivers, the natural frontiers of your problem. In the end your program will look as if the language had been designed for it. And when language and program fit one another well, you end up with code which is clear, small, and efficient. – Paul Graham DSLs 2010 - 160
  • 161.
    …Guidelines • Start small, don‟t over engineer • Grow your language iteratively • Play with end users • Let your DSL fly (theirs not yours) • Don‟t expect to get it right first time • Play in a sandbox http://www.slideshare.net/glaforge/implementing-groovy-domainspecific-languages-s2g-forum-munich-2010 DSLs 2010 - 161
  • 162.
    Topics • DSL Origins • Groovy Intro • Groovy DSL Features • Groovy DSL Examples • Advanced Topics and Guidelines © ASERT 2006-2010 More Info DSLs 2010 - 162
  • 163.
    Further Info… • Compilers : Principles, Techniques, and Tools/ Edition 2, Alfred Aho, Ravi Sethi, Jeffrey Ullman, Monica Lam • Practical API Design : Confessions of a Java Framework Architect, Jaroslav Tulach © ASERT 2006-2010 • Language Implementation Patterns : Create Your Own Domain-Specific and General Programming Languages, Terence Parr DSLs 2010 - 163
  • 164.
    …Further Info • A model-driven framework for domain specific languages, Martin Karlsch • Design Guidelines for Domain Specific Languages, Gabor Karsai et al • Program Comprehension for Domain- Specific Languages, Pereira et al © ASERT 2006-2010 • Diagrammatic Representations in Domain- Specific Languages, Konstantinos Tourlas • DSLs in Action, Debasish Ghosh • Domain-Specific Languages, Martin Fowler • DSLs in Boo : Domain Specific Languages in .NET, Ayende Rahien DSLs 2010 - 164
  • 165.
    GinA 2ed “ReGinA”is coming ... DSLs 2010 - 165