views:

494

answers:

3

I'm writing a DSL in Ruby to control an Arduino project I'm working on; Bardino. It's a bar monkey that will be software controlled to serve drinks. The Arduino takes commands via the serial port to tell the Arduino what pumps to turn on and for how long.

It currently reads a recipe (see below) and prints it back out. The code for serial communications still need to be worked in as well as some other ideas that I have mentioned below.

This is my first DSL and I'm working off of a previous example so it's very rough around the edges. Any critiques, code improvements (are there any good references for Ruby DSL best practices or idioms?) or any general comments.

I currently have a rough draft of the DSL so a drink recipe looks like the following (Github link):

desc "Simple glass of water"
recipe "water" do
  ingredients(
  "Water" => 2.ounces
  )
end

This in turn is interpreted and currently results with the following (Github link):

[mwilliams@Danzig barduino-tender]$ ruby barduino-tender.rb examples/water.rb
Preparing: Simple glass of water

Ingredients:
 Water: 2 ounces

This is a good start for the DSL, however, I do think it could be implemented a little bit better. Some ideas I had below:

  1. Defining what "ingredients" are available using the name of the ingredient and the number pump that it's connected to. Maybe using a hash? ingredients = {"water" => 1, "vodka" => 2}. This way, when an ingredient is interpreted it can either a) send the pump number over the serial port followed by the number of ounces for the Arduino to dispense b) tell the user that ingredient does not exist and abort so nothing is dispensed c) easily have the capability to change or add new ingredients if they're changed.
  2. Making the recipe look less code like, which is the main purpose of a DSL, maybe build a recipe builder? Using the available ingredients to prompt the user for a drink name, ingredients involved and how much?

The Github project is here, feel free to fork and make pull requests, or post your code suggestions and examples here for other users to see. And if you're at all curious, the Arduino code, using the Ruby Arduino Development framework is here.

Update

I modified and cleaned things up a bit to reflect Orion Edwards suggestion for a recipe. It now looks like the following.

description 'Screwdriver' do 
  serve_in 'Highball Glass'
  ingredients do
    2.ounces :vodka
    5.ounces :orange_juice
  end
end

I also added a hash (key being the ingredient and the value the pump number it's hooked up to). I think this provided much progress. I'll leave the question open for any further suggestions for now, but will ultimately select Orion's answer. The updated DSL code is here.

+5  A: 

Without looking into implementation details (or your github links), I'd try write a DSL like this:

(stealing from here: http://supercocktails.com/1310/Long-Island-Iced-Tea-)

describe "Long Island Iced Tea" do
  serve_in 'Highball Glass'

  ingredients do
    half.ounce.of :vodka
    half.ounce.of :tequila
    half.ounce.of :light_rum
    half.ounce.of :gin
    1.dash.of :coca_cola
    #ignoring lemon peel as how can a robot peel a lemon?
  end

  steps do
    add :vodka, :tequila, :light_rum, :gin
    stir :gently
    add :coca_cola
  end
end

Hope that helps!

Orion Edwards
you could put 0.5.ounces.of :vodka if you felt like it, I just find the alias for 'half' to be more readable
Orion Edwards
Thanks for that tip, that's actually _a lot_ more readable and I will adjust accordingly in the next revision.
mwilliams
+1  A: 

Orion's DSL looks very nice. The only change I'd possibly suggest from you "updated" code is

  1. Replace description with recipe. It is a more descriptive term
  2. Since the set of ingredients and actions is fixed, bind the ingredients to variables rather than symbols i.e you have vodka = :vodka defined someplace. Its is easier to say

    mix vodka, gin and triple_sec # instead of using :vodka, :gin and :triple_sec.

anyways that's a minor nit.

I personally disagree with binding ingredients to variables, because well, variables are designed to vary. binding to a variable implicitly "tells" the developer that. 'vodka' should never be allowed to be re-bound to actually be 'cheese'.
Orion Edwards
agree with the replacing 'describe' with 'recipe_for' or similar though
Orion Edwards
+2  A: 

If you want the recipe to look more natural, why not (from the same recipe Orion Ewards used, thanks!):

Recipe for Long Island Iced Tea #1
Ingredients:
  1/2 oz Vodka
  1/2 oz Tequila
  1/2 oz Light Rum
  1/2 oz Gin
  1 Dash Coca-Cola
  # ignored Twist of Lemon Peel (or Lime)

Then add Treetop to the mix. You could have rules such as:

grammar Cocktail
  rule cocktail
    title ingredients
  end

  rule title
    'Recipe for' S text:(.*) EOF
  end

  rule ingredients
    ingredient+
  end

  rule ingredient
    qty S liquid
  end
# ...
end

Which the treetop compiler will transform into a nice ruby module. Then:

parser = CocktailParser.new
r = parser.parse(recipe)
Christian Lescuyer