compiled-knowledge 4.0.0a20__cp313-cp313-win32.whl

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.

Potentially problematic release.


This version of compiled-knowledge might be problematic. Click here for more details.

Files changed (178) hide show
  1. ck/__init__.py +0 -0
  2. ck/circuit/__init__.py +17 -0
  3. ck/circuit/_circuit_cy.c +37523 -0
  4. ck/circuit/_circuit_cy.cp313-win32.pyd +0 -0
  5. ck/circuit/_circuit_cy.pxd +32 -0
  6. ck/circuit/_circuit_cy.pyx +768 -0
  7. ck/circuit/_circuit_py.py +836 -0
  8. ck/circuit/tmp_const.py +74 -0
  9. ck/circuit_compiler/__init__.py +2 -0
  10. ck/circuit_compiler/circuit_compiler.py +26 -0
  11. ck/circuit_compiler/cython_vm_compiler/__init__.py +1 -0
  12. ck/circuit_compiler/cython_vm_compiler/_compiler.c +19824 -0
  13. ck/circuit_compiler/cython_vm_compiler/_compiler.cp313-win32.pyd +0 -0
  14. ck/circuit_compiler/cython_vm_compiler/_compiler.pyx +380 -0
  15. ck/circuit_compiler/cython_vm_compiler/cython_vm_compiler.py +121 -0
  16. ck/circuit_compiler/interpret_compiler.py +223 -0
  17. ck/circuit_compiler/llvm_compiler.py +388 -0
  18. ck/circuit_compiler/llvm_vm_compiler.py +546 -0
  19. ck/circuit_compiler/named_circuit_compilers.py +57 -0
  20. ck/circuit_compiler/support/__init__.py +0 -0
  21. ck/circuit_compiler/support/circuit_analyser/__init__.py +13 -0
  22. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.c +10618 -0
  23. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.cp313-win32.pyd +0 -0
  24. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_cy.pyx +98 -0
  25. ck/circuit_compiler/support/circuit_analyser/_circuit_analyser_py.py +93 -0
  26. ck/circuit_compiler/support/input_vars.py +148 -0
  27. ck/circuit_compiler/support/llvm_ir_function.py +234 -0
  28. ck/example/__init__.py +53 -0
  29. ck/example/alarm.py +366 -0
  30. ck/example/asia.py +28 -0
  31. ck/example/binary_clique.py +32 -0
  32. ck/example/bow_tie.py +33 -0
  33. ck/example/cancer.py +37 -0
  34. ck/example/chain.py +38 -0
  35. ck/example/child.py +199 -0
  36. ck/example/clique.py +33 -0
  37. ck/example/cnf_pgm.py +39 -0
  38. ck/example/diamond_square.py +68 -0
  39. ck/example/earthquake.py +36 -0
  40. ck/example/empty.py +10 -0
  41. ck/example/hailfinder.py +539 -0
  42. ck/example/hepar2.py +628 -0
  43. ck/example/insurance.py +504 -0
  44. ck/example/loop.py +40 -0
  45. ck/example/mildew.py +38161 -0
  46. ck/example/munin.py +22982 -0
  47. ck/example/pathfinder.py +53747 -0
  48. ck/example/rain.py +39 -0
  49. ck/example/rectangle.py +161 -0
  50. ck/example/run.py +30 -0
  51. ck/example/sachs.py +129 -0
  52. ck/example/sprinkler.py +30 -0
  53. ck/example/star.py +44 -0
  54. ck/example/stress.py +64 -0
  55. ck/example/student.py +43 -0
  56. ck/example/survey.py +46 -0
  57. ck/example/triangle_square.py +54 -0
  58. ck/example/truss.py +49 -0
  59. ck/in_out/__init__.py +3 -0
  60. ck/in_out/parse_ace_lmap.py +216 -0
  61. ck/in_out/parse_ace_nnf.py +322 -0
  62. ck/in_out/parse_net.py +480 -0
  63. ck/in_out/parser_utils.py +185 -0
  64. ck/in_out/pgm_pickle.py +42 -0
  65. ck/in_out/pgm_python.py +268 -0
  66. ck/in_out/render_bugs.py +111 -0
  67. ck/in_out/render_net.py +177 -0
  68. ck/in_out/render_pomegranate.py +184 -0
  69. ck/pgm.py +3475 -0
  70. ck/pgm_circuit/__init__.py +1 -0
  71. ck/pgm_circuit/marginals_program.py +352 -0
  72. ck/pgm_circuit/mpe_program.py +237 -0
  73. ck/pgm_circuit/pgm_circuit.py +79 -0
  74. ck/pgm_circuit/program_with_slotmap.py +236 -0
  75. ck/pgm_circuit/slot_map.py +35 -0
  76. ck/pgm_circuit/support/__init__.py +0 -0
  77. ck/pgm_circuit/support/compile_circuit.py +83 -0
  78. ck/pgm_circuit/target_marginals_program.py +103 -0
  79. ck/pgm_circuit/wmc_program.py +323 -0
  80. ck/pgm_compiler/__init__.py +2 -0
  81. ck/pgm_compiler/ace/__init__.py +1 -0
  82. ck/pgm_compiler/ace/ace.py +299 -0
  83. ck/pgm_compiler/factor_elimination.py +395 -0
  84. ck/pgm_compiler/named_pgm_compilers.py +63 -0
  85. ck/pgm_compiler/pgm_compiler.py +19 -0
  86. ck/pgm_compiler/recursive_conditioning.py +231 -0
  87. ck/pgm_compiler/support/__init__.py +0 -0
  88. ck/pgm_compiler/support/circuit_table/__init__.py +17 -0
  89. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.c +16396 -0
  90. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.cp313-win32.pyd +0 -0
  91. ck/pgm_compiler/support/circuit_table/_circuit_table_cy.pyx +332 -0
  92. ck/pgm_compiler/support/circuit_table/_circuit_table_py.py +304 -0
  93. ck/pgm_compiler/support/clusters.py +568 -0
  94. ck/pgm_compiler/support/factor_tables.py +406 -0
  95. ck/pgm_compiler/support/join_tree.py +332 -0
  96. ck/pgm_compiler/support/named_compiler_maker.py +43 -0
  97. ck/pgm_compiler/variable_elimination.py +91 -0
  98. ck/probability/__init__.py +0 -0
  99. ck/probability/empirical_probability_space.py +50 -0
  100. ck/probability/pgm_probability_space.py +32 -0
  101. ck/probability/probability_space.py +622 -0
  102. ck/program/__init__.py +3 -0
  103. ck/program/program.py +137 -0
  104. ck/program/program_buffer.py +180 -0
  105. ck/program/raw_program.py +67 -0
  106. ck/sampling/__init__.py +0 -0
  107. ck/sampling/forward_sampler.py +211 -0
  108. ck/sampling/marginals_direct_sampler.py +113 -0
  109. ck/sampling/sampler.py +62 -0
  110. ck/sampling/sampler_support.py +232 -0
  111. ck/sampling/uniform_sampler.py +72 -0
  112. ck/sampling/wmc_direct_sampler.py +171 -0
  113. ck/sampling/wmc_gibbs_sampler.py +153 -0
  114. ck/sampling/wmc_metropolis_sampler.py +165 -0
  115. ck/sampling/wmc_rejection_sampler.py +115 -0
  116. ck/utils/__init__.py +0 -0
  117. ck/utils/iter_extras.py +163 -0
  118. ck/utils/local_config.py +270 -0
  119. ck/utils/map_list.py +128 -0
  120. ck/utils/map_set.py +128 -0
  121. ck/utils/np_extras.py +51 -0
  122. ck/utils/random_extras.py +64 -0
  123. ck/utils/tmp_dir.py +94 -0
  124. ck_demos/__init__.py +0 -0
  125. ck_demos/ace/__init__.py +0 -0
  126. ck_demos/ace/copy_ace_to_ck.py +15 -0
  127. ck_demos/ace/demo_ace.py +49 -0
  128. ck_demos/all_demos.py +88 -0
  129. ck_demos/circuit/__init__.py +0 -0
  130. ck_demos/circuit/demo_circuit_dump.py +22 -0
  131. ck_demos/circuit/demo_derivatives.py +43 -0
  132. ck_demos/circuit_compiler/__init__.py +0 -0
  133. ck_demos/circuit_compiler/compare_circuit_compilers.py +32 -0
  134. ck_demos/circuit_compiler/show_llvm_program.py +26 -0
  135. ck_demos/pgm/__init__.py +0 -0
  136. ck_demos/pgm/demo_pgm_dump.py +18 -0
  137. ck_demos/pgm/demo_pgm_dump_stress.py +18 -0
  138. ck_demos/pgm/demo_pgm_string_rendering.py +15 -0
  139. ck_demos/pgm/show_examples.py +25 -0
  140. ck_demos/pgm_compiler/__init__.py +0 -0
  141. ck_demos/pgm_compiler/compare_pgm_compilers.py +63 -0
  142. ck_demos/pgm_compiler/demo_compiler_dump.py +60 -0
  143. ck_demos/pgm_compiler/demo_factor_elimination.py +47 -0
  144. ck_demos/pgm_compiler/demo_join_tree.py +25 -0
  145. ck_demos/pgm_compiler/demo_marginals_program.py +53 -0
  146. ck_demos/pgm_compiler/demo_mpe_program.py +55 -0
  147. ck_demos/pgm_compiler/demo_pgm_compiler.py +38 -0
  148. ck_demos/pgm_compiler/demo_recursive_conditioning.py +33 -0
  149. ck_demos/pgm_compiler/demo_variable_elimination.py +33 -0
  150. ck_demos/pgm_compiler/demo_wmc_program.py +29 -0
  151. ck_demos/pgm_compiler/time_fe_compiler.py +93 -0
  152. ck_demos/pgm_inference/__init__.py +0 -0
  153. ck_demos/pgm_inference/demo_inferencing_basic.py +188 -0
  154. ck_demos/pgm_inference/demo_inferencing_mpe_cancer.py +45 -0
  155. ck_demos/pgm_inference/demo_inferencing_wmc_and_mpe_sprinkler.py +154 -0
  156. ck_demos/pgm_inference/demo_inferencing_wmc_student.py +110 -0
  157. ck_demos/programs/__init__.py +0 -0
  158. ck_demos/programs/demo_program_buffer.py +24 -0
  159. ck_demos/programs/demo_program_multi.py +24 -0
  160. ck_demos/programs/demo_program_none.py +19 -0
  161. ck_demos/programs/demo_program_single.py +23 -0
  162. ck_demos/programs/demo_raw_program_interpreted.py +21 -0
  163. ck_demos/programs/demo_raw_program_llvm.py +21 -0
  164. ck_demos/sampling/__init__.py +0 -0
  165. ck_demos/sampling/check_sampler.py +71 -0
  166. ck_demos/sampling/demo_marginal_direct_sampler.py +40 -0
  167. ck_demos/sampling/demo_uniform_sampler.py +38 -0
  168. ck_demos/sampling/demo_wmc_direct_sampler.py +40 -0
  169. ck_demos/utils/__init__.py +0 -0
  170. ck_demos/utils/compare.py +120 -0
  171. ck_demos/utils/convert_network.py +45 -0
  172. ck_demos/utils/sample_model.py +216 -0
  173. ck_demos/utils/stop_watch.py +384 -0
  174. compiled_knowledge-4.0.0a20.dist-info/METADATA +50 -0
  175. compiled_knowledge-4.0.0a20.dist-info/RECORD +178 -0
  176. compiled_knowledge-4.0.0a20.dist-info/WHEEL +5 -0
  177. compiled_knowledge-4.0.0a20.dist-info/licenses/LICENSE.txt +21 -0
  178. compiled_knowledge-4.0.0a20.dist-info/top_level.txt +2 -0
ck/in_out/parse_net.py ADDED
@@ -0,0 +1,480 @@
1
+ from __future__ import annotations
2
+
3
+ import string
4
+ from typing import Protocol, Optional, List
5
+
6
+ from ck.in_out.parser_utils import ParseError, ParserInput
7
+ from ck.pgm import PGM, Factor, State
8
+ from ck.utils.iter_extras import combos_ranges
9
+
10
+
11
+ def read_network(input_stream, *, name: Optional[str] = None, network_builder: Optional[NetworkBuilder] = None) -> PGM:
12
+ """
13
+ Parser for Hugin "net" file format.
14
+
15
+ Read a network and return it as a PGM.
16
+ The input can be a string or a stream.
17
+ If the input is empty, then its is treated as an error.
18
+
19
+ This input is expected to conform to the following format.
20
+
21
+ <network> ::= <net_block> <node_block>* <potential_block>*
22
+
23
+ <net_block> ::= 'net' <block>
24
+
25
+ <node_block> ::= 'node' <NAME> <block>
26
+
27
+ <potential_block> ::= 'potential' <link> <block>
28
+
29
+ <block> ::= '{' <sentence>* '}'
30
+ <sentence> ::= <NAME> '=' <value> ';'
31
+
32
+ <link> ::= '(' <NAME> ')'
33
+ | '(' <NAME> '|' <NAME>+ ')'
34
+
35
+ <value> ::= <STRING> | <NUMBER> | <list>
36
+ <list> ::='(' <value>* ')'
37
+
38
+ The sentences of a <net_block> are ignored.
39
+
40
+ The sentences of a <node_block>
41
+ <name> of 'states' mandatory, with value that is a list of <STRING>
42
+ other sentences are ignored.
43
+
44
+ The sentences of a <potential_block>
45
+ <name> of 'data' mandatory, with value that is a list of (list of)* <NUMBER> (shape matching the link)
46
+ other sentences are ignored.
47
+
48
+ Here is a simple example input:
49
+ net{}
50
+ node a
51
+ {
52
+ states = ( "a0" "a1" );
53
+ }
54
+ node b
55
+ {
56
+ states = ( "b0" "b1" "b2" );
57
+ }
58
+
59
+ potential ( a )
60
+ {
61
+ data = ( 0.5 0.5 );
62
+ }
63
+ potential ( b | a )
64
+ {
65
+ data = ((0.4 0.4 0.2)(0.4 0.4 0.2)) ;
66
+ }
67
+ """
68
+ # Decorate the input stream
69
+ input_stream = ParserInput(input_stream)
70
+
71
+ if network_builder is None:
72
+ network_builder = PGM_NetworkBuilder(PGM_NetworkBuilder.add_function_dense)
73
+
74
+ bn = network_builder.start(name)
75
+
76
+ try:
77
+ _read_net(network_builder, bn, input_stream)
78
+ c = _read_past_space(input_stream)
79
+ if len(c) != 0:
80
+ input_stream.raise_error('unexpected extra characters at end of input')
81
+ except ParseError as e:
82
+ raise e
83
+ except Exception as e:
84
+ input_stream.raise_error(e)
85
+
86
+ return network_builder.done(bn)
87
+
88
+
89
+ class NetworkBuilder(Protocol):
90
+ """
91
+ This is a protocol used by read_network to build the network
92
+ """
93
+
94
+ def start(self, name) -> object:
95
+ ...
96
+
97
+ def add_node(self, network, name, states):
98
+ ...
99
+
100
+ def add_factor(self, network, child_name, parent_names, data):
101
+ ...
102
+
103
+ def done(self, network) -> PGM:
104
+ ...
105
+
106
+
107
+ class PGM_NetworkBuilder(NetworkBuilder):
108
+ """
109
+ This implementation of NetworkBuilder build a PGM object.
110
+ At construction time, pass one of the add_function_[...] methods
111
+ to the constructor, which selects the type of potential function
112
+ that is used to hold parameters.
113
+ """
114
+
115
+ @staticmethod
116
+ def add_function_dense(factor: Factor, data):
117
+ function = factor.set_dense()
118
+ shape = function.shape
119
+
120
+ def my_iter():
121
+ for key in combos_ranges(shape, flip=True):
122
+ parent = key[1:]
123
+ child = key[0]
124
+ d = data
125
+ for i in parent:
126
+ d = d[i]
127
+ yield d[child]
128
+
129
+ function.set_iter(my_iter())
130
+
131
+ @staticmethod
132
+ def add_function_cpt(factor: Factor, data):
133
+ function = factor.set_cpt()
134
+ shape = function.shape
135
+ parent_shape = shape[1:]
136
+ for parent in combos_ranges(parent_shape):
137
+ cpd = data
138
+ for i in parent:
139
+ cpd = cpd[i]
140
+ function.set_cpd(parent, cpd)
141
+
142
+ @staticmethod
143
+ def add_function_sparse(factor: Factor, data):
144
+ function = factor.set_sparse()
145
+ shape = function.shape
146
+ for key in combos_ranges(shape):
147
+ parent = key[1:]
148
+ child = key[0]
149
+ d = data
150
+ for i in parent:
151
+ d = d[i]
152
+ function[key] = d[child]
153
+
154
+ @staticmethod
155
+ def add_function_compact(factor: Factor, data):
156
+ function = factor.set_compact()
157
+ shape = function.shape
158
+ for key in combos_ranges(shape):
159
+ parent = key[1:]
160
+ child = key[0]
161
+ d = data
162
+ for i in parent:
163
+ d = d[i]
164
+ function[key] = d[child]
165
+
166
+ def __init__(self, potential_function_type):
167
+ """
168
+ Args:
169
+ potential_function_type: should be one of:
170
+ PGM_NetworkBuilder.add_function_dense,
171
+ PGM_NetworkBuilder.add_function_cpt,
172
+ PGM_NetworkBuilder.add_function_sparse,
173
+ PGM_NetworkBuilder.add_function_compact.
174
+ """
175
+ self.add_pot_function = potential_function_type
176
+
177
+ def start(self, name):
178
+ return [PGM(name), {}]
179
+
180
+ def add_node(self, pgm_pair, name, states):
181
+ pgm, rv_map = pgm_pair
182
+ rv = pgm.new_rv(name, states)
183
+ rv_map[name] = rv
184
+
185
+ def add_factor(self, pgm_pair, child_name, parent_names, data):
186
+ pgm, rv_map = pgm_pair
187
+ rvs = [rv_map[rv_name] for rv_name in [child_name] + parent_names]
188
+ factor = pgm.new_factor(*rvs)
189
+ self.add_pot_function(factor, data)
190
+
191
+ def done(self, pgm_pair) -> PGM:
192
+ pgm, rv_map = pgm_pair
193
+ return pgm
194
+
195
+
196
+ def _read_net(network_builder: NetworkBuilder, bn, input_stream) -> None:
197
+ _read_net_block(input_stream)
198
+ _read_blocks(network_builder, bn, input_stream)
199
+
200
+
201
+ def _pass_callback(*_) -> None:
202
+ pass
203
+
204
+
205
+ def _read_past_space(input_stream) -> str:
206
+ """
207
+ Returns:
208
+ either empty string, '', if end of input, otherwise a single character string that is not whitespace.
209
+ """
210
+ return input_stream.read_past_space(single_line=False, comment_char='%')
211
+
212
+
213
+ def _read_name(input_stream) -> str:
214
+ """
215
+ Returns:
216
+ the read name
217
+ """
218
+ c = _read_past_space(input_stream)
219
+ name = ""
220
+ while len(c) == 1:
221
+ if (
222
+ c in string.ascii_letters
223
+ or c in string.digits
224
+ or c in "_-."
225
+ ):
226
+ name += c
227
+ else:
228
+ input_stream.pushback(c)
229
+ break
230
+ c = input_stream.read_one()
231
+
232
+ if len(name) == 0:
233
+ input_stream.raise_error('expected a name but none read')
234
+
235
+ return name
236
+
237
+
238
+ def _read_keyword(input_stream) -> str:
239
+ """
240
+ Returns:
241
+ the read keyword
242
+ """
243
+ c = _read_past_space(input_stream)
244
+ keyword = ""
245
+ while len(c) == 1:
246
+ if c in string.ascii_letters:
247
+ keyword += c
248
+ else:
249
+ input_stream.pushback(c)
250
+ break
251
+ c = input_stream.read_one()
252
+ if len(keyword) == 0:
253
+ input_stream.raise_error('expected a keyword but none read')
254
+ return keyword.lower()
255
+
256
+
257
+ def _read_net_block(input_stream) -> None:
258
+ keyword = _read_keyword(input_stream)
259
+ if keyword != 'net':
260
+ input_stream.raise_error(f'expected keyword "net" but got: {keyword!r}')
261
+ _read_block(_pass_callback, input_stream)
262
+
263
+
264
+ def _read_blocks(network_builder, bn, input_stream) -> None:
265
+ lookahead = _read_past_space(input_stream)
266
+ while lookahead != '':
267
+ input_stream.pushback(lookahead)
268
+ keyword = _read_keyword(input_stream)
269
+ if keyword == 'node':
270
+ _read_node_block(network_builder, bn, input_stream)
271
+ elif keyword == 'potential':
272
+ _read_potential_block(network_builder, bn, input_stream)
273
+ else:
274
+ input_stream.raise_error(f'expected keyword "node" or "potential" but got: {keyword!r}')
275
+ lookahead = _read_past_space(input_stream)
276
+
277
+
278
+ def _read_node_block(network_builder, bn, input_stream) -> None:
279
+ """
280
+ Assumes already read the 'node' keyword from the input.
281
+ """
282
+ name = _read_name(input_stream)
283
+
284
+ names = [name, None]
285
+ states = [None]
286
+
287
+ def callback(_name: str, _value):
288
+ if _name == 'label':
289
+ if names[1] is not None:
290
+ input_stream.raise_error(f'duplicate "label" sentence in node: {_name}')
291
+ names[0] = names[1] = _value
292
+ elif _name == 'states':
293
+ if states[0] is not None:
294
+ input_stream.raise_error(f'duplicate "states" sentence in node: {_name}')
295
+ assert _value is not None
296
+ states[0] = _value
297
+
298
+ _read_block(callback, input_stream)
299
+
300
+ states: Optional[List[State]] = states[0]
301
+ name: str = names[0]
302
+
303
+ if states is None:
304
+ input_stream.raise_error(f'no "states" found in node: {name}')
305
+ elif len(states) < 2:
306
+ input_stream.raise_error(f'must be at least 2 states in a node: {name}')
307
+ network_builder.add_node(bn, name, states)
308
+
309
+
310
+ def _read_potential_block(network_builder, bn, input_stream):
311
+ """
312
+ Assumes already read the 'potential' keyword from the input.
313
+ """
314
+ link = _read_link(input_stream)
315
+ data = [None]
316
+
317
+ def callback(name, value):
318
+ if name == 'data':
319
+ if data[0] is not None:
320
+ input_stream.raise_error(f'duplicate "data" sentence in potential: {link}')
321
+ assert value is not None
322
+ data[0] = value
323
+
324
+ _read_block(callback, input_stream)
325
+ network_builder.add_factor(bn, link[0], link[1:], data[0])
326
+
327
+
328
+ def _read_link(input_stream) -> List[str]:
329
+ """
330
+ Returns:
331
+ the read link
332
+ """
333
+ c = _read_past_space(input_stream)
334
+ if c != '(':
335
+ input_stream.raise_error('reading a link, expected "("')
336
+
337
+ name = _read_name(input_stream)
338
+ link = [name]
339
+
340
+ c = _read_past_space(input_stream)
341
+ if c == ')':
342
+ return link
343
+ if c != '|':
344
+ input_stream.raise_error('reading a link, expected ")" or "|"')
345
+
346
+ c = _read_past_space(input_stream)
347
+ while c != ')':
348
+ if c == '':
349
+ input_stream.raise_error('unexpected end of input while reading a link for a potential')
350
+ input_stream.pushback(c)
351
+ name = _read_name(input_stream)
352
+ link.append(name)
353
+ c = _read_past_space(input_stream)
354
+ return link
355
+
356
+
357
+ def _read_block(sentence_callback, input_stream):
358
+ c = _read_past_space(input_stream)
359
+ if c != '{':
360
+ input_stream.raise_error('parse error - expected "{"')
361
+ c = _read_past_space(input_stream)
362
+ while c != '}':
363
+ if c == '':
364
+ input_stream.raise_error('parse error - unexpected end of input while reading a block')
365
+ input_stream.pushback(c)
366
+ _read_sentence(sentence_callback, input_stream)
367
+ c = _read_past_space(input_stream)
368
+
369
+
370
+ def _read_sentence(sentence_callback, input_stream):
371
+ name = _read_name(input_stream)
372
+ c = _read_past_space(input_stream)
373
+ if c != '=':
374
+ input_stream.raise_error('parse error - expected "="')
375
+ value = _read_value(input_stream)
376
+ c = _read_past_space(input_stream)
377
+ if c != ';':
378
+ input_stream.raise_error('parse error - expected ";"')
379
+ sentence_callback(name, value)
380
+
381
+
382
+ def _read_value(input_stream) -> List | str | float:
383
+ """
384
+ Returns:
385
+ (value, lookahead)
386
+ """
387
+ lookahead = _read_past_space(input_stream)
388
+ if lookahead == '':
389
+ input_stream.raise_error('parse error - expected value - unexpected end of input')
390
+
391
+ input_stream.pushback(lookahead)
392
+ if lookahead == '(':
393
+ # reading a list
394
+ value = _read_list(input_stream)
395
+ elif lookahead == '"':
396
+ # reading a string
397
+ value = _read_string(input_stream)
398
+ else:
399
+ # reading a number
400
+ value = _read_number(input_stream)
401
+ return value
402
+
403
+
404
+ def _read_list(input_stream) -> List:
405
+ """
406
+ Returns:
407
+ the read list of values
408
+ """
409
+ c = _read_past_space(input_stream)
410
+ if c != '(':
411
+ input_stream.raise_error('parse error - expected "("')
412
+ result = []
413
+ c = _read_past_space(input_stream)
414
+ while c != ')':
415
+ if c == '':
416
+ input_stream.raise_error('parse error - unexpected end of input while reading list')
417
+ input_stream.pushback(c)
418
+ value = _read_value(input_stream)
419
+ result.append(value)
420
+ c = _read_past_space(input_stream)
421
+ return result
422
+
423
+
424
+ def _read_string(input_stream) -> str:
425
+ """
426
+ Returns:
427
+ the read string value
428
+ """
429
+ c = _read_past_space(input_stream)
430
+ if c != '"':
431
+ input_stream.raise_error('parse error - expected open quote (")')
432
+ result = ""
433
+ while True:
434
+ c = input_stream.read_one()
435
+ if c == '':
436
+ input_stream.raise_error('parse error - unexpected end of input while reading string')
437
+ if c == '"':
438
+ break
439
+ if c == '\\':
440
+ c = input_stream.read_one()
441
+ if c in '\\\'\"':
442
+ pass
443
+ elif c == 'n':
444
+ c = '\n'
445
+ elif c == 'r':
446
+ c = '\r'
447
+ elif c == 'v':
448
+ c = '\v'
449
+ elif c == 'f':
450
+ c = '\f'
451
+ elif c == 'a':
452
+ c = '\a'
453
+ elif c == 'b':
454
+ c = '\b'
455
+ elif c == 't':
456
+ c = '\t'
457
+ else:
458
+ input_stream.raise_error(f'parse error - unexpected escape code while reading string: \\{c}')
459
+ result += c
460
+ return result
461
+
462
+
463
+ def _read_number(input_stream) -> float:
464
+ """
465
+ Return:
466
+ the read numeric value
467
+ """
468
+ value = ''
469
+ c = _read_past_space(input_stream)
470
+ while len(c) == 1:
471
+ if not (c in string.digits or c in ".eE+-"):
472
+ input_stream.pushback(c)
473
+ break
474
+ value += c
475
+ c = input_stream.read_one()
476
+ try:
477
+ value = float(value)
478
+ except ValueError:
479
+ input_stream.raise_error('parse error - could not parse number')
480
+ return value
@@ -0,0 +1,185 @@
1
+ """
2
+ Support for parsers.
3
+
4
+ Defines:
5
+ ParserInput
6
+ ParseError.
7
+ """
8
+ import re as _re
9
+ import string as _string
10
+ from io import StringIO as _StringIO
11
+
12
+
13
+ class ParseError(Exception):
14
+ """
15
+ Raised when a parse error occurs.
16
+ Keeps track of the location of the error (line number and character position in the line).
17
+ """
18
+
19
+ def __init__(self, line_num: int, char_pos: int, message: str):
20
+ full_msg = f'line {line_num}, pos {char_pos}: {message}'
21
+ super(ParseError, self).__init__(full_msg)
22
+ self.line_num: int = line_num
23
+ self.char_pos: int = char_pos
24
+
25
+
26
+ class ParserInput:
27
+ """
28
+ A wrapper for an input stream of characters with
29
+ integrated exception raising and infinite pushback.
30
+ """
31
+
32
+ def __init__(self, input_stream):
33
+ self._input = _check_input(input_stream)
34
+ self._prev_line_len = 0
35
+ self._cur_line = 0
36
+ self._cur_char = 1
37
+ self._lookahead = []
38
+
39
+ def read_one(self) -> str:
40
+ """
41
+ Read one character from the input stream.
42
+
43
+ Returns:
44
+ a character from the input stream, or '' if EOF.
45
+ """
46
+ if len(self._lookahead) > 0:
47
+ c = self._lookahead.pop()
48
+ else:
49
+ c = self._input.read(1)
50
+ if c == '\n':
51
+ self._prev_line_len = self._cur_char
52
+ self._cur_line += 1
53
+ self._cur_char = 1
54
+ else:
55
+ self._cur_char += 1
56
+ return c
57
+
58
+ def readline(self) -> str:
59
+ """
60
+ Returns:
61
+ the next line (including the trailing '\n') or '' if EOF.
62
+ """
63
+ line = ''
64
+ while True:
65
+ c = self.read_one()
66
+ line += c
67
+ if c == '' or c == '\n':
68
+ return line
69
+
70
+ def read_past_space(self, single_line: bool, comment_char=None) -> str:
71
+ """
72
+ Returns:
73
+ either empty string, '', if end of input, otherwise a single character string that is not whitespace.
74
+ If single_line is True, then '\n' is treated as eof.
75
+ """
76
+ c = self.read_one()
77
+ while True:
78
+ if not single_line:
79
+ while len(c) == 1 and c in _string.whitespace:
80
+ c = self.read_one()
81
+ else:
82
+ while len(c) == 1 and c in _string.whitespace and c != '\n':
83
+ c = self.read_one()
84
+
85
+ if comment_char is None or c != comment_char:
86
+ break
87
+
88
+ # in a comment
89
+ c = self.read_one()
90
+ while len(c) == 1 and c != '\n':
91
+ c = self.read_one()
92
+
93
+ if single_line and c == '\n':
94
+ self.pushback('\n')
95
+ c = ''
96
+
97
+ return c
98
+
99
+ def pushback(self, c: str) -> None:
100
+ """
101
+ Push a character back to the input stream.
102
+
103
+ Args:
104
+ c: a character to push back to the input stream.
105
+ """
106
+ assert (len(c) == 1)
107
+ self._lookahead.append(c)
108
+ # Do our best to keep the input position counters sensible
109
+ if c == '\n':
110
+ self._cur_line -= 1
111
+ self._cur_char = self._prev_line_len
112
+ self._prev_line_len = 0
113
+ else:
114
+ self._cur_char -= 1
115
+
116
+ def raise_error(self, message: str) -> None:
117
+ """
118
+ Raise a ParseError with the given message and the current line number and character position.
119
+
120
+ Args:
121
+ message: the error message.
122
+ """
123
+ if isinstance(message, Exception):
124
+ message = f'Exception: {message.__class__.__name__} {message}'
125
+ raise ParseError(self._cur_line, self._cur_char, message)
126
+
127
+
128
+ def _check_input(input_stream):
129
+ """
130
+ If the given argument is a string, then wrap it in a StringIO.
131
+ """
132
+ if isinstance(input_stream, str):
133
+ return _StringIO(input_stream)
134
+ else:
135
+ return input_stream
136
+
137
+
138
+ def escape_string(s: str, *, double_quotes: bool = False, single_quotes: bool = False) -> str:
139
+ """
140
+ Return the given string with backslash escaping for special characters.
141
+ Args:
142
+ s: The string to process.
143
+ double_quotes: should double quotes be escaped?
144
+ single_quotes: should single quotes be escaped?
145
+
146
+ Returns:
147
+
148
+ """
149
+ s = s.replace('\\', '\\\\') # must come first
150
+ s = s.replace('\n', '\\n')
151
+ s = s.replace('\r', '\\r')
152
+ s = s.replace('\v', '\\v')
153
+ s = s.replace('\f', '\\f')
154
+ s = s.replace('\f', '\\f')
155
+ s = s.replace('\a', '\\a')
156
+ s = s.replace('\b', '\\b')
157
+ s = s.replace('\t', '\\t')
158
+ if double_quotes:
159
+ s = s.replace('"', '\\"')
160
+ if single_quotes:
161
+ s = s.replace("'", "\\'")
162
+ return s
163
+
164
+
165
+ def clean_string(s: str, valid_chars: str = '[a-zA-Z0-9_]', replace_char: str = '_') -> str:
166
+ """
167
+ Remove any invalid char from the given string and replace with
168
+ the given 'replace_char'
169
+
170
+ Args:
171
+ s: The string to process.
172
+ valid_chars: the set of valid characters (as a regular expression).
173
+ replace_char: the character to replace invalid characters with.
174
+
175
+ Returns:
176
+ the cleaned string.
177
+ """
178
+ re = _re.compile(valid_chars)
179
+ result = ''
180
+ for c in s:
181
+ if re.match(c):
182
+ result += c
183
+ else:
184
+ result += replace_char
185
+ return result
@@ -0,0 +1,42 @@
1
+ import pickle
2
+ from pathlib import Path
3
+
4
+ from ck.pgm import PGM
5
+
6
+
7
+ def write_pickle(pgm: PGM, file) -> None:
8
+ """
9
+ Write the PGM as a pickle file.
10
+
11
+ Args:
12
+ pgm: The PGM to write.
13
+ file: Either a file name, file Path, or an object with a write method.
14
+ """
15
+ if isinstance(file, (str, Path)):
16
+ with open(file, 'wb') as f:
17
+ pickle.dump(pgm, f)
18
+ else:
19
+ pickle.dump(pgm, file)
20
+
21
+
22
+ def read_pickle(file) -> PGM:
23
+ """
24
+ Read a PGM as a pickle file.
25
+
26
+ Args:
27
+ file: Either a file name, file Path, or an object with a read method.
28
+
29
+ Returns:
30
+ A PGM object.
31
+
32
+ Raises:
33
+ RuntimeError: if the unpickled object is not a PGM.
34
+ """
35
+ if isinstance(file, (str, Path)):
36
+ with open(file, 'rb') as f:
37
+ pgm = pickle.load(f)
38
+ else:
39
+ pgm = pickle.load(file)
40
+ if not isinstance(pgm, PGM):
41
+ raise RuntimeError('not a PGM object')
42
+ return pgm