Skip to content

Commit 8d1d36c

Browse files
committed
Merge branch 'interpreter_pattern'
2 parents a973059 + 5e6a084 commit 8d1d36c

File tree

3 files changed

+161
-27
lines changed

3 files changed

+161
-27
lines changed
Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,144 @@
1+
---
2+
layout: recipe
3+
title: Interpreter Pattern
4+
chapter: Design Patterns
5+
---
6+
7+
h2. Problem
8+
9+
Someone else needs to run parts of your code in a controlled fashion. Alternately, your language of choice cannot express the problem domain in a concise fashion.
10+
11+
h2. Solution
12+
13+
Use the Interpreter pattern to create a domain-specific language that you translate into specific code.
14+
15+
Assume, for example, that the user wants to perform math inside of your application. You could let them forward code to _eval_ but that would let them run arbitrary code. Instead, you can provide a miniature "stack calculator" language that you parse separately in order to only run mathematical operations while reporting more useful error messages.
16+
17+
{% highlight coffeescript %}
18+
class StackCalculator
19+
parseString: (string) ->
20+
@stack = [ ]
21+
for token in string.split /\s+/
22+
@parseToken token
23+
24+
if @stack.length > 1
25+
throw "Not enough operators: numbers left over"
26+
else
27+
@stack[0]
28+
29+
parseToken: (token, lastNumber) ->
30+
if isNaN parseFloat(token) # Assume that anything other than a number is an operator
31+
@parseOperator token
32+
else
33+
@stack.push parseFloat(token)
34+
35+
parseOperator: (operator) ->
36+
if @stack.length < 2
37+
throw "Can't operate on a stack without at least 2 items"
38+
39+
right = @stack.pop()
40+
left = @stack.pop()
41+
42+
result = switch operator
43+
when "+" then left + right
44+
when "-" then left - right
45+
when "*" then left * right
46+
when "/"
47+
if right is 0
48+
throw "Can't divide by 0"
49+
else
50+
left / right
51+
else
52+
throw "Unrecognized operator: #{operator}"
53+
54+
@stack.push result
55+
56+
calc = new StackCalculator
57+
58+
calc.parseString "5 5 +" # => { result: 10 }
59+
60+
calc.parseString "4.0 5.5 +" # => { result: 9.5 }
61+
62+
calc.parseString "5 5 + 5 5 + *" # => { result: 100 }
63+
64+
try
65+
calc.parseString "5 0 /"
66+
catch error
67+
error # => "Can't divide by 0"
68+
69+
try
70+
calc.parseString "5 -"
71+
catch error
72+
error # => "Can't operate on a stack without at least 2 items"
73+
74+
try
75+
calc.parseString "5 5 5 -"
76+
catch error
77+
error # => "Not enough operators: numbers left over"
78+
79+
try
80+
calc.parseString "5 5 5 foo"
81+
catch error
82+
error # => "Unrecognized operator: foo"
83+
{% endhighlight %}
84+
85+
h2. Discussion
86+
87+
As an alternative to writing our own interpreter, you can co-op the existing CoffeeScript interpreter in a such a way that its normal syntax makes for more natural (and therefore more comprehensible) expressions of your algorithm.
88+
89+
{% highlight coffeescript %}
90+
class Sandwich
91+
constructor: (@customer, @bread='white', @toppings=[], @toasted=false)->
92+
93+
white = (sw) ->
94+
sw.bread = 'white'
95+
sw
96+
97+
wheat = (sw) ->
98+
sw.bread = 'wheat'
99+
sw
100+
101+
turkey = (sw) ->
102+
sw.toppings.push 'turkey'
103+
sw
104+
105+
ham = (sw) ->
106+
sw.toppings.push 'ham'
107+
sw
108+
109+
swiss = (sw) ->
110+
sw.toppings.push 'swiss'
111+
sw
112+
113+
mayo = (sw) ->
114+
sw.toppings.push 'mayo'
115+
sw
116+
117+
toasted = (sw) ->
118+
sw.toasted = true
119+
sw
120+
121+
sandwich = (customer) ->
122+
new Sandwich customer
123+
124+
to = (customer) ->
125+
customer
126+
127+
send = (sw) ->
128+
toastedState = sw.toasted and 'a toasted' or 'an untoasted'
129+
130+
toppingState = ''
131+
if sw.toppings.length > 0
132+
if sw.toppings.length > 1
133+
toppingState = " with #{sw.toppings[0..sw.toppings.length-2].join ', '} and #{sw.toppings[sw.toppings.length-1]}"
134+
else
135+
toppingState = " with #{sw.toppings[0]}"
136+
"#{sw.customer} requested #{toastedState}, #{sw.bread} bread sandwich#{toppingState}"
137+
138+
send sandwich to 'Charlie' # => "Charlie requested an untoasted, white bread sandwich"
139+
send turkey sandwich to 'Judy' # => "Judy requested an untoasted, white bread sandwich with turkey"
140+
send toasted ham turkey sandwich to 'Rachel' # => "Rachel requested a toasted, white bread sandwich with turkey and ham"
141+
send toasted turkey ham swiss sandwich to 'Matt' # => "Matt requested a toasted, white bread sandwich with swiss, ham and turkey"
142+
{% endhighlight %}
143+
144+
This example allows for layers of functions by how it returns the modified object so that outer functions can modify it in turn. By borrowing a very and the particle _to_, the example lends natural grammar to the construction and ends up reading like an actual sentence when used correctly. This way, both your CoffeeScript skills and your existing language skills can help catch code problems.

chapters/jquery/ajax.textile

Lines changed: 17 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,35 +6,15 @@ chapter: jQuery
66

77
h2. Problem
88

9-
You need to make an AJAX request using jQuery.
9+
You want to make AJAX calls using jQuery.
1010

1111
h2. Solution
1212

1313
{% highlight coffeescript %}
14-
$ ?= require 'jquery' # Optional. For Node.js compatibility
15-
16-
jQuery ->
17-
console.log "document loaded"
18-
$('form').submit (e) ->
19-
console.log "form submit"
20-
e.preventDefault()
21-
form = this
22-
23-
$.ajax
24-
type: "POST"
25-
url: $(form).attr('action')
26-
data: $(form).serialize()
27-
success: ->
28-
console.log("success")
14+
$ ?= require 'jquery' # For Node.js compatibility
2915

30-
{% endhighlight %}
31-
32-
h2. Discussion
33-
34-
In addition to the Swiss Army knife of jQuery AJAX methods - _ajax()_ - the library also provides convenience methods for specific kinds of requests. Note that the _jQuery_ and _$_ variables can be used interchangeably.
35-
36-
{% highlight coffeescript %}
37-
$ ->
16+
$(document).ready ->
17+
# Basic Examples
3818
$.get '/', (data) ->
3919
$('body').append "Successfully got the page."
4020

@@ -46,10 +26,21 @@ $ ->
4626

4727
jQuery 1.5 and later have added a new, supplimental API for handling different callbacks.
4828

49-
{% highlight coffeescript %}
29+
# Advanced Settings
30+
$.ajax '/',
31+
type: 'GET'
32+
dataType: 'html' error: (jqXHR, textStatus, errorThrown) ->
33+
$('body').append "AJAX Error: #{textStatus}"
34+
success: (data, textStatus, jqXHR) ->
35+
$('body').append "Successful AJAX call: #{data}"
36+
37+
# For jQuery 1.5+
5038
request = $.get '/'
5139
request.success (data) -> $('body').append "Successfully got the page again."
5240
request.error (jqXHR, textStatus, errorThrown) -> $('body').append "AJAX Error: ${textStatus}."
41+
5342
{% endhighlight %}
5443

55-
See also "Callback bindings":../jquery/callback-bindings-jquery and the "jQuery AJAX API":http://api.jquery.com/jQuery.ajax/ for additional methods and options.
44+
h2. Discussion
45+
46+
The jQuery and $ variables can be used interchangeably. See also "Callback bindings":../jquery/callback-bindings-jquery.

wanted-recipes.textile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,6 @@ h2. Design patterns
140140
* Behavioral Patterns
141141
** Chain of Responsibility
142142
** Command
143-
** Interpreter
144143
** Iterator
145144
** Mediator
146145
** Observer

0 commit comments

Comments
 (0)