Going to Mars with Groovy Domain-Specific Languages
Upcoming SlideShare
Loading in...5
×
 

Going to Mars with Groovy Domain-Specific Languages

on

  • 16,124 views

Practical example of implementing and evolving a simple DSL in Groovy, to drive a robot rover on the planet Mars.

Practical example of implementing and evolving a simple DSL in Groovy, to drive a robot rover on the planet Mars.

Statistics

Views

Total Views
16,124
Views on SlideShare
13,655
Embed Views
2,469

Actions

Likes
59
Downloads
329
Comments
5

14 Embeds 2,469

http://glaforge.appspot.com 1918
http://d.hatena.ne.jp 364
https://twitter.com 86
http://127.0.0.1 49
http://www.techgig.com 21
http://192.168.0.141 14
http://us-w1.rockmelt.com 8
https://si0.twimg.com 2
http://webcache.googleusercontent.com 2
http://localhost 1
http://www.techgig.timesjobs.com 1
http://translate.googleusercontent.com 1
http://twitter.com 1
https://www.linkedin.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

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

Going to Mars with Groovy Domain-Specific Languages Going to Mars with Groovy Domain-Specific Languages Presentation Transcript

  • GroovyDomainSpecific Guillaume Laforge Groovy Project Manager SpringSource / VMwareLanguages @glaforge 1
  • ГИЙОМЛАФОРЖ
  • Guillaume Laforge• Groovy Project Manager at VMware •Initiator of the Grails framework •Creator of the Gaelyk and Caelyf toolkits• Co-author of Groovy in Action• Follow me on... •My blog: http://glaforge.appspot.com •Twitter: @glaforge •Google+: http://gplus.to/glaforge 3
  • IntroductionDefinition, Examples, Goals, Pros & Cons 4
  • 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 • Also known as: fluent / human interfaces, language oriented programming, little or mini languages, macros, business natural languages... 5
  • Technical examplesGlade 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> <Signal> <xsl:element name="{name()}"> Regex <xsl:for-each select="@*"> <name>destroy</name> <handler>gtk_main_quit</handler> <xsl:element name="{name()}"> <xsl:value-of select="."/> "x.z?z{1,3}y" </Signal> <title>Hello</title> </xsl:element> <type>GTK_WINDOW_TOPLEVEL</type> </xsl:for-each> <position>GTK_WIN_POS_NONE</position> <xsl:apply-templates select="*|text()"/> <allow_shrink>True</allow_shrink> </xsl:element> <allow_grow>True</allow_grow> </xsl:template> <auto_shrink>False</auto_shrink> </xsl:stylesheet> <widget> <class>GtkButton</class> <name>Hello World</name> <can_focus>True</can_focus> fetchmail <label>Hello World</label> # Poll this site first each cycle. </widget> poll pop.provider.net proto pop3</widget> user "jsmith" with pass "secret1" is "smith" here</GTK-Interface> user jones with pass "secret2" is "jjones" here with options keep SQL # Poll this site second, unless Lord Voldemort zaps us first. poll billywig.hogwarts.com with proto imap: user harry_potter with pass "floo" is harry_potter here SELECT * FROM TABLE # Poll this site third in the cycle. WHERE NAME LIKE %SMI # Password will be fetched from ~/.netrc ORDER BY NAME poll mailhost.net with proto imap: user esr is esr here
  • Antimalaria drug Insurance policy risk HR skills representationresistance simulation calculation engine Nuclear safety simulationsMarket data feeds analysis Loan acceptance rules engine
  • Goals of DSLs• 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 thanks to a clean separation• Let business rules have their own lifecycle 8
  • Pros and cons Pros Cons • Domain experts can help, validate, • Learning cost vs. limited applicability modify, and often develop DSL programs • Cost of designing, implementing & maintaining DSLs as well as tools/IDEs • Somewhat self-documenting • Attaining proper scope • Enhance quality, productivity, • Trade-offs between domain specificity reliability, maintainability, portability, and general purpose language reusability constructs • Safety; as long as the language • Efficiency cost constructs are safe, any DSL sentence can be considered safe • Proliferation of similar non-standard DSLs 9
  • Groovy provides...• A flexible and malleable syntax •scripts, native syntax constructs (list, map, ranges), closures, less punctuation...• Compile-time and runtime meta-programming •metaclasses, AST transformations •also operator overloading• The ability to easily integrate into Java / Spring apps •also security and safety 10
  • Let’s get started! 11
  • Your mission: build a DSL for a Mars robot
  • We need a robot! class!Robot!{} 14
  • It should move... class!Robot!{ !!!!void)move()!{} } 15
  • ..in a direction! class!Robot!{ !!!!void)move(String!dir)!{} } 16
  • More explicit direction class!Robot!{ !!!!void)move(Direction!dir)!{} } enum!Direction!{ !!!!left,!right,!forward,!backward } 17
  • Now how can we control it?import)static)mars.Direction.*;import)mars.Robot;public)class)Command!{))))public)static)void)main(String[]!args)!{!!!!!!!!Robot!robot!=)new!Robot();!!!!!!!!robot.move(left);!!!!}} 18
  • Now how can we control it?import)static)mars.Direction.*;import)mars.Robot;public)class)Command!{))))public)static)void)main(String[]!args)!{!!!!!!!!Robot!robot!=)new!Robot();!!!!!!!!robot.move(left);!!!!}} Syntactical noise! 18
  • Now how can we control it?))))))))))))))))))))))))))))))—import)static)mars.Direction.*;)))))))))))))))))—import)mars.Robot;——————————————————————public)class)Command!{))))————————————————————————————————————————))))public)static)void)main(String[]!args)!{))))))))—————))))))))))))))))))))—!!!!!!!!Robot!robot!=)new!Robot();))))))))))))))))))—))))——!!!!!!!!robot.move(left);))))—!!!!}—} Syntactical noise! 18
  • Scripts vs ClassesOptional semicolons & parensimport)static)mars.Direction.*import)mars.Robot!!!!!!!!def))!robot!=)new!Robot()!!!!!!!!robot.move!left 19
  • Scripts vs ClassesOptional semicolons & parensimport)static)mars.Direction.*import)mars.Robot Optional typing!!!!!!!!def))!robot!=)new!Robot()!!!!!!!!robot.move!left 19
  • Scripts vs ClassesOptional semicolons & parensimport)static)mars.Direction.*import)mars.Robot Optional typing!!!!!!!!def))!robot!=)new!Robot()!!!!!!!!robot.move!left But I don’t want to compile a script for every command! 19
  • Integration
  • GroovyShell to the rescue 21
  • GroovyShell to the rescue def$shell%=$new%GroovyShell() shell.evaluate( %%%%new%File("command.groovy") ) 21
  • GroovyShell to the rescue def$shell%=$new%GroovyShell() shell.evaluate( integration.groovy %%%%new%File("command.groovy") ) 21
  • GroovyShell to the rescue def$shell%=$new%GroovyShell() shell.evaluate( integration.groovy %%%%new%File("command.groovy") )import$static$mars.Direction.*import$mars.Robotdef$robot%=$new%Robot()robot.move%left 21
  • GroovyShell to the rescue def$shell%=$new%GroovyShell() shell.evaluate( integration.groovy %%%%new%File("command.groovy") )import$static$mars.Direction.*import$mars.Robot command.groovydef$robot%=$new%Robot()robot.move%left 21
  • Integration mechanisms• Different solutions available: •Groovy’s own mechanisms • GroovyScriptEngine, Eval, GroovyClassLoader, GroovyShell •Java 6: javax.script.* / JSR-223 • Groovy provides a JSR-223 implementation •Spring’s lang namespace• Groovy provides the highest level of flexibility and customization, but JSR-223 is a standard... 22
  • Integration mechanisms• Different solutions available: •Groovy’s own mechanisms • GroovyScriptEngine, Eval, GroovyClassLoader, GroovyShell •Java 6: javax.script.* / JSR-223 • Groovy provides a JSR-223 implementation •Spring’s lang namespace• Groovy provides the highest level of flexibility and customization, but JSR-223 is a standard... 22
  • What’s wrong with our DSL? import)static)mars.Direction.* import)mars.Robot def)robot!=)new!Robot() robot.move!left 23
  • What’s wrong with our DSL? Can’t we hide those imports? import)static)mars.Direction.* import)mars.Robot def)robot!=)new!Robot() robot.move!left 23
  • What’s wrong with our DSL? Can’t we hide those imports? import)static)mars.Direction.* import)mars.Robot def)robot!=)new!Robot() robot.move!left Can’t we inject the robot? 23
  • What’s wrong with our DSL? Can’t we hide those imports? import)static)mars.Direction.* import)mars.Robot def)robot!=)new!Robot() robot.move!left Can’t we inject Do we really need to the robot? repeat ‘robot’? 23
  • I’m sorry Dave,you can’t do that!
  • I’m sorry Dave,you can’t do that!
  • What we really want is... !move!left! 25
  • Let’s inject a robot!• We can pass data in / out of scripts through the Binding •it’s basically just like a map of variable name keys and their associated values 26
  • Let’s inject a robot!• We can pass data in / out of scripts through the Binding •it’s basically just like a map of variable name keys and their associated values def)binding!=)new!Binding([ !!!!robot:!new!Robot() ]) def)shell!=)new!GroovyShell(binding) shell.evaluate( !!!!new!File("command.groovy") ) 26
  • Let’s inject a robot!• We can pass data in / out of scripts through the Binding •it’s basically just like a map of variable name keys and their associated values integration.groovy def)binding!=)new!Binding([ !!!!robot:!new!Robot() ]) def)shell!=)new!GroovyShell(binding) shell.evaluate( !!!!new!File("command.groovy") ) 26
  • Better? import)static)mars.Direction.* robot.move!left 27
  • Better?Robot import removed import)static)mars.Direction.* robot.move!left 27
  • Better?Robot import removed import)static)mars.Direction.* robot.move!left Robot injected, no ‘new’ needed 27
  • How to inject the direction?• Using the binding... def)binding!=)new!Binding([ !!!!robot:!new!Robot(), !!!!left:!!!!!Direction.left, !!!!right:!!!!Direction.right, !!!!backward:!Direction.backward, !!!!forward:!!Direction.forward ]) def)shell!=)new!GroovyShell(binding) shell.evaluate( !!!!new!File("command.groovy") ) 28
  • How to inject the direction? Fragile in case of new directions!• Using the binding... def)binding!=)new!Binding([ !!!!robot:!new!Robot(), !!!!left:!!!!!Direction.left, !!!!right:!!!!Direction.right, !!!!backward:!Direction.backward, !!!!forward:!!Direction.forward ]) def)shell!=)new!GroovyShell(binding) shell.evaluate( !!!!new!File("command.groovy") ) 28
  • How to inject the direction?• Using the binding... def)binding!=)new!Binding([ !!!!robot:!new!Robot(), !!!!*:!Direction.values() !!!!!!!!!!!!.collectEntries!{ !!!!!!!!!!!!!!!![(it.name()):!it] !!!!!!!!!!!!} ]) def)shell!=)new!GroovyShell(binding) shell.evaluate( !!!!new!File("command.groovy") ) 29
  • How to inject the direction?• Using compiler customizers...• Let’s have a look at them 30
  • Compilation customizers• Ability to apply some customization to the Groovy compilation process• Three available customizers •ImportCustomizer: add transparent imports •ASTTransformationCustomizer: injects an AST transform •SecureASTCustomizer: restrict the groovy language to an allowed subset• But you can implement your own 31
  • Imports customizer new!GroovyShell(new!Binding([robot:!new)Robot()])) !!!!.evaluate("import!static!Direction.*n"!+ !!!!!!!!!!!!!!"robot.move!left") 32
  • Imports customizer Cheat with string concatenation? Bad! new!GroovyShell(new!Binding([robot:!new)Robot()])) !!!!.evaluate("import!static!Direction.*n"!+ !!!!!!!!!!!!!!"robot.move!left") 32
  • Imports customizer new!GroovyShell(new!Binding([robot:!new)Robot()])) !!!!.evaluate("import!static!Direction.*n"!+ !!!!!!!!!!!!!!"robot.move!left") 32
  • Imports customizer def!configuration!=!new!CompilerConfiguration() ! def!imports!=!new!ImportCustomizer() imports.addStaticStar(mars.Direction.name) configuration.addCompilationCustomizers(imports)! ! new!GroovyShell(new!Binding([robot:!new)Robot()]),!!!!!!! ! !!!!!!!!!!!!!!!!configuration) !!!!.evaluate("robot.move!left")!!!!!! 33
  • AST transformation customizer def!configuration!=!new!CompilerConfiguration() ! def!imports!=!new!ImportCustomizer() imports.addStaticStar(mars.Direction.name) configuration.addCompilationCustomizers(imports, !!!!!!!!!!!!!new!ASTTransformationCustomizer(Log)) ! new!GroovyShell(new!Binding([robot:!new)Robot()]),!!!!!!! ! !!!!!!!!!!!!!!!!configuration) !!!!.evaluate("robot.move!left"!+!"n" !!!!!!!!!!!!!!"log.info!‘Robot!moved’")!!!!!!!!!!! 34
  • AST transformation customizer def!configuration!=!new!CompilerConfiguration() ! def!imports!=!new!ImportCustomizer() imports.addStaticStar(mars.Direction.name) configuration.addCompilationCustomizers(imports, !!!!!!!!!!!!!new!ASTTransformationCustomizer(Log)) ! new!GroovyShell(new!Binding([robot:!new)Robot()]),!!!!!!! ! !!!!!!!!!!!!!!!!configuration) !!!!.evaluate("robot.move!left"!+!"n" !!!!!!!!!!!!!!"log.info!‘Robot!moved’")!!!!!!!!!!!@Log injects a logger in scripts and classes 34
  • Secure the onboardtrajectory calculator
  • Secure AST customizer Idea: Secure the rocket’s onboard trajectory calculation system by allowing only math expressions to be evaluated by the calculator• Let’s setup our environment •an import customizer to import java.lang.Math.* •prepare a secure AST customizerdef%imports%=%new%ImportCustomizer().addStaticStars(java.lang.Math)def%secure%=%new%SecureASTCustomizer() 36
  • Secure AST customizer...secure.with!{ //!disallow!closure!creation closuresAllowed!=!false! //!disallow!method!definitions methodDefinitionAllowed!=!false!! //!empty!white!list!=>!forbid!imports importsWhitelist!=![]! staticImportsWhitelist!=![] //!only!allow!the!java.lang.Math.*!static!import staticStarImportsWhitelist!=![java.lang.Math]... 37
  • Secure AST customizer Disallow closures... and methodssecure.with!{ //!disallow!closure!creation closuresAllowed!=!false! //!disallow!method!definitions methodDefinitionAllowed!=!false!! //!empty!white!list!=>!forbid!imports importsWhitelist!=![]! staticImportsWhitelist!=![] //!only!allow!the!java.lang.Math.*!static!import staticStarImportsWhitelist!=![java.lang.Math]... 37
  • Secure AST customizer Disallow closures... and methodssecure.with!{ //!disallow!closure!creation closuresAllowed!=!false! //!disallow!method!definitions Black / white list methodDefinitionAllowed!=!false! imports! //!empty!white!list!=>!forbid!imports importsWhitelist!=![]! staticImportsWhitelist!=![] //!only!allow!the!java.lang.Math.*!static!import staticStarImportsWhitelist!=![java.lang.Math]... 37
  • Secure AST customizer... //%language%tokens%allowed tokensWhitelist%=%[ PLUS,%MINUS,%MULTIPLY,%DIVIDE,%MOD,%POWER,%PLUS_PLUS,%MINUS_MINUS,% COMPARE_EQUAL,%COMPARE_NOT_EQUAL,%COMPARE_LESS_THAN,%COMPARE_LESS_THAN_EQUAL,% COMPARE_GREATER_THAN,%COMPARE_GREATER_THAN_EQUAL ]% //%types%allowed%to%be%used%(including%primitive%types) constantTypesClassesWhiteList%=%[ Integer,%Float,%Long,%Double,%BigDecimal,% Integer.TYPE,%Long.TYPE,%Float.TYPE,%Double.TYPE ]% //%classes%who%are%allowed%to%be%receivers%of%method%calls receiversClassesWhiteList%=%[% Math,%Integer,%Float,%Double,%Long,%BigDecimal%]}... 38
  • Secure AST customizer You can build a subset of the Groovy syntax!... //%language%tokens%allowed tokensWhitelist%=%[ PLUS,%MINUS,%MULTIPLY,%DIVIDE,%MOD,%POWER,%PLUS_PLUS,%MINUS_MINUS,% COMPARE_EQUAL,%COMPARE_NOT_EQUAL,%COMPARE_LESS_THAN,%COMPARE_LESS_THAN_EQUAL,% COMPARE_GREATER_THAN,%COMPARE_GREATER_THAN_EQUAL ]% //%types%allowed%to%be%used%(including%primitive%types) constantTypesClassesWhiteList%=%[ Integer,%Float,%Long,%Double,%BigDecimal,% Integer.TYPE,%Long.TYPE,%Float.TYPE,%Double.TYPE ]% //%classes%who%are%allowed%to%be%receivers%of%method%calls receiversClassesWhiteList%=%[% Math,%Integer,%Float,%Double,%Long,%BigDecimal%]}... 38
  • Secure AST customizer You can build a subset of the Groovy syntax!... //%language%tokens%allowed tokensWhitelist%=%[ PLUS,%MINUS,%MULTIPLY,%DIVIDE,%MOD,%POWER,%PLUS_PLUS,%MINUS_MINUS,% COMPARE_EQUAL,%COMPARE_NOT_EQUAL,%COMPARE_LESS_THAN,%COMPARE_LESS_THAN_EQUAL,% COMPARE_GREATER_THAN,%COMPARE_GREATER_THAN_EQUAL ]% //%types%allowed%to%be%used%(including%primitive%types) Black / white list constantTypesClassesWhiteList%=%[ usage of classes Integer,%Float,%Long,%Double,%BigDecimal,% Integer.TYPE,%Long.TYPE,%Float.TYPE,%Double.TYPE ]% //%classes%who%are%allowed%to%be%receivers%of%method%calls receiversClassesWhiteList%=%[% Math,%Integer,%Float,%Double,%Long,%BigDecimal%]}... 38
  • Secure AST customizer• Ready to evaluate our flight equations! def%config%=%new%CompilerConfiguration() config.addCompilationCustomizers(imports,%secure) def%shell%=%new%GroovyShell(config) % shell.evaluate%cos%PI/3• But the following would have failed: shell.evaluate%System.exit(0) 39
  • Back to our robot... robot.move)left 40
  • Back to our robot... Still need to get rid of the robot prefix! robot.move)left 40
  • Can we remove it?
  • Can we remove it? да !
  • How to remove the ‘robot’?• Instead of calling the move() method on the robot instance, we should be able to call the move() method directly from within the script• Two approaches: • Inject a ‘move’ closure in the • Use a base script class with a ‘move’ method delegating to the binding with a method pointer robot 42
  • Inject a closure in the binding def$robot%=%new%Robot() binding%=$new%Binding([ %%%%robot:%robot, %%%%*:%Direction.values() %%%%%%%%%%%%.collectEntries%{ %%%%%%%%%%%%%%%%[(it.name()):%it] %%%%%%%%%%%%}, %%%%move:%robot.&move ]) 43
  • Inject a closure in the binding def$robot%=%new%Robot() binding%=$new%Binding([ %%%%robot:%robot, %%%%*:%Direction.values() %%%%%%%%%%%%.collectEntries%{ %%%%%%%%%%%%%%%%[(it.name()):%it] %%%%%%%%%%%%}, %%%%move:%robot.&move ]) Method pointer (a closure) on robot’s move instance method 43
  • Define a base script class abstract)class!RobotBaseScriptClass!!! !!!!!!!extends!Script!{ !!!!void!move(Direction!dir)!{ !!!!!!!!def)robot!=!this.binding.robot !!!!!!!!robot.move!dir !!!!} } 44
  • Define a base script class abstract)class!RobotBaseScriptClass!!! !!!!!!!extends!Script!{ !!!!void!move(Direction!dir)!{ !!!!!!!!def)robot!=!this.binding.robot !!!!!!!!robot.move!dir !!!!} }The move() method isnow at the script level 44
  • Define a base script class abstract)class!RobotBaseScriptClass!!! !!!!!!!extends!Script!{ !!!!void!move(Direction!dir)!{ !!!!!!!!def)robot!=!this.binding.robot !!!!!!!!robot.move!dir !!!!} }The move() method is Access the robot throughnow at the script level the script’s binding 44
  • Configure the base script classdef)conf)=)new)CompilerConfiguration()conf.scriptBaseClass)=)RobotBaseScriptClass 45
  • Configure the base script classdef)conf)=)new)CompilerConfiguration()conf.scriptBaseClass)=)RobotBaseScriptClass Scripts evaluated with this configuration will inherit from that class 45
  • Ready for lift off! %%move%left
  • Beep, beep...yes but how do you define the speed? ...beep...
  • Oh no!
  • What we could do now is... move!left,!at:!3.km/h 49
  • What we could do now is... Mix of named and normal parameters move!left,!at:!3.km/h 49
  • What we could do now is... Mix of named and normal parameters move!left,!at:!3.km/h How to add a km property to numbers? 49
  • Adding properties to numbers• We need to: • define units, distance and speed • have a nice notation for them • that’s where we add properties to numbers! 50
  • Unit enum and Distance classenum$DistanceUnit%{%%%%centimeter%(cm,%%%%0.01),%%%%meter%%%%%%(%m,%%%%1),%%%%%kilometer%%(km,%1000)%%%%%%%%%String%abbreviation%%%%double%multiplier%%%%%%%%Unit(String%abbr,%double%mult)%{%%%%%%%%this.abbreviation%=%abbr%%%%%%%%this.multiplier%=%mult%%%%%}%%%%String%toString()%{%abbreviation%}%} 51
  • Unit enum and Distance class @TupleConstructor%enum$DistanceUnit%{ class%Distance%{%%%%centimeter%(cm,%%%%0.01), %%%%double%amount%%%%%meter%%%%%%(%m,%%%%1),% %%%%DistanceUnit%unit%%%%kilometer%%(km,%1000)%%%%% %%%%String%toString()%{%%%%%String%abbreviation %%%%%%%%"$amount%$unit"%%%%%double%multiplier %%%%}%%%%% }%%%%Unit(String%abbr,%double%mult)%{%%%%%%%%this.abbreviation%=%abbr%%%%%%%%this.multiplier%=%mult%%%%%}%%%%String%toString()%{%abbreviation%}%} 51
  • Different techniques• To add dynamic methods or properties, there are several approaches at your disposal: • ExpandoMetaClass • custom MetaClass • Categories• Let’s have a look at the ExpandoMetaClass 52
  • Using ExpandoMetaClass Number.metaClass.getCm%=%{%f>% %%%%new%Distance(delegate,%Unit.centimeter)% } Number.metaClass.getM%=%{%f>% %%%%new%Distance(delegate,%Unit.meter)% } Number.metaClass.getKm%=%{%f>% %%%%new%Distance(delegate,%Unit.kilometer)% } 53
  • Using ExpandoMetaClass Add that tointegration.groovy Number.metaClass.getCm%=%{%f>% %%%%new%Distance(delegate,%Unit.centimeter)% } Number.metaClass.getM%=%{%f>% %%%%new%Distance(delegate,%Unit.meter)% } Number.metaClass.getKm%=%{%f>% %%%%new%Distance(delegate,%Unit.kilometer)% } 53
  • Using ExpandoMetaClass Add that tointegration.groovy Number.metaClass.getCm%=%{%f>% %%%%new%Distance(delegate,%Unit.centimeter)% } Number.metaClass.getM%=%{%f>% %%%%new%Distance(delegate,%Unit.meter)% } Number.metaClass.getKm%=%{%f>% %%%%new%Distance(delegate,%Unit.kilometer)% } ‘delegate’ is the current number 53
  • Using ExpandoMetaClass Add that tointegration.groovy Usage in your DSLs Number.metaClass.getCm%=%{%f>% %%%%new%Distance(delegate,%Unit.centimeter)% } 40.cm! Number.metaClass.getM%=%{%f>% %%%%new%Distance(delegate,%Unit.meter)% 3.5.m } 4.km Number.metaClass.getKm%=%{%f>% %%%%new%Distance(delegate,%Unit.kilometer)% } ‘delegate’ is the current number 53
  • Distance okay, but speed?• For distance, we just added a property access after the number, but we now need to divide (‘div’) by the time 2.km/h 54
  • Distance okay, but speed?• For distance, we just added a property access after the number, but we now need to divide (‘div’) by the time The div() method on Distance 2.km/h 54
  • Distance okay, but speed?• For distance, we just added a property access after the number, but we now need to divide (‘div’) by the time The div() method on Distance 2.km/h An h duration instance in the binding 54
  • First, let’s look at timeenum$TimeUnit%{%%%%hour%%%%%%(h,%%%3600),%%%%minute%%%%(min,%%%60),%%%%%kilometer%(s,%%%%%%1)%%%%%%%%%String%abbreviation%%%%double%multiplier%%%%%%%%TimeUnit(String%abbr,%double%mult)%{%%%%%%%%this.abbreviation%=%abbr%%%%%%%%this.multiplier%=%mult%%%%%}%%%%String%toString()%{%abbreviation%}%} 55
  • First, let’s look at time @TupleConstructor% class%Duration%{enum$TimeUnit%{ %%%%double%amount%%%%%hour%%%%%%(h,%%%3600), %%%%TimeUnit%unit%%%%minute%%%%(min,%%%60),%%%%%kilometer%(s,%%%%%%1)% %%%%String%toString()%{%%%%% %%%%%%%%"$amount%$unit"%%%%%String%abbreviation %%%%}%%%%%double%multiplier }%%%%%%%%TimeUnit(String%abbr,%double%mult)%{%%%%%%%%this.abbreviation%=%abbr%%%%%%%%this.multiplier%=%mult%%%%%}%%%%String%toString()%{%abbreviation%}%} 55
  • Inject the ‘h’ hour constant in the binding def)binding!=)new!Binding([ !!!!robot:!new!Robot(), !!!!*:!Direction.values() !!!!!!!!!!!!.collectEntries!{ !!!!!!!!!!!!!!!![(it.name()):!it] !!!!!!!!!!!!}, !!!!h:!new!Duration(1,!TimeUnit.hour) ]) 56
  • Now at (light!) speed @TupleConstructor) class)Speed){ ))))Distance)distance ))))Duration)duration ))))String)toString()){) ))))))))"$distance/$duration") ))))}) } 57
  • Operator overloading a%+%b%%//%a.plus(b) a%f%b%%//%a.minus(b) • Currency amounts a%*%b%%//%a.multiply(b) • 15.euros + 10.dollars a%/%b%%//%a.div(b) a%%%b%%//%a.modulo(b) • Distance handling a%**%b%//%a.power(b) • 10.km - 10.m a%|%b%%//%a.or(b) a%&%b%%//%a.and(b) • Workflow, concurrency a%^%b%%//%a.xor(b) • taskA | taskB & taskC a[b]%%%//%a.getAt(b) a%<<%b%//%a.leftShift(b) • Credit an account a%>>%b%//%a.rightShift(b) +a%%%%%//%a.unaryPlus() • account << 10.dollars account += 10.dollars fa%%%%%//%a.unaryMinus() account.credit 10.dollars ~a%%%%%//%a.bitwiseNegate() 58
  • Operator overloading• Update the Distance class with a div() method following the naming convetion for operators class%Distance%{ %%%%... %%%%Speed%div(Duration%t)%{ %%%%%%%%new%Speed(this,%t) %%%%} %%%%... } 59
  • Operator overloading• Update the Distance class with a div() method following the naming convetion for operators class%Distance%{ %%%%... %%%%Speed%div(Duration%t)%{ %%%%%%%%new%Speed(this,%t) %%%%} %%%%... } Optional return 59
  • Equivalence of notation• Those two notations are actually equivalent: 2.km/h 2.getKm().div(h) 60
  • Equivalence of notation• Those two notations are actually equivalent: 2.km/h This one might be slightly more verbose! 2.getKm().div(h) 60
  • Named parameters usage move!left,!at:!3.km/h 61
  • Named parameters usage move!left,!at:!3.km/h Normal parameter 61
  • Named parameters usage move!left,!at:!3.km/h Normal Named parameter parameter 61
  • Named parameters usage move!left,!at:!3.km/h Normal Named parameter parameter Will call: def!take(Map!m,!Direction!q) 61
  • Named parameters usage move!left,!at:!3.km/h Normal Named parameter parameter Will call: def!take(Map!m,!Direction!q) All named parameters go into the map argument 61
  • Named parameters usage move!left,!at:!3.km/h Normal Named parameter parameter Will call: def!take(Map!m,!Direction!q) All named parameters go Positional parameters into the map argument come afterwards 61
  • Named parameters usage move!left,!at:!3.km/h 62
  • Named parameters usage move!left,!at:!3.km/h Can we get rid of the comma? 62
  • Named parameters usage move!left,!at:!3.km/h Can we get rid of What about the the comma? colon too? 62
  • Command chains• A grammar improvement in Groovy 1.8 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) 63
  • Command chains %move%left%%at%3.km/h% 64
  • Command chains Alternation of method names %move%left%%at%3.km/h% 64
  • Command chains Alternation of method names %move%left%%at%3.km/h% and parameters (even named ones) 64
  • Command chains %move%left%%at%3.km/h% 64
  • Command chains Equivalent to: %%%%%(%%%%).%%(%%%%%%) %move%left%%at%3.km/h% 64
  • Look Ma!No par ens, no dots!
  • Command chains //%Java%fluent%API%approach class%Robot%{ %%%%... $$$$def%move(Direction%dir)%{ %%%%%%%%this.dir%=%dir %%%%%%%%return%this %%%%} %%%%def%at(Speed%speed)%{ %%%%%%%%this.speed%=%speed %%%%%%%%return%this %%%%} %%%%... } 66
  • Command chains def)move(Direction)dir)){ ))))[at:){)Speed)speed)F> ))))))))))))... ))))))))}] ))))}] } 67
  • Command chains def)move(Direction)dir)){ ))))[at:){)Speed)speed)F> ))))))))))))... Nested maps ))))))))}] and closures ))))}] } 67
  • Command chains def)move(Direction)dir)){ ))))[at:){)Speed)speed)F> ))))))))))))... Nested maps ))))))))}] and closures ))))}] } Usage in your DSLs move%left%at%3.km/h 67
  • Command chains 68
  • Command chains //%methods%with%multiple%arguments%(commas) 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good //%closure%parameters%for%new%control%structures 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} //%zerofarg%methods%require%parens 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} //%zerofarg%methods%require%parens select%all%%unique()%from%names 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} //%zerofarg%methods%require%parens select%all%%unique()%from%names //%possible%with%an%odd%number%of%terms 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} //%zerofarg%methods%require%parens select%all%%unique()%from%names //%possible%with%an%odd%number%of%terms deploy%left%%arm 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor %%%%(%%%%%%).%%%%(%%%%%%%%%%%).%%%(%%%%%%) //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} //%zerofarg%methods%require%parens select%all%%unique()%from%names //%possible%with%an%odd%number%of%terms deploy%left%%arm 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor %%%%(%%%%%%).%%%%(%%%%%%%%%%%).%%%(%%%%%%) //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good %%%%%(%%%%%%%%%%%).%%%%%%(%%%%) //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} //%zerofarg%methods%require%parens select%all%%unique()%from%names //%possible%with%an%odd%number%of%terms deploy%left%%arm 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor %%%%(%%%%%%).%%%%(%%%%%%%%%%%).%%%(%%%%%%) //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good %%%%%(%%%%%%%%%%%).%%%%%%(%%%%) //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} %%%%%(%%).%%%%(%%).%%%%(%%) //%zerofarg%methods%require%parens select%all%%unique()%from%names //%possible%with%an%odd%number%of%terms deploy%left%%arm 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor %%%%(%%%%%%).%%%%(%%%%%%%%%%%).%%%(%%%%%%) //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good %%%%%(%%%%%%%%%%%).%%%%%%(%%%%) //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} %%%%%(%%).%%%%(%%).%%%%(%%) //%zerofarg%methods%require%parens select%all%%unique()%from%names %%%%%%(%%%).%%%%%%%%.%%%%(%%%%%) //%possible%with%an%odd%number%of%terms deploy%left%%arm 68
  • Command chains //%methods%with%multiple%arguments%(commas) take%coffee%%with%sugar,%milk%%and%liquor %%%%(%%%%%%).%%%%(%%%%%%%%%%%).%%%(%%%%%%) //%leverage%namedfargs%as%punctuation check%that:%vodka%%tastes%good %%%%%(%%%%%%%%%%%).%%%%%%(%%%%) //%closure%parameters%for%new%control%structures given%{}%%when%{}%%then%{} %%%%%(%%).%%%%(%%).%%%%(%%) //%zerofarg%methods%require%parens select%all%%unique()%from%names %%%%%%(%%%).%%%%%%%%.%%%%(%%%%%) //%possible%with%an%odd%number%of%terms deploy%left%%arm %%%%%%(%%%%). 68
  • Final result 69
  • Final result move!forward!at!3.km/h 69
  • move forward at 3.km/h
  • move forward at 3.km/h Yes! We did it!
  • What about security and safety?
  • Security and safety JVM Security Managers SecureASTCustomizer Sandboxing Controlling scripts execution 72
  • Play it safe in a sandbox
  • Playing it safe...• You have to think carefully about what DSL users are allowed to do with your DSL• Forbid things which are not allowed •leverage the JVM’s Security Managers • this might have an impact on performance •use a Secure AST compilation customizer • not so easy to think about all possible cases •avoid long running scripts with *Interrupt transformations 74
  • Security Managers• Groovy is just a language leaving on the JVM, so you have access to the usual Security Managers mechanism • Nothing Groovy specific here • Please check the documentation on Security Managers and how to design policy files 75
  • SecureASTCustomizer def!secure!=!new!SecureASTCustomizer() secure.with!{ //!disallow!closure!creation !!!closuresAllowed!=!false! //!disallow!method!definitions !!!methodDefinitionAllowed!=!false! //!empty!white!list!=>!forbid!certain!imports !!!importsWhitelist!=![...]! !!!staticImportsWhitelist!=![...] //!only!allow!some!static!import !!!staticStarImportsWhitelist!=![...] //!language!tokens!allowed !!!!tokensWhitelist!=![...] //!types!allowed!to!be!used !!!constantTypesClassesWhiteList!=![...] //!classes!who!are!allowed!to!be!receivers!of!method!calls !!!receiversClassesWhiteList!=![...] } def!config!=!new!CompilerConfiguration() config.addCompilationCustomizers(secure) def!shell!=!new!GroovyShell(config) 76
  • Controlling code execution• Your application may run user’s code •what if the code runs in infinite loops or for too long? •what if the code consumes too many resources?• 3 new transforms at your rescue • @ThreadInterrupt: adds Thread#isInterrupted checks so your executing thread stops when interrupted • @TimedInterrupt: adds checks in method and closure bodies to verify it’s run longer than expected • @ConditionalInterrupt: adds checks with your own conditional logic to break out from the user code 77
  • @ThreadInterrupt @ThreadInterrupt import%groovy.transform.ThreadInterrupt% % while%(true)%{ %%%%//%Any%extraterestrial%around? } 78
  • @ThreadInterrupt @ThreadInterrupt import%groovy.transform.ThreadInterrupt% % while%(true)%{{ %%%%if%(Thread.currentThread.isInterrupted()) %%%%%%%%throw$new%InterruptedException() } %%%%//%Any%extraterestrial%around? } 78
  • @TimedInterrupt @TimedInterrupt(10) import!groovy.transform.TimedInterrupt! ! while!(true)!{ !!!!move!left !!!!//!circle!forever }• InterruptedException thrown when checks indicate code ran longer than desired 79
  • @ConditionalInterrupt• Specify your own condition to be inserted at the start of method and closure bodies • check for available resources, number of times run, etc.• Leverages closure annotation parameters from Groovy 1.8 @ConditionalInterrupt({$battery.level$<$O.1$}) import%groovy.transform.ConditionalInterrupt 100.times%{%%%% %%%%move%forward%at%10.km/h } 80
  • Using compilation customizers• In our previous three examples, the usage of the interrupts were explicit, and users had to type them • if they want to deplete the battery of your robot, they won’t use interrupts, so you have to impose interrupts yourself• With compilation customizers you can inject those interrupts thanks to the ASTTransformationCustomizer 81
  • What have we learnt? 82
  • Groovy Power!™• A flexible and malleable syntax • scripts vs classes, optional typing, colons and parens• Groovy offers useful dynamic features for DSLs • operator overloading, ExpandoMetaClass• Can write almost plain natural language sentences • for readable, concise and expressive DSLs• Groovy DSLs are easy to integrate, and can be secured to run safely in your own sandbox 83
  • Groovy Power!™ Groovy is a great fit for DSLs!• A flexible and malleable syntax • scripts vs classes, optional typing, colons and parens• Groovy offers useful dynamic features for DSLs • operator overloading, ExpandoMetaClass• Can write almost plain natural language sentences • for readable, concise and expressive DSLs• Groovy DSLs are easy to integrate, and can be secured to run safely in your own sandbox 83
  • And there’s more!• We haven’t dived into... •How to implement your own control structures with the help of closures •How to create Groovy « builders » •How to hijack the Groovy syntax to develop our own language extensions with AST Transformations •Source preprocessing for custom syntax •How to use the other dynamic metaprogramming techniques available •How to improve error reporting with customizers •IDE support with DSL descriptors (GDSL and DSLD) 84
  • Thank you! ge e Lafor lopment Gui llaum ovy Deve Head of Gro om @ gmail.c laforge rge Email: g @glafo o/glaforg e Twitter : http://gplus.t spot.com : G oogle+ //glaforge.app p: B log: htt 85
  • Questions & AnswersGot questions, really? 86
  • Questions & Answers I might have answers!Got questions, really? 86
  • Image credits• Pills: http://www.we-ew.com/wp-content/uploads/2011/01/plan-b-pills.jpg• Chains: http://2.bp.blogspot.com/-GXDVqUYSCa0/TVdBsON4tdI/AAAAAAAAAW4/EgJOUmAxB28/s1600/breaking-chains5_copy9611.jpg• Russian flag: http://good-wallpapers.com/pictures/4794/Russian%20Flag.jpg• Space odissey: http://dearjesus.files.wordpress.com/2010/04/2001_a_space_odyssey_1.jpg• HAL red: http://2.bp.blogspot.com/-yjsyPxUFicY/TcazwAltOaI/AAAAAAAAAho/GVT7wGhnrUM/s1600/2001-a-space-odyssey-HAL.jpg• Back in time: http://4.bp.blogspot.com/-Pt44Dk9J2EM/TrQx9YNmVcI/AAAAAAAAAk4/ivWw9Lja05k/s1600/clocky.jpg• USSR Space posters: http://www.flickr.com/photos/justinvg• Russian General: http://rickrozoff.files.wordpress.com/2012/02/general-nikolai-makarov.jpg• Rocket: http://blog.al.com/breaking/2009/03/soyuz_launch.jpg• Progress capsule: http://www.spacedaily.com/images/progress-ocean-desk-1024.jpg• Buran: http://www.buran.fr/mriya-antonov/Photos//050-Exhibition%20au%20Bourget%20avec%20Bourane-Airshow%20with%20Buran%20at%20Le%20Bourget-1134884.jpg• Man in space: http://vintagespace.files.wordpress.com/2010/11/voskhod-2_leonov.jpg• Sputnik 2: http://launiusr.files.wordpress.com/2010/06/sputnik2.jpg• Lunakod: http://www.astr.ua.edu/keel/space/lunakhod_moscow.jpg• Sandbox: http://www.turnbacktogod.com/wp-content/uploads/2008/09/sandbox.jpg• Repair: http://www.oneangels.com/wp-content/uploads/2012/03/repair1.jpg• Mars rover: http://wallpapers.free-review.net/wallpapers/49/Mars_rover%2C_Mars_-_03.jpg• Mars rover 2: http://www.universetoday.com/wp-content/uploads/2011/06/551038main_pia14156-43_946-710.jpg• Thumb: http://www.wpclipart.com/sign_language/thumbs_up_large.png.html• Night sky: http://www.aplf-planetariums.info/galeries/ciel_profond/2004-07-01-Voie_Lactee_Scorpion-Jean-Luc_PUGLIESI.jpg• Obama yes we can: http://www.dessinemoiunboulon.net/wp-content/uploads/2009/01/obama-yes-we-can_04-nov-08.jpg• Hook: http://winningware.com/blog/wp-content/uploads/2009/12/FishHookXSmall.jpg• HP 48 GX: http://calculators.torensma.net/files/images/hewlett-packard_hp-48g.jpg• Omer: http://www.irmin.com/wallpaper/TV/Homer%20Simpson%20Oh%20No.jpg• Cadenat: http://acsgsecurite.com/upl/site/cadenat.png 87