Python デザインパターン サンプルコード Interpreter
結城 浩「Java言語で学ぶデザインパターン入門」をPython化
Python3(3.11)で動くソースコード(.pyファイル .ipynbファイル)あります
「anaconda3」on .py「PyCharm」.ipynb「Jupyter Notebook」
(2023-11-19)Python3.11で動作確認済み
Node (parse) ↑ ↑ ↑ ↑ ↑ ProgramNode CommandListNode CommandNode RepeatCommandNode PrimitiveCommandNode (parse) (parse) (parse) (parse) (parse) Context StringTokenizer (nextToken, currentToken, skipToken, currentNumber) (nextToken, hasMoreTokens)
1 2 3 4 5 6
<program> ::= program <command list> <command list> ::= <command>* end <command> ::= <repeat command> | <primitive command> <repeat command> ::= repeat <number> <command list> <primitive command> ::= go | right | left <number> ::= ???
text = "program end" node = [program []] text = "program go end" node = [program [go]] text = "program go right go right go right go right end" node = [program [go, right, go, right, go, right, go, right]] text = "program repeat 4 go right end end" node = [program [[repeat 4 [go, right]]]] text = "program repeat 4 repeat 3 go right go left end right end end" node = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]]
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 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139
#!/usr/bin/env python3 # -*- coding: utf-8 -*- import sys from abc import ABCMeta, abstractmethod import re class Node(metaclass=ABCMeta): @abstractmethod def parse(self, context): pass # <program> ::= program <command list> class ProgramNode(Node): def __init__(self): self.commandListNode = None def parse(self, context): context.skipToken("program") self.commandListNode = CommandListNode() self.commandListNode.parse(context) def __str__(self): # インスタンスをprintすると呼ばれる return "[program {}]".format(self.commandListNode) # <command list> ::= <command>* end class CommandListNode(Node): def __init__(self): self.list = [] def parse(self, context): while True: if not context.currentToken(): raise ParseException("Missing 'end'") elif context.currentToken() == "end": context.skipToken("end") break else: commandNode = CommandNode() commandNode.parse(context) self.list.append(commandNode) def __str__(self): # インスタンスをprintすると呼ばれる return "[{}]".format(", ".join([str(el) for el in self.list])) # <command> ::= <repeat command> | <primitive command> class CommandNode(Node): def __init__(self): self.node = None def parse(self, context): if context.currentToken() == "repeat": self.node = RepeatCommandNode() self.node.parse(context) else: self.node = PrimitiveCommandNode() self.node.parse(context) def __str__(self): # インスタンスをprintすると呼ばれる return str(self.node) # <repeat command> :== repeat <number> <command list> class RepeatCommandNode(Node): def __init__(self): self.number = None self.commandListNode = [] def parse(self, context): context.skipToken("repeat") self.number = context.currentNumber() context.nextToken() self.commandListNode = CommandListNode() self.commandListNode.parse(context) def __str__(self): # インスタンスをprintすると呼ばれる return "[repeat {} {}]".format(self.number, self.commandListNode) # <primitive command> ::= go | right | left class PrimitiveCommandNode(Node): def __init__(self): self.name = "" def parse(self, context): self.name = context.currentToken() context.skipToken(self.name) if self.name not in ("go", "right", "left"): raise ParseException("{} is undefined".format(self.name)) def __str__(self): # インスタンスをprintすると呼ばれる return self.name class Context(object): def __init__(self, text): self.currentToken_ = None self.tokenizer = StringTokenizer(text) self.nextToken() def nextToken(self): # 次のトークンを得る(次のトークンに進む) if self.tokenizer.hasMoreTokens(): self.currentToken_ = self.tokenizer.nextToken() else: self.currentToken_ = None return self.currentToken_ def currentToken(self): # 現在のトークンを得る(次のトークンへは進まない) return self.currentToken_ def skipToken(self, token): # 現在のトークンをチェックしてから次のトークンを得る # (次のトークンに進む) if token != self.currentToken_: raise ParseException( \ "Warning: {} is expected, but {} is found" \ .format(token, self.currentToken_) ) self.nextToken() def currentNumber(self): # 現在のトークンを数値として得る(次のトークンへは進まない) try: number = int(self.currentToken_) except ValueError as e: raise ParseException("Warning: {} ".format(e)) return number class StringTokenizer(object): # Javaではutilクラス def __init__(self, text): # トークンに分割 self.tokens = re.split("[ \t\n\r\f]", text) #正規表現で分割 def nextToken(self): # 次のトークンを得る(次のトークンに進む) token = self.tokens[0] self.tokens = self.tokens[1:] return token def hasMoreTokens(self): # 次のトークンがあるかどうかを調べる return len(self.tokens) > 0 class ParseException(Exception): # 引数が使える自作例外 pass def main(): with open("program.txt") as reader: # ファイル読み込みの典型的な書き方 for text in reader: text = text.strip() # 文字列の前後から空白文字を削除 sys.stdout.write('text = "{}"\n'.format(text)) node = ProgramNode() # 構文ツリーの最上位 node.parse(Context(text)) sys.stdout.write("node = {}\n".format(node)) if __name__ == '__main__': main() """ text = "program end" node = [program []] text = "program go end" node = [program [go]] text = "program go right go right go right go right end" node = [program [go, right, go, right, go, right, go, right]] text = "program repeat 4 go right end end" node = [program [[repeat 4 [go, right]]]] text = "program repeat 4 repeat 3 go right go left end right end end" node = [program [[repeat 4 [[repeat 3 [go, right, go, left]], right]]]] """