Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - SpringOne2GX 2011

24,720 views

Published on

Domain-Specific Languages with Groovy

Published in: Technology

Groovy DSLs, from Beginner to Expert - Guillaume Laforge and Paul King - SpringOne2GX 2011

  1. 1. Groovy.DSLs(from: beginner, to: expert)Paul KingCore Groovy CommitterASERT DirectorGuillaume LaforgeGroovy Project ManagerSpringSource, a division of VMware© 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  2. 2. Groovy.DSLs(from: beginner, to: expert)Paul King 2 011Core Groovy Committer E ditionASERT DirectorGuillaume LaforgeGroovy Project ManagerSpringSource, a division of VMware© 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  3. 3. Guillaume Laforge• Groovy Project Manager at VMware • Initiator of the Grails framework • Creator of the Gaelyk toolkit• Co-author of Groovy in Action• Speaking worldwide w/ a French accent• Follow me on... • My blog: http://glaforge.appspot.com • Twitter: @glaforge • Google+: http://gplus.to/glaforge2 @glaforge — @paulk_asert
  4. 4. Guillaume Laforge• Groovy Project Manager at VMware • Initiator of the Grails framework • Creator of the Gaelyk toolkit• Co-author of Groovy in Action• Speaking worldwide w/ a French accent• Follow me on... • My blog: http://glaforge.appspot.com • Twitter: @glaforge • Google+: http://gplus.to/glaforge2 @glaforge — @paulk_asert
  5. 5. Paul King• Core Groovy Committer• Co-author of Groovy in Action• Leads ASERT, a Brisbane, Australia, company providing software development, training & mentoring support• International speaker• Follow me on • Website: http://www.asert.com.au • Twitter: @paulk_asert3 @glaforge — @paulk_asert
  6. 6. Domain-Specific Languages• Wikipedia definition – 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 to General Purprose Languages• Somewhere between declarative data and GPLs• Also known as: fluent / human interfaces, language oriented programming, little or mini languages, macros, business4 @glaforge — @paulk_asert
  7. 7. Goals of Domain-Specific Languages• Use a more expressive language than a general-purpose one• Share a common metaphore of understanding between develoeprs 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 thanks to a clean seperation5 @glaforge — @paulk_asert
  8. 8. Antimalaria drug Insurance policy risk HR skillsresistance calculation engine representationsimulation Nuclear safety simulationsMarket data feeds analysis Loan acceptance rules engine
  9. 9. Technical examples Glade XSLT <?xml version="1.0"?> <?xml version="1.0"?> <GTK-Interface> <xsl:stylesheetversion="1.0" <widget> xmlns:xsl="http://www.w3.org/1999/XSL/Transform"> <class>GtkWindow</class> <xsl:output method="xml"/> <name>HelloWindow</name> <xsl:template match="*"> <border_width>5</border_width> <xsl:element name="{name()}"> <Signal> <xsl:for-each select="@*"> Regex <xsl:element name="{name()}"> "x.z?z{1,3}y" <name>destroy</name> <handler>gtk_main_quit</handler> <xsl:value-of select="."/> </Signal> </xsl:element> <title>Hello</title> </xsl:for-each> <type>GTK_WINDOW_TOPLEVEL</type> <xsl:apply-templates select="*|text()"/> <position>GTK_WIN_POS_NONE</position> </xsl:element> <allow_shrink>True</allow_shrink> </xsl:template> <allow_grow>True</allow_grow> </xsl:stylesheet> <auto_shrink>False</auto_shrink> <widget> <class>GtkButton</class> fetchmail <name>Hello World</name> <can_focus>True</can_focus> # Poll this site first each cycle. <label>Hello World</label> poll pop.provider.net proto pop3 </widget> user "jsmith" with pass "secret1" is "smith" here </widget> user jones with pass "secret2" is "jjones" here with options </GTK-Interface> keep SQL # Poll this site second, unless Lord Voldemort zaps us first. poll billywig.hogwarts.com with proto imap: SELECT * FROM TABLE user harry_potter with pass "floo" is harry_potter here WHERE NAME LIKE %SMI # Poll this site third in the cycle. ORDER BY NAME # Password will be fetched from ~/.netrc poll mailhost.net with proto imap: user esr is esr here Troff cat thesis.ms | chem | tbl | refer | grap | pic | eqn | groff -Tps > thesis.ps 8 Source: Applying minilanguages: http://www.faqs.org/docs/artu/ch08s02.html @glaforge — @paulk_asert
  10. 10. Pros and cons of DSLs• Pros – Safety; as long as the – Domain experts can help, language constructs are safe, validate, modify, and often any DSL sentence can be develop DSL programs considered safe – Somewhat self-documenting – Enhance quality, • Cons productivity, reliability, – Learning cost vs. limited maintainability, portability, applicability reusability – Cost of designing, implementing & maintaining DSLs as well as tools/IDEs – Attaining proper scope8 @glaforge — @paulk_asert
  11. 11. • The flexible nature of the language – method call and syntax conventions – native syntax constructs – adding properties to numbers – command chain expressions – operator overloading – hierarchical structures with builders – AST transformations9 © 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  12. 12. Scripts vs classes • Hide all the boilerplate technical code – an end-user doesn’t need to know about classes10 @glaforge — @paulk_asert
  13. 13. Scripts vs classes • Hide all the boilerplate technical code – an end-user doesn’t need to know about classes public
class
Rule
{ 



public
static
void
main(String[]
args)
{ 







System.out.println("Hello"); 



} }10 @glaforge — @paulk_asert
  14. 14. Scripts vs classes • Hide all the boilerplate technical code – an end-user doesn’t need to know about classes public
class
Rule
{ 



public
static
void
main(String[]
args)
{ 







System.out.println("Hello"); 



} } println
"Hello"10 @glaforge — @paulk_asert
  15. 15. Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire11 @glaforge — @paulk_asert
  16. 16. Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire//
given
this
API
methodpublic
Rate<LoanType,
Duration,
BigDecimal>[]
lookupTable()
{
...
}11 @glaforge — @paulk_asert
  17. 17. Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire//
given
this
API
methodpublic
Rate<LoanType,
Duration,
BigDecimal>[]
lookupTable()
{
...
} //
verbose
Java
notation Rate<LoanType,
Duration,
BigDecimal>[]
table
=
lookupTable();11 @glaforge — @paulk_asert
  18. 18. Optional typing • No need to bother with types or even generics – unless you want to! – but strongly typed if you so desire//
given
this
API
methodpublic
Rate<LoanType,
Duration,
BigDecimal>[]
lookupTable()
{
...
} //
verbose
Java
notation Rate<LoanType,
Duration,
BigDecimal>[]
table
=
lookupTable(); //
leaner
Groovy
variant def
table
=
lookupTable()11 @glaforge — @paulk_asert
  19. 19. Native syntax constructs12 @glaforge — @paulk_asert
  20. 20. Native syntax constructs //
Lists12 @glaforge — @paulk_asert
  21. 21. Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday]12 @glaforge — @paulk_asert
  22. 22. Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps12 @glaforge — @paulk_asert
  23. 23. Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas]12 @glaforge — @paulk_asert
  24. 24. Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas] //
Ranges
(you
can
create
your
own)12 @glaforge — @paulk_asert
  25. 25. Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas] //
Ranges
(you
can
create
your
own) def
bizDays
=
Monday..Friday12 @glaforge — @paulk_asert
  26. 26. Native syntax constructs //
Lists def
days
=
[Monday,
Tuesday,
Wednesday] //
Maps def
states
=
[CA:
California,
TX:
Texas] //
Ranges
(you
can
create
your
own) def
bizDays
=
Monday..Friday def
allowedAge
=
18..6512 @glaforge — @paulk_asert
  27. 27. Optional parens & semis• Make statements and expressions look more like natural languages13 @glaforge — @paulk_asert
  28. 28. Optional parens & semis• Make statements and expressions look more like natural languages move(left);13 @glaforge — @paulk_asert
  29. 29. Optional parens & semis• Make statements and expressions look more like natural languages move(left); move
left13 @glaforge — @paulk_asert
  30. 30. Adding properties to numbers• Several approaches to adding properties to numbers – through a category class
PillsCategory
{ 



static
getPills(Number
n)
{
n
} } use(PillsCategory)
{ 



2.pills









//
2.getPills() } – through ExpandoMetaClass Number.metaClass.getPills
{
‐>
delegate
} 2.pills
















//
2.getPills()14 @glaforge — @paulk_asert
  31. 31. Named arguments• In Groovy you can mix named and unnamed arguments for method parameters – named params are actually put in a map parameter – plus optional parens & semis 

take
2.pills,
 


of:
chloroquinine,
 after:
6.hours //
Calls
a
method
signature
like: def
take(Map
m,
MedicineQuantity
mq)15 @glaforge — @paulk_asert
  32. 32. Command chain expressions16 @glaforge — @paulk_asert
  33. 33. Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println16 @glaforge — @paulk_asert
  34. 34. Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println16 @glaforge — @paulk_asert
  35. 35. Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println• Less dots, less parens allow you to – write more readable business rules – in almost plain English sentences • (or any language, of course)16 @glaforge — @paulk_asert
  36. 36. Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println• Less dots, less parens allow you to – write more readable business rules – in almost plain English sentences • (or any language, of course)16 @glaforge — @paulk_asert
  37. 37. Command chain expressions• A grammar improvement allowing you to drop dots & parens when chaining method calls – an extended version of top-level statements like println• Less dots, less parens allow you to – write more readable business rules – in almost plain English sentences • (or any language, of course)• Let’s have a look at some examples16 @glaforge — @paulk_asert
  38. 38. Command chain expressions 
turn
left

then
right
17 @glaforge — @paulk_asert
  39. 39. Command chain expressions Alternation of method names 
turn
left

then
right
17 @glaforge — @paulk_asert
  40. 40. Command chain expressions Alternation of method names 
turn
left

then
right
 and parameters (even named ones)17 @glaforge — @paulk_asert
  41. 41. Command chain expressions 
turn
left

then
right
17 @glaforge — @paulk_asert
  42. 42. Command chain expressions Equivalent to: 




(



).



(




) 
turn
left

then
right
17 @glaforge — @paulk_asert
  43. 43. LookM a!N op are ns,no do ts!
  44. 44. Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours19 @glaforge — @paulk_asert
  45. 45. Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours Normal argument19 @glaforge — @paulk_asert
  46. 46. Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours Normal argument Named arguments19 @glaforge — @paulk_asert
  47. 47. Command chain expressions Before... we used to do... take
2.pills,
of:
chloroquinine,
after:
6.hours Normal argument Named arguments Woud call: def
take(Map
m,
Quantity
q)19 @glaforge — @paulk_asert
  48. 48. Command chain expressions Now, even less punctuation! take
2.pills

of

chloroquinine

after

6.hours20 @glaforge — @paulk_asert
  49. 49. Command chain expressions Now, even less punctuation! 



(






).


(












).





(






) take
2.pills

of

chloroquinine

after

6.hours20 @glaforge — @paulk_asert
  50. 50. Command chain expressions //
environment
initialization Integer.metaClass.getPills
{
‐>
delegate
} Integer.metaClass.getHours
{
‐>
delegate
} 
 //
variable
injection def
chloroquinine
=
/*...*/ 
 { } //
implementing
the
DSL
logic def
take(n)
{ 



[of:
{
drug
‐> 







[after:
{
time
‐>
/*...*/
}] 



}] } //
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ take
2.pills
of
chloroquinine
after
6.hours21 @glaforge — @paulk_asert
  51. 51. Command chain expressions take
2.pills

of

chloroquinine

after

6.hours22 @glaforge — @paulk_asert
  52. 52. Command chain expressions take
2.pills

of

chloroquinine

after

6.hours ... some dots remain ...22 @glaforge — @paulk_asert
  53. 53. Command chain expressions Yes, we can... get rid of them :-) take
2

pills
of

chloroquinine

after

6
hours23 @glaforge — @paulk_asert
  54. 54. Command chain expressions Yes, we can... get rid of them :-) 



(
).




(

).













(




).
(




) take
2

pills
of

chloroquinine

after

6
hours23 @glaforge — @paulk_asert
  55. 55. Command chain expressions //
variable
injection def
(of,
after,
hours)
=
/*...*/ 
 //
implementing
the
DSL
logic { } def
take(n)
{ 



[pills:
{
of
‐> 







[chloroquinine:
{
after
‐> 











[6:
{
time
‐>
}] 







}] 



}] } //
‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐‐ take
2
pills
of
chloroquinine
after
6
hours24 @glaforge — @paulk_asert
  56. 56. Command chain expressions @glaforge — @paulk_asert 25
  57. 57. Command chain expressions //
methods
with
multiple
arguments
(commas) @glaforge — @paulk_asert 25
  58. 58. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor @glaforge — @paulk_asert 25
  59. 59. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation @glaforge — @paulk_asert 25
  60. 60. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good @glaforge — @paulk_asert 25
  61. 61. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures @glaforge — @paulk_asert 25
  62. 62. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} @glaforge — @paulk_asert 25
  63. 63. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens @glaforge — @paulk_asert 25
  64. 64. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names @glaforge — @paulk_asert 25
  65. 65. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms @glaforge — @paulk_asert 25
  66. 66. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  67. 67. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  68. 68. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  69. 69. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} 




(

).



(

).



(

) //
zero‐arg
methods
require
parens select
all

unique()
from
names //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  70. 70. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} 




(

).



(

).



(

) //
zero‐arg
methods
require
parens select
all

unique()
from
names 





(


).







.



(




) //
possible
with
an
odd
number
of
terms take
3

cookies @glaforge — @paulk_asert 25
  71. 71. Command chain expressions //
methods
with
multiple
arguments
(commas) take
coffee

with
sugar,
milk

and
liquor 



(





).



(










).


(





) //
leverage
named‐args
as
punctuation check
that:
margarita

tastes
good 




(














).





(



) //
closure
parameters
for
new
control
structures given
{}

when
{}

then
{} 




(

).



(

).



(

) //
zero‐arg
methods
require
parens select
all

unique()
from
names 





(


).







.



(




) //
possible
with
an
odd
number
of
terms take
3

cookies 



(
). @glaforge — @paulk_asert 25
  72. 72. Operator overloadinga
+
b

//
a.plus(b)a
‐
b

//
a.minus(b) • Currency amountsa
*
b

//
a.multiply(b) – 15.euros + 10.dollarsa
/
b

//
a.divide(b)a
%
b

//
a.modulo(b) • Distance handlinga
**
b
//
a.power(b)a
|
b

//
a.or(b) – 10.kilometers - 10.metersa
&
b

//
a.and(b)a
^
b

//
a.xor(b) • Workflow, concurrencya[b]


//
a.getAt(b) – taskA | taskB & taskCa
<<
b
//
a.leftShift(b)a
>>
b
//
a.rightShift(b)+a




//
a.unaryPlus() • Credit an account‐a




//
a.unaryMinus() – account << 10.dollars~a




//
a.bitwiseNegate() account += 10.dollars26 @glaforge — @paulk_asert
  73. 73. Builders• For hierachical structures, extend: – BuilderSupport – FactoryBuilderSupport • nice for extending builders with new nodes and attributes import
groovy.xml.* 
• Roll your own.. def
builder
=
new
MarkupBuilder() builder.html
{ – with invokeMethod() 



head
{
title
"Chicago!"
} or methodMissing() 



body
{ 







div(id:
"main")
{ – with get/setProperty() 











p
"Groovy
rocks!" 







} or propertyMissing() 



} }27 @glaforge — @paulk_asert
  74. 74. AST Transformations• Hook into the compiler process to do compile-time metaprogramming by modifying the Abstract Syntax Tree Transformation28 @glaforge — @paulk_asert
  75. 75. @InheritConstructors• Classes like Exception are painful when extended, as all the base constructors should be replicated class
CustomException
extends
Exception
{ 



CustomException()























{
super()






} 



CustomException(String
msg)













{
super(msg)



} 



CustomException(String
msg,
Throwable
t)
{
super(msg,
t)
} 



CustomException(Throwable
t)












{
super(t)





} }29 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  76. 76. @InheritConstructors• Classes like Exception are painful when extended, as all the base constructors should be replicated import
groovy.transform.* @InheritConstructors class
CustomException
extends
Exception
{ 



CustomException()























{
super()






} 



CustomException(String
msg)













{
super(msg)



} 



CustomException(String
msg,
Throwable
t)
{
super(msg,
t)
} 



CustomException(Throwable
t)












{
super(t)





} }29 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  77. 77. @InheritConstructor... under the covers//
...ClassNode
sNode
=
cNode.getSuperClass();










for
(ConstructorNode
cn
:
sNode.getDeclaredConstructors())
{



Parameter[]
params
=
cn.getParameters();



if
(cn.isPrivate())
continue;



Parameter[]
newParams
=
new
Parameter[params.length];



List<Expression>
args
=
copyParamsToArgs(params,
newParams);



if
(isClashing(cNode,
newParams))
continue;



BlockStatement
body
=
new
BlockStatement();



ConstructorCallExpression
callArgs
=







new
ConstructorCallExpression(ClassNode.SUPER,






































new
ArgumentListExpression(args));



body.addStatement(new
ExpressionStatement(callArgs));



cNode.addConstructor(cn.getModifiers(),
newParams,

























cn.getExceptions(),
body);}//
...30 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  78. 78. Advanced example with Spockdef
"length
of
Spocks
&
his
friends
names"()
{



expect:







name.size()
==
length



where:







name




|
length







"Spock"

|
5







"Kirk"


|
4







"Scotty"
|
6}31 @glaforge — http://glaforge.appspot.com — http://gplus.to/glaforge
  79. 79. • A week in the life of DSL inc. – find the examples on GitHub: https://github.com/paulk-asert/ DSLsFromBeginnerToExpert32 © 2011 SpringOne 2GX 2011. All rights reserved. Do not distribute without permission.
  80. 80. MONDAY33 @glaforge — @paulk_asert
  81. 81. Currency DSL… From:



customer@acme.org To:





Guillaume Subject:
Project
Request Dear
Guillaume, We
would
like
a
DSL
for
capturing
currency
 expressions.
Just
US
currency
to
start
with. Thanks,
Happy
Customer34 @glaforge — @paulk_asert
  82. 82. …Currency DSL... 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
==
5735 @glaforge — @paulk_asert
  83. 83. …Currency DSL… From:



customer@acme.org 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
Customer36 @glaforge — @paulk_asert
  84. 84. ...Currency DSL...//
Categoryclass
CoinMath
{



static
multiply(Integer
self,
Coin
c)
{







self
*
c.value



}}
use
(CoinMath)
{



assert
2
*
quarter
+










1
*
nickel
+










2
*
penny
==
57}37 @glaforge — @paulk_asert
  85. 85. ...Currency DSL...//
Categoryclass
CoinMath
{



static
multiply(Integer
self,
Coin
c)
{







self
*
c.value



}}
use
(CoinMath)
{



assert
2
*
quarter
+










1
*
nickel
+ //
EMC
equivalent










2
*
penny
==
57 Integer.metaClass.multiply
=
{} 



Coin
c
‐>
delegate
*
c.value } assert
2
*
quarter
+
 






1
*
nickel
+
 






2
*
penny
==
5737 @glaforge — @paulk_asert
  86. 86. …Currency DSL… From:



customer@acme.org 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
Customer38 @glaforge — @paulk_asert
  87. 87. ... Currency DSLclass
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}39 @glaforge — @paulk_asert
  88. 88. ... Currency DSLclass
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
==
5739 @glaforge — @paulk_asert
  89. 89. TUESDAYhttp://www.google.com/imgres?imgurl=http://i733.photobucket.com/albums/ww336/snake1953/Day_of_Week/Tue/HappyTuesda 40 @glaforge — @paulk_asert%3D1%26hl%3Den%26client%3Dfirefox-a%26sa%3DN%26rls%3Dorg.mozilla:en-US:official%26biw%3D1488%26bih%3D852%
  90. 90. Business logic DSL From:



customer@acme.org To:





Paul
King Subject:
Project
Request Dear
Paul, Could
you
please
create
us
a
small
DSL
for
capturing our
business
rules.
We
are
thinking
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.41 @glaforge — @paulk_asert
  91. 91. Business logic DSL• Options – Embedded Groovy – Regex parser – Antlr parser – Split / StringTokenizer – Parser combinators42 @glaforge — @paulk_asert
  92. 92. Business logic DSL From:



customer@acme.org 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.43 @glaforge — @paulk_asert
  93. 93. Business logic DSL def
shell
=
new
GroovyShell() shell.evaluate( 



price
=
3 



quantity
=
10 



total
=
price
*
quantity 



assert
total
==
30 )44 @glaforge — @paulk_asert
  94. 94. Business logic DSL From:



customer@acme.org 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.45 @glaforge — @paulk_asert
  95. 95. Business logic DSL 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.") 


} 


//
... } 46 @glaforge — @paulk_asertPlease also see the ArithmeticShell which is included under examples in the Groovy distribution
  96. 96. Business logic DSL def
shell
=
new
CustomerShell() shell.evaluate( 



price
=
3 



quantity
=
10 



total
=
price
*
quantity 



assert
total
==
30 )47 @glaforge — @paulk_asert
  97. 97. WEDNESDAY 48 @glaforge — @paulk_aserthttp://www.crystalscomments.com/1/days/wednesday/animals/006.jpg
  98. 98. Stock Exchange Order DSL From:



customer@finance‐broker.org 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
CustomerExample inspiredfrom «DSLs in Action»:http://www.manning.com/ghosh/ 49 @glaforge — @paulk_asert
  99. 99. //
‐‐‐‐‐
Implementation
of
the
Fluent
API
‐‐‐‐‐… Stock Exchange Order DSL…enum
Action
{
Buy,
Sell
}
 println
new
Order()class
Order
{ 



.sell(150,
"IBM")



def
security



def
quantity,
limitPrice 



.limitPrice(300)



boolean
allOrNone



def
valueCalculation 



.allOrNone(true)



Action
action 



.valueAs
{
qty,
unitPrice
‐>




def
buy(Integer
quantity,
String
security)
{ 








qty
*
unitPrice
‐
100
}







this.quantity
=
quantity 








this.security
=
security







this.action
=
Action.Buy println
new
Order()







return
this 



.buy(200,
"GOOG")



}



def
sell(Integer
quantity,
String
security)
{ 



.limitPrice(200)







this.quantity
=
quantity 



.allOrNone(true)







this.security
=
security







this.action
=
Action.Sell 



.valueAs{
qty,
unitPrice
‐>







return
this 







qty
*
unitPrice
‐
500
}



}



def
limitPrice(Integer
limit)
{







this.limitPrice
=
limit;
return
this



} Sell 150 shares of IBM at valuation of 44900



def
allOrNone(boolean
allOrNone)
{







this.allOrNone
=
allOrNone;
return
this Buy 200 shares of GOOG at valuation of 39500



}



def
valueAs(Closure
valueCalculation)
{







this.valueCalculation
=
valueCalculation;
return
this



}



String
toString()
{







"$action
$quantity
shares
of
$security
at
valuation
of
${valueCalculation(quantity,
limitPrice)}"



}}50 @glaforge — @paulk_asert
  100. 100. Stock Exchange Order DSL From:



customer@finance‐broker.org 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
Customer51 @glaforge — @paulk_asert
  101. 101. Stock Exchange Order DSL class
Order
{ 



def
security,
quantity,
limitPrice,
allOrNone,
value,
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]
}52 @glaforge — @paulk_asert
  102. 102. Stock Exchange Order DSL 



def
getTo()
{
this
} newOrder.to.buy(100.shares.of(IBM))
{ 

 



limitPrice

300 



def
valueAs(closure)
{ 



allOrNone


true 







value
=
closure(quantity,
limitPrice) 



valueAs




{
qty,
unitPrice
‐> 



} 







qty
*
unitPrice
‐
200
} } 
 } class
Security
{ 
 



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



limitPrice

200 
 class
Quantity
{ 



allOrNone


false 



Security
security 



valueAs




{
qty,
unitPrice
‐> 



Integer
quantity 







qty
*
unitPrice
‐
500
} } } 
 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,
its
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()53 @glaforge — @paulk_asert
  103. 103. Stock Exchange Order DSL From:



customer@finance‐broker.org 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
Customer54 @glaforge — @paulk_asert
  104. 104. Stock Exchange Order DSL newOrder.to.buy(100.shares.of(IBM))
{class
Order
{ 



limitPrice

300... 



allOrNone


true



def
allOrNone,
valueCalculation,
bs 



valueAs




{
qty
*
unitPrice
‐
200
}...



private
buySell(securityQuantity,
closure)
{ }... 








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







//
debug
print
resulting
order 



limitPrice

200







println
toString() 



allOrNone


false







return
this 



valueAs




{
qty
*
unitPrice
‐
500
}



} }...



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



}...}55 @glaforge — @paulk_asert
  105. 105. Stock Exchange Order DSL From:



customer@finance‐broker.org 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
Customer56 @glaforge — @paulk_asert
  106. 106. Stock Exchange Order DSL 



//
Characteristics
of
the
order:
"of
GOOG
{...}" 



def
of(SecurityAndCharacteristics
secAndCharact)
{ 







security
=
secAndCharact.security 







def
c
=
secAndCharact.characteristics.clone() 







c.delegationStrategy
=
Closure.DELEGATE_ONLY 







c.delegate
=
this 







c() 







//
debug
print
of
the
resulting
order 







println
toString() 







return
this 



} 
 




//
Valuation
closure:
"of
{
qty,
unitPrice
‐>
...
}" 




def
of(Closure
valueCalculation)
{ 








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



} }
57 @glaforge — @paulk_asert http://groovyconsole.appspot.com/script/226001 by glaforge
  107. 107. Stock Exchange Order DSL class
Security
{ 



String
name } 
 class
SecurityAndCharacteristics
{ 



Security
security 



Closure
characteristics } 
 class
CustomBinding
extends
Binding
{ 



def
getVariable(String
word)
{ 







//
return
System.out
when
the
script
requests
to
write
to
out 







if
(word
==
"out")
System.out 







//
dont
throw
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
}58 @glaforge — @paulk_asert
  108. 108. Stock Exchange Order 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
} }59 http://groovyconsole.appspot.com/script/226001 @glaforge — @paulk_asert by glaforge
  109. 109. Stock Exchange Order DSL From:



customer@finance‐broker.org To:





Guillaume Subject:
Project
Request Date:



Wednesday
evening Dear
Guillaume, Brilliant!
Even
our
CEO
could
write
orders! Thanks,
Happy
Customer60 @glaforge — @paulk_asert
  110. 110. THURSDAY 61 @glaforge — @paulk_aserthttp://t1.gstatic.com/images?q=tbn:_VBloEP8YC-6IM:http://msp248.photobucket.com/albums/gg171/ingrid2002/TextPL252028
  111. 111. Einstein’s Riddle DSL … From:



customer@acme.org 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
Customer62 @glaforge — @paulk_asert
  112. 112. Einstein’s Riddle• Wikipedia: The zebra puzzle is a well-known logic puzzle – It is often called Einsteins Puzzle or Einsteins 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 worlds population can solve it.” – The puzzle is also sometimes attributed to Lewis Carroll. However, there is no known evidence for Einsteins or Carrolls authorship; and the original puzzle cited below mentions brands of cigarette, such as Kools, that did not exist during Carrolls lifetime or Einsteins boyhood63 @glaforge — @paulk_asert
  113. 113. Einstein’s Riddle• 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 house64 @glaforge — @paulk_asert
  114. 114. Einstein’s Riddle• Some premises: – The British person lives in the red house • And a question: – The Swede keeps dogs as pets – Who owns the fish? – 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 house64 @glaforge — @paulk_asert
  115. 115. Einstein’s Riddle : Prolog%
from
http://www.baptiste‐wicht.com/2010/09/solve‐einsteins‐riddle‐using‐prolog%
Preliminary
definitionspersons(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
househint1([(brit,red,_,
_,
_)|_]).hint1([_|T])
:‐
hint1(T).%
The
Swede
keeps
dogs
as
petshint2([(swede,_,_,_,dog)|_]).hint2([_|T])
:‐
hint2(T).%
The
Dane
drinks
teahint3([(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)....65 @glaforge — @paulk_asert
  116. 116. Einstein’s Riddle DSL From:



customer@acme.org 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
Customer66 @glaforge — @paulk_asert
  117. 117. Einstein’s Riddle : Polyglot@GrabResolver(https://oss.sonatype.org/content/repositories/snapshots/)@Grab(org.prolog4j:prolog4j‐api:0.2.1‐SNAPSHOT)@Grab(org.prolog4j:prolog4j‐tuprolog:0.2.1‐SNAPSHOT)import
org.prolog4j.*def
p
=
ProverFactory.proverp.addTheory(new
File(/GroovyExamples/tuProlog/src/einstein.pl).text)def
sol
=
p.solve("solution(Persons).")println
sol.get(Persons)67 @glaforge — @paulk_asert
  118. 118. Einstein’s Riddle DSL … From:



customer@acme.org 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
Customer68 @glaforge — @paulk_asert
  119. 119. Einstein’s Riddle : Polyglot w/ DSL //
define
some
domain
classes
and
objects enum
Pet
{
dog,
cat,
bird,
fish,
horse
} enum
Color
{
green,
white,
red,
blue,
yellow
} enum
Smoke
{
dunhill,
blends,
pallmall,
prince,
bluemaster
} enum
Drink
{
water,
tea,
milk,
coffee,
beer
} enum
Nationality
{
Norwegian,
Dane,
Brit,
German,
Swede
} 
 dogs
=
dog;
birds
=
bird;
cats
=
cat;
horses
=
horse a
=
owner
=
house
=
the
=
abode
=
person
=
man
=
is
=
to
= 



side
=
next
=
who
=
different
=
ignored //
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). )69 @glaforge — @paulk_asert
  120. 120. Einstein’s Riddle : Polyglot w/ DSL… //
define
some
helper
methods
(our
interface
to
prolog)
 def
addPairHint(Map
m)
{ 



def
from
=
m.from?.toString()?.toLowerCase() 



p.addTheory(""" 



hint$hintNum([(${from
?:
_},${m.color
?:
_},${m.drink
?:
_}, 







${m.smoke
?:
_},${m.pet
?:
_})|_]). 



hint$hintNum([_|T])
:‐
hint$hintNum(T). 



""") 



hintNum++ } 
 def
addPositionHint(Map
m,
int
pos)
{ 



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++ } ...70 @glaforge — @paulk_asert
  121. 121. Einstein’s Riddle : Polyglot w/ DSL //
now
implement
DSL
in
terms
of
helper
methods def
the(Nationality
n)
{ 



def
ctx
=
[from:n] 



[ 







drinks:
{
d
‐>
addPairHint(ctx
+
[drink:d])
}, 







smokes:
{
s
‐>
addPairHint(ctx
+
[smoke:s])
}, 







keeps:
{
p
‐>
addPairHint(ctx
+
[pet:p])
}, 







rears:
{
p
‐>
addPairHint(ctx
+
[pet:p])
}, 







owns:
{
_the
‐>
[first:
{
house
‐>
addPositionHint(ctx,
1)
}]
}, 







has:
{
_a
‐> 











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











} 







}]} 



] } ...71 @glaforge — @paulk_asert
  122. 122. Einstein’s Riddle : Polyglot w/ DSL//
now
define
the
DSLthe
man
from
the
centre
house
drinks
milkthe
Norwegian
owns
the
first
housethe
Dane
drinks
teathe
German
smokes
princethe
Swede
keeps
dogs
//
alternate
ending:
has
a
pet
dogthe
Brit
has
a
red
house

//
alternate
ending:
red
abodethe
owner
of
the
green
house
drinks
coffeethe
owner
of
the
yellow
house
smokes
dunhillthe
person
known
to
smoke
pallmall
rears
birds
//
alternate
end:
keeps
birdsthe
man
known
to
smoke
bluemaster
drinks
beerthe
green
house
is
on
the
left
side
of
the
white
housethe
man
known
to
smoke
blends
lives
next
to
the
one
who
keeps
catsthe
man
known
to
keep
horses
lives
next
to
the
man
who
smokes
dunhillthe
man
known
to
smoke
blends
lives
next
to
the
one
who
drinks
waterthe
Norwegian
lives
next
to
the
blue
house72 @glaforge — @paulk_asert
  123. 123. Einstein’s Riddle DSL From:



customer@acme.org 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
Customer73 @glaforge — @paulk_asert
  124. 124. Einstein’s Riddle : Polyglot w/ DSL //
now
implement
DSL
in
terms
of
helper
methods def
the(Nationality
n)
{ 



def
ctx
=
[from:n] 



[ 







drinks:
{
d
‐>
addPairHint(ctx
+
[drink:d])
}, 







smokes:
{
s
‐>
addPairHint(ctx
+
[smoke:s])
}, 







keeps:
{
p
‐>
addPairHint(ctx
+
[pet:p])
}, 







... 



] } ... the
German
smokes
prince the(German).smokes(prince)n
=
Germanctx
=
[from:
German][drinks:
…,
smokes:
{
s
‐>
addPairHint([from:
German,
smoke:
s])
},
keeps:
…,
… addPairHint([from:
German,
smoke:
] prince]) 74 @glaforge — @paulk_asert
  125. 125. 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: ‘house’ is key for a Map and could be any value75 @glaforge — @paulk_asert
  126. 126. Einstein’s Riddle : Polyglot w/ DSL def
the(Color
c1)
{[ 



house:
{
_is
‐>
[on:
{
_the
‐>
[left:
{
_side
‐>
[of:
{
__the
‐> 







Color.values().collectEntries{
c2
‐>
[c2.toString(),
{
_dummy
‐> 











addToLeftHint([color:c1],
[color:c2]) 







}]} 



}]}]}]} ]} 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)
}76 @glaforge — @paulk_asert ‘house’ is now understood
  127. 127. Einstein’s Riddle DSL From:



customer@acme.org 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
Customer77 @glaforge — @paulk_asert
  128. 128. Einstein’s Riddle : Choco w/ DSL @GrabResolver(http://www.emn.fr/z‐info/choco‐solver/mvn/repository/) @Grab(choco:choco:2.1.1‐SNAPSHOT) import
static
choco.Choco.* import
choco.kernel.model.variables.integer.* 
 def
m
=
new
choco.cp.model.CPModel() m.metaClass.plus
=
{
m.addConstraint(it);
m
} def
s
=
new
choco.cp.solver.CPSolver() choco.Choco.metaClass.static.eq
=
{
c,
v
‐>
delegate.eq(c,
v.ordinal())
} def
makeEnumVar(st,
arr)
{
 



choco.Choco.makeIntVar(st,
0,
arr.size()‐1,
choco.Options.V_ENUM)
} 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) } ...78 @glaforge — @paulk_asert
  129. 129. Einstein’s Riddle : Choco w/ DSL //
define
DSL
(simplistic
non‐refactored
version) def
neighbours(var1,
val1,
var2,
val2)
{ 



and( 







ifOnlyIf(eq(var1[0],
val1),
eq(var2[1],
val2)), 







implies(eq(var1[1],
val1),
or(eq(var2[0],
val2),
eq(var2[2],
val2))), 







implies(eq(var1[2],
val1),
or(eq(var2[1],
val2),
eq(var2[3],
val2))), 







implies(eq(var1[3],
val1),
or(eq(var2[2],
val2),
eq(var2[4],
val2))), 







ifOnlyIf(eq(var1[4],
val1),
eq(var2[3],
val2)) 



) } iff
=
{
e1,
c1,
e2,
c2
‐>
and(*(0..<num).collect{ 



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)}]
}, ...79 @glaforge — @paulk_asert
  130. 130. Einstein’s Riddle : Choco w/ DSL //
define
rules m
+=
all
pets
are
different m
+=
all
colors
are
different m
+=
all
smokes
are
different m
+=
all
drinks
are
different m
+=
all
nations
are
different m
+=
the
man
from
the
centre
house
drinks
milk m
+=
the
Norwegian
owns
the
first
house m
+=
the
Dane
drinks
tea m
+=
the
German
smokes
prince 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 ...80 @glaforge — @paulk_asert
  131. 131. Einstein’s Riddle : Choco w/ DSL def
pretty(s,
c,
arr,
i)
{
 



c.values().find{
it.ordinal()
==
s.getVar(arr[i])?.value
}
 } 
 //
invoke
logic
solver s.read(m) def
more
=
s.solve() while
(more)
{ 



for
(i
in
0..<num)
{ 







print


The













+
pretty(s,
Nationality,
nations,
i) 







print



has
a
pet






+
pretty(s,
Pet,
pets,
i) 







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
Einsteins
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
house81 @glaforge — @paulk_asert
  132. 132. FRIDAY 82 @glaforge — @paulk_aserthttp://t3.gstatic.com/images?q=tbn:mgkj1lXpQ2-uWM:http://i298.photobucket.com/albums/mm269/bearyjuicylicious/comments
  133. 133. Nasa Robot From:



customer@acme.org To:





Guillaume
Laforge Subject:
Project
Request Date:



Early
morning Dear
Guillaume, We’ve
got
a
contract
with
NASA
to
send
a
 rover
on
Mars.
We’d
need
a
DSL
to
lead
 the
robot
on
the
rocky
soil.
Can
you
 help? Thanks,
Happy
Customer83 @glaforge — @paulk_asert
  134. 134. Nasa Robot import
static
Direction.* enum
Direction
{ 



left,
right,
forward,
backward } class
Robot
{ 



void
move(Direction
dir)
{ 







println
"robot
moved
$dir" 



} } Nothing special here. def
robot
=
new
Robot() But can we make it prettier? robot.move
left84 @glaforge — @paulk_asert
  135. 135. Nasa Robotpackage
projectmars.domainimport
static
Direction.*enum
Direction
{
left,
right,
forward,
backward
}class
Robot
{



void
move(Direction
dir)
{ Some simplistic GroovyShell







println
"robot
moved
$dir"



}} package
projectmars.integration def
shell
=
new
GroovyShell(this.class.classLoader) shell.evaluate
 



import
projectmars.domain.Robot 



import
static
projectmars.domain.Direction.* 



 



def
robot
=
new
Robot() 



 



robot.move
left 85 @glaforge — @paulk_asert
  136. 136. …Nasa Robot… package
projectmars.domain import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{ 







println
"robot
moved
$dir" 



} } Inject robot into binding def
binding
=
new
Binding(robot:
new
Robot()) def
shell
=
new
GroovyShell(this.class.classLoader,
binding) shell.evaluate
 



import
static
projectmars.domain.Direction.* 



robot.move
left 86 @glaforge — @paulk_asert
  137. 137. Nasa Robot package
projectmars.domain import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{ 







println
"robot
moved
$dir" 



} } /* Inject directions into binding def
binding
=
new
Binding( 



robot:
new
Robot(), 



left:
Direction.left,
right:
Direction.right, 



forward:
Direction.forward,
backward:
Direction.backward) */ def
binding
=
new
Binding( 



robot:
new
Robot(), 



*:Direction.values().collectEntries
{
[(it.name()):
it]
}) def
shell
=
new
GroovyShell(this.class.classLoader,
binding) shell.evaluate
 



robot.move
left 87 @glaforge — @paulk_asert
  138. 138. …Nasa Robot…package
projectmars.domainimport
org.codehaus.groovy.control.CompilerConfigurationimport
org.codehaus.groovy.control.customizers.*import
static
Direction.*enum
Direction
{
left,
right,
forward,
backward
}class
Robot
{



void
move(Direction
dir)
{
println
"robot
moved
$dir"
}}def
binding
=
new
Binding(robot:
new
Robot()) Inject importdef
importCustomizer
=
new
ImportCustomizer()importCustomizer.addStaticStars
projectmars.domain.Directiondef
config
=
new
CompilerConfiguration()config.addCompilationCustomizers
importCustomizerdef
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config)shell.evaluate




robot.move
left88 @glaforge — @paulk_asert
  139. 139. package
projectmars.domain…Nasa Robot…import
org.codehaus.groovy.control.CompilerConfiguration import
org.codehaus.groovy.control.customizers.* import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{
println
"robot
moved
$dir"
} } def
binding
=
new
Binding(robot:
new
Robot()) def
importCustomizer
=
new
ImportCustomizer() importCustomizer.addStaticStars
projectmars.domain.Direction def
config
=
new
CompilerConfiguration() config.addCompilationCustomizers
importCustomizer def
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config) shell.evaluate
 



move
left 
+
 



def
move(dir)
{ 







robot.move
dir Can we make ‘robot’ implicit? 



} 89 @glaforge — @paulk_asert
  140. 140. package
projectmars.domainNasa Robotimport
org.codehaus.groovy.control.CompilerConfiguration import
org.codehaus.groovy.control.customizers.* import
static
Direction.* enum
Direction
{
left,
right,
forward,
backward
} class
Robot
{ 



void
move(Direction
dir)
{
println
"robot
moved
$dir"
} } def
binding
=
new
Binding(robot:
new
Robot()) def
importCustomizer
=
new
ImportCustomizer() importCustomizer.addStaticStars
Direction.class.name def
config
=
new
CompilerConfiguration() Better ‘robot’ implicit config.addCompilationCustomizers
importCustomizer config.scriptBaseClass
=
RobotBaseScriptClass.class.name def
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config) shell.evaluate
 



move
left abstract
class
RobotBaseScriptClass
extends
Script
{ 



void
move(Direction
dir)
{ 







this.binding.robot.move
dir 



} }90 @glaforge — @paulk_asert
  141. 141. package
projectmars.domainNasa Robotimport
org.codehaus.groovy.control.CompilerConfigurationimport
org.codehaus.groovy.control.customizers.*import
static
Direction.*enum
Direction
{
left,
right,
forward,
backward
}class
Robot
{



void
move(Direction
dir)
{
println
"robot
moved
$dir"
}} Better ‘robot’ implicitdef
robot
=
new
Robot()def
binding
=
new
Binding(robot:
robot,
move:
robot.&move)def
importCustomizer
=
new
ImportCustomizer()importCustomizer.addStaticStars
Direction.class.namedef
config
=
new
CompilerConfiguration()config.addCompilationCustomizers
importCustomizerdef
shell
=
new
GroovyShell(this.class.classLoader,
binding,
config)shell.evaluate




move
left91 @glaforge — @paulk_asert
  142. 142. …Nasa Robot shell.evaluate
 



mOvE
lefT Case insensitive? abstract
class
RobotBaseScriptClass
extends
Script
{ 



@Delegate
@Lazy
Robot
robot
=
this.binding.robot 



 



def
invokeMethod(String
name,
args)
{ 







robot."${name.toLowerCase()}"(*args) 



} } class
CustomBinding
extends
Binding
{ 



private
Map
variables 



 



CustomBinding(Map
vars)
{ 







this.variables
=
[ 











*:vars,
 











*:Direction.values().collectEntries
{
[(it.name()):
it]
} 







] 



} 



 



def
getVariable(String
name)
{ 







variables[name.toLowerCase()] 



}92 } @glaforge — @paulk_asert
  143. 143. package
projectmars.domainNasa Robot import
org.codehaus.groovy.control.CompilerConfiguration import
org.codehaus.groovy.control.customizers.* import
groovy.transform.TupleConstructor import
static
Direction.* import
static
Duration.* enum
Direction
{ 



left,
right,
forward,
backward } enum
Unit
{ 



centimeter
(cm,
0.01), Going further? 



meter





(
m,



1), 



kilometer

(km,
1000)



 



String
abbreviation 



double
multiplier 



 



Unit(String
abbr,
double
mult)
{ 







this.abbreviation
=
abbr 







this.multiplier
=
mult 



} 



 



String
toString()
{
abbreviation
} } …93 @glaforge — @paulk_asert

×