groovy dsls from beginner to expert

194
Chicago, October 19 - 22, 2010 Paul King @paulk_asert Groovy Committer ASERT, Australia Groovy.DSLs(from: beginner, to: expert) Guillaume Laforge @glaforge Groovy Project Manager SpringSource / VMWare

Upload: paul-king

Post on 15-Jan-2015

10.356 views

Category:

Technology


7 download

DESCRIPTION

Paul King and Guillaume Laforge present Groovy.DSLs(from:beginner, to:expert) at SpringOne2GX in Chicago in October 2010

TRANSCRIPT

Page 1: groovy DSLs from beginner to expert

Chicago, October 19 - 22, 2010

Paul King

@paulk_asert

Groovy Committer

ASERT, Australia

Groovy.DSLs(from: beginner, to: expert)

Guillaume Laforge

@glaforge

Groovy Project Manager

SpringSource / VMWare

Page 2: groovy DSLs from beginner to expert

Topics

Introduction

• Groovy DSL Features

• A Week In The Life Of DSL INC

• Summary

• More Info

DSLs 2010 - 2

© A

SE

RT

200

6-2

010

Page 3: groovy DSLs from beginner to expert

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

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

© A

SE

RT

200

6-2

010

Page 4: groovy DSLs from beginner to expert

1. e4 e5

2. Nf3 Nc6 3. Bb5 a6

Page 5: groovy DSLs from beginner to expert

L2 U F-1 B L2 F B -1 U L2

Page 6: groovy DSLs from beginner to expert

Visual!

Page 7: groovy DSLs from beginner to expert

Technical Examples

Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html

<?xml version="1.0"?>

<xsl:stylesheetversion="1.0"

xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

<xsl:output method="xml"/>

<xsl:template match="*">

<xsl:element name="{name()}">

<xsl:for-each select="@*">

<xsl:element name="{name()}">

<xsl:value-of select="."/>

</xsl:element>

</xsl:for-each>

<xsl:apply-templates select="*|text()"/>

</xsl:element>

</xsl:template>

</xsl:stylesheet>

cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps

<?xml version="1.0"?>

<GTK-Interface>

<widget>

<class>GtkWindow</class>

<name>HelloWindow</name>

<border_width>5</border_width>

<Signal>

<name>destroy</name>

<handler>gtk_main_quit</handler>

</Signal>

<title>Hello</title>

<type>GTK_WINDOW_TOPLEVEL</type>

<position>GTK_WIN_POS_NONE</position>

<allow_shrink>True</allow_shrink>

<allow_grow>True</allow_grow>

<auto_shrink>False</auto_shrink>

<widget>

<class>GtkButton</class>

<name>Hello World</name>

<can_focus>True</can_focus>

<label>Hello World</label>

</widget>

</widget>

</GTK-Interface>

# Poll this site first each cycle. poll pop.provider.net proto pop3 user "jsmith" with pass "secret1" is "smith" here user jones with pass "secret2" is "jjones" here with options keep

# Poll this site second, unless Lord Voldemort zaps us first. poll billywig.hogwarts.com with proto imap: user harry_potter with pass "floo" is harry_potter here

# Poll this site third in the cycle. # Password will be fetched from ~/.netrc poll mailhost.net with proto imap: user esr is esr here

Glade

Troff

XSLT

fetchmail

Regex

"x.z?z{1,3}y"

SQL

SELECT * FROM TABLE

WHERE NAME LIKE '%SMI'

ORDER BY NAME

DSLs 2010 - 7

Page 8: groovy DSLs from beginner to expert

Origins

• It has always been a

holy grail of computer

users to be able to

speak directly to our

computers

• 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 - 8

© A

SE

RT

200

6-2

010

Page 9: groovy DSLs from beginner to expert

Origins

• It has always been a

holy grail of computer

users to be able to

speak directly to our

computers

DSLs 2010 - 9

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

© A

SE

RT

200

6-2

010

Page 10: groovy DSLs from beginner to expert

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

“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 - 10

© A

SE

RT

200

6-2

010

Page 11: groovy DSLs from beginner to expert

Origins: Minilanguages...

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

%!PS-Adobe-3.0 %%Creator: groff version 1.20.1 ... 597.6 12 72 12 DL(increasing loopiness)297.71 8.2 Q(/etc/passwd)102.67 94.6 Q(.ne)110.715 106.6 Q(wsrc)-.25 E(SNG)195.2 100.6 Q(re)243.8 94.6 Q (ge)-.15 E(xps)-.15 E(Glade)247.26 106.6 Q(m4)306.81 58.6 Q -1(Ya)303.43 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) 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) 354.84 106.6 Q(f)-.25 E(Postscript)343.875 118.6 Q(dc)412.88 94.6 Q(bc) 412.88 106.6 Q(Emacs Lisp)462.53 94.6 Q(Ja)465.395 106.6 Q -.25(va)-.2 G (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 %%Trailer end %%EOF

Input DSL: pic for above picture Output "DSL": Postscript for above picture

# Minilanguage taxonomy # Base ellipses define smallellipse {ellipse width 3.0 height 1.5} M: ellipse width 3.0 height 1.8 fill 0.2 line from M.n to M.s dashed D: smallellipse() with .e at M.w + (0.8, 0) line from D.n to D.s dashed I: smallellipse() with .w at M.e - (0.8, 0)

# Arrow headings arrow from D.w + (0.4, 0.8) to D.e + (-0.4, 0.8) "flat to structured" "" at last arrow.c ... # Interpreters "Emacs Lisp" "JavaScript" at 0.25 between M.e and I.e "sh" "tcl" at 0.55 between M.e and I.e "Perl" "Python" "Java" at 0.8 between M.e and I.e

DSLs 2010 - 11

Page 12: groovy DSLs from beginner to expert

...Origins: Minilanguages

PowerPoint DSL for previous slide

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' ) } }

DSLs 2010 - 12

Page 13: groovy DSLs from beginner to expert

Subject Matter Experts, Business Analysts...

Page 14: groovy DSLs from beginner to expert

HAI CAN HAS STDIO? I HAS A VAR IM IN YR LOOP UP VAR!!1 VISIBLE VAR IZ VAR BIGGER THAN 10? KTHXBYE IM OUTTA YR LOOP KTHXBYE

Developer producing LOLCODE

Page 15: groovy DSLs from beginner to expert

And in the end... nobody understands

each other

Page 16: groovy DSLs from beginner to expert

DSL: a potential solution?

16 16

• Use a more expressive language than a

general purpose one

• Share a common metaphor of

understanding between developers and

subject matter experts

• Have domain experts help with the design

of the business logic of an application

• Avoid cluttering business code with

too much boilerplate technical code

• Cleanly separate business logic from

application code

• Let business rules have their own lifecycle

Page 17: groovy DSLs from beginner to expert

17 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.

Towards more readibility (1)

20%

17

Page 18: groovy DSLs from beginner to expert

18 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.

Towards more readibility (2)

80%

Page 19: groovy DSLs from beginner to expert

Why a DSL? • Advantages: – Domain experts can

understand, validate, modify,

and often even develop DSL

programs

– Somewhat self-documenting

– Enhance quality,

productivity, reliability,

maintainability, portability

and reusability

– Safety; as long as the

language constructs are safe

any sentence written with

them can be considered safe

• Disadvantages: – Learning cost vs. limited

applicability

– Cost of designing,

implementing & maintaining

DSL as well as the tools/IDEs

– Attaining proper scope

– Trade-offs between domain-

specificity and general-

purpose programming

language constructs

– Efficiency costs

– Proliferation of similar non-

standard DSLs, i.e. different

but similar DSLs used within

two insurance companies

Source: http://en.wikipedia.org/wiki/Domain-specific_language DSLs 2010 - 19

© A

SE

RT

200

6-2

010

Page 20: groovy DSLs from beginner to expert

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

• External (custom language) – May involve writing your own traditional parser

– Using parser combinators

– Interpreting

– String grepping

• Other characteristics – Functional vs OO

DSLs 2010 - 20

© A

SE

RT

200

6-2

010

Page 21: groovy DSLs from beginner to expert

...DSL usage patterns...

Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 21

© A

SE

RT

200

6-2

010

Page 22: groovy DSLs from beginner to expert

...DSL usage patterns

Source: DSLs in Action, Debasish Ghosh, Manning now in MEAP DSLs 2010 - 22

© A

SE

RT

200

6-2

010

Page 23: groovy DSLs from beginner to expert

DSL usage patterns (Advanced)

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.

© A

SE

RT

200

6-2

010

Page 24: groovy DSLs from beginner to expert

DSLs 2010 - 24

© A

SE

RT

200

6-2

010

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.”

Groovy = Java – boiler plate code + optional dynamic typing + closures + domain specific languages + builders + metaprogramming

Page 25: groovy DSLs from beginner to expert

DSLs 2010 - 25

© A

SE

RT

200

6-2

010

Groovy Goodies Overview • Fully object oriented

• Closures: reusable

and assignable

pieces of code

• Operators can be

overloaded

• Multimethods

• Literal declaration for

lists (arrays), maps,

ranges and regular

expressions

• GPath: efficient

object navigation

• GroovyBeans

• grep and switch

• Templates, builder,

swing, Ant, markup,

XML, SQL, XML-RPC,

Scriptom, Grails,

tests, Mocks

Page 26: groovy DSLs from beginner to expert

Growing Acceptance …

A slow and steady start but now gaining in

momentum, maturity and mindshare

Now free

Page 27: groovy DSLs from beginner to expert

DSLs 2010 - 27

© A

SE

RT

200

6-2

010

The Landscape of JVM Languages

Java bytecode calls

for static types

Dynamic features call

for dynamic types

optional

static

types

The terms “Java Virtual Machine” and “JVM” mean a Virtual Machine for the Java™ platform.

Page 28: groovy DSLs from beginner to expert

DSLs 2010 - 28

© A

SE

RT

200

6-2

010

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() 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)...

Page 29: groovy DSLs from beginner to expert

A Better Java...

DSLs 2010 - 29

© A

SE

RT

200

6-2

010

import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { String s = (String) strings.get(i); if (s.length() <= length) { result.add(s); } } return result; } 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(); List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } }

This code is valid Java and valid Groovy

Based on an example by Jim Weirich & Ted Leung

Page 30: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 30

© A

SE

RT

200

6-2

010

import java.util.List; import java.util.ArrayList; class Erase { private List removeLongerThan(List strings, int length) { List result = new ArrayList(); for (int i = 0; i < strings.size(); i++) { String s = (String) strings.get(i); if (s.length() <= length) { result.add(s); } } return result; } 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(); List shortNames = e.removeLongerThan(names, 3); System.out.println(shortNames.size()); for (int i = 0; i < shortNames.size(); i++) { String s = (String) shortNames.get(i); System.out.println(s); } } }

Do the semicolons add anything? And shouldn‟t we us more modern list notation? Why not import common libraries?

Page 31: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 31

© A

SE

RT

200

6-2

010

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() 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) } } }

Page 32: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 32

© A

SE

RT

200

6-2

010

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() 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) } } }

Do we need the static types? Must we always have a main method and class definition? How about improved consistency?

Page 33: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 33

© A

SE

RT

200

6-2

010

def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } 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) }

Page 34: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 34

© A

SE

RT

200

6-2

010

def removeLongerThan(strings, length) { def result = new ArrayList() for (s in strings) { if (s.size() <= length) { result.add(s) } } return result } 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) }

Shouldn‟t we have special notation for lists? And special facilities for list processing? Is „return‟ needed at end?

Page 35: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 35

© A

SE

RT

200

6-2

010

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) }

Page 36: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 36

© A

SE

RT

200

6-2

010

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) }

Is the method now needed? Easier ways to use common methods? Are brackets required here?

Page 37: groovy DSLs from beginner to expert

...A Better Java...

DSLs 2010 - 37

© A

SE

RT

200

6-2

010

names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it }

Page 38: groovy DSLs from beginner to expert

...A Better Java

DSLs 2010 - 38

© A

SE

RT

200

6-2

010

names = ["Ted", "Fred", "Jed", "Ned"] println names shortNames = names.findAll{ it.size() <= 3 } println shortNames.size() shortNames.each{ println it }

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

Page 39: groovy DSLs from beginner to expert

Grapes / Grab

DSLs 2010 - 39

© A

SE

RT

200

6-2

010

// 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' assert fruit.inverse().yellow == 'lemon'

Page 40: groovy DSLs from beginner to expert

Malleable Syntax

DSLs 2010 - 40

© A

SE

RT

200

6-2

010

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

Groovy 1.8+

def "length of Spock's & his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 }

Page 41: groovy DSLs from beginner to expert

41 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.

Real-life Groovy DSL examples

• Anti-malaria drug resistance simulation

• Human Resources employee skills

representation

• Insurance policies risk calculation engine

• Loan acceptance rules engine for a

financial platform

• Mathematica-like lingua for nuclear safety

simulations

• Market data feeds evolution scenarios

• and more...

Page 42: groovy DSLs from beginner to expert

42 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.

DSLs within the Groovy Ecosystem

• Testing DSLs – EasyB, Spock, JBehave, …

• Builders – MarkupBuilder, SwingBuilder

• Build Tool DSLs – AntBuilder, Gant, Gradle

• Grails – BeanBuilder, Criteria, ConfigSlurper

• GPars – DSL for concurrent activities

• and more...

Page 43: groovy DSLs from beginner to expert

Testing DSLs: Spock...

© A

SE

RT

200

6-2

010

DSLs 2010 - 43

Page 44: groovy DSLs from beginner to expert

...Testing DSLs: Spock...

© A

SE

RT

200

6-2

010

• Testing framework for Java and Groovy

• Highly expressive specification language – No assertion API

– No record &

replay

mocking API

– No

superfluous

annotations

– Meaningful

assert error

messages

– Extensible

– Compatible

with JUnit

reportingwise

@Speck @RunWith(Sputnik) class PublisherSubscriberSpeck { def "events are received by all subscribers"() { def pub = new Publisher() def sub1 = Mock(Subscriber) def sub2 = Mock(Subscriber) pub.subscribers << sub1 << sub2 when: pub.send("event") then: 1 * sub1.receive("event") 1 * sub2.receive("event") } }

DSLs 2010 - 44

Page 45: groovy DSLs from beginner to expert

...Testing DSLs: Spock...

© A

SE

RT

200

6-2

010

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 should succeed") def "when creating a new blog entry"() { given: page = new WebClient().getPage('http://localhost:8080/postForm') form = page.getFormByName('post') when: form.getInputByName('title').setValueAttribute("$author was here (and so was Spock)") form.getSelectByName('category').getOptions().find { it.text == category }.setSelected(true) form.getSelectByName('author').getOptions().find { it.text == author }.setSelected(true) form.getTextAreaByName('content').setText(content) result = form.getInputByName('btnPost').click() subheadings = result.getElementsByTagName('h3') para = result.getByXPath('//TABLE//TR/TD/P')[0] ...

DSLs 2010 - 45

Page 46: groovy DSLs from beginner to expert

... Testing DSLs: Spock

© A

SE

RT

200

6-2

010

... then: page.titleText == 'Welcome to SimpBlog' result.getElementsByTagName('h1').item(0).textContent.matches("Post.*: $author was here.*") subheadings.item(1).textContent == "Category: $category" subheadings.item(2).textContent == "Author: $author" and: para.textContent == content where: author << ['Bart', 'Homer', 'Lisa'] category << ['Home', 'Work', 'Food'] content << ['foo', 'bar', 'baz'] } }

// Optional use of 'and:'

DSLs 2010 - 46

Page 47: groovy DSLs from beginner to expert

© A

SE

RT

200

6-2

010

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' 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 - 47

Page 48: groovy DSLs from beginner to expert

…Testing DSLs: EasyB

• When run will be marked as pending – perfect for ATDD

© A

SE

RT

200

6-2

010

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" 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 - 48

Page 49: groovy DSLs from beginner to expert

Testing DSLS: Cucumber...

© A

SE

RT

200

6-2

010

# 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

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 - 49

Page 50: groovy DSLs from beginner to expert

... Testing DSLS: Cucumber...

© A

SE

RT

200

6-2

010

DSLs 2010 - 50

Page 51: groovy DSLs from beginner to expert

...Testing DSLS: Cucumber

© A

SE

RT

200

6-2

010

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) }

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 - 51

Page 52: groovy DSLs from beginner to expert

Grails Criteria

DSLs 2010 - 52

© A

SE

RT

200

6-2

010

// 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") } maxResults(10) order("holderLastName", "desc") } // source: Grails doco: 5. Object Relational Mapping (GORM): 5.4.2 Criteria

Page 53: groovy DSLs from beginner to expert

Grails Criteria Example

DSLs 2010 - 53

© A

SE

RT

200

6-2

010

// Book is a POJO in our domain/model def book = Book.findByTitle("The Stand") book = Book.findByTitleLike("Harry Pot%") book = Book.findByReleaseDateBetween( firstDate, secondDate ) 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

Page 54: groovy DSLs from beginner to expert

Grails Bean Builder Example

DSLs 2010 - 54

© A

SE

RT

200

6-2

010

bb.beans { marge(Person) { name = "marge" husband = { Person p -> name = "homer" age = 45 props = [overweight:true, height:"1.8m"] } 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

Page 55: groovy DSLs from beginner to expert

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 - 55

Page 56: groovy DSLs from beginner to expert

...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 }

thread { y << 5 }

No race-conditions No deadlocks No live-locks Completely deterministic programs BEAUTIFUL code

DSLs 2010 - 56

Page 57: groovy DSLs from beginner to expert

Topics

• Introduction

Groovy DSL Features

• A Week In The Life Of DSL INC

• Summary

• More Info

DSLs 2010 - 57

© A

SE

RT

200

6-2

010

Page 58: groovy DSLs from beginner to expert

Groovy DSL Features

DSLs 2010 - 58

© A

SE

RT

200

6-2

010

Page 59: groovy DSLs from beginner to expert

Import / Import Static

• Imports

• Static Imports

DSLs 2010 - 59

© A

SE

RT

200

6-2

010

@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'

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

What Java gives us plus aliases

Page 60: groovy DSLs from beginner to expert

Static Imports...

DSLs 2010 - 60

© A

SE

RT

200

6-2

010

import groovy.swing.SwingXBuilder 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()

Java Static Imports Discussed later: Builders Closures

Page 61: groovy DSLs from beginner to expert

...Static Imports...

DSLs 2010 - 61

© A

SE

RT

200

6-2

010

import groovy.swing.SwingXBuilder 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()

Java gives us this

Page 62: groovy DSLs from beginner to expert

...Static Imports...

DSLs 2010 - 62

© A

SE

RT

200

6-2

010

import groovy.swing.SwingXBuilder 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()

Java gives us this

Page 63: groovy DSLs from beginner to expert

...Static Imports...

DSLs 2010 - 63

© A

SE

RT

200

6-2

010

Source: http://www.thedoghousediaries.com/?p=1406

Page 64: groovy DSLs from beginner to expert

...Static Imports

DSLs 2010 - 64

© A

SE

RT

200

6-2

010

import groovy.swing.SwingXBuilder import static java.lang.Math.* import static java.awt.Color.GREEN as Lime 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()

Java doesn‟t give us this!

Page 65: groovy DSLs from beginner to expert

Literal Syntax Conventions

• Lists – Special syntax for list literals

– Additional common methods (operator overloading)

• Maps – Special syntax for map literals

– Additional common methods

• Ranges – Special syntax for various kinds of ranges

DSLs 2010 - 65

© A

SE

RT

200

6-2

010

def list = [3, new Date(), 'Jan'] assert list + list == list * 2

def map = [a: 1, b: 2] assert map['a'] == 1 && map.b == 2

def letters = 'a'..'z' def numbers = 0..<10

Page 66: groovy DSLs from beginner to expert

Literal Syntax Conventions in DSLs

DSLs 2010 - 66

© A

SE

RT

200

6-2

010

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: ' if (now[DAY_OF_WEEK] in MONDAY..FRIDAY) println roomrate.weekday * multiplier else println roomrate.weekend * multiplier }

Your room rate is: 150 Your room rate is: 142.50 Your room rate is: 135.0

Literal list & map syntax Ranges Also Static import aliases BigDecimal arithmetic

Page 67: groovy DSLs from beginner to expert

Compact Syntax...

• Java

• Groovy

DSLs 2010 - 67

© A

SE

RT

200

6-2

010

public class BuySharesJava { public static void buyShares(int qty, String name) { // business logic here ... System.out.println("buying " + qty + " shares of " + name); } public static void main(String[] args) { buyShares(3, "BHP"); } }

def buyShares(qty, name) { // business logic here ... println "buying $qty shares of $name" } buyShares 3, 'BHP'

Script syntax Conventions for visibility GDK methods: println Implicit typing GString interpolation Optional brackets & „;‟

Page 68: groovy DSLs from beginner to expert

...Compact Syntax...

• Java

• Groovy

DSLs 2010 - 68

© A

SE

RT

200

6-2

010

import java.util.Date; import java.util.HashMap; 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); 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 Last name: ${cust.last ?: 'unknown'}""" } printDetails first: 'John', last: 'Smith'

Script syntax, Named params, Extra imports Conventions for visibility, GDK methods Implicit typing, Multi-line GString Elvis operator, Optional brackets & „;‟

Page 69: groovy DSLs from beginner to expert

...Compact Syntax

• Java

• Groovy

DSLs 2010 - 69

© A

SE

RT

200

6-2

010

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); String last = cust.getLast(); System.out.println("Last name: " + (last != null ? last : "unknown")); } public static void main(String[] args) { CustomerJ c = new CustomerJ(); c.setFirst("John"); c.setLast("Smith"); printDetails(c); } } class CustomerJ { private String first; private String last; public String getFirst() { return first; } public void setFirst(String first) { this.first = first; } public String getLast() { return last; } public void setLast(String last) { this.last = last; } }

class CustomerG { String first, last }

def printDetails(cust) { println """Details as at: ${new Date()} First name: $cust.first Last name: ${cust.last ?: 'unknown'}""" }

printDetails new CustomerG(first: 'John', last: 'Smith')

Plus: Named params for constructors JavaBean conventions Leverage Duck Typing

Page 70: groovy DSLs from beginner to expert

Using With Example

DSLs 2010 - 70

© A

SE

RT

200

6-2

010

letters = ['a', 'b', 'c'] range = 'b'..'d' letters.with { add 'd' remove 'a' } assert letters == range

map = [a:10, b:4, c:7] map.with { assert (a + b) / c == 2 }

Just normal methods for ArrayList here

Page 71: groovy DSLs from beginner to expert

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

around, combined in structured ways to form more

complex algorithms and data; functional coding style

DSLs 2010 - 71

© A

SE

RT

200

6-2

010

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

Page 72: groovy DSLs from beginner to expert

...Closures...

DSLs 2010 - 72

© A

SE

RT

200

6-2

010

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

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

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

Name

Page 73: groovy DSLs from beginner to expert

...Closures...

DSLs 2010 - 73

© A

SE

RT

200

6-2

010

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

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

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

Code

Page 74: groovy DSLs from beginner to expert

...Closures...

DSLs 2010 - 74

© A

SE

RT

200

6-2

010

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

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

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

Parameter(s)

Default parameter

Page 75: groovy DSLs from beginner to expert

...Closures...

DSLs 2010 - 75

© A

SE

RT

200

6-2

010

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

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

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

Call closure

Alternative syntax

Page 76: groovy DSLs from beginner to expert

...Closures...

DSLs 2010 - 76

© A

SE

RT

200

6-2

010

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

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

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

Free variable

Page 77: groovy DSLs from beginner to expert

...Closures...

DSLs 2010 - 77

© A

SE

RT

200

6-2

010

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

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

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

Bound to environment/context when called

Page 78: groovy DSLs from beginner to expert

Closures in DSLs...

DSLs 2010 - 78

© A

SE

RT

200

6-2

010

import static java.util.Calendar.* 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, 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'] ] // ...

Nothing special here. Just a list of maps.

Page 79: groovy DSLs from beginner to expert

... Closures in DSLs...

DSLs 2010 - 79

© A

SE

RT

200

6-2

010

// ... 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) } } 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

Page 80: groovy DSLs from beginner to expert

... Closures in DSLs...

DSLs 2010 - 80

© A

SE

RT

200

6-2

010

// ... 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) } } 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

Page 81: groovy DSLs from beginner to expert

... Closures in DSLs

DSLs 2010 - 81

© A

SE

RT

200

6-2

010

// ... def specials = [noDiscount, tenPercentOffAll, tenPercentOffGifts, tenPercentOffOverForty, tenPercentOffBooks, tenPercentOffDVDs, buyThreeGetOneFree] [ SUNDAY..SATURDAY, [cart1, cart2] ].combinations().each { day, cart -> printf "%d %2.2f\n", day, cart.sum(specials[day-1]) }

1 240.00 2 216.00 3 220.00 4 225.00 5 221.00 6 235.00 7 190.00 1 110.00 2 99.00 3 105.00 4 105.00 5 105.00 6 104.00 7 110.00

Page 82: groovy DSLs from beginner to expert

Operator Overloading Example

DSLs 2010 - 82

© A

SE

RT

200

6-2

010

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));

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

• Java

• Groovy

Page 83: groovy DSLs from beginner to expert

Groovy Lab Example

DSLs 2010 - 83

© A

SE

RT

200

6-2

010

// 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 // 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))

Page 84: groovy DSLs from beginner to expert

© A

SE

RT

200

6-2

010

Better Control Structures: Switch Poker…

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() } rankValues = { List cards -> cards.collect{ rank(it) }.sort() } // ...

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

hand1 hand2

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

DSLs 2010 - 84

Page 85: groovy DSLs from beginner to expert

© A

SE

RT

200

6-2

010

…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) } 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 - 85

Page 86: groovy DSLs from beginner to expert

© A

SE

RT

200

6-2

010

… 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 case straight : return 5 case threeOfAKind : return 4 case twoPair : return 3 case pair : return 2 default : return 1 } } // ...

DSLs 2010 - 86

Page 87: groovy DSLs from beginner to expert

Type Transformation Example

DSLs 2010 - 87

© A

SE

RT

200

6-2

010

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) { 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() }

Page 88: groovy DSLs from beginner to expert

DSLs 2010 - 88

Groovy DSL Features

© A

SE

RT

200

6-2

010

Page 89: groovy DSLs from beginner to expert

Metaprogramming

• Runtime Metaprogramming – Categories including DGM

– invokeMethod, methodMissing, getProperty

– GroovyInterceptable

– ExpandoMetaClass

DSLs 2010 - 89

© A

SE

RT

200

6-2

010

Page 90: groovy DSLs from beginner to expert

DSLs 2010 - 90

© A

SE

RT

200

6-2

010

Builders…

<html> <head> <title>Hello</title> </head> <body> <ul> <li>world 1</li> <li>world 2</li> <li>world 3</li> <li>world 4</li> <li>world 5</li> </ul> </body> </html>

import groovy.xml.* def page = new MarkupBuilder() page.html { head { title 'Hello' } body { ul { for (count in 1..5) { li "world $count" } } } }

• MarkupBuilder

Page 91: groovy DSLs from beginner to expert

...Builders…

DSLs 2010 - 91

© A

SE

RT

200

6-2

010

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] ) }

• GraphicsBuilder

Page 92: groovy DSLs from beginner to expert

…Builders new AntBuilder().with { echo(file:'Temp.java', ''' class Temp { public static void main(String[] args) { System.out.println("Hello"); } } ''') javac(srcdir:'.', includes:'Temp.java', fork:'true') java(classpath:'.', classname:'Temp', fork:'true') echo('Done') } // => // [javac] Compiling 1 source file // [java] Hello // [echo] Done

© A

SE

RT

200

6-2

010

DSLs 2010 - 92

Page 93: groovy DSLs from beginner to expert

93 Copyright 2010 SpringSource. Copying, publishing or distributing without express written permission is prohibited.

A builder for HR

Page 94: groovy DSLs from beginner to expert

DSLs 2010 - 94

© A

SE

RT

200

6-2

010

ExpandoMetaClass…

import static java.lang.Character.isUpperCase String.metaClass.swapCase = { delegate.collect { ch -> isUpperCase(ch as char) ? ch.toLowerCase() : ch.toUpperCase() }.join() } 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()

Page 95: groovy DSLs from beginner to expert

DSLs 2010 - 95

© A

SE

RT

200

6-2

010

…ExpandoMetaClass

class Person { String name } class MortgageLender { def borrowMoney() { "buy house" } } def lender = new MortgageLender() Person.metaClass.buyHouse = lender.&borrowMoney def p = new Person() assert "buy house" == p.buyHouse()

Page 96: groovy DSLs from beginner to expert

Adding your own control structures

• Thread enhancement

DSLs 2010 - 96

ROCK! . 25 . 33 . 34 . 35 . 36 .. 131 .. 134 .. 137 .. 138 .. 139 ... 232 ... 234 ... 237 ... 238 ... 239 .... 334 .... 336 .... 337 .... 338 .... 339

import java.util.concurrent.locks.ReentrantLock import static System.currentTimeMillis as now def startTime = now() ReentrantLock.metaClass.withLock = { critical -> lock() try { critical() } finally { unlock() } } def lock = new ReentrantLock() def worker = { threadNum -> 4.times { count -> lock.withLock { print " " * threadNum print "." * (count + 1) println " ${now() - startTime}" } Thread.sleep 100 } } 5.times { Thread.start worker.curry(it) } println "ROCK!"

Source: http://chrisbroadfoot.id.au/articles/2008/08/06/groovy-threads

Page 97: groovy DSLs from beginner to expert

EMC DSL…

DSLs 2010 - 97

© A

SE

RT

200

6-2

010

Page 98: groovy DSLs from beginner to expert

...EMC Neo4j DSL…

DSLs 2010 - 98

© A

SE

RT

200

6-2

010

@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 { 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) } } // ...

Page 99: groovy DSLs from beginner to expert

…EMC Neo4j DSL

DSLs 2010 - 99

© A

SE

RT

200

6-2

010

// ... 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!" relationship.message = "brave Neo4j" tx.success() } finally { tx.finish() println "$firstNode.message $relationship.message $secondNode.message" // => Hello, brave Neo4j world! graphDb.shutdown() }

Page 100: groovy DSLs from beginner to expert

Game example...

DSLs 2010 - 100

© A

SE

RT

200

6-2

010

// 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) { 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.* ...

Page 101: groovy DSLs from beginner to expert

...Game example...

DSLs 2010 - 101

© A

SE

RT

200

6-2

010

... class Room { def description def contents = [] } ...

Page 102: groovy DSLs from beginner to expert

...Game example...

DSLs 2010 - 102

© A

SE

RT

200

6-2

010

... class Player { def currentRoom def inventory = [] void look() { println "You are in ${currentRoom?.description?:'the void'} \ which contains ${listItems(currentRoom?.contents)}" } 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" } } ...

Page 103: groovy DSLs from beginner to expert

...Game example...

DSLs 2010 - 103

© A

SE

RT

200

6-2

010

... void drop(item) { if (inventory?.remove(item)) { currentRoom?.contents << item println "You dropped the $item" } else { println "You don't have the $item" } } def propertyMissing(String name) { if (metaClass.respondsTo(this, name)) { this."$name"() } name } } ...

Page 104: groovy DSLs from beginner to expert

...Game example...

DSLs 2010 - 104

© A

SE

RT

200

6-2

010

... 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 inv look take emerald inv look take key drop emerald inv look } assert player.inventory == ['dagger', 'key'] ...

Page 105: groovy DSLs from beginner to expert

...Game example

DSLs 2010 - 105

© A

SE

RT

200

6-2

010

... // now try some error conditions plainRoom.description = null player.with { drop gold take gold drop emerald take emerald take emerald look }

Page 106: groovy DSLs from beginner to expert

GEP3 example...

DSLs 2010 - 106

© A

SE

RT

200

6-2

010

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) } given = { it } given 100 please show the square_root // ==> 10.0

Page 107: groovy DSLs from beginner to expert

...GEP3 example...

DSLs 2010 - 107

© A

SE

RT

200

6-2

010

Object.metaClass.of = { delegate[0](delegate[1](it)) } Object.metaClass.the = { clos -> [delegate[0], clos] } show = [{ println it }] square_root = { Math.sqrt(it) } please = { it } please show the square_root of 100 // ==> 10.0

Page 108: groovy DSLs from beginner to expert

...GEP3 example...

DSLs 2010 - 108

© A

SE

RT

200

6-2

010

show = { println it } square_root = { Math.sqrt(it) } def please(action) { [the: { what -> [of: { n -> action(what(n)) }] }] } please show the square_root of 100 // ==> 10.0

Inspiration for this example came from …

Page 109: groovy DSLs from beginner to expert

...GEP3 example...

DSLs 2010 - 109

© A

SE

RT

200

6-2

010

// Japanese DSL using GEP3 rules Object.metaClass.を = Object.metaClass.の = { clos -> clos(delegate) } まず = { it } 表示する = { println it } 平方根 = { 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

Page 110: groovy DSLs from beginner to expert

...GEP3 example

DSLs 2010 - 110

© A

SE

RT

200

6-2

010

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

Page 111: groovy DSLs from beginner to expert

Medical Prescription DSL…

DSLs 2010 - 111

© A

SE

RT

200

6-2

010

class Drug { String name String toString() { name } } class Measure { Number number String unit, units String toString() { number == 1 ? "1 $unit" : "$number $units" } } class Quantity extends Measure {} class Duration extends Measure {} // ...

http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu

Page 112: groovy DSLs from beginner to expert

…Medical Prescription DSL…

DSLs 2010 - 112

© A

SE

RT

200

6-2

010

// ... // ---- 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 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" }] }] } // ...

Page 113: groovy DSLs from beginner to expert

…Medical Prescription DSL

DSLs 2010 - 113

© A

SE

RT

200

6-2

010

// ... // ---- DSL ------------------------------- take 2.pills of chloroquinine after 6.hours

http://groovyconsole.appspot.com/script/240001 by glaforge/Mihai Cazacu

Page 114: groovy DSLs from beginner to expert

Groovy DSL Features

DSLs 2010 - 114

© A

SE

RT

200

6-2

010

Transformation

Page 115: groovy DSLs from beginner to expert

AST Transformations

• With metaprogramming, Groovy‟s able to

modify the behaviour of programs... at

runtime

• AST Transformations provide a compile-

time approach to modify programs – AST: Abstract Syntax Tree

– Ability to change what‟s being compiled at compile-

time!

• No runtime impact!

• Lets you change the semantics of your programs!

• Nice way of implementing patterns and removing boiler-plate

technical code

• Two kinds of transformations: global & local

DSLs 2010 - 115

Page 116: groovy DSLs from beginner to expert

Better Design Patterns: Immutable...

• Java Immutable Class – As per Joshua Bloch

Effective Java

DSLs 2010 - 116

© A

SE

RT

200

6-2

010

public final class Punter { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; } public Punter(String first, String last) { this.first = first; this.last = last; } // ...

// ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Punter other = (Punter) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; } @Override public String toString() { return "Punter(first:" + first + ", last:" + last + ")"; } }

Page 117: groovy DSLs from beginner to expert

...Better Design Patterns: Immutable...

• Java Immutable Class – As per Joshua Bloch

Effective Java

DSLs 2010 - 117

© A

SE

RT

200

6-2

010

public final class Punter { private final String first; private final String last; public String getFirst() { return first; } public String getLast() { return last; } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((first == null) ? 0 : first.hashCode()); result = prime * result + ((last == null) ? 0 : last.hashCode()); return result; } public Punter(String first, String last) { this.first = first; this.last = last; } // ...

// ... @Override public boolean equals(Object obj) { if (this == obj) return true; if (obj == null) return false; if (getClass() != obj.getClass()) return false; Punter other = (Punter) obj; if (first == null) { if (other.first != null) return false; } else if (!first.equals(other.first)) return false; if (last == null) { if (other.last != null) return false; } else if (!last.equals(other.last)) return false; return true; } @Override public String toString() { return "Punter(first:" + first + ", last:" + last + ")"; } }

boilerplate

Page 118: groovy DSLs from beginner to expert

...Better Design Patterns: Immutable

DSLs 2010 - 118

© A

SE

RT

200

6-2

010

@Immutable class Punter { String first, last }

Page 119: groovy DSLs from beginner to expert

Spock

DSLs 2010 - 119

© A

SE

RT

200

6-2

010

def "length of Spock's & his friends' names"() { expect: name.size() == length where: name | length "Spock" | 5 "Kirk" | 4 "Scotty" | 6 }

Page 120: groovy DSLs from beginner to expert

AST Builder

• Numerous approaches, still evolving.

“From code” approach:

• Produces:

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

BlockStatement -> ReturnStatement -> MethodCallExpression -> VariableExpression("this") -> ConstantExpression("println") -> ArgumentListExpression -> ConstantExpression("Hello World")

DSLs 2010 - 120

Page 121: groovy DSLs from beginner to expert

© A

SE

RT

200

6-2

010

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' 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 - 121

Page 122: groovy DSLs from beginner to expert

© A

SE

RT

200

6-2

010

…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 } when "the system converts this number to the roman numeral equivalent", { theConvertedNumber = RomanNumerals.fromInteger(theNumber) } then "the result should be #romanNumerals", { theConvertedNumber.shouldBe romanNumerals } }

DSLs 2010 - 122

Source: http://www.wakaleo.com/blog/285-example-driven-testing-with-easyb

Page 123: groovy DSLs from beginner to expert

EasyB Preprocessing...

• EasyB supports either of these:

• But we would like this:

given ("some data") { println '... setting expectations' }

Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html

given "some data", { println '... setting expectations' }

given "some data" { println '... setting expectations' }

DSLs 2010 - 123

But also consider Command Expressions (GEP-3) for Groovy1.8+.

Page 124: groovy DSLs from beginner to expert

...EasyB Preprocessing...

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

Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html

DSLs 2010 - 124

Adds the comma in where required before Groovy sees it.

Page 125: groovy DSLs from beginner to expert

...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)

Source: http://hamletdarcy.blogspot.com/2010/02/groovy-antlr-plugins-for-better-dsls.html

DSLs 2010 - 125

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

Page 126: groovy DSLs from beginner to expert

GParsec...

• Miniature parsers

• Parser Combinators

def isLetter = { ch -> return Character.isLetter(ch) } def isDigit = { ch -> return Character.isDigit(ch) } def letter = satisfyC(isLetter) def digit = satisfyC(isDigit)

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

DSLs 2010 - 126

Page 127: groovy DSLs from beginner to expert

...GParsec...

• BNF

• Groovy definitions

variableDeclarator : identifier ( '=' expression )? variableDefinitions : variableDeclarator ( ',' variableDeclarator)*

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

DSLs 2010 - 127

Page 128: groovy DSLs from beginner to expert

...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 - 128

Page 129: groovy DSLs from beginner to expert

Using External Parsers

DSLs 2010 - 129

© A

SE

RT

200

6-2

010

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

Page 130: groovy DSLs from beginner to expert

Topics

• Introduction

• Groovy DSL Features

A Week In The Life Of DSL INC

• Summary

• More Info

DSLs 2010 - 130

© A

SE

RT

200

6-2

010

Page 131: groovy DSLs from beginner to expert

MONDAY

DSLs 2010 - 131

© A

SE

RT

200

6-2

010

Page 132: groovy DSLs from beginner to expert

Currency DSL…

DSLs 2010 - 132

© A

SE

RT

200

6-2

010

From: [email protected] To: Guillaume Subject: Project Request Dear Guillaume, We would like a DSL for capturing currency expressions. Just US currency to start with. Thanks, Happy Customer

Page 133: groovy DSLs from beginner to expert

…Currency DSL...

DSLs 2010 - 133

© A

SE

RT

200

6-2

010

enum Coin { penny(1), nickel(5), dime(10), quarter(25) Coin(int value) { this.value = value } int value } import static Coin.* assert 2 * quarter.value + 1 * nickel.value + 2 * penny.value == 57

Page 134: groovy DSLs from beginner to expert

…Currency DSL…

DSLs 2010 - 134

© A

SE

RT

200

6-2

010

From: [email protected] To: Guillaume Subject: Project Request Dear Guillaume, That works fairly well but can we get rid of the ‘.value’ part of those expressions. They are confusing our users. Thanks, Happy Customer

Page 135: groovy DSLs from beginner to expert

...Currency DSL...

DSLs 2010 - 135

© A

SE

RT

200

6-2

010

class CoinMath { static multiply(Integer self, Coin c) { self * c.value } } use (CoinMath) { 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

Page 136: groovy DSLs from beginner to expert

…Currency DSL…

DSLs 2010 - 136

© A

SE

RT

200

6-2

010

From: [email protected] To: Guillaume Subject: Project Request Dear Guillaume, Much better but our users are sometimes using plural as well as singular expressions. Is there anything that you can do about that? Thanks, Happy Customer

Page 137: groovy DSLs from beginner to expert

... Currency DSL

DSLs 2010 - 137

© A

SE

RT

200

6-2

010

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 }

// 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

Page 138: groovy DSLs from beginner to expert

TUESDAY

DSLs 2010 - 138

© A

SE

RT

200

6-2

010

http://www.google.com/imgres?imgurl=http://i733.photobucket.com/albums/ww336/snake1953/Day_of_Week/Tue/HappyTuesday-Cat-001.gif&imgrefurl=http://www.care2.com/c2c/groups/disc.html%3Fgpp%3D1102%26pst%3D1108882&usg=__5gEADX6OKtNLi0XvjGo4CdknKf8=&h=390&w=398&sz=32&hl=en&start=0&sig2=eQpFsyBn6nIUrbuMVZliPg&zoom=1&tbnid=JzNvImbpWvV1DM:&tbnh=138&tbnw=141&ei=3IG-TMalE9ifnwfHifj8AQ&prev=/images%3Fq%3Dtuesday%2Bcat%26um%3D1%26hl%3Den%26client%3Dfirefox-a%26sa%3DN%26rls%3Dorg.mozilla:en-US:official%26biw%3D1488%26bih%3D852%26tbs%3Disch:1&um=1&itbs=1&iact=hc&vpx=132&vpy=207&dur=4782&hovh=222&hovw=227&tx=119&ty=237&oei=3IG-TMalE9ifnwfHifj8AQ&esq=1&page=1&ndsp=40&ved=1t:429,r:8,s:0http://i733.photobucket.com/albums/ww336/snake1953/Day_of_Week/Tue/HappyTuesday-Cat-001.gif

Page 139: groovy DSLs from beginner to expert

Business logic DSL…

DSLs 2010 - 139

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Dear Paul, Could you please create us a small DSL for capturing our business rules. We are thinking of something like: price = 3 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 but please consider the requirements as being pretty much locked down.

Page 140: groovy DSLs from beginner to expert

…Business logic DSL…

• Options – Embedded Groovy

– Regex parser

– Antlr parser

– Split / StringTokenizer

– Parser combinators

• … Many options, details not important … DSLs 2010 - 140

© A

SE

RT

200

6-2

010

Page 141: groovy DSLs from beginner to expert

… Business logic DSL …

DSLs 2010 - 141

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Date: Mid 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 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.

Page 142: groovy DSLs from beginner to expert

… Business logic DSL …

DSLs 2010 - 142

© A

SE

RT

200

6-2

010

def shell = new GroovyShell()

shell.evaluate('''

price = 3

quantity = 10

total = price * quantity

assert total == 30

''')

Page 143: groovy DSLs from beginner to expert

… Business logic DSL …

DSLs 2010 - 143

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Date: This afternoon 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 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.

Page 144: groovy DSLs from beginner to expert

… Business logic DSL …

DSLs 2010 - 144

© A

SE

RT

200

6-2

010

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) 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.") } // ... }

Please also see the ArithmeticShell which is included under examples in the Groovy distribution

Page 145: groovy DSLs from beginner to expert

… Business logic DSL

DSLs 2010 - 145

© A

SE

RT

200

6-2

010

def shell = new CustomerShell()

shell.evaluate('''

price = 3

quantity = 10

total = price * quantity

assert total == 30

''')

Page 146: groovy DSLs from beginner to expert

WEDNESDAY

DSLs 2010 - 146

© A

SE

RT

200

6-2

010

http://www.crystalscomments.com/1/days/wednesday/animals/006.jpg

Page 147: groovy DSLs from beginner to expert

Stock Exchange Order DSL…

DSLs 2010 - 147

© A

SE

RT

200

6-2

010

Example inspired from DSLs in Action: http://www.manning.com/ghosh/

From: [email protected] To: Guillaume Subject: Project Request Date: Wednesday morning Dear Guillaume, For our order processing system we have a need to capture client orders using a DSL. An order includes the name of the security to be transacted (buy or sell) as well as quantity and unit price details to specify any constraint that the counterparty would like to impose on the price of transaction. Thanks, Happy Customer

Page 148: groovy DSLs from beginner to expert

… Stock Exchange Order DSL…

DSLs 2010 - 148

© A

SE

RT

200

6-2

010

// ----- Implementation of the Fluent API ----- enum Action { Buy, Sell } class Order { def security def quantity, limitPrice boolean allOrNone def valueCalculation Action action def buy(Integer quantity, String security) { this.quantity = quantity this.security = security this.action = Action.Buy return this } def sell(Integer quantity, String security) { this.quantity = quantity this.security = security this.action = Action.Sell return this } def limitPrice(Integer limit) { this.limitPrice = limit; return this } def allOrNone(boolean allOrNone) { this.allOrNone = allOrNone; return this } def valueAs(Closure valueCalculation) { this.valueCalculation = valueCalculation; return this } String toString() { "$action $quantity shares of $security at valuation of ${valueCalculation(quantity, limitPrice)}" } }

println new Order() .sell(150, "IBM") .limitPrice(300) .allOrNone(true) .valueAs{ qty, unitPrice -> qty * unitPrice - 100 } println new Order() .buy(200, "GOOG") .limitPrice(200) .allOrNone(true) .valueAs{ qty, unitPrice -> qty * unitPrice - 500 }

Sell 150 shares of IBM at valuation of 44900 Buy 200 shares of GOOG at valuation of 39500

Page 149: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 149

© A

SE

RT

200

6-2

010

From: [email protected] To: Guillaume Subject: Project Request Date: Wednesday morning Dear Guillaume, That version was great but can you make the DSL a little more fluent. The brokers aren’t programmers after all! Thanks, Happy Customer

Page 150: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 150

© A

SE

RT

200

6-2

010

class Order {

def security

def quantity

def limitPrice

def allOrNone

def value

def bs

def buy(securityQuantity, closure) {

bs = 'Bought'

buySell(securityQuantity, closure)

}

def sell(securityQuantity, closure) {

bs = 'Sold'

buySell(securityQuantity, closure)

}

private buySell(securityQuantity, closure) {

// multiple assignment

(security, quantity) = [securityQuantity.security, securityQuantity.quantity]

// better clone the closure to avoid multi-threading access issues

def c = closure.clone()

// delegate the method calls inside the closure to our methodMissing

c.delegationStrategy = Closure.DELEGATE_ONLY

c.delegate = this

def valuation = c()

println "$bs $quantity $security.name at valuation of $valuation"

}

// methods inside the closure will assign the Order properties

def methodMissing(String name, args) {

this."$name" = args[0]

}

...

Page 151: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 151

© A

SE

RT

200

6-2

010

...

def getTo() { this }

def valueAs(closure) {

value = closure(quantity, limitPrice)

}

}

class Security {

String name

}

class Quantity {

Security security

Integer quantity

}

Integer.metaClass.getShares = { -> delegate }

Integer.metaClass.of = { new Quantity(security: it, quantity: delegate) }

class CustomBinding extends Binding {

def getVariable(String symbol) {

// create a new order each time

// for when you pass several orders

if (symbol == "newOrder")

new Order()

// otherwise, it's an instrument

// trick to avoid using strings: use IBM instead of 'IBM'

else

new Security(name: symbol)

}

}

// use the script binding for retrieving IBM, etc.

binding = new CustomBinding()

newOrder.to.buy(100.shares.of(IBM)) {

limitPrice 300

allOrNone true

valueAs { qty, unitPrice ->

qty * unitPrice - 200 }

}

newOrder.to.sell 200.shares.of(GOOG), {

limitPrice 200

allOrNone false

valueAs { qty, unitPrice ->

qty * unitPrice - 500 }

}

Page 152: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 152

© A

SE

RT

200

6-2

010

From: [email protected] To: Guillaume Subject: Project Request Date: Wednesday lunch time Dear Guillaume, That version was great but can we simplify the constraint to remove the parameters, i.e.: change { qty, unitPrice -> qty * unitPrice - 200 } to this: { qty * unitPrice - 200 } Thanks, Happy Customer

Page 153: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 153

© A

SE

RT

200

6-2

010

class Order {

...

def allOrNone

def valueCalculation

def bs

...

private buySell(securityQuantity,

closure) {

...

valueCalculation = c()

// debug print resulting order

println toString()

return this

}

...

def valueAs(Closure wrapee) {

// 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 = wrapee.clone()

cloned.resolveStrategy = Closure.DELEGATE_ONLY

cloned.delegate = [qty: qty, unitPrice: unitPrice]

cloned()

}

return wrapped

}

String toString() {

"$bs $quantity shares of $security.name and valuation of

${valueCalculation(quantity, limitPrice)}"

}

}

newOrder.to.buy(100.shares.of(IBM)) {

limitPrice 300

allOrNone true

valueAs { qty * unitPrice - 500 }

}

newOrder.to.sell 200.shares.of(GOOG), {

limitPrice 200

allOrNone false

valueAs { qty * unitPrice - 100 }

}

Page 154: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 154

© A

SE

RT

200

6-2

010

From: [email protected] To: Guillaume Subject: Project Request Date: Wednesday afternoon Dear Guillaume, Fantastic! This is getting better all the time! But can we get rid most of the ‘.’ and bracket symbols! Thanks, Happy Customer

Page 155: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 155

© A

SE

RT

200

6-2

010

// 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) { // 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" } } // ...

http://groovyconsole.appspot.com/script/226001 by glaforge

Page 156: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 156

© A

SE

RT

200

6-2

010

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

Page 157: groovy DSLs from beginner to expert

…Stock Exchange Order DSL…

DSLs 2010 - 157

© A

SE

RT

200

6-2

010

// ---- Stock exchange orders DSL ---- order to buy 200.shares of GOOG { 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

Page 158: groovy DSLs from beginner to expert

…Stock Exchange Order DSL

DSLs 2010 - 158

© A

SE

RT

200

6-2

010

From: [email protected] To: Guillaume Subject: Project Request Date: Wednesday evening Dear Guillaume, Brilliant! Even our CEO could write orders! Thanks, Happy Customer

Page 159: groovy DSLs from beginner to expert

THURSDAY

DSLs 2010 - 159

© A

SE

RT

200

6-2

010

http://t1.gstatic.com/images?q=tbn:_VBloEP8YC-6IM:http://msp248.photobucket.com/albums/gg171/ingrid2002/TextPL25202851729.gif&t=1

Page 160: groovy DSLs from beginner to expert

Einstein‟s Riddle DSL …

DSLs 2010 - 160

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Date: Early morning Dear Paul, We would like a DSL for capturing our business rules. Our business rules are captured in the form of logic clauses. They are very much like those found in Einstein’s riddle. Thanks, Happy Customer

Page 161: groovy DSLs from beginner to expert

Einstein‟s Riddle…

• Wikipedia: The zebra puzzle is a well-

known logic puzzle – It is often called Einstein's Puzzle or Einstein's Riddle

because it is said to have been invented by Albert

Einstein as a boy, with the claim that Einstein said

“only 2 percent of the world's population can solve it.”

– The puzzle is also sometimes attributed to Lewis

Carroll. However, there is no known evidence for

Einstein's or Carroll's authorship; and the original

puzzle cited below mentions brands of cigarette, such

as Kools, that did not exist during Carroll's lifetime or

Einstein's boyhood

DSLs 2010 - 161

© A

SE

RT

200

6-2

010

Page 162: groovy DSLs from beginner to expert

…Einstein‟s Riddle

DSLs 2010 - 162

© A

SE

RT

200

6-2

010

• Some premises: – The British person lives in the red house

– The Swede keeps dogs as pets

– The Dane drinks tea

– The green house is on the left of the white house

– The green homeowner drinks coffee

– The man who smokes Pall Mall keeps birds

– The owner of the yellow house smokes Dunhill

– The man living in the center house drinks milk

– The Norwegian lives in the first house

– The man who smokes Blend lives next to the one who keeps cats

– The man who keeps the horse lives next to the man who smokes Dunhill

– The man who smokes Bluemaster drinks beer

– The German smokes Prince

– The Norwegian lives next to the blue house

– The man who smokes Blend has a neighbor who drinks water

• And a question: – Who owns the fish?

Page 163: groovy DSLs from beginner to expert

Einstein‟s Riddle : Prolog

© A

SE

RT

200

6-2

010

% 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). % 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 - 163

Page 164: groovy DSLs from beginner to expert

Einstein‟s Riddle DSL …

DSLs 2010 - 164

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Date: Early morning Dear Paul, Thanks for your Prolog solution but we don’t have Prolog installed. Do you have a version that runs on the JVM? Thanks, Happy Customer

Page 165: groovy DSLs from beginner to expert

Einstein‟s Riddle : Polyglot

© A

SE

RT

200

6-2

010

@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).") //println sol.solution.get('Persons') // jlog to avoid converter println sol.get('Persons') // jtrolog/tuProlog

DSLs 2010 - 165

Page 166: groovy DSLs from beginner to expert

Einstein‟s Riddle DSL …

DSLs 2010 - 166

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Date: Early morning Dear Paul, Thanks for that version but in terms of maintaining the rules, we would like a more fluent expression of the rules rather than Prolog? Any thoughts? Thanks, Happy Customer

Page 167: groovy DSLs from beginner to expert

Einstein‟s Riddle : Polyglot w/ DSL…

© A

SE

RT

200

6-2

010

// 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'

DSLs 2010 - 167

// 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). ''')

Page 168: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Polyglot w/ DSL…

© A

SE

RT

200

6-2

010

DSLs 2010 - 168

// 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) { 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++ } ...

Page 169: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Polyglot w/ DSL…

© A

SE

RT

200

6-2

010

DSLs 2010 - 169

// 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 -> [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]) } ] } }]} ] } ...

Page 170: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Polyglot w/ DSL…

© A

SE

RT

200

6-2

010

DSLs 2010 - 170

// 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 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

Page 171: groovy DSLs from beginner to expert

Einstein‟s Riddle DSL …

DSLs 2010 - 171

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Date: Early morning Dear Paul, That’s great. We even get some code completion When using an IDE. Is there anything more we can do to get more completion? Thanks, Happy Customer

Page 172: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Polyglot w/ DSL…

© A

SE

RT

200

6-2

010

DSLs 2010 - 172

// 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]) }, ... ] } ...

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])

Page 173: groovy DSLs from beginner to expert

…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:

• But other parts are not known, e.g. the

word „house‟ in the fragment below:

DSLs 2010 - 173

© A

SE

RT

200

6-2

010

„house‟ is key for a Map and could be any value

Page 174: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Polyglot w/ DSL

© A

SE

RT

200

6-2

010

DSLs 2010 - 174

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) }

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

„house‟ is now understood

Page 175: groovy DSLs from beginner to expert

Einstein‟s Riddle DSL …

DSLs 2010 - 175

© A

SE

RT

200

6-2

010

From: [email protected] To: Paul King Subject: Project Request Date: Early morning Dear Paul, That’s fantastic! But we have just started standardizing on Choco as our logic solving engine. I guess we need to start from scratch. Let me know what you think. Thanks, Happy Customer

Page 176: groovy DSLs from beginner to expert

Einstein‟s Riddle : Choco w/ DSL…

© A

SE

RT

200

6-2

010

DSLs 2010 - 176

@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) } 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) } ...

Page 177: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Choco w/ DSL…

© A

SE

RT

200

6-2

010

DSLs 2010 - 177

// 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{ 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)}] }, ...

Page 178: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Choco w/ DSL…

© A

SE

RT

200

6-2

010

DSLs 2010 - 178

// 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 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 ...

Page 179: groovy DSLs from beginner to expert

…Einstein‟s Riddle : Choco w/ DSL

• Output:

DSLs 2010 - 179

© A

SE

RT

200

6-2

010

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) 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() }

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

Page 180: groovy DSLs from beginner to expert

FRIDAY

DSLs 2010 - 180

© A

SE

RT

200

6-2

010

http://t3.gstatic.com/images?q=tbn:mgkj1lXpQ2-uWM:http://i298.photobucket.com/albums/mm269/bearyjuicylicious/comments/FRIDAY-CAT.gif&t=1

Page 181: groovy DSLs from beginner to expert

Important business meetings all afternoon!

DSLs 2010 - 181

© A

SE

RT

200

6-2

010

http://www.flickr.com/photos/bobswanson/5093775403/sizes/l/in/pool-52240148330@N01/

Page 182: groovy DSLs from beginner to expert

Topics

• Introduction

• Groovy DSL Features

• A Week In The Life Of DSL INC

Summary

• More Info

DSLs 2010 - 182

© A

SE

RT

200

6-2

010

Page 183: groovy DSLs from beginner to expert

Some Finishing Guidelines

• Language and program evolve together …

In the end your program will look as if the

language had been designed for it … you

end up with code which is clear, small,

and efficient.

– Paul Graham

• Don‟t think just about Coding up your DSL

– But also about its target audience, its evolution,

required tool support and its testability

DSLs 2010 - 183

Page 184: groovy DSLs from beginner to expert

Start small, with key concepts Beware over-engineering!

Page 185: groovy DSLs from beginner to expert

Grow your language progressively

Page 186: groovy DSLs from beginner to expert

Get your hands dirty Play with the end-users

Page 187: groovy DSLs from beginner to expert

Let your DSL fly, it‟s not yours, it‟s theirs!

Page 188: groovy DSLs from beginner to expert

Tight feedback loop Iterative process

Page 189: groovy DSLs from beginner to expert

Stay humble, You can‟t get it right the 1st time.

Don‟t design alone at your desk Involve the end users from the start

Page 190: groovy DSLs from beginner to expert

Playing it safe in a sandbox

Page 191: groovy DSLs from beginner to expert

Topics

• Introduction

• Groovy DSL Features

• A Week In The Life Of DSL INC

• Summary

More Info

DSLs 2010 - 191

© A

SE

RT

200

6-2

010

Page 192: groovy DSLs from beginner to expert

Further Info…

DSLs 2010 - 192

© A

SE

RT

200

6-2

010

• 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

• Language Implementation Patterns :

Create Your Own Domain-Specific and

General Programming Languages,

Terence Parr

Page 193: groovy DSLs from beginner to expert

…Further Info

DSLs 2010 - 193

© A

SE

RT

200

6-2

010

• 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

• 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

Page 194: groovy DSLs from beginner to expert

GinA 2ed “ReGinA” is coming!

DSLs 2010 - 194

Contains a

chapter on

DSLs!