prefscript 1.1__py3-none-any.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.
ascii7io.py ADDED
@@ -0,0 +1,43 @@
1
+ '''
2
+ Single-integer encoding and decoding of (printable) 7-bit ASCII strings
3
+ '''
4
+
5
+ TWO_pow_7 = 1 << 7
6
+
7
+ def str2int(s):
8
+ 'integer from any string, even with nonprintable chars'
9
+ r = 0
10
+ for c in s:
11
+ r = r * TWO_pow_7 + ord(c)
12
+ return r
13
+
14
+ def int2raw_str(n):
15
+ 'string from any integer, even with nonprintable chars'
16
+ r = list()
17
+ while n > 0:
18
+ r.append(chr(n % TWO_pow_7))
19
+ n //= TWO_pow_7
20
+ return ''.join(reversed(r))
21
+
22
+ def int2str(n):
23
+ 'string from any integer, nonprintable chars replaced by underscores'
24
+ r = list()
25
+ while n > 0:
26
+ c = n % TWO_pow_7
27
+ if 31 < c:
28
+ r.append(chr(c))
29
+ else:
30
+ r.append('_')
31
+ n //= TWO_pow_7
32
+ return ''.join(reversed(r))
33
+
34
+ if __name__ == "__main__":
35
+ print(TWO_pow_7)
36
+ for s in ['', 'a', 'aa', 'abcde']:
37
+ n = str2int(s)
38
+ print(s, n, int2str(n))
39
+ print(hw := str2int("Hello, World!"))
40
+ print(int2raw_str(hw))
41
+ print(int2str(hw))
42
+ for i in range(10000, 100000): print(int2str(i*103), end = ' ')
43
+
@@ -0,0 +1 @@
1
+ from .src.cantorpairs import __version__, dp, pr_L, pr_R, tup_e, tup_i, s_tup, pr
@@ -0,0 +1,117 @@
1
+ '''
2
+ Author: Jose L Balcazar, ORCID 0000-0003-4248-4528, april 2023 onwards
3
+ Copyleft: MIT License (https://en.wikipedia.org/wiki/MIT_License)
4
+
5
+ Ancillary functions for PReFScript, the Partial Recursive Functions lab.
6
+
7
+ Implements a slight variant of Cantor's pair / unpair functions;
8
+ adapted from `https://en.wikipedia.org/wiki/Pairing_function`
9
+ The dotted pair is a bijection between `NxN` and `N-{0}`:
10
+ the natural number zero is used as "nil".
11
+
12
+ Project started: mid Germinal 2003.
13
+ Current version: 0.2, early Thermidor 2024.
14
+ Not pip-installable as of today. See README at `https://github.com/balqui/cantorpairs`
15
+
16
+ After each push, the following extra incantation is most likely
17
+ necessary in the local copy of the git repo for `prefscript`:
18
+ `git submodule update --remote`
19
+ '''
20
+
21
+ from functools import cache
22
+ from math import isqrt
23
+
24
+ __version__ = "0.3"
25
+
26
+ @cache
27
+ def _isqrt(n):
28
+ return isqrt(n)
29
+
30
+ # ~ def _isqrt(n):
31
+ # ~ '''
32
+ # ~ int square root via binary search, own program
33
+ # ~ because math.sqrt fails with big numbers, e.g. the
34
+ # ~ decoding of dp(10, 10^17) comes out wrong.
35
+ # ~ '''
36
+ # ~ def rr(n, k):
37
+ # ~ """
38
+ # ~ Pre: k <= sqrt(n)
39
+ # ~ Post: a <= sqrt(n) < b and b - a = k
40
+ # ~ """
41
+ # ~ if 4*k*k > n:
42
+ # ~ return k, 2*k
43
+ # ~ a, b = rr(n, 2*k)
44
+ # ~ m = (a + b)//2 # m = a + k
45
+ # ~ if m*m <= n:
46
+ # ~ return m, b
47
+ # ~ else:
48
+ # ~ return a, m
49
+ # ~ assert n >= 0
50
+ # ~ if n == 0:
51
+ # ~ return 0
52
+ # ~ return rr(n, 1)[0]
53
+
54
+ @cache
55
+ def _unpair(z):
56
+ "local sq root instead of math.sqrt"
57
+ assert z > 0
58
+ w = (_isqrt(8*(z - 1) + 1) - 1)//2
59
+ t = (w*w + w)//2
60
+ x = z - 1 - t
61
+ return x, w - x
62
+
63
+ @cache
64
+ def dp(x, y):
65
+ return ((x + y)*(x + y + 1))//2 + x + 1
66
+
67
+ def pr_L(z):
68
+ if z == 0:
69
+ return 0
70
+ return _unpair(z)[0]
71
+
72
+ def pr_R(z):
73
+ if z == 0:
74
+ return 0
75
+ return _unpair(z)[1]
76
+
77
+ def tup_e(*nums):
78
+ '''
79
+ nums: arbitrary quantity of numbers to encode the sequence;
80
+ fall back into the iterable version, the tuple cast will do nothing
81
+ '''
82
+ return tup_i(nums)
83
+
84
+ def tup_i(nums):
85
+ '''
86
+ nums expected to be an iterable here;
87
+ the reversed nature of the encoding needs to
88
+ expand it into a sequence;
89
+ end of sequence (= empty sequence) encoded
90
+ by 0 in its role of # nil, out of dotted pair range
91
+ '''
92
+ t = 0
93
+ for i in reversed(tuple(nums)):
94
+ t = dp(i, t)
95
+ return t
96
+
97
+ @cache
98
+ def s_tup(t, k):
99
+ '''
100
+ suffix tuple: t assumed a tuple of at least k components,
101
+ return the suffix tuple from k-th on;
102
+ t itself for k == 0, empty tuple 0 if k larger than len of t
103
+ '''
104
+ if t == 0:
105
+ "empty tuple or original k too large"
106
+ return 0
107
+ if k == 0:
108
+ "full suffix"
109
+ return t
110
+ return s_tup(pr_R(t), k-1)
111
+
112
+ def pr(t, k):
113
+ '''
114
+ projection function: get the k-th component;
115
+ returns zero (meaning end of tuple) if k is too large
116
+ '''
117
+ return pr_L(s_tup(t, k))
@@ -0,0 +1,38 @@
1
+ '''
2
+ Author: Jose L Balcazar, ORCID 0000-0003-4248-4528, april 2023 onwards
3
+ Copyleft: MIT License (https://en.wikipedia.org/wiki/MIT_License)
4
+
5
+ Ancillary functions for the Partial Recursive Functions lab.
6
+
7
+ Examples of usage of pairing and tupling.
8
+ '''
9
+
10
+ import cantorpairs as cp
11
+
12
+ for i in range(4):
13
+ for j in range(4):
14
+ print(i, j, cp.dp(i, j))
15
+
16
+ for i in range(90, 100):
17
+ print(cp.pr_L(i), cp.pr_R(i), cp.dp(cp.pr_L(i), cp.pr_R(i)), i)
18
+
19
+ t = cp.tup_e(4, 7, 56, 101)
20
+ for i in range(5):
21
+ 'last call is actually out of range'
22
+ print(cp.pr(t, i))
23
+
24
+ st = cp.s_tup(t, 2)
25
+ print(st)
26
+ for i in range(3):
27
+ print(cp.pr(st, i))
28
+
29
+ while t:
30
+ print(t, cp.pr_L(t))
31
+ t = cp.pr_R(t)
32
+
33
+ t = cp.tup_i(range(8, 16))
34
+ while t:
35
+ print(t, cp.pr_L(t))
36
+ t = cp.pr_R(t)
37
+
38
+
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2023 Balqui
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
@@ -0,0 +1,60 @@
1
+ Metadata-Version: 2.1
2
+ Name: prefscript
3
+ Version: 1.1
4
+ Summary: Partial Recursive Functions for Scripting
5
+ Author-email: José Luis Balcázar <jose.luis.balcazar@upc.edu>
6
+ Project-URL: Homepage, https://github.com/balqui/prefscript
7
+ Project-URL: Bug Tracker, https://github.com/balqui/prefscript/issues
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: License :: OSI Approved :: MIT License
10
+ Classifier: Operating System :: OS Independent
11
+ Requires-Python: >=3.10
12
+ Description-Content-Type: text/markdown
13
+ License-File: LICENSE
14
+ Requires-Dist: pytokr >=1.0
15
+
16
+ # PReFScript:
17
+ ## Partial Recursive Functions for Scripting
18
+
19
+ Author: Jose L Balcazar, ORCID 0000-0003-4248-4528
20
+
21
+ Project started: mid Germinal 2003.
22
+
23
+ Current version: 1.1, late Thermidor 2024.
24
+
25
+ Copyleft: MIT License (https://en.wikipedia.org/wiki/MIT_License)
26
+
27
+ A Python-based environment to explore and experiment with partial
28
+ recursive functions; naturally doubles as a (purely functional)
29
+ programming language, but it is not intended to be used much as such.
30
+
31
+ ### Scripts
32
+
33
+ In PReFScript, a script is a sequence of functions defined in terms of each
34
+ other and of a few basic functions via the partial recursion rules
35
+ of composition and minimization. All functions are from the
36
+ natural numbers into the natural numbers and may be undefined
37
+ for some inputs. In order to handle tuples or sequences of natural
38
+ numbers, a Cantor-like encoding is used.
39
+ The always available basic functions include:
40
+ `k_1`, the constant 1 function;
41
+ `id`, the identity function;
42
+ addition and multiplication, `add` and `mul` respectively,
43
+ that interpret the single number received as the Cantor encoding
44
+ of a pair `<x.y>` and compute the corresponding operation on `x` and
45
+ `y`; modified difference `diff` that receives likewise a Cantor-encoded
46
+ pair `<x.y>` and computes `max(0, x - y)` so that we always stay
47
+ within the natural numbers; and two functions related to projections
48
+ of Cantor-encoded sequences.
49
+
50
+ Scripts are maintained in objects of the class PReFScript,
51
+ that can be imported into your own Python program.
52
+ Alternatively, a stand-alone interpreter is also provided.
53
+ Thus, you have available two main ways of programming in PReFScript.
54
+
55
+ ### Installation and ways to use PReFScript functions
56
+
57
+ See [doc.md](https://github.com/balqui/prefscript/blob/main/docs/doc.md)
58
+ for all the details (somewhat incomplete as of today).
59
+
60
+
@@ -0,0 +1,11 @@
1
+ ascii7io.py,sha256=Y8f0PpDN70gupANxP7RuMtv3SQPcgqzpgXlCJsp_09s,930
2
+ prefscript.py,sha256=r8d6mLEVe7HUpMOa5Bim9WrgjF_U8RBkM5iR5wYNtoU,19274
3
+ cantorpairs/__init__.py,sha256=-4OzgrOj2Hzhy1eFY7TDXIsSyqVq9PvRKM3oomABYBM,82
4
+ cantorpairs/src/cantorpairs.py,sha256=tNuD6cv47WFdpJ_xyTax2M10SX5WZdv4MeFdf6eFLB0,3008
5
+ cantorpairs/src/cp_ex.py,sha256=kGCinoGwJ_1bH_GX4_MGD0OgUbJYnaCqjmDdXsviBNk,763
6
+ prefscript-1.1.dist-info/LICENSE,sha256=0xhadHw-a-cPJZsa0f5f3UAx4YKU9Z_w9UhNYPHOYfk,1063
7
+ prefscript-1.1.dist-info/METADATA,sha256=jEHzdRUj683ZtFnNrgQWgkFA9ju5IuntYDjALRSUQFA,2375
8
+ prefscript-1.1.dist-info/WHEEL,sha256=R0nc6qTxuoLk7ShA2_Y-UWkN8ZdfDBG2B6Eqpz2WXbs,91
9
+ prefscript-1.1.dist-info/entry_points.txt,sha256=bHZwFwLC12173rDpb8EsuhZ317vJ2S8bvplMNtEbSwo,46
10
+ prefscript-1.1.dist-info/top_level.txt,sha256=WM-rR-zT019ery_AnBIUzQaDbP22z45URQx-m0Zyxa0,32
11
+ prefscript-1.1.dist-info/RECORD,,
@@ -0,0 +1,5 @@
1
+ Wheel-Version: 1.0
2
+ Generator: setuptools (72.1.0)
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
5
+
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ prefscript = prefscript:run
@@ -0,0 +1,3 @@
1
+ ascii7io
2
+ cantorpairs
3
+ prefscript
prefscript.py ADDED
@@ -0,0 +1,437 @@
1
+ '''
2
+ PReFScript: A Partial Recursive Functions Lab
3
+
4
+ Rather: Towards a Partial Recursive Functions lab.
5
+
6
+ Author: Jose L Balcazar, ORCID 0000-0003-4248-4528, april 2023 onwards
7
+ Copyleft: MIT License (https://en.wikipedia.org/wiki/MIT_License)
8
+
9
+ Project started: mid Germinal 2003.
10
+ Current version: 0.5, mid Thermidor 2024.
11
+
12
+ A Python-based environment to explore and experiment with partial
13
+ recursive functions; naturally doubles as a (purely functional)
14
+ programming language, but it is not intended to be used as such.
15
+
16
+ Each function in a script has: associated Gödel number, nickname,
17
+ comments, and code (string); also, the last operation used to
18
+ construct it. Then, in a separate dict (used as namespace for
19
+ eval calls), a runnable version of the code.
20
+
21
+ Nicknames are alphanum strings not starting with a number (no surprise).
22
+ '''
23
+
24
+ from collections import defaultdict as ddict
25
+ from re import compile as re_compile, finditer as re_finditer
26
+ from ascii7io import int2str, str2int, int2raw_str # users are not expected to need int2raw_str
27
+ import cantorpairs
28
+ cp = cantorpairs
29
+
30
+ __version__ = "1.1"
31
+
32
+ # ~ In order to omit Gödel numbers too high, around 300 decimal digits,
33
+ # ~ LIMIT_GNUM set to 2**1000 but computed much faster via bit shift
34
+
35
+ LIMIT_GNUM = 2 << 999
36
+
37
+ class FunData(dict):
38
+ 'Simple class for PReFScript functions data'
39
+
40
+ def __init__(self, nick = None, comment = None, how_def = None, def_on = None):
41
+ dict.__init__(self)
42
+ self["nick"] = nick # function name
43
+ self["comment"] = comment
44
+ self["how_def"] = how_def
45
+ self["def_on"] = def_on
46
+
47
+ def __str__(self):
48
+ return self["nick"] + "\n " + self["comment"]
49
+
50
+ def how_def(self):
51
+ if self["how_def"] == "basic":
52
+ return "basic"
53
+ return self["how_def"] + ": " + ' '.join(on_what for on_what in self["def_on"])
54
+
55
+
56
+ def mu(x, test):
57
+ "ancillary linear search function for implementing mu-minimization"
58
+ z = 0
59
+ while not test(cp.dp(x, z)):
60
+ z += 1
61
+ return z
62
+
63
+
64
+ def prim_rec(is_base, base, recurse):
65
+ "ancillary course-of-values primitive recursion more efficient than by minimization"
66
+
67
+ def c_of_v(x):
68
+ "create the full course of values"
69
+ sq = 0
70
+ for y in range(x + 1):
71
+ new = base(y) if is_base(y) else recurse(cp.dp(y, sq))
72
+ sq = cp.dp(new, sq)
73
+ return sq
74
+
75
+ return lambda x: cp.pr_L(c_of_v(x))
76
+
77
+
78
+ class Parser:
79
+ '''Prepare an re-based parser to be used upon reading scripts
80
+ The single Parser with single parse generator is likely to mess up
81
+ the recursive imports, I bet it does not work yet.
82
+ '''
83
+
84
+ def __init__(self):
85
+ "group names require Python >= 3.11"
86
+ from re import compile as re_compile, finditer as re_finditer
87
+ about = r"\s*\.about(?P<about>.*)\n" # arbitrary documentation
88
+ pragma = r"\s*\.pragma\s+(?P<which>\w+):?\s+(?P<what>\w+)\s*" # compilation directives
89
+ importing = r"\s*\.import\s+(?P<to_import>[\w._-]+)\s*" # additional external script
90
+ a7str = r'''(?P<quote>['"])(?P<a7str>.*)(?P=quote)'''
91
+ startdef = r"\d+\s+define\:\s*"
92
+ group1_nick = r"(?P<nick>\w+)\s+"
93
+ group2_comment = r"\[\s*(?P<comment>(\w|\s|[.,:;<>=)(?!/+*-])+)\]\s+"
94
+ group4_how = r"(?P<how>pair|comp|mu|compair|primrec|ascii_const)\s+"
95
+ group5_on_what = r"((?P<on_what>([a-zA-Z_]\w*\s+)+)|" + a7str + ")" # nick args required not to start with a number
96
+ define = (startdef +
97
+ group1_nick +
98
+ group2_comment +
99
+ group4_how +
100
+ group5_on_what)
101
+ self.the_parser = re_compile(define + '|' + about + '|' + pragma + '|' + importing)
102
+
103
+ def parse(self, source):
104
+ for thing in re_finditer(self.the_parser, source):
105
+ "can one find out non-matched portions to message the user about?"
106
+ things = thing.groupdict(default = '')
107
+ if about := things['about']:
108
+ yield 'about', about
109
+ if which := things['which']:
110
+ yield 'pragma', (which, things['what'])
111
+ if to_import := things['to_import']:
112
+ yield 'import', to_import
113
+ if nick := things['nick']:
114
+ if things['how'] == "ascii_const":
115
+ on_what = [things['a7str']]
116
+ else:
117
+ on_what = things['on_what'].split()
118
+ yield "define", FunData(nick, things['comment'], things['how'], on_what)
119
+
120
+
121
+ class SyntErr:
122
+ "handle syntactic errors in the script - VERY PRIMITIVE for the time being"
123
+
124
+ def __init__(self):
125
+ from sys import stderr
126
+ self.e = stderr
127
+
128
+ def report(self, nonfatal = False, info = ''):
129
+ "return value to be given to the valid field / alt: fatal here and nonvalid at script"
130
+ p = 'Nonf' if nonfatal else 'F'
131
+ print(p + 'atal error in PReFScript:', info, sep = '\n ', file = self.e)
132
+ return nonfatal
133
+
134
+
135
+
136
+ class PReFScript:
137
+
138
+ def __init__(self, store_goedel_numbers = ""):
139
+ '''
140
+ Dicts for storing the functions:
141
+ main for the function data,
142
+ gnums for Gödel numbers,
143
+ pycode for Python runnable code,
144
+ key is always nick for all of them;
145
+ include here the basic functions;
146
+ their implementation assumes 'import cantorpairs as cp'
147
+ '''
148
+ self.valid = True # program is correct until proven wrong
149
+ self.main = dict() # RENAME some day, as I am using also 'main' for the main function to be called
150
+ self.strcode = dict()
151
+ self.pycode = dict()
152
+ self.gnums = dict()
153
+ self.abouts = list()
154
+ self.pragmas = ddict(str)
155
+ self.store_gnums = store_goedel_numbers
156
+ self.add_basic("k_1", "The constant 1 function", "lambda x: 1", 0)
157
+ self.add_basic("id", "The identity function", "lambda x: x", 1)
158
+ self.add_basic("s_tup", "Single-argument version of suffix tuple",
159
+ "lambda x: cp.s_tup(cp.pr_L(x), cp.pr_R(x))", 2)
160
+ self.add_basic("proj", "Single-argument version of projection",
161
+ "lambda x: cp.pr(cp.pr_L(x), cp.pr_R(x))", 3)
162
+ self.add_basic("add", "Addition x+y of the two components of input <x.y>",
163
+ "lambda x: cp.pr_L(x) + cp.pr_R(x)", 4)
164
+ self.add_basic("mul", "Multiplication x*y of the two components of input <x.y>",
165
+ "lambda x: cp.pr_L(x) * cp.pr_R(x)", 5)
166
+ self.add_basic("diff", "Modified difference max(0, x-y) of the two components of input <x.y>",
167
+ "lambda x: max(0, cp.pr_L(x) - cp.pr_R(x))", 6)
168
+ self.parser = Parser()
169
+ self.synt_err_handler = SyntErr()
170
+
171
+
172
+ def add_basic(self, nick, comment, code, num):
173
+ data = FunData()
174
+ data["nick"] = nick
175
+ data["comment"] = comment
176
+ data["how_def"] = "basic"
177
+ data["def_on"] = tuple()
178
+ if self.store_gnums:
179
+ self.gnums[nick] = cp.dp(0, num)
180
+ self.main[nick] = data
181
+ self.strcode[nick] = code
182
+ self.pycode[nick] = eval(code, globals() | self.pycode)
183
+
184
+
185
+ def list(self, what = None, w_code = 0):
186
+ '''
187
+ if what is None: list everything
188
+ else: search for that what on the dicts
189
+ w_code 0: no code, 1: how and on what, 2: strcode also
190
+ Gödel number printed depending on self.store_gnums and how big it is
191
+ '''
192
+ def list_one(nick, w_code):
193
+ print("\n" + str(self.main[nick]))
194
+ if w_code:
195
+ 'print how it is defined'
196
+ print(" " + self.main[nick].how_def())
197
+ if w_code == 2:
198
+ 'print also the Python code in this case only'
199
+ print(" " + self.strcode[nick])
200
+ if self.store_gnums:
201
+ if nick in self.gnums:
202
+ gnum = self.gnums[nick]
203
+ print(" Gödel number:", gnum,
204
+ "= <" + str(cp.pr_L(gnum)) + "." + str(cp.pr_R(gnum)) + ">")
205
+ else:
206
+ self.valid &= self.synt_err_handler(fatal = False, info = "Gödel number too large, omitted.")
207
+
208
+ if what is not None:
209
+ list_one(what, w_code)
210
+ else:
211
+ for nick in self.main:
212
+ list_one(nick, w_code)
213
+
214
+
215
+ def define(self, new_funct):
216
+ 'here comes a new function to add to the collection'
217
+
218
+ if (nick := new_funct['nick']) in self.main:
219
+ 'repeated nick, check for consistency'
220
+ if (self.main[nick]["how_def"] != new_funct['how_def'] or
221
+ self.main[nick]["def_on"] != new_funct['def_on']):
222
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
223
+ info = f"Repeated, inconsistent definitions for function '{nick}' found.")
224
+ else:
225
+ self.main[nick] = new_funct
226
+ on_what = new_funct['def_on']
227
+
228
+ if new_funct['how_def'] == "comp":
229
+ self.strcode[nick] = "lambda x: " + on_what[0] + "(" + on_what[1] + "(x))"
230
+ if self.store_gnums and on_what[0] in self.gnums and on_what[1] in self.gnums:
231
+ gnum = cp.dp(1, cp.dp(self.gnums[on_what[0]], self.gnums[on_what[1]]))
232
+ if gnum < LIMIT_GNUM:
233
+ self.gnums[nick] = gnum
234
+ else:
235
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
236
+ info = f"Gödel number for '{nick}' too large, omitted.")
237
+
238
+ elif new_funct['how_def'] == "pair":
239
+ self.strcode[nick] = "lambda x: cp.dp(" + on_what[0] + "(x), " + on_what[1] + "(x))"
240
+ if self.store_gnums and on_what[0] in self.gnums and on_what[1] in self.gnums:
241
+ gnum = cp.dp(2, cp.dp(self.gnums[on_what[0]], self.gnums[on_what[1]]))
242
+ if gnum < LIMIT_GNUM:
243
+ self.gnums[nick] = gnum
244
+ else:
245
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
246
+ info = f"Gödel number for '{nick}' too large, omitted.")
247
+
248
+ elif new_funct['how_def'] == "mu":
249
+ self.strcode[nick] = "lambda x: mu(x, " + on_what[0] + ")"
250
+ if self.store_gnums and on_what[0] in self.gnums:
251
+ gnum = cp.dp(3, self.gnums[on_what[0]])
252
+ if gnum < LIMIT_GNUM:
253
+ self.gnums[nick] = gnum
254
+ else:
255
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
256
+ info = f"Gödel number for '{nick}' too large, omitted.")
257
+
258
+ elif new_funct['how_def'] == "compair":
259
+ if not self.pragmas['extended']:
260
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
261
+ info = "Use of compair requires '.pragma extended: True', changed.")
262
+ self.pragmas['extended'] = 'True'
263
+ self.strcode[nick] = "lambda x: " + on_what[0] + "( cp.dp(" + on_what[1] + "(x), " + on_what[2] + "(x)))"
264
+ if (self.store_gnums and on_what[0] in self.gnums and
265
+ on_what[1] in self.gnums and on_what[2] in self.gnums):
266
+ gnum = cp.dp(1, cp.dp(self.gnums[on_what[0]],
267
+ cp.dp(2, cp.dp(self.gnums[on_what[1]], self.gnums[on_what[2]]))))
268
+ if gnum < LIMIT_GNUM:
269
+ self.gnums[nick] = gnum
270
+ else:
271
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
272
+ info = f"Gödel number for '{nick}' too large, omitted.")
273
+
274
+ elif new_funct['how_def'] == "primrec":
275
+ if not self.pragmas['extended']:
276
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
277
+ info = "Use of primrec requires '.pragma extended: True', changed.")
278
+ self.pragmas['extended'] = 'True'
279
+ self.strcode[nick] = "prim_rec(" + on_what[0] + ", " + on_what[1] + ", " + on_what[2] + ")"
280
+ if (self.store_gnums and on_what[1] in self.gnums and on_what[2] in self.gnums):
281
+ gnum = cp.dp(4, cp.dp(int(on_what[0]),
282
+ cp.dp(self.gnums[on_what[1]], self.gnums[on_what[2]])))
283
+ if gnum < LIMIT_GNUM:
284
+ self.gnums[nick] = gnum
285
+ else:
286
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
287
+ info = f"Gödel number for '{nick}' too large, omitted.")
288
+
289
+ else:
290
+ "ascii_const, as no other 'how' captured by parser - kept out of the Goedel numbering for the time being"
291
+ if not self.pragmas['extended']:
292
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
293
+ info = "Use of ascii constants requires '.pragma extended: True', changed.")
294
+ self.pragmas['extended'] = 'True'
295
+ self.strcode[nick] = "lambda x: str2int( '" + on_what[0] + "' )"
296
+
297
+ self.pycode[nick] = eval(self.strcode[nick], globals() | self.pycode)
298
+
299
+
300
+ def to_python(self, what):
301
+ 'returns the Python-runnable version of the function'
302
+ if what not in self.pycode:
303
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
304
+ info = f"No Python code for function '{what}' found.")
305
+ return None
306
+ return self.pycode[what]
307
+
308
+
309
+ def find_script_in_file(self, filename, main):
310
+ try:
311
+ with open(filename) as infile:
312
+ return infile.read()
313
+ except IOError:
314
+ if main:
315
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
316
+ info = f"Script {filename} not found.")
317
+ else:
318
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
319
+ info = f"Imported script {filename} not found.")
320
+
321
+
322
+ def load(self, filename, main = True):
323
+ 'load in definitions from .prfs file(s) - use .import to recurse into further loading'
324
+ if filename.endswith('.prfs'):
325
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
326
+ info = f"File name {filename} NOT expected to include the '.prfs' extension.")
327
+ else:
328
+ filename += '.prfs'
329
+ script = self.find_script_in_file(filename, main)
330
+ if not script:
331
+ return None
332
+ lastread = None
333
+ for label, what in self.parser.parse(script):
334
+ 'make the FunData or store the about or the pragma or the import'
335
+ if label == 'pragma':
336
+ if main:
337
+ "pragmas in main file are stored"
338
+ self.pragmas[what[0]] = what[1]
339
+ elif what[0] == 'extended' and what[1] == 'True':
340
+ "pragmas in imported files are ignored except extended when set to True"
341
+ if self.pragmas['extended'] != 'True':
342
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
343
+ info = f"Warning: the .pragma extended declaration found in {filename} affects globally.")
344
+ self.pragmas[what[0]] = what[1]
345
+ if label == 'about':
346
+ self.abouts.append('about ' + filename + ': ' + what)
347
+ if label == 'define':
348
+ self.define(what)
349
+ lastread = what['nick'] # nickname of the last function defined, used for default main
350
+ if label == "import":
351
+ "non-main recursive call"
352
+ self.load(what, main = False)
353
+ if main and not self.pragmas['main']:
354
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
355
+ info = f"No main .pragma found in '{filename}'.")
356
+ if self.valid and lastread:
357
+ "warn that main assumed is lastread"
358
+ self.valid &= self.synt_err_handler.report(nonfatal = True,
359
+ info = f"Function '{lastread}' guessed to be the main function, cross fingers.")
360
+ self.pragmas['main'] = lastread
361
+ elif self.valid:
362
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
363
+ info = f"No guess available for the main function.")
364
+
365
+
366
+ def dialog(self):
367
+ nick = input("Function name? ")
368
+ comment = input("What is it? ")
369
+ how = input("How is it made? [pair or comp or mu] ")
370
+ on_what = input("Applied to what? [1 or 2 space-sep names] ")
371
+ self.define(FunData(nick.strip(), comment.strip(), how.strip(), tuple(on_what.split())))
372
+
373
+
374
+ def check_names(self):
375
+ "checks that all function names needed to run main have been defined"
376
+
377
+ def check_name(self, name, need = 'pragma main'):
378
+ if name not in checked:
379
+ checked.add(name)
380
+ if name in self.main:
381
+ if self.main[name]['how_def'] != "ascii_const":
382
+ for nname in self.main[name]['def_on']:
383
+ check_name(self, nname, f"'{name}'")
384
+ else:
385
+ "newly found undefined name"
386
+ self.valid &= self.synt_err_handler.report(nonfatal = False,
387
+ info = f"Function '{name}' not found but needed by {need}.")
388
+
389
+ checked = set()
390
+ check_name(self, self.pragmas['main'])
391
+
392
+
393
+ def run():
394
+ 'Stand-alone CLI command to be handled as entry point - no Goedel numbers stored; handle the filename as argument'
395
+ from argparse import ArgumentParser
396
+ from pytokr import pytokr
397
+ read, loop = pytokr(iter = True)
398
+ aparser = ArgumentParser(prog = 'prefscript',
399
+ description = 'Partial Recursive Functions Scripting interpreter')
400
+ aparser.add_argument('filename', help = 'script filename to be run, extension .prfs assumed')
401
+ aparser.add_argument('-a', '--about',
402
+ help = "show contents of .about directives in file and .import'd files before running",
403
+ action = "store_true")
404
+ aparser.add_argument('-v', '--version',
405
+ action = "version", version = f'{__version__}')
406
+ args = aparser.parse_args()
407
+ f = PReFScript()
408
+ f.load(args.filename)
409
+ if f.valid:
410
+ f.check_names()
411
+ if f.valid:
412
+ 'run it on data from stdin according to input/output/main pragmas'
413
+ if args.about:
414
+ print('\n'.join(f.abouts))
415
+
416
+ r = f.to_python(f.pragmas["main"])
417
+
418
+ if f.pragmas["output"] in ('', "int"):
419
+ post = lambda x: x
420
+ elif f.pragmas["output"] == "ascii":
421
+ post = int2str
422
+ elif f.pragmas["output"] == "bool":
423
+ post = bool
424
+
425
+ if f.pragmas["input"] in ('', "int"):
426
+ arg = int(input())
427
+ elif f.pragmas["input"] == "none":
428
+ arg = 666 # for one
429
+ elif f.pragmas["input"] == "intseq":
430
+ arg = cp.tup_i(map(int, loop()))
431
+
432
+ if f.valid:
433
+ print(post(r(arg)))
434
+
435
+
436
+ if __name__ == "__main__":
437
+ run()