For example assume you work for a casino and you have been tasked with designing a system that will notify the poker room employees when a new table needs to be opened or when you are looking to open a new table. The rules for opening a table vary based on the stakes of the table and the length of the waiting list. For example, you need more people waiting for a no limit game because people go broke more quickly and you don't want the table to be short-handed shortly after you open it. The rules would be expressed in your DSL like this:
if the '$5-$10 Limit' list is more than 12 then notify the floor to openThe first context in which I will execute the DSL is the context that notifies the employees.
if the '$1-$2 No Limit' list is more than 15 then notify the floor to open
if the '$5-$10 Limit' list is more than 8 then notify the brush to announce
if the '$1-$2 No Limit' list is more than 10 then notify the brush to announce
class ContextOne < DslContextContextOne uses the DSL to check the List for the size per stakes and sends notifications when necessary. This is of course sample code and my List object is just a stub to verify that everything works correctly. I'll add a link to the sample code at the end of the post.
bubble :than, :is, :list, :the, :to
def more(value)
'> ' + value.to_s
end
def method_missing(sym, *args)
@stakes = sym
eval "List.size_for(sym) #{args.first}"
end
def floor(value)
__position(value, :floor)
end
def brush(value)
__position(value, :brush)
end
def open
__action(:open)
end
def announce
__action(:announce)
end
def __action(to)
{ :action => to }
end
def __position(value, title)
value[:position] = title
value
end
def notify(value)
[@stakes, value]
end
end
Based on this same script you could execute a second context that returns a list of the different games that are currently being spread.
class ContextTwo < DslContextAs you can see, adding additional contexts is very easy. Another could be added to display all positions that are set up to receive notices.
bubble :than, :is, :list, :the, :to, :more, :notify, :floor, :open, :brush
def announce
@stakes
end
alias open announce
def method_missing(sym, *args)
@stakes = sym
end
end
class ContextThree < DslContextExecuting a DSL script in multiple contexts begins to blur the line between code and data. The script 'code' can also be executed to do things such as generate reports (i.e. A report of which employees are contacted by the system). The script could also be executed in a context that will show how long before a table will be opened (i.e. the rule states that 15 are needed, the system knows 10 are on the list so it displays the message '5 more people needed before the game can start').
bubble :than, :is, :list, :the, :to, :more, :notify, :announce, :open, :open
def announce; end
def open; end
def brush(value)
:brush
end
def floor(value)
:floor
end
def method_missing(sym, *args)
true
end
end
A note on implementation: In my experience it is much easier to create a class per context and execute in the scope of that object. An alternative is to execute the script in the scope of one object that creates a generic object graph. The problem with this approach is finding an object graph that is generic enough to be useful in several situations. Clearly, I prefer the first approach.
Sample Code: http://www.jayfields.com/src/dslcontext.txt
Interesting choice of using the bubble method to make the DSL look more english like.
ReplyDeleteIn making this more accessible to non programmers, you might make the syntax to free form, since I can pretty much put bubble methods anywhere.
I suppose you could have a syntax check context to help out the customer with that.
I'm on the fence on making it look too much like english, because the expectation gets raised. "How come I can't write XYZ, my co worker understands that?" I guess I lean towards looks like english, but still gives you a field there is a contrained set of legitimate combinations.
Hi!
ReplyDeleteI just want to say thanks for this post.
I have just started with Ruby ( http://tiago.org/ps/2007/07/05/ruby-hello-world/ - a very naive first experience) and your blog, especially this post was a fundamental starting point.
Thanks!