pgvm 0.1.0__tar.gz → 0.2.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgvm
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: PGVM is an abbreviation for Pointer Graph Virtual Machine.
5
5
  Author-email: masaniki <masaniki.software@gmail.com>
6
6
  License-Expression: MIT
@@ -5,7 +5,7 @@ build-backend = "setuptools.build_meta"
5
5
 
6
6
  [project]
7
7
  name = "pgvm"
8
- version = "0.1.0"
8
+ version = "0.2.0"
9
9
  authors = [
10
10
  { name="masaniki", email="masaniki.software@gmail.com" },
11
11
  ]
@@ -0,0 +1,361 @@
1
+
2
+ import graphviz
3
+
4
+ from .error import PGVMRuntimeError, PGVMInvalidPath, PGVMSyntaxError
5
+
6
+ class EditableGraph():
7
+ """
8
+ @Summ: 編集可能なgraph構造を与えるclass.
9
+
10
+ @Desc:
11
+ - node番号は非負整数である。負の値はerror用。
12
+ """
13
+ PATH_DELIMITER="/"
14
+
15
+ def __init__(self,graph,rootNode:int,program:list=None):
16
+ """
17
+ @Summ: constructor.
18
+
19
+ @InsVars:
20
+ data:
21
+ @Summ: {node番号(int):{edgeLabel(str):node番号(int)}}。
22
+ @Type: Dict
23
+ rootNode:
24
+ @Summ: root nodeのnode番号。
25
+ @Type: Int
26
+ maxNodeIdx:
27
+ @Summ: node番号の最大値を記録する。
28
+ @Type: Int
29
+ program:
30
+ @Summ: programのdataを入れる。
31
+ @Type: Str型二次元配列。
32
+ programCounter:
33
+ @Summ: 次に実行するprogramの行番号を記録する。
34
+ @Desc: 例外messageを出すために必要。
35
+ @Type: Int
36
+ programLength:
37
+ @Summ: 次に実行するprogramの行数を記録する。
38
+ @Type: Int
39
+ """
40
+ self.graph=graph
41
+ self.rootNode=rootNode
42
+ self.maxNodeIdx=None
43
+ self.program=program
44
+ self.programCounter=0
45
+ if(program is None):
46
+ self.programLength=0
47
+ else:
48
+ self.programLength=len(program)
49
+
50
+ def loadProgramCSV(self,csvFile):
51
+ """
52
+ @Summ: programが書かれたCSV fileをloadする関数。
53
+
54
+ @Args:
55
+ csvFile:
56
+ @Summ: programが書かれたcsv file名。
57
+ @SemType: str型の二次元配列。
58
+ """
59
+ with open(csvFile,mode="r",encoding="utf-8") as f:
60
+ lineList=f.readlines()
61
+ programData=[]
62
+ for line in lineList:
63
+ if(line[-1]=="\n"):
64
+ notBreakLine=line[:-1]
65
+ else:
66
+ notBreakLine=line
67
+ argList=notBreakLine.split(",")
68
+ programData.append(argList)
69
+ self.program=programData
70
+ self.programLength=len(programData)
71
+
72
+ def visualizeProgram(self,filename):
73
+ """
74
+ @Summ: programをgraph構造で可視化する関数。
75
+ """
76
+ digraph=graphviz.Digraph()
77
+ digraph.filename=filename
78
+ digraph.format="svg"
79
+ digraph.attr("graph",rankdir="LR")
80
+ digraph.node(name="start")
81
+ digraph.node(name="end")
82
+ digraph.edge("start","row_0:command")
83
+ for i in range(self.programLength):
84
+ argList=self.program[i]
85
+ command=argList[0]
86
+ match command:
87
+ case "gn":
88
+ nodeLabel=f"<command> {command}|{argList[1]}|<jump1> {argList[2]}"
89
+ if(int(argList[2])<self.programLength):
90
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name=f"row_{argList[2]}:head")
91
+ else:
92
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name="end")
93
+ case "ge":
94
+ nodeLabel=f"<command> {command}|{argList[1]}|{argList[2]}|<jump1> {argList[3]}"
95
+ if(int(argList[3])<self.programLength):
96
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name=f"row_{argList[3]}:head")
97
+ else:
98
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name="end")
99
+ case "se":
100
+ nodeLabel=f"<command> {command}|{argList[1]}|{argList[2]}|<jump1> {argList[3]}"
101
+ if(int(argList[3])<self.programLength):
102
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name=f"row_{argList[3]}:head")
103
+ else:
104
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name="end")
105
+ case "del":
106
+ nodeLabel=f"<command> {command}|{argList[1]}|<jump1> {argList[2]}"
107
+ if(int(argList[2])<self.programLength):
108
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name=f"row_{argList[2]}:head")
109
+ else:
110
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name="end")
111
+ case "if":
112
+ nodeLabel=f"<command> {command}|{argList[1]}|{argList[2]}|<jump1> {argList[3]}|<jump2> {argList[4]}"
113
+ if(int(argList[3])<self.programLength):
114
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name=f"row_{argList[3]}:head")
115
+ else:
116
+ digraph.edge(tail_name=f"row_{i}:jump1",head_name="end")
117
+ if(int(argList[4])<self.programLength):
118
+ digraph.edge(tail_name=f"row_{i}:jump2",head_name=f"row_{argList[4]}:head")
119
+ else:
120
+ digraph.edge(tail_name=f"row_{i}:jump2",head_name="end")
121
+ case _:
122
+ raise PGVMSyntaxError(self.programCounter, f"{command} is invalid command.")
123
+ digraph.node(name=f"row_{i}",label=nodeLabel, shape="record")
124
+ digraph.render()
125
+
126
+
127
+ def __issueNewNode(self):
128
+ """
129
+ @Summ: 新しいnode番号を発行する関数。
130
+
131
+ @Desc: 最大値を利用して新しい番号を生成している。
132
+
133
+ @Returns:
134
+ @Summ: 新しいnode番号。
135
+ @Type: Int
136
+ """
137
+ if(self.maxNodeIdx is None):
138
+ self.maxNodeIdx=max(self.graph.keys())
139
+ newNodeIdx=self.maxNodeIdx+1
140
+ self.maxNodeIdx=newNodeIdx
141
+ self.graph[newNodeIdx]={}
142
+ return newNodeIdx
143
+
144
+ def visualize(self,filename):
145
+ """
146
+ @Summ: graphvizで可視化する関数。
147
+ """
148
+ digraph=graphviz.Digraph()
149
+ digraph.filename=filename
150
+ digraph.format="svg"
151
+ digraph.attr("graph",rankdir="LR")
152
+ for nodeIdx,edgeDict in self.graph.items():
153
+ # print(nodeIdx)
154
+ digraph.node(str(nodeIdx))
155
+ for edgeLabel,destNode in edgeDict.items():
156
+ digraph.edge(tail_name=str(nodeIdx), head_name=str(destNode), label=edgeLabel)
157
+ digraph.render()
158
+
159
+ def accessNode(self,path:list):
160
+ """
161
+ @Summ: pathからedgeを参照する関数。
162
+
163
+ @Args:
164
+ path:
165
+ @Summ: 参照するedge.
166
+ @SemType: Str型List.
167
+ @Returns:
168
+ - @Summ: nodeの到達履歴。
169
+ @Desc: 到達不可能になったらそこで終了する。
170
+ @Type: Int型List
171
+ - @Summ: 使わなかったedgeLabelの数。この数字を未到達度と呼ぶ。
172
+ @Desc: 0で到達完了を表す。
173
+ @Type: Int
174
+ """
175
+ history=[self.rootNode]
176
+ curEdgeDict=self.graph[self.rootNode]
177
+ pathLength=len(path)
178
+ unreached=pathLength
179
+ for i in range(pathLength):
180
+ edgeLabel=path[i]
181
+ curNode=curEdgeDict.get(edgeLabel)
182
+ if(curNode is None):
183
+ break
184
+ history.append(curNode)
185
+ curEdgeDict=self.graph[curNode]
186
+ unreached-=1
187
+ return history,unreached
188
+
189
+
190
+ def execute(self):
191
+ """
192
+ @Summ: programを実行する関数。
193
+ """
194
+ self.programCounter=0
195
+ while(True):
196
+ if(self.programLength<=self.programCounter):
197
+ break
198
+ argList=self.program[self.programCounter]
199
+ command=argList[0]
200
+ arglen=len(argList)
201
+ match command:
202
+ case "se":
203
+ if(arglen!=4):
204
+ raise PGVMSyntaxError(self.programCounter, "the argument count is should be 4.")
205
+ self.switchEdge(argList[1],argList[2])
206
+ self.programCounter=int(argList[3])
207
+ case "del":
208
+ if(arglen!=3):
209
+ raise PGVMSyntaxError(self.programCounter, "the argument count is should be 3.")
210
+ self.executeDeletion(argList[1])
211
+ self.programCounter=int(argList[2])
212
+ case "gn":
213
+ if(arglen!=3):
214
+ raise PGVMSyntaxError(self.programCounter, "the argument count is should be 3.")
215
+ self.generateNode(argList[1])
216
+ self.programCounter=int(argList[2])
217
+ case "ge":
218
+ if(arglen!=4):
219
+ raise PGVMSyntaxError(self.programCounter, "the argument count is should be 4.")
220
+ self.generateEdge(argList[1],argList[2])
221
+ self.programCounter=int(argList[3])
222
+ case "if":
223
+ if(arglen!=5):
224
+ raise PGVMSyntaxError(self.programCounter, "the argument count is should be 5.")
225
+ isSame=self.executeIf(argList[1],argList[2])
226
+ if(isSame):
227
+ self.programCounter=int(argList[3])
228
+ else:
229
+ self.programCounter=int(argList[4])
230
+ case _:
231
+ raise PGVMSyntaxError(self.programCounter, "unknown command.")
232
+ return
233
+
234
+ def executeDeletion(self,path:str):
235
+ """
236
+ @Summ: 到達可能なedgeを削除する関数。
237
+
238
+ @Args:
239
+ path:
240
+ @Summ: 既存のpath。このedgeを削除する。
241
+ @Type: Str
242
+ """
243
+ edgeList=path.split(self.PATH_DELIMITER)
244
+ lastNodePath=edgeList[:-1]
245
+ lastEdge=edgeList[-1]
246
+ history,unreached=self.accessNode(lastNodePath)
247
+ if(unreached==0):
248
+ lastNode=history[-1]
249
+ del self.graph[lastNode][lastEdge]
250
+ else:
251
+ raise PGVMInvalidPath(self.programCounter, path)
252
+
253
+ def generateNode(self,path):
254
+ """
255
+ @Summ: 未知のnodeへの新しいedgeを生成する関数。
256
+
257
+ @Args:
258
+ path:
259
+ @Summ: 新規生成するpathを表す。
260
+ @Desc: 未到達度=1である必要がある。
261
+ @Type: Str
262
+ """
263
+ edgeList=path.split(self.PATH_DELIMITER)
264
+ history,unreached=self.accessNode(edgeList)
265
+ if(unreached!=1):
266
+ raise PGVMInvalidPath(self.programCounter, path)
267
+ lastEdge=edgeList[-1]
268
+ lastNode=history[-1]
269
+ newNode=self.__issueNewNode()
270
+ self.graph[lastNode][lastEdge]=newNode
271
+
272
+ def generateEdge(self,path1,path2):
273
+ """
274
+ @Summ: 既知のnodeへの新しいedgeを生成する関数。
275
+
276
+ @Args:
277
+ path1:
278
+ @Summ: 終点を動かすpathを指定する。
279
+ @Desc: 未知のpathしか受け付けない。
280
+ @Type: Str
281
+ path2:
282
+ @Summ: edgeの新しい終着点を指定する。
283
+ @Desc: 既知のpathしか受け付けない。
284
+ @Type: Str
285
+ """
286
+ path1EdgeList=path1.split(self.PATH_DELIMITER)
287
+ path2EdgeList=path2.split(self.PATH_DELIMITER)
288
+ # self.changeEdgeDestination(path1EdgeList,path2EdgeList)
289
+ path1history,unused1=self.accessNode(path1EdgeList)
290
+ lastEdgeLabel=path1EdgeList[-1]
291
+ if(unused1!=1):
292
+ raise PGVMInvalidPath(self.programCounter, path1)
293
+ edgeStartNode=path1history[-1]
294
+ path2history,unused2=self.accessNode(path2EdgeList)
295
+ if(unused2>0):
296
+ raise PGVMInvalidPath(self.programCounter, path2)
297
+ newEndNode=path2history[-1]
298
+ self.graph[edgeStartNode][lastEdgeLabel]=newEndNode
299
+
300
+ def switchEdge(self,path1,path2):
301
+ """
302
+ @Summ: 既知のpathの終点を既知のnodeに切り替える関数。
303
+
304
+ @Desc: 始点と終点が同じ辺(二重辺)は禁止。
305
+
306
+ @Args:
307
+ path1:
308
+ @Summ: 終点を動かすpathを指定する。
309
+ @Desc: 既知のpathしか受け付けない。
310
+ @Type: Str
311
+ path2:
312
+ @Summ: edgeの新しい終着点を指定する。
313
+ @Desc: 既知のpathしか受け付けない。
314
+ @Type: Str
315
+ """
316
+ path1EdgeList=path1.split(self.PATH_DELIMITER)
317
+ path2EdgeList=path2.split(self.PATH_DELIMITER)
318
+ path1history,unused1=self.accessNode(path1EdgeList)
319
+ lastEdge=path1EdgeList[-1]
320
+ if(unused1>0):
321
+ raise PGVMInvalidPath(self.programCounter, path1)
322
+ newStartNode=path1history[-2]
323
+ path2history,unreached2=self.accessNode(path2EdgeList)
324
+ if(unreached2>0):
325
+ raise PGVMInvalidPath(self.programCounter, path2)
326
+ newEndNode=path2history[-1]
327
+ edgeInfo=self.graph[newStartNode]
328
+ if(newEndNode in edgeInfo.values()):
329
+ raise PGVMRuntimeError(self.programCounter, "Edge endpoint is duplicated.") #既存のedgeの終端が被る。
330
+ self.graph[newStartNode][lastEdge]=newEndNode
331
+
332
+ def executeIf(self,path1,path2):
333
+ """
334
+ @Summ: if文を実行する関数。
335
+
336
+ @Args:
337
+ path1:
338
+ @Summ: if文の第一引数。比較するpathその1。
339
+ @Type: Str
340
+ path2:
341
+ @Summ: if文の第二引数。比較するpathその2。
342
+ @Type: Str
343
+ @Returns:
344
+ @Summ: 同じ値の時にTrue.
345
+ @Type: Bool
346
+ """
347
+ path1EdgeList=path1.split(self.PATH_DELIMITER)
348
+ path1History,path1unreached=self.accessNode(path1EdgeList)
349
+ path2EdgeList=path2.split(self.PATH_DELIMITER)
350
+ path2History,path2unreached=self.accessNode(path2EdgeList)
351
+ if(path1unreached!=0):
352
+ raise PGVMInvalidPath(self.programCounter, path1)
353
+ if(path2unreached!=0):
354
+ raise PGVMInvalidPath(self.programCounter, path2)
355
+ path1LastNode=path1History[-1]
356
+ path2LastNode=path2History[-1]
357
+ if(path1LastNode==path2LastNode):
358
+ return True
359
+ else:
360
+ return False
361
+
@@ -0,0 +1,28 @@
1
+
2
+ class PGVMRuntimeError(RuntimeError):
3
+ """
4
+ @Summ: PGNM用の例外class.
5
+ """
6
+ def __init__(self, line:int, message:str):
7
+ super().__init__()
8
+ self.line=line
9
+ self.message=message
10
+
11
+ def __str__(self):
12
+ return f"line: {self.line}:\n\t{self.message}"
13
+
14
+ class PGVMInvalidPath(PGVMRuntimeError):
15
+ """
16
+ @Summ: pathが参照できない時のerror.
17
+ """
18
+ def __init__(self, line:int, path:str):
19
+ super().__init__(line,message=f"{path} is not accesible.")
20
+ self.line=line
21
+ self.path=path
22
+
23
+ class PGVMSyntaxError(PGVMRuntimeError):
24
+ """
25
+ @Summ: PGLの構文解析上のerror.
26
+ """
27
+ def __init__(self, line:int, message:str):
28
+ super().__init__(line,message)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pgvm
3
- Version: 0.1.0
3
+ Version: 0.2.0
4
4
  Summary: PGVM is an abbreviation for Pointer Graph Virtual Machine.
5
5
  Author-email: masaniki <masaniki.software@gmail.com>
6
6
  License-Expression: MIT
@@ -3,9 +3,10 @@ README.md
3
3
  pyproject.toml
4
4
  src/pgvm/__init__.py
5
5
  src/pgvm/editable_graph.py
6
+ src/pgvm/error.py
6
7
  src/pgvm.egg-info/PKG-INFO
7
8
  src/pgvm.egg-info/SOURCES.txt
8
9
  src/pgvm.egg-info/dependency_links.txt
9
10
  src/pgvm.egg-info/requires.txt
10
11
  src/pgvm.egg-info/top_level.txt
11
- tests/test_main.py
12
+ tests/test_graph_execution.py
@@ -0,0 +1,110 @@
1
+ from pathlib import Path
2
+ import sys
3
+ from copy import deepcopy
4
+
5
+ import yaml
6
+
7
+ #sys.pathを弄る。
8
+ projectDir=Path(__file__).parent.parent
9
+ packageDir=projectDir/"src"
10
+ sys.path.append(str(packageDir))
11
+
12
+ from pgvm import EditableGraph
13
+
14
+ def testSuitExecution(suitDir:Path):
15
+ """
16
+ @Summ: suit単位でtestを実行する関数。
17
+
18
+ @Desc: isTestのdefault値はtrue.
19
+
20
+ @Args:
21
+ suitDir:
22
+ @Summ: test suitのdirectory.
23
+ @Type: Path
24
+ """
25
+ suitInputFile=suitDir/"suit_input.yaml"
26
+ suitOutputFile=suitDir/"suit_output.yaml"
27
+ with open(suitInputFile,mode="r", encoding="utf-8") as f:
28
+ inputDict=yaml.safe_load(f)
29
+ resultDict={"detail":{}}
30
+ abstract=True
31
+ defaultConfig=inputDict.get("default")
32
+ if(defaultConfig is None):
33
+ defaultConfig={}
34
+ for caseDir in suitDir.iterdir():
35
+ if(caseDir.is_file()):
36
+ continue
37
+ caseName=caseDir.name
38
+ # inputDictでfiltering.
39
+ caseValue=inputDict.get(caseName,{})
40
+ caseConfig=deepcopy(defaultConfig)
41
+ for key,value in caseValue.items():
42
+ caseConfig[key]=value
43
+ isTest=caseConfig.get("test",False)
44
+ if(isTest==False):
45
+ continue
46
+ else:
47
+ del caseConfig["test"]
48
+ result=testCaseExecution(caseDir,**caseConfig)
49
+ if(result==False):
50
+ abstract=False
51
+ resultDict["detail"][caseName]=result
52
+ resultDict["abstract"]=abstract
53
+ with open(suitOutputFile,mode="w",encoding="utf-8") as f:
54
+ yaml.safe_dump(resultDict,f)
55
+ return
56
+
57
+
58
+ def testCaseExecution(caseDir:Path,outputName:str="output.yaml",expectedName:str|None=None,isDetail:bool=False)->bool:
59
+ """
60
+ @Summ: caes単位でtestを実行する関数。
61
+
62
+ @Args:
63
+ caseDir:
64
+ @Summ: test caseのdirectoryを指定する。
65
+ ouputName:
66
+ @Summ: 出力するfile名。
67
+ expectedName:
68
+ @Summ: 期待する出力file名。
69
+ @Desc: Noneの時は出力飲みする。
70
+ isDetail:
71
+ @Summ: 期待file以外のfileも出力する。
72
+ @Default: false
73
+ @Returns:
74
+ @Summ: 実際出力と期待出力が同じ時にtrue.
75
+ @Desc: 実際出力するだけの時もtrue.
76
+ """
77
+ graphFile=caseDir/"graph.yaml"
78
+ programFile=caseDir/"program.csv"
79
+ beforeDot=caseDir/"before.dot"
80
+ afterDot=caseDir/"after.dot"
81
+ outputFile=caseDir/outputName
82
+ with open(graphFile,mode="r",encoding="utf-8") as f:
83
+ graphDict=yaml.safe_load(f)
84
+ eg01=EditableGraph(graphDict,10)
85
+ if(isDetail):
86
+ eg01.visualize(beforeDot)
87
+ eg01.loadProgramCSV(programFile)
88
+ eg01.execute()
89
+ with open(outputFile,mode="w",encoding="utf-8") as f:
90
+ yaml.safe_dump(eg01.graph,f)
91
+ if(isDetail):
92
+ eg01.visualize(afterDot)
93
+ # 期待fileと比較する場合。
94
+ if(expectedName is None):
95
+ return True
96
+ else:
97
+ expectedFile=caseDir/expectedName
98
+ with open(expectedFile,mode="r",encoding="utf-8") as f:
99
+ expDict=yaml.safe_load(f)
100
+ # 2つのdict型を比較する。
101
+ return expDict==eg01.graph
102
+
103
+ if(__name__=="__main__"):
104
+ suitDir=Path(__file__).parent/"graph_execution"
105
+ caseDir=Path(__file__).parent/"graph_execution"/"invalid_command"
106
+ # isSuccess=testCaseExecution(caseDir,"expected.yaml",None,True)
107
+ # print(isSuccess)
108
+ # testSuitExecution(suitDir)
109
+ testCaseExecution(caseDir,isDetail=True)
110
+
@@ -1,303 +0,0 @@
1
- import graphviz
2
-
3
- class EditableGraph():
4
- """
5
- @Summ: 編集可能なgraph構造を与えるclass.
6
-
7
- @Desc:
8
- - node番号は非負整数である。負の値はerror用。
9
- """
10
- ROOT_NODE=0
11
- NEW_NODE=1
12
- DELETE_NODE=2
13
- NEW_EDGE_LABEL="new"
14
- DELETE_EDGE_LABEL="delete"
15
- PATH_DELIMITER="/"
16
-
17
-
18
- @classmethod
19
- def __initializeGraph(cls):
20
- """
21
- @Summ: 初期状態のgraphを出力する関数。
22
- """
23
- initGraph={cls.ROOT_NODE:{"new":cls.NEW_NODE, "delete":cls.DELETE_NODE},
24
- cls.NEW_NODE:{},
25
- cls.DELETE_NODE:{}
26
- }
27
- return initGraph
28
-
29
-
30
- def __init__(self,data,connectingNode:int,program:list=None):
31
- """
32
- @Summ: constructor.
33
-
34
- @InsVars:
35
- data:
36
- @Summ: {node番号(int):{edgeLabel(str):node番号(int)}}。
37
- @Type: Dict
38
- maxNodeIdx:
39
- @Summ: node番号の最大値を記録する。
40
- @Type: Int
41
- program:
42
- @Summ: programのdataを入れる。
43
- @Type: Str型二次元配列。
44
- programCounter:
45
- @Summ: 次に実行するprogramの行番号を記録する。
46
- @Desc: 例外messageを出すために必要。
47
- @Type: Int
48
- programLength:
49
- @Summ: 次に実行するprogramの行数を記録する。
50
- @Type: Int
51
- """
52
- initGraph=self.__initializeGraph()
53
- initGraph[self.ROOT_NODE]["file"]=connectingNode
54
- initGraph|=data
55
- self.data=initGraph
56
- self.maxNodeIdx=None
57
- self.program=program
58
- self.programCounter=0
59
- if(program is None):
60
- self.programLength=0
61
- else:
62
- self.proramLength=len(program)
63
-
64
- def loadProgramCSV(self,csvFile):
65
- """
66
- @Summ: programが書かれたCSV fileをloadする関数。
67
-
68
- @Args:
69
- csvFile:
70
- @Summ: programが書かれたcsv file名。
71
- @SemType: str型の二次元配列。
72
- """
73
- with open(csvFile,mode="r",encoding="utf-8") as f:
74
- lineList=f.readlines()
75
- programData=[]
76
- for line in lineList:
77
- if(line[-1]=="\n"):
78
- notBreakLine=line[:-1]
79
- else:
80
- notBreakLine=line
81
- argList=notBreakLine.split(",")
82
- programData.append(argList)
83
- self.program=programData
84
- self.programLength=len(programData)
85
-
86
- def __issueNewNode(self):
87
- """
88
- @Summ: 新しいnode番号を発行する関数。
89
-
90
- @Desc: 最大値を利用して新しい番号を生成している。
91
-
92
- @Returns:
93
- @Summ: 新しいnode番号。
94
- @Type: Int
95
- """
96
- if(self.maxNodeIdx is None):
97
- self.maxNodeIdx=max(self.data.keys())
98
- newNodeIdx=self.maxNodeIdx+1
99
- self.maxNodeIdx=newNodeIdx
100
- self.data[newNodeIdx]={}
101
- return newNodeIdx
102
-
103
- def visualize(self,filename):
104
- """
105
- @Summ: graphvizで可視化する関数。
106
- """
107
- graph=graphviz.Digraph()
108
- graph.filename=filename
109
- graph.format="svg"
110
- graph.attr("graph",rankdir="LR")
111
- for nodeIdx,edgeDict in self.data.items():
112
- # print(nodeIdx)
113
- graph.node(str(nodeIdx))
114
- for edgeLabel,destNode in edgeDict.items():
115
- graph.edge(tail_name=str(nodeIdx), head_name=str(destNode), label=edgeLabel)
116
- graph.render()
117
-
118
- def accessNode(self,path:list):
119
- """
120
- @Summ: pathからedgeを参照する関数。
121
-
122
- @Args:
123
- path:
124
- @Summ: 参照するedge.
125
- @SemType: Str型List.
126
- @Returns:
127
- - @Summ: nodeの到達履歴。
128
- @Desc: 到達不可能になったらそこで終了する。
129
- @Type: Int型List
130
- - @Summ: 使わなかったedgeLabelの数。この数字を未到達度と呼ぶ。
131
- @Desc: 0で到達完了を表す。
132
- @Type: Int
133
- """
134
- history=[self.ROOT_NODE]
135
- curEdgeDict=self.data[self.ROOT_NODE]
136
- pathLength=len(path)
137
- unreached=pathLength
138
- for i in range(pathLength):
139
- edgeLabel=path[i]
140
- curNode=curEdgeDict.get(edgeLabel)
141
- if(curNode is None):
142
- break
143
- history.append(curNode)
144
- curEdgeDict=self.data[curNode]
145
- unreached-=1
146
- return history,unreached
147
-
148
- def changeEdgeDestination(self,oldPath,newPath):
149
- """
150
- @Summ: edgeの終点を変更する関数。
151
-
152
- @Desc:
153
- - nodeの生成を伴わないedgeの生成まではできる。
154
- - 始点と終点が同じ辺(二重辺)は禁止。
155
-
156
- @Args:
157
- oldPath:
158
- @Summ: 現在のedgeの終着点。
159
- @Desc:
160
- - 未到達度0の時は、既存のedgeの終点を付け替える作業。
161
- - 未到達度1の時は、新しいedgeLabelを生成する。
162
- - それ以上の未到達度は受け付けない。
163
- @SemType: Str型List.
164
- newPath:
165
- @Summ: edgeの新しい終着点を指定する。
166
- @Desc: 未到達度0のpathしか受け付けない。
167
- @SemType: Str型List.
168
- """
169
- oldDestHistory,oldUnused=self.accessNode(oldPath)
170
- lastEdgeLabel=oldPath[-1]
171
- if(oldUnused==0):
172
- edgeStartNode=oldDestHistory[-2]
173
- elif(oldUnused==1):
174
- edgeStartNode=oldDestHistory[-1]
175
- else:
176
- raise RuntimeError(f"line: {self.programCounter}:\n\t{oldPath} is not accessable.")
177
- newDestHistory,newUnused=self.accessNode(newPath)
178
- if(newUnused>0):
179
- raise RuntimeError(f"line: {self.programCounter}:\n\t{newPath} is not accessable.")
180
- newEndNode=newDestHistory[-1]
181
- edgeInfo=self.data[edgeStartNode]
182
- if(newEndNode in edgeInfo.values()):
183
- raise RuntimeError(f"line: {self.programCounter}:\n\t Edge endpoint is duplicated.") #既存のedgeの終端が被る。
184
- self.data[edgeStartNode][lastEdgeLabel]=newEndNode
185
-
186
- def deleteEdge(self,path):
187
- """
188
- @Summ: 到達可能なedgeを削除する関数。
189
-
190
- @Args:
191
- path:
192
- @Summ: 削除するedge.
193
- @SemType: Str型List.
194
- """
195
- lastNodePath=path[:-1]
196
- lastEdge=path[-1]
197
- history,unreached=self.accessNode(lastNodePath)
198
- if(unreached==0):
199
- lastNode=history[-1]
200
- del self.data[lastNode][lastEdge]
201
- else:
202
- raise RuntimeError(f"line: {self.programCounter}:\n\t{path} is not accessable.")
203
-
204
- def generateEdge(self,path):
205
- """
206
- @Summ: nodeの生成を伴うedgeの生成を行う関数。
207
-
208
- @Args:
209
- path:
210
- @Summ: 新しく生成するpath.
211
- @Desc: 未到達度=1である必要がある。
212
- @SemType: Str型List.
213
- """
214
- history,unreached=self.accessNode(path)
215
- if(unreached!=1):
216
- raise RuntimeError(f"line: {self.programCounter}:\n\t{path} is not accessable.")
217
- lastEdge=path[-1]
218
- lastNode=history[-1]
219
- newNode=self.__issueNewNode()
220
- self.data[lastNode][lastEdge]=newNode
221
-
222
- def execute(self):
223
- """
224
- @Summ: programを実行する関数。
225
- """
226
- self.programCounter=0
227
- while(True):
228
- if(self.programLength<=self.programCounter):
229
- break
230
- argList=self.program[self.programCounter]
231
- if(argList[0]=="cp"):
232
- self.executeCopy(argList[1],argList[2])
233
- self.programCounter=int(argList[3])
234
- continue
235
- elif(argList[0]=="if"):
236
- isSame=self.executeIf(argList[1],argList[2])
237
- if(isSame):
238
- self.programCounter=int(argList[3])
239
- else:
240
- self.programCounter=int(argList[4])
241
- return
242
-
243
- def executeCopy(self,path1,path2):
244
- """
245
- @Summ: copy文を実行する関数。
246
-
247
- @Args:
248
- path1:
249
- @Summ: copy文の第一引数。編集するpathを表す。
250
- @Type: Str
251
- path2:
252
- @Summ: copy文の第二引数。上書きするpathを表す。
253
- @Type: Str
254
- """
255
- path1EdgeList=path1.split(self.PATH_DELIMITER)
256
- if(path2==self.NEW_EDGE_LABEL):
257
- self.generateEdge(path1EdgeList)
258
- elif(path2==self.DELETE_EDGE_LABEL):
259
- self.deleteEdge(path1EdgeList)
260
- else:
261
- path2EdgeList=path2.split(self.PATH_DELIMITER)
262
- self.changeEdgeDestination(path1EdgeList,path2EdgeList)
263
-
264
- def executeIf(self,path1,path2):
265
- """
266
- @Summ: if文を実行する関数。
267
-
268
- @Desc:
269
- - path1==未到達度1∧path2==DELETE_NODEの時、edgeが存在しない時にTrueを出力する。
270
-
271
- @Args:
272
- path1:
273
- @Summ: if文の第一引数。比較するpathその1。
274
- @Type: Str
275
- path2:
276
- @Summ: if文の第二引数。比較するpathその2。
277
- @Type: Str
278
- @Returns:
279
- @Summ: 同じ値の時にTrue.
280
- @Type: Bool
281
- """
282
- path1EdgeList=path1.split(self.PATH_DELIMITER)
283
- path1History,path1unreached=self.accessNode(path1EdgeList)
284
- if(path2==self.DELETE_EDGE_LABEL):
285
- if(path1unreached==0):
286
- return False
287
- elif(path1unreached==1):
288
- return True
289
- else:
290
- raise RuntimeError(f"line {self.programCounter}:\n\t{path2} is not accessable.")
291
- path2EdgeList=path2.split(self.PATH_DELIMITER)
292
- path2History,path2unreached=self.accessNode(path2EdgeList)
293
- if(path1unreached!=0):
294
- raise RuntimeError(f"line {self.programCounter}:\n\t{path1} is not accessable.")
295
- if(path2unreached!=0):
296
- raise RuntimeError(f"line {self.programCounter}:\n\t{path2} is not accessable.")
297
- path1LastNode=path1History[-1]
298
- path2LastNode=path2History[-1]
299
- if(path1LastNode==path2LastNode):
300
- return True
301
- else:
302
- return False
303
-
@@ -1,124 +0,0 @@
1
- from pathlib import Path
2
- import sys
3
-
4
- #sys.pathを弄る。
5
- projectDir=Path(__file__).parent.parent.parent
6
- sys.path.append(str(projectDir))
7
-
8
- from library import module1
9
-
10
- testDir=Path(__file__).parent/"test"
11
-
12
- def test_example_file(exampleName:str,outName:str=None,isTest:bool=None)->bool:
13
- """
14
- Abst: 1つの事例をtestする関数。
15
-
16
- Expl: 入力dataや出力dataがfileの場合。
17
-
18
- Args:
19
- exampleName(str): test caseのdirecotry名。
20
- outName(str): 出力先のfile名。拡張子は不要。
21
- isTest(bool): 期待出力と実際出力を比較する⇒True。
22
- Returns:
23
- bool: 期待通りの出力⇒True。
24
- """
25
- if(outName is None):
26
- outName="output"
27
- if(isTest is None):
28
- isTest=True
29
- exampleDir=testDir/exampleName
30
- inFile=exampleDir/"input.txt"
31
- outFile=exampleDir/f"{outName}.txt"
32
- expFile=exampleDir/"expected.txt"
33
- parser1=module1()
34
- tree=parser1.parseFromFile(inFile)
35
- with open(outFile,mode="w",encoding="utf-8") as f:
36
- f.write(tree.pretty())
37
- if(isTest):
38
- with open(outFile,mode="r",encoding="utf-8") as f:
39
- outText=f.read()
40
- with open(expFile,mode='r',encoding="utf-8") as f:
41
- expText=f.read()
42
- return outText==expText
43
-
44
- def test_example_dir(exampleName:str,outName:str=None,isTest:bool=None)->bool:
45
- """
46
- Abst: 1つの事例をtestする関数。
47
-
48
- Expl: 入力dataや出力dataがdirectoryの場合。
49
-
50
- Args:
51
- exampleName(str): test caseのdirecotry名。
52
- outName(str): 出力先のfile名。拡張子は不要。
53
- isTest(bool): 期待出力と実際出力を比較する⇒True。
54
- Returns:
55
- bool: 期待通りの出力⇒True。
56
- """
57
- if(outName is None):
58
- outName="output"
59
- if(isTest is None):
60
- isTest=True
61
- exampleDir=testDir/exampleName
62
- inDir=exampleDir/"in"
63
- inFile=inDir/"in.txt"
64
- outDir=exampleDir/f"{outName}"
65
- if(not outDir.is_dir()):
66
- outDir.mkdir()
67
- outFile=outDir/"out.txt"
68
- expDir=exampleDir/"exp"
69
- expFile=expDir/"exp.txt"
70
- parser1=module1()
71
- tree=parser1.parseFromFile(inFile)
72
- with open(outFile,mode="w",encoding="utf-8") as f:
73
- f.write(tree.pretty())
74
- if(isTest):
75
- with open(outFile,mode="r",encoding="utf-8") as f:
76
- outText=f.read()
77
- with open(expFile,mode='r',encoding="utf-8") as f:
78
- expText=f.read()
79
- return outText==expText
80
-
81
-
82
- def test_all(outName:str=None,isTest:bool=None)->dict:
83
- """
84
- Abst: 全てのtest caseをtestする関数。
85
-
86
- Args:
87
- outName(str): 出力先のfile名。拡張子は不要。
88
- isTest(bool): 期待出力と実際出力を比較する⇒True。
89
- Returns:
90
- dict: {testCase名(str):真理値(bool)}
91
- """
92
- resultDict={}
93
- for exampleDir in testDir.iterdir():
94
- exampleName=exampleDir.name
95
- result=test_example_dir(exampleName,outName=outName,isTest=isTest)
96
- resultDict[exampleName]=result
97
- return resultDict
98
-
99
- def test_clear(deleteList:list):
100
- """
101
- Abst: test caseのdirectoryを綺麗にする関数。
102
-
103
- Expl: fileの削除にしか対応していない。
104
-
105
- Notes: blacklist方式にすることで、意図しないfile削除を起こさないようにしている。
106
-
107
- Args:
108
- deleteList(list): 削除するfile名のlist。file名はexample directoryからの相対pathで指定する。
109
- """
110
- for exampleDir in testDir.iterdir():
111
- for fileDir in exampleDir.iterdir():
112
- fileName=fileDir.name
113
- if(fileName in deleteList):
114
- fileDir.unlink()
115
-
116
-
117
- if(__name__=="__main__"):
118
- tup=("case2", 70)
119
- x=tup[0]
120
- y=tup[1]
121
- test_example_file("case01","out",isTest=False)
122
- # test_all("out",isTest=False)
123
-
124
-
File without changes
File without changes
File without changes
File without changes