Dopo una piccola introduzione al linguaggio Groovy, dove verranno illustrate brevemente alcune peculiarità, si passerà ad esaminare le caratteristiche che lo rendono adatto per la generazione di DSL.
Verrà quindi presentato come caso pratico la costruzione di una DSL e sarà mostrato come è possibile embeddarla in un progetto Java.
2. INDICE
• cos’è Groovy?
• caratteristiche interessanti (a mio giudizio), in ordine
arbitrariamente sparso
• cos’è un DSL?
• caratteristiche che rendono Groovy adatto a costruire un DSL
• un esempio semplice (lo facciamo al volo)
• un esempio meno semplice (non lo facciamo al volo)
nota: in parallelo vedremo esempi
3. COS’È GROOVY (PARTE 1)?
• linguaggio per la JVM alternativo a Java
• ispirato a: Ruby, Python, Smalltalk
• consente compilazione dinamica, ma può anche
generare bytecode
• tipizzazione forte dinamica
• closure
6. COMMAND-LINE GROOVY
SWITCH -L
• $ groovy -l 1234 -e “if(line==‘DATE’){println new Date()}”
Groovy is listening on port 1234
$ telnet localhost 1234
Connect to localhost.
Escape character is ‘^]’.
DATE
Mon Mar 17 21:38:49 CET 2014!
• $ groovy -l 80 SimpleWebServer.groovy
Esempio nei sorgenti Groovy: serve i file di una cartella
come webserver
7. COS’È GROOVY (PARTE 2)?
• è possibile usare direttamente l’API Java
• accetta (salvo alcune eccezioni) anche sintassi Java (es. {1,2,3,4} non è
array, ma [1,2,3,4] è un ArrayList)
• import automatico (di convenienza) di alcune classi Java (es. java.io,
java.lang, java.net, java.util…)
• println -> System.out.println
• parentesi opzionali nella chiamata a metodi
• notazione breve per getter e setter (o.field -> o.getField())
• ; opzionale (salvo alcune eccezioni)
• return opzionale (viene restituita l’ultima valutazione)
• this usata in contesti statici punta alla classe
8. COS’È GROOVY (PARTE 3)?
• GString interpolation: “Hello ${name}”
• Lazily interpolation (eval in conversione a String): “Nr. ${-> i}”
• == -> equals, equals -> is: non più “a != null && a.equals()” !!
• in: è un operatore basato su contains(): 3 in [1,2,3,4]
• sintassi nativa per alcune strutture dati: [1,2,3] list, [TN:’Trento’,
BZ:’Bolzano] map, 1..10 range
• contesti booleani: if( myString!=null && myString.length>0 ){} ->
if(myString){}
• safe-dereferencing: email?.destinatario?.indirizzo
• costrutto and-or (Elvis operator):
def result = name != null ? name : “Unknow”
9. FOR E FOR EACH
• for (int i=0; i<n; i++) { … }!
• for (i in 0..n-1) { … }!
• for (i in 0..<n) { … }!
• n.times { … }
• (1..5).each { println “nr. $it” }!
• 1..5 instanceof List!
• { println “nr. $it” }(3)
nr. 3
10. GROOVY BEANS
class Book {
String title
String description
}
def b1 = new Book()
b1.setTitle(“Anna Karenina”)
b1.description = “A very long book…”!
println b2.getDescription()
println b2.title
def b2 = new Book(title:”Anna Karenina”)
println b2.title
11. ALTRO SUI GROOVY BEANS
• Annotation based AST transformation (groovy.transform.*):!
• @Immutable (read only bean)!
• @ToString(includeNames=true, excludes=‘description,year’)!
• @EqualsAndHashCode!
• @Canonical (@ToString + @EqualsAndHashCode)!
• @TupleConstructor -> new Book(“Anna Karenina”, “Very long
book…”)!
• @AutoClone(style=AutoCloneStyle.COPY_CONSTRUCTOR)
def book1 = new Book(title:”Anna Karenina”)
def book2 = book1.clone()
assert book1.title == book2.title!
• AutoCloneStyle.SERIALIZABLE se implementa Serializable
12. ALTRE COSE BELLE
• methodMissing e propertyMissing per gestire
accessi a proprietà o metodi mancanti
• supporto nativo per markup XML, Json (Slurper e
Builder)
• conversioni bean in xml e json
• ExpandoMetaClass
• built-in memoize
13. DSL
DOMAIN SPECIFIC LANGUAGE
• Linguaggio di programmazione dedicato ad uno
specifico dominio (contrapposto a “general-purpose”)
• statistica (R e S)
• programmazione matriciale (Mata)
• Logo
• SQL
• …
14. DEFINIRE UN DSL IN GROOVY
Combinazione di tre punti chiave:
• fluent API
• embedded shell
• “specificità” del linguaggio
15. FLUENT API
È un implementazione di un API “method chaining”,
ovvero che permette chiamate di metodi “a catena”.
Es. JavaFX
Scene scene = SceneBuilder.create().width(516).height(387)
! .root(
! ! GroupBuilder.create().children(
! ! ! ImageViewBuilder.create().image(new Image(“..”)),
! ! ! [omissam]
! ! ).build()).build();
16. EMBEDING GROOVY IN JAVA
// Semplice esecuzione di codice Groovy!
GroovyShell shell = new GroovyShell();
String groovyCode = “println ‘Hello ’ + ‘Groovy’”;
String out = shell.evaluate(groovyCode);
// Esecuzione codice Groovy con variabile embeddata!
Book myBook = new Book();
myBook.setTitle(“Anna Karenina”);!
Binding binding = new Binding();
binding.setVariable(“book”, myBook);
String groovyCode = “println ‘Reading ‘ + book.title”;
GroovyShell shell = new GroovyShell(binding);
String out = shell.evaluate(groovyCode);
17. SPECIFICITÀ DI GROOVY
• le chiamate di metodi possono omettere le parentesi
• scriptBaseClass: la classe script base che rappresenta il contesto
di esecuzione dello script
• ImportCustomizer: importazione diretta nello script di classi e
package (anche *)
• SecureASTCustomizer: gestione della sicurezza (es. liste
bianche/nere)
• ASTTransformationCustomizer: per aggiungere
automaticamente annotazioni di trasformazione ai metodi
18. ESEMPIO SEMPLICE
Voglio creare un DSL in grado di eseguire:
compute 4 plus 3 plus 2 minus 1 print total!
• Una classe che implementa i metodi di linguaggio (compute, plus, minus, etc) come API
fluent
• Una classe astratta come base dello script groovy (si occuperà di proxare i metodi sullo
script)
• Un enum per le costanti (es. total)
• GroovyShell con:
• binding della classe linguaggio
• classe base astratta (vedi sopra)
• importazione custom delle costanti (vedi sopra)
Vediamo in pratica come procedere! (sorry, no slides here…)
19. LA CLASSE DI LINGUAGGIO
class Language {
Integer tot;
def compute(Integer a){
tot=a
this
}
def plus(Integer a){
tot += a
this
}
def minus(Integer a){
tot -= a
this
}
def print(Consts c){
tot
}
}
20. LA SCRIPTBASE E LA COSTANTE
abstract class AbstractScriptBaseClass extends Script{
def compute(Integer a){
this.lang.compute(a)
}
def plus(Integer a){
this.lang.plus(a)
}
def minus(Integer a){
this.lang.minus(a)
}
def print(String t){
this.lang.print(t)
}
}
public enum Consts {
total
}
21. TESTIAMO L’API
void testAPI() {
Language lang = new Language()
String total = "total"
// test API con sintassi tradizionale
Integer tot1 = lang.compute(4).plus(3).plus(2).minus(1).print(total)
// test API omettendo le parentesi
Integer tot2 = lang.compute 4 plus 3 plus 2 minus 1 print total
assert tot1 == 8
assert tot2 == 8
}
22. TESTIAMO IL DSL
void testShell() {
// il codice scritto nel DSL
def code = "compute 4 plus 3 plus 2 minus 1 print total"
Language lang = new Language()
Binding binding = new Binding();
binding.setVariable("lang", lang)
CompilerConfiguration conf = new CompilerConfiguration()
conf.scriptBaseClass = AbstractScriptBaseClass.class.name
ImportCustomizer imports = new ImportCustomizer()
imports.addStaticStars(Consts.name)
conf.addCompilationCustomizers(imports)
GroovyShell shell = new GroovyShell(binding, conf)
Integer tot = (Integer) shell.evaluate(code)
assert tot == 8
}
23. ESEMPIO MENO SEMPLICE
Un DSL per definire e risolvere problemi di geometria piana euclidea
(https://github.com/tizianolattisi/peg)
create triangle name "ABC"
extend "AC" to "D" with measure:"BC"
extend "BC" to "E" with measure:"AC"
create segment name "ED"
extend "DE" to "H"
extend "AB" to "H"
apply "10.8" on "ad", "bc" //angoli opposti
apply "10.3" on "CED", "ABC"
apply "10.6" on "ABC", "cba", "CED", "edc"
create segment name "BD"
apply "10.10" on "BCD", "BC", "CD"