Xavier Nayrac

Rubyiste accro au TDD, serial blogger, apprenti data scientist, heureux utilisateur de Vim, accordéoniste.
Si vous vous sentez particulièrement généreux, suivez moi sur Twitter.

Écrire un DSL en Ruby facilement avec Docile

| Comments

Niveau : intermédiaire

Je suis en train de lire «A new kind of science» de Stephen Wolfram et ça me donne envie de me replonger dans les automates cellulaires. Le jeu de la vie est sûrement le programme que j’ai écrit le plus souvent, mais curieusement jamais en Ruby.

J’ai commencé à écrire un framework pour automate cellulaire, qui n’aboutira peut-être pas faute de temps. Quoiqu’il en soit, je voudrais que ce framework soit utilisable par des non-développeurs, d’où le recours à un DSL (Domain Specific Language). Et pour développer mon DSL, je n’ai pas trouver plus simple que la gem docile.

Voici le DSL que je voudrais:

my_automaton.rb
1
2
3
4
5
6
7
8
9
automaton "Test Automaton" do
  dimensions       2
  type             :elementary
  width            100
  height           100
  rule             :b36s26
  pattern          :random
  live_probability 0.1789
end

Docile encourage l’utilisation du design pattern builder. Alors allons-y pour une classe builder qui va contenir les valeurs par défaut de notre futur Automaton:

automaton_builder.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
class AutomatonBuilder
  def initialize(name)
    @name = name
    @dimensions = 2
    @type = :elementary
    @width = 0
    @height = 0
    @rule = :b3s23
    @pattern = :random
    @live_probability = 0.5
  end

  def dimensions(val); @dimensions = val; self; end
  def type(val); @type = val; self; end
  def width(val); @width = val; self; end
  def height(val); @height = val; self; end
  def rule(val); @rule = val; self; end
  def pattern(val); @pattern = val; self; end
  def live_probability(val); @live_probability = val; self; end

  def build
    Automaton.new(@name, @dimensions, @type, @width, @height,
                  @rule, @pattern, @live_probability)
  end
end

Il nous faut maintenant une classe Automaton:

automaton.rb
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
class Automaton
  def initialize(name, dimensions, type, width, height, rule,
                pattern, live_probability)
    @name = name
    @dimensions = dimensions
    @type = type
    @width = width
    @height = height
    @rule = rule
    @pattern = pattern
    @live_probability = live_probability
  end

  def run
    puts "#{@name} running"
  end
end

Et pour finir, on demande à Docile d’évaluer notre DSL puis on charge le fichier my_automaton.rb. Il ne reste plus qu’à lancer la machine:

main.rb
1
2
3
4
5
6
7
8
9
10
11
require 'docile'
require './automaton_builder'
require './automaton'

def automaton(name, &block)
  @auto = Docile.dsl_eval(AutomatonBuilder.new(name), &block).build
end

load ARGV[0]

@auto.run

La boucle est bouclée. Vous remarquerez que la méthode automaton définie dans main.rb ci-dessus est celle qui est utilisée dans le DSL (my_automaton.rb).

$ ruby ./main.rb my_automaton.rb 
Test Automaton running

Et voilà. C’est presque trop facile d’écrire un DSL avec Docile…

À demain.

Articles connexes

Commentaires