• Share
  • Email
  • Embed
  • Like
  • Save
  • Private Content
GroovyDSLs
 

GroovyDSLs

on

  • 7,582 views

Using Groovy to write (predominantly internal/embedded) Domains Specific Languages (DSLs)

Using Groovy to write (predominantly internal/embedded) Domains Specific Languages (DSLs)

Statistics

Views

Total Views
7,582
Views on SlideShare
7,552
Embed Views
30

Actions

Likes
13
Downloads
209
Comments
0

2 Embeds 30

http://dnosenko.name 29
http://www.gilbird.com 1

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

    GroovyDSLs GroovyDSLs Presentation Transcript

    • © ASERT 2006-2010 Writing Domain Specific Languages (DSLs) using Groovy Dr Paul King @paulk_asert paulk at asert.com.au September 2010
    • 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
    • Topics • DSL Origins • Groovy Intro • Groovy DSL Features • Groovy DSL Examples Advanced Topics & Guidelines © ASERT 2006-2010 • More Info DSLs 2010 - 146
    • 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 - 147
    • …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 - 148
    • Topics • DSL Origins • Groovy Intro • Groovy DSL Features • Groovy DSL Examples • Advanced Topics and Guidelines © ASERT 2006-2010 More Info DSLs 2010 - 149
    • 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 - 150
    • …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 - 151
    • GinA 2ed “ReGinA” is coming ... DSLs 2010 - 152