Interpreter (Interpreter Pattern)¶
Od wzorca Komenda jest już blisko do Interpretera.
Note
Interpreter wykonuje polecenia, działając na globalnym kontekście (danych)
Wspomniany powyżej kontekst to zmienne, do których mogą odwoływać się polecenia. Wydzielmy zatem klasę Turtle,
która będzie owym kontekstem - obiektem na który wpływać będą komendy:
1from math import sin, cos, pi
2
3
4class Turtle:
5 def __init__(self):
6 """Default turtle constructor"""
7 self.x, self.y, self.a, self.pen, self.speed = 0, 0, 0, True, 1
8
9 def __str__(self): return "%d %d" % (self.x, self.y)
10
Żółw zna swoje położenie, umie też podać swoją reprezentację jako napis. Teraz powinniśmy zmodyfikować komendy
(czyli klasy R, L itp tak, aby metoda __call__() przyjmowała żółwia (obiekt klasy Turtle) jako argument.
Zmiany te trzeba oczywiście wprowadzić najpierw do klasy bazowej:
1class TurtleCommand:
2 """Base class for a turtle command"""
3
4 def __str__(self):
5 raise NotImplemented
6
7 def __call__(self, turtle, drawing):
8 raise NotImplemented
9
10
11class R(TurtleCommand):
12
13 def __init__(self, angle):
14 """Turn right by a given angle"""
15 self.angle = angle
16
17 def __call__(self, turtle, drawing):
18 turtle.a -= self.angle
19
20 def __str__(self):
21 return "R " + str(self.angle)
22
23
24class L(TurtleCommand):
25
26 def __init__(self, angle):
27 """Turn left by a given angle"""
Analogiczne zmiany wprowadzono do klas F i B. Ponieważ komendy są obiektami,
możemy przechowywać je w liście, a nawet zdediniować komendę, która zawiera w sobie inne komendy (klasa Procedure):
1class F(TurtleCommand):
2
3 def __init__(self, n_steps=1):
4 """Go forward command"""
5 self.n_steps = n_steps
6
7 def __call__(self, turtle, drawing):
8 turtle.y += self.n_steps * sin(turtle.a / 180.0 * pi) * turtle.speed
9 turtle.x += self.n_steps * cos(turtle.a / 180.0 * pi) * turtle.speed
10 if turtle.pen:
11 drawing.line_to(turtle.x, turtle.y)
12 else:
13 drawing.move_to(turtle.x, turtle.y)
14
15 def __str__(self):
16 return "F " + str(self.n_steps) + "" if self.n_steps != 1 else "F"
17
18
19class J(TurtleCommand):
20 def __init__(self, params):
Samo wykorzystanie interpretera przebiega podobnie, jak poprzednio (linie 3-7). Dodatkowo w linii 1 definiowana jest procedura rysująca kwadrat. Odpowiednie komendy są wstawiane do tej procedury w linii 2.
1 def __call__(self, turtle, drawing):
2 turtle.x, turtle.y = self.xy[0], self.xy[1]
3 drawing.move_to(turtle.x, turtle.y)
4
5 def __str__(self):
6 return "J " + str(self.xy[0]) + " " + str(self.xy[1])
7
Dodatkowo dla każdej komendy zdefiniowano metodę __str__(), co umożliwia np. nagranie programu na dysk
lub wypisanie na ekranie. Kompletny program znajduje się poniżej:
(rozwiń kod programu TurtleCommands.py)/summary> 1from math import sin, cos, pi
2
3
4class Turtle:
5 def __init__(self):
6 """Default turtle constructor"""
7 self.x, self.y, self.a, self.pen, self.speed = 0, 0, 0, True, 1
8
9 def __str__(self): return "%d %d" % (self.x, self.y)
10
11
12class TurtleCommand:
13 """Base class for a turtle command"""
14
15 def __str__(self):
16 raise NotImplemented
17
18 def __call__(self, turtle, drawing):
19 raise NotImplemented
20
21
22class R(TurtleCommand):
23
24 def __init__(self, angle):
25 """Turn right by a given angle"""
26 self.angle = angle
27
28 def __call__(self, turtle, drawing):
29 turtle.a -= self.angle
30
31 def __str__(self):
32 return "R " + str(self.angle)
33
34
35class L(TurtleCommand):
36
37 def __init__(self, angle):
38 """Turn left by a given angle"""
39 self.angle = angle
40
41 def __call__(self, turtle, drawing):
42 turtle.a += self.angle
43
44 def __str__(self):
45 return "L " + str(self.angle)
46
47
48class A(TurtleCommand):
49
50 def __init__(self, angle):
51 """Set a given angle (in degrees)"""
52 self.angle = angle
53
54 def __call__(self, turtle, drawing):
55 turtle.a = self.angle
56
57 def __str__(self):
58 return "A " + str(self.angle)
59
60class U(TurtleCommand):
61
62 def __call__(self, turtle, drawing):
63 turtle.pen = False
64
65 def __str__(self):
66 return "U"
67
68
69class D(TurtleCommand):
70
71 def __call__(self, turtle, drawing):
72 turtle.pen = True
73
74 def __str__(self):
75 return "D"
76
77
78class F(TurtleCommand):
79
80 def __init__(self, n_steps=1):
81 """Go forward command"""
82 self.n_steps = n_steps
83
84 def __call__(self, turtle, drawing):
85 turtle.y += self.n_steps * sin(turtle.a / 180.0 * pi) * turtle.speed
86 turtle.x += self.n_steps * cos(turtle.a / 180.0 * pi) * turtle.speed
87 if turtle.pen:
88 drawing.line_to(turtle.x, turtle.y)
89 else:
90 drawing.move_to(turtle.x, turtle.y)
91
92 def __str__(self):
93 return "F " + str(self.n_steps) + "" if self.n_steps != 1 else "F"
94
95
96class J(TurtleCommand):
97 def __init__(self, params):
98 """Turtle jumps to an arbitrary position given in absolute coordinates"""
99 self.xy = [float(token) for token in params.split()]
100
101 def __call__(self, turtle, drawing):
102 turtle.x, turtle.y = self.xy[0], self.xy[1]
103 drawing.move_to(turtle.x, turtle.y)
104
105 def __str__(self):
106 return "J " + str(self.xy[0]) + " " + str(self.xy[1])
107
108
109class Faster(TurtleCommand):
110
111 def __init__(self, f):
112 """Increase turtle speed by a given value"""
113 self.f = f
114
115 def __call__(self, turtle, drawing):
116 turtle.speed += self.f
117
118 def __str__(self):
119 return "FASTER " + str(self.f)
120
121
122class Slower(TurtleCommand):
123
124 def __init__(self, f):
125 """Decrease turtle speed by a given value"""
126 self.f = f
127
128 def __call__(self, turtle, drawing):
129 turtle.speed += self.f
130
131 def __str__(self):
132 return "SLOWER " + str(self.f)
133
134
135class Procedure(TurtleCommand):
136
137 def __init__(self, name):
138 """Procedure is a group of commands"""
139 self.__commands = []
140 self.__name = name
141
142 def do_next(self, next_command):
143 self.__commands.append(next_command)
144 return self
145
146 def __call__(self, turtle, drawing):
147 for cmd in self.__commands:
148 cmd(turtle, drawing)
149
150 def __str__(self):
151 out = self.__name+" := ["
152 for cmd in self.__commands:
153 out += str(cmd)+" "
154 return out + "]"
155
156
157class Repeat(TurtleCommand):
158
159 def __init__(self, n, cmd):
160 """Repeats a command multiple times"""
161 self.__command = cmd
162 self.__n = n
163
164 @property
165 def n(self):
166 return self.__n
167
168 def __call__(self, turtle, drawing):
169 for i in range(self.__n):
170 self.__command(turtle, drawing)
171
172 def __str__(self):
173 return "REPEAT " + str(self.__n) + " " + str(self.__command)
174
175
176class GraphicDevice:
177 def line_to(self, x, y):
178 raise NotImplemented
179
180 def move_to(self, x, y):
181 raise NotImplemented
182
183
184class EchoDevice(GraphicDevice):
185 def line_to(self, x, y):
186 print("line to %.1f %.1f" %( x, y))
187
188 def move_to(self, x, y):
189 print("move to %.1f %.1f" %( x, y))
190
191
192class VisualifeBridge(GraphicDevice):
193 def __init__(self, viewport):
194 self.__viewport = viewport
195 self.__x, self.__y = 0, 0
196 self.__n_lines = 0
197
198 def line_to(self, x, y):
199 self.__viewport.line("line-%d" % self.__n_lines, self.__x, self.__y, x, y)
200 self.__n_lines += 1
201 self.__x, self.__y = x, y
202
203 def move_to(self, x, y):
204 self.__x, self.__y = x, y
205
206
207class Logo:
208
209 def __init__(self, device=EchoDevice()):
210 self.__makers = {}
211 self.__turtle = Turtle()
212 self.__device = device
213 self.add_command("R", lambda angle: R(float(angle)))
214 self.add_command("L", lambda angle: L(float(angle)))
215 self.add_command("A", lambda angle: A(float(angle)))
216 self.add_command("F", lambda steps: F(float(steps)))
217 self.add_command("FASTER", lambda value: Faster(float(value)))
218 self.add_command("SLOWER", lambda value: Slower(float(value)))
219 self.add_command("U", U())
220 self.add_command("D", D())
221 self.add_command("J", lambda value: J(value))
222 self.add_command("JUMP", lambda value: J(value))
223 self.add_command("DEF", self.make_procedure)
224 self.add_command("REPEAT", self.make_repeat)
225 self.__loop_cnt = 0
226
227 @property
228 def turtle(self): return self.__turtle
229
230 def draw(self, *cmds):
231 for ci in cmds:
232 if isinstance(ci, str): # ---------- If it's a string, use factory to produce a command
233 cmd = self.produce_command(ci)
234 if not ci.startswith("DEF") and cmd: # ---------- Execute, if it's not a definition
235 cmd(self.__turtle, self.__device)
236 elif ci: # ---------- Execute a command
237 if ci: ci(self.__turtle, self.__device)
238
239 def add_command(self, name, cmd_maker):
240 self.__makers[name] = cmd_maker
241
242 def make_procedure(self, procedure_string):
243 name = procedure_string.split(maxsplit=1)[0]
244 p = Procedure(name)
245 cmnds = procedure_string[procedure_string.find('[')+1:procedure_string.find(']')].split(";")
246 for cmd in cmnds:
247 p.do_next(self.produce_command(cmd))
248
249 self.__makers[name] = p # ---------- here we register the new procedure in this factory
250 return p
251
252 def make_repeat(self, repeat_string):
253 tokens = repeat_string.split(maxsplit=1)
254 if tokens[1].find('[') >= 0:
255 tokens[1] = "DEF loop_" + str(self.__loop_cnt) + " := " + tokens[1]
256 self.__loop_cnt += 1
257 return Repeat(int(tokens[0]), self.produce_command(tokens[1]))
258
259 def produce_command(self, a_string):
260 if a_string.strip() == "": return None
261 tokens = a_string.split(maxsplit=1)
262 if isinstance(self.__makers[tokens[0]], TurtleCommand):
263 return self.__makers[tokens[0].strip()]
264 else:
265 return self.__makers[tokens[0].strip()](*tokens[1:])
266
267squares = """
268JUMP 3 3
269REPEAT 4 [F 194; R -90]
270JUMP 80 80
271DEF one_side := [F 10; R 90]
272DEF square := [REPEAT 4 one_side]
273REPEAT 36 [R 10; square]
274A 80
275F 100
276A -75
277F 110
278SLOWER 0.1
279REPEAT 36 [R 10; square]
280JUMP 10 180
281FASTER 0.2
282DEF diamond := [F 3; L 20; F 3; L 160; F 3; L 20; F 3]
283REPEAT 20 [A -130; diamond; A -80; diamond; A 0; F 7]
284"""
285
286if __name__ == "__main__":
287 square = Procedure("square")
288 square.do_next(F(10)).do_next(R(90)).do_next(F(10)).do_next(R(90)).do_next(F(10)).do_next(R(90)).do_next(F(10)).do_next(R(90))
289 path = [R(90), F(10), L(0), F(20), square]
290
291 import sys
292 sys.path.append("/Users/dgront/src.git/visualife/")
293 from visualife.core import SvgViewport
294
295 v = SvgViewport("logo.svg", 0, 0, 200, 200)
296 v.style = "stroke:black; stroke-width: 0.25;"
297 logo = Logo(VisualifeBridge(v))
298 commands = squares.splitlines()
299 logo.draw(*commands)
300 v.close()
1from math import sin, cos, pi
2
3
4class Turtle:
5 def __init__(self):
6 """Default turtle constructor"""
7 self.x, self.y, self.a, self.pen, self.speed = 0, 0, 0, True, 1
8
9 def __str__(self): return "%d %d" % (self.x, self.y)
10
11
12class TurtleCommand:
13 """Base class for a turtle command"""
14
15 def __str__(self):
16 raise NotImplemented
17
18 def __call__(self, turtle, drawing):
19 raise NotImplemented
20
21
22class R(TurtleCommand):
23
24 def __init__(self, angle):
25 """Turn right by a given angle"""
26 self.angle = angle
27
28 def __call__(self, turtle, drawing):
29 turtle.a -= self.angle
30
31 def __str__(self):
32 return "R " + str(self.angle)
33
34
35class L(TurtleCommand):
36
37 def __init__(self, angle):
38 """Turn left by a given angle"""
39 self.angle = angle
40
41 def __call__(self, turtle, drawing):
42 turtle.a += self.angle
43
44 def __str__(self):
45 return "L " + str(self.angle)
46
47
48class A(TurtleCommand):
49
50 def __init__(self, angle):
51 """Set a given angle (in degrees)"""
52 self.angle = angle
53
54 def __call__(self, turtle, drawing):
55 turtle.a = self.angle
56
57 def __str__(self):
58 return "A " + str(self.angle)
59
60class U(TurtleCommand):
61
62 def __call__(self, turtle, drawing):
63 turtle.pen = False
64
65 def __str__(self):
66 return "U"
67
68
69class D(TurtleCommand):
70
71 def __call__(self, turtle, drawing):
72 turtle.pen = True
73
74 def __str__(self):
75 return "D"
76
77
78class F(TurtleCommand):
79
80 def __init__(self, n_steps=1):
81 """Go forward command"""
82 self.n_steps = n_steps
83
84 def __call__(self, turtle, drawing):
85 turtle.y += self.n_steps * sin(turtle.a / 180.0 * pi) * turtle.speed
86 turtle.x += self.n_steps * cos(turtle.a / 180.0 * pi) * turtle.speed
87 if turtle.pen:
88 drawing.line_to(turtle.x, turtle.y)
89 else:
90 drawing.move_to(turtle.x, turtle.y)
91
92 def __str__(self):
93 return "F " + str(self.n_steps) + "" if self.n_steps != 1 else "F"
94
95
96class J(TurtleCommand):
97 def __init__(self, params):
98 """Turtle jumps to an arbitrary position given in absolute coordinates"""
99 self.xy = [float(token) for token in params.split()]
100
101 def __call__(self, turtle, drawing):
102 turtle.x, turtle.y = self.xy[0], self.xy[1]
103 drawing.move_to(turtle.x, turtle.y)
104
105 def __str__(self):
106 return "J " + str(self.xy[0]) + " " + str(self.xy[1])
107
108
109class Faster(TurtleCommand):
110
111 def __init__(self, f):
112 """Increase turtle speed by a given value"""
113 self.f = f
114
115 def __call__(self, turtle, drawing):
116 turtle.speed += self.f
117
118 def __str__(self):
119 return "FASTER " + str(self.f)
120
121
122class Slower(TurtleCommand):
123
124 def __init__(self, f):
125 """Decrease turtle speed by a given value"""
126 self.f = f
127
128 def __call__(self, turtle, drawing):
129 turtle.speed += self.f
130
131 def __str__(self):
132 return "SLOWER " + str(self.f)
133
134
135class Procedure(TurtleCommand):
136
137 def __init__(self, name):
138 """Procedure is a group of commands"""
139 self.__commands = []
140 self.__name = name
141
142 def do_next(self, next_command):
143 self.__commands.append(next_command)
144 return self
145
146 def __call__(self, turtle, drawing):
147 for cmd in self.__commands:
148 cmd(turtle, drawing)
149
150 def __str__(self):
151 out = self.__name+" := ["
152 for cmd in self.__commands:
153 out += str(cmd)+" "
154 return out + "]"
155
156
157class Repeat(TurtleCommand):
158
159 def __init__(self, n, cmd):
160 """Repeats a command multiple times"""
161 self.__command = cmd
162 self.__n = n
163
164 @property
165 def n(self):
166 return self.__n
167
168 def __call__(self, turtle, drawing):
169 for i in range(self.__n):
170 self.__command(turtle, drawing)
171
172 def __str__(self):
173 return "REPEAT " + str(self.__n) + " " + str(self.__command)
174
175
176class GraphicDevice:
177 def line_to(self, x, y):
178 raise NotImplemented
179
180 def move_to(self, x, y):
181 raise NotImplemented
182
183
184class EchoDevice(GraphicDevice):
185 def line_to(self, x, y):
186 print("line to %.1f %.1f" %( x, y))
187
188 def move_to(self, x, y):
189 print("move to %.1f %.1f" %( x, y))
190
191
192class VisualifeBridge(GraphicDevice):
193 def __init__(self, viewport):
194 self.__viewport = viewport
195 self.__x, self.__y = 0, 0
196 self.__n_lines = 0
197
198 def line_to(self, x, y):
199 self.__viewport.line("line-%d" % self.__n_lines, self.__x, self.__y, x, y)
200 self.__n_lines += 1
201 self.__x, self.__y = x, y
202
203 def move_to(self, x, y):
204 self.__x, self.__y = x, y
205
206
207class Logo:
208
209 def __init__(self, device=EchoDevice()):
210 self.__makers = {}
211 self.__turtle = Turtle()
212 self.__device = device
213 self.add_command("R", lambda angle: R(float(angle)))
214 self.add_command("L", lambda angle: L(float(angle)))
215 self.add_command("A", lambda angle: A(float(angle)))
216 self.add_command("F", lambda steps: F(float(steps)))
217 self.add_command("FASTER", lambda value: Faster(float(value)))
218 self.add_command("SLOWER", lambda value: Slower(float(value)))
219 self.add_command("U", U())
220 self.add_command("D", D())
221 self.add_command("J", lambda value: J(value))
222 self.add_command("JUMP", lambda value: J(value))
223 self.add_command("DEF", self.make_procedure)
224 self.add_command("REPEAT", self.make_repeat)
225 self.__loop_cnt = 0
226
227 @property
228 def turtle(self): return self.__turtle
229
230 def draw(self, *cmds):
231 for ci in cmds:
232 if isinstance(ci, str): # ---------- If it's a string, use factory to produce a command
233 cmd = self.produce_command(ci)
234 if not ci.startswith("DEF") and cmd: # ---------- Execute, if it's not a definition
235 cmd(self.__turtle, self.__device)
236 elif ci: # ---------- Execute a command
237 if ci: ci(self.__turtle, self.__device)
238
239 def add_command(self, name, cmd_maker):
240 self.__makers[name] = cmd_maker
241
242 def make_procedure(self, procedure_string):
243 name = procedure_string.split(maxsplit=1)[0]
244 p = Procedure(name)
245 cmnds = procedure_string[procedure_string.find('[')+1:procedure_string.find(']')].split(";")
246 for cmd in cmnds:
247 p.do_next(self.produce_command(cmd))
248
249 self.__makers[name] = p # ---------- here we register the new procedure in this factory
250 return p
251
252 def make_repeat(self, repeat_string):
253 tokens = repeat_string.split(maxsplit=1)
254 if tokens[1].find('[') >= 0:
255 tokens[1] = "DEF loop_" + str(self.__loop_cnt) + " := " + tokens[1]
256 self.__loop_cnt += 1
257 return Repeat(int(tokens[0]), self.produce_command(tokens[1]))
258
259 def produce_command(self, a_string):
260 if a_string.strip() == "": return None
261 tokens = a_string.split(maxsplit=1)
262 if isinstance(self.__makers[tokens[0]], TurtleCommand):
263 return self.__makers[tokens[0].strip()]
264 else:
265 return self.__makers[tokens[0].strip()](*tokens[1:])
266
267squares = """
268JUMP 3 3
269REPEAT 4 [F 194; R -90]
270JUMP 80 80
271DEF one_side := [F 10; R 90]
272DEF square := [REPEAT 4 one_side]
273REPEAT 36 [R 10; square]
274A 80
275F 100
276A -75
277F 110
278SLOWER 0.1
279REPEAT 36 [R 10; square]
280JUMP 10 180
281FASTER 0.2
282DEF diamond := [F 3; L 20; F 3; L 160; F 3; L 20; F 3]
283REPEAT 20 [A -130; diamond; A -80; diamond; A 0; F 7]
284"""
285
286if __name__ == "__main__":
287 square = Procedure("square")
288 square.do_next(F(10)).do_next(R(90)).do_next(F(10)).do_next(R(90)).do_next(F(10)).do_next(R(90)).do_next(F(10)).do_next(R(90))
289 path = [R(90), F(10), L(0), F(20), square]
290
291 import sys
292 sys.path.append("/Users/dgront/src.git/visualife/")
293 from visualife.core import SvgViewport
294
295 v = SvgViewport("logo.svg", 0, 0, 200, 200)
296 v.style = "stroke:black; stroke-width: 0.25;"
297 logo = Logo(VisualifeBridge(v))
298 commands = squares.splitlines()
299 logo.draw(*commands)
300 v.close()