passagemath-repl 10.4.62__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.
Files changed (162) hide show
  1. passagemath_repl-10.4.62.data/scripts/sage-cachegrind +25 -0
  2. passagemath_repl-10.4.62.data/scripts/sage-callgrind +16 -0
  3. passagemath_repl-10.4.62.data/scripts/sage-cleaner +230 -0
  4. passagemath_repl-10.4.62.data/scripts/sage-coverage +327 -0
  5. passagemath_repl-10.4.62.data/scripts/sage-eval +14 -0
  6. passagemath_repl-10.4.62.data/scripts/sage-fixdoctests +708 -0
  7. passagemath_repl-10.4.62.data/scripts/sage-inline-fortran +12 -0
  8. passagemath_repl-10.4.62.data/scripts/sage-ipynb2rst +50 -0
  9. passagemath_repl-10.4.62.data/scripts/sage-ipython +16 -0
  10. passagemath_repl-10.4.62.data/scripts/sage-massif +25 -0
  11. passagemath_repl-10.4.62.data/scripts/sage-notebook +267 -0
  12. passagemath_repl-10.4.62.data/scripts/sage-omega +25 -0
  13. passagemath_repl-10.4.62.data/scripts/sage-preparse +302 -0
  14. passagemath_repl-10.4.62.data/scripts/sage-run +27 -0
  15. passagemath_repl-10.4.62.data/scripts/sage-run-cython +10 -0
  16. passagemath_repl-10.4.62.data/scripts/sage-runtests +9 -0
  17. passagemath_repl-10.4.62.data/scripts/sage-startuptime.py +163 -0
  18. passagemath_repl-10.4.62.data/scripts/sage-valgrind +34 -0
  19. passagemath_repl-10.4.62.dist-info/METADATA +77 -0
  20. passagemath_repl-10.4.62.dist-info/RECORD +162 -0
  21. passagemath_repl-10.4.62.dist-info/WHEEL +5 -0
  22. passagemath_repl-10.4.62.dist-info/top_level.txt +1 -0
  23. sage/all__sagemath_repl.py +119 -0
  24. sage/doctest/__init__.py +4 -0
  25. sage/doctest/__main__.py +236 -0
  26. sage/doctest/all.py +4 -0
  27. sage/doctest/check_tolerance.py +261 -0
  28. sage/doctest/control.py +1727 -0
  29. sage/doctest/external.py +534 -0
  30. sage/doctest/fixtures.py +383 -0
  31. sage/doctest/forker.py +2665 -0
  32. sage/doctest/marked_output.py +102 -0
  33. sage/doctest/parsing.py +1708 -0
  34. sage/doctest/parsing_test.py +79 -0
  35. sage/doctest/reporting.py +733 -0
  36. sage/doctest/rif_tol.py +124 -0
  37. sage/doctest/sources.py +1657 -0
  38. sage/doctest/test.py +584 -0
  39. sage/doctest/tests/1second.rst +4 -0
  40. sage/doctest/tests/99seconds.rst +4 -0
  41. sage/doctest/tests/abort.rst +5 -0
  42. sage/doctest/tests/atexit.rst +7 -0
  43. sage/doctest/tests/fail_and_die.rst +6 -0
  44. sage/doctest/tests/initial.rst +15 -0
  45. sage/doctest/tests/interrupt.rst +7 -0
  46. sage/doctest/tests/interrupt_diehard.rst +14 -0
  47. sage/doctest/tests/keyboardinterrupt.rst +11 -0
  48. sage/doctest/tests/longtime.rst +5 -0
  49. sage/doctest/tests/nodoctest +5 -0
  50. sage/doctest/tests/random_seed.rst +4 -0
  51. sage/doctest/tests/show_skipped.rst +18 -0
  52. sage/doctest/tests/sig_on.rst +9 -0
  53. sage/doctest/tests/simple_failure.rst +8 -0
  54. sage/doctest/tests/sleep_and_raise.rst +106 -0
  55. sage/doctest/tests/tolerance.rst +31 -0
  56. sage/doctest/util.py +750 -0
  57. sage/interfaces/cleaner.py +48 -0
  58. sage/interfaces/quit.py +163 -0
  59. sage/misc/all__sagemath_repl.py +51 -0
  60. sage/misc/banner.py +235 -0
  61. sage/misc/benchmark.py +221 -0
  62. sage/misc/classgraph.py +131 -0
  63. sage/misc/copying.py +22 -0
  64. sage/misc/cython.py +694 -0
  65. sage/misc/dev_tools.py +745 -0
  66. sage/misc/edit_module.py +304 -0
  67. sage/misc/explain_pickle.py +3079 -0
  68. sage/misc/gperftools.py +361 -0
  69. sage/misc/inline_fortran.py +212 -0
  70. sage/misc/messaging.py +86 -0
  71. sage/misc/pager.py +21 -0
  72. sage/misc/profiler.py +179 -0
  73. sage/misc/python.py +70 -0
  74. sage/misc/remote_file.py +53 -0
  75. sage/misc/sage_eval.py +246 -0
  76. sage/misc/sage_input.py +3621 -0
  77. sage/misc/sagedoc.py +1742 -0
  78. sage/misc/sh.py +38 -0
  79. sage/misc/trace.py +90 -0
  80. sage/repl/__init__.py +16 -0
  81. sage/repl/all.py +15 -0
  82. sage/repl/attach.py +625 -0
  83. sage/repl/configuration.py +186 -0
  84. sage/repl/display/__init__.py +1 -0
  85. sage/repl/display/fancy_repr.py +354 -0
  86. sage/repl/display/formatter.py +318 -0
  87. sage/repl/display/jsmol_iframe.py +290 -0
  88. sage/repl/display/pretty_print.py +153 -0
  89. sage/repl/display/util.py +163 -0
  90. sage/repl/image.py +302 -0
  91. sage/repl/inputhook.py +91 -0
  92. sage/repl/interface_magic.py +298 -0
  93. sage/repl/interpreter.py +854 -0
  94. sage/repl/ipython_extension.py +593 -0
  95. sage/repl/ipython_kernel/__init__.py +1 -0
  96. sage/repl/ipython_kernel/__main__.py +4 -0
  97. sage/repl/ipython_kernel/all_jupyter.py +10 -0
  98. sage/repl/ipython_kernel/install.py +301 -0
  99. sage/repl/ipython_kernel/interact.py +278 -0
  100. sage/repl/ipython_kernel/kernel.py +217 -0
  101. sage/repl/ipython_kernel/widgets.py +466 -0
  102. sage/repl/ipython_kernel/widgets_sagenb.py +587 -0
  103. sage/repl/ipython_tests.py +163 -0
  104. sage/repl/load.py +326 -0
  105. sage/repl/preparse.py +2218 -0
  106. sage/repl/prompts.py +90 -0
  107. sage/repl/rich_output/__init__.py +4 -0
  108. sage/repl/rich_output/backend_base.py +648 -0
  109. sage/repl/rich_output/backend_doctest.py +316 -0
  110. sage/repl/rich_output/backend_emacs.py +151 -0
  111. sage/repl/rich_output/backend_ipython.py +596 -0
  112. sage/repl/rich_output/buffer.py +311 -0
  113. sage/repl/rich_output/display_manager.py +829 -0
  114. sage/repl/rich_output/example.avi +0 -0
  115. sage/repl/rich_output/example.canvas3d +1 -0
  116. sage/repl/rich_output/example.dvi +0 -0
  117. sage/repl/rich_output/example.flv +0 -0
  118. sage/repl/rich_output/example.gif +0 -0
  119. sage/repl/rich_output/example.jpg +0 -0
  120. sage/repl/rich_output/example.mkv +0 -0
  121. sage/repl/rich_output/example.mov +0 -0
  122. sage/repl/rich_output/example.mp4 +0 -0
  123. sage/repl/rich_output/example.ogv +0 -0
  124. sage/repl/rich_output/example.pdf +0 -0
  125. sage/repl/rich_output/example.png +0 -0
  126. sage/repl/rich_output/example.svg +54 -0
  127. sage/repl/rich_output/example.webm +0 -0
  128. sage/repl/rich_output/example.wmv +0 -0
  129. sage/repl/rich_output/example_jmol.spt.zip +0 -0
  130. sage/repl/rich_output/example_wavefront_scene.mtl +7 -0
  131. sage/repl/rich_output/example_wavefront_scene.obj +17 -0
  132. sage/repl/rich_output/output_basic.py +391 -0
  133. sage/repl/rich_output/output_browser.py +103 -0
  134. sage/repl/rich_output/output_catalog.py +54 -0
  135. sage/repl/rich_output/output_graphics.py +320 -0
  136. sage/repl/rich_output/output_graphics3d.py +345 -0
  137. sage/repl/rich_output/output_video.py +231 -0
  138. sage/repl/rich_output/preferences.py +432 -0
  139. sage/repl/rich_output/pretty_print.py +339 -0
  140. sage/repl/rich_output/test_backend.py +201 -0
  141. sage/repl/user_globals.py +214 -0
  142. sage/tests/__init__.py +1 -0
  143. sage/tests/all.py +3 -0
  144. sage/tests/article_heuberger_krenn_kropf_fsm-in-sage.py +630 -0
  145. sage/tests/arxiv_0812_2725.py +351 -0
  146. sage/tests/benchmark.py +1923 -0
  147. sage/tests/book_schilling_zabrocki_kschur_primer.py +795 -0
  148. sage/tests/book_stein_ent.py +651 -0
  149. sage/tests/book_stein_modform.py +558 -0
  150. sage/tests/cmdline.py +790 -0
  151. sage/tests/combinatorial_hopf_algebras.py +52 -0
  152. sage/tests/finite_poset.py +623 -0
  153. sage/tests/functools_partial_src.py +27 -0
  154. sage/tests/gosper-sum.py +218 -0
  155. sage/tests/lazy_imports.py +28 -0
  156. sage/tests/modular_group_cohomology.py +80 -0
  157. sage/tests/numpy.py +21 -0
  158. sage/tests/parigp.py +76 -0
  159. sage/tests/startup.py +27 -0
  160. sage/tests/symbolic-series.py +76 -0
  161. sage/tests/sympy.py +16 -0
  162. sage/tests/test_deprecation.py +31 -0
sage/repl/preparse.py ADDED
@@ -0,0 +1,2218 @@
1
+ # sage_setup: distribution = sagemath-repl
2
+ """
3
+ The Sage Preparser
4
+
5
+ EXAMPLES:
6
+
7
+ Preparsing::
8
+
9
+ sage: preparse('2/3')
10
+ 'Integer(2)/Integer(3)'
11
+ sage: preparse('2.5')
12
+ "RealNumber('2.5')"
13
+ sage: preparse('2^3')
14
+ 'Integer(2)**Integer(3)'
15
+ sage: preparse('a^b') # exponent
16
+ 'a**b'
17
+ sage: preparse('a**b')
18
+ 'a**b'
19
+ sage: preparse('G.0') # generator
20
+ 'G.gen(0)'
21
+ sage: preparse('a = 939393R') # raw
22
+ 'a = 939393'
23
+ sage: implicit_multiplication(True)
24
+ sage: preparse('a b c in L') # implicit multiplication
25
+ 'a*b*c in L'
26
+ sage: preparse('2e3x + 3exp(y)')
27
+ "RealNumber('2e3')*x + Integer(3)*exp(y)"
28
+
29
+ A string with escaped quotes in it (the point here is that the
30
+ preparser does not get confused by the internal quotes)::
31
+
32
+ sage: "\"Yes,\" he said."
33
+ '"Yes," he said.'
34
+ sage: s = "\\"; s
35
+ '\\'
36
+
37
+ A hex literal::
38
+
39
+ sage: preparse('0x2e3')
40
+ 'Integer(0x2e3)'
41
+ sage: 0xA
42
+ 10
43
+ sage: 0xe
44
+ 14
45
+
46
+ Raw and hex work correctly::
47
+
48
+ sage: type(0xa1)
49
+ <class 'sage.rings.integer.Integer'>
50
+ sage: type(0xa1r)
51
+ <class 'int'>
52
+ sage: type(0Xa1R)
53
+ <class 'int'>
54
+
55
+ The preparser can handle PEP 515 (see :issue:`28490`)::
56
+
57
+ sage: 1_000_000 + 3_000
58
+ 1003000
59
+
60
+ In Sage, methods can also be called on integer and real literals (note
61
+ that in pure Python this would be a syntax error)::
62
+
63
+ sage: 16.sqrt()
64
+ 4
65
+ sage: 87.factor()
66
+ 3 * 29
67
+ sage: 15.10.sqrt() # needs sage.rings.real_mpfr
68
+ 3.88587184554509
69
+ sage: preparse('87.sqrt()')
70
+ 'Integer(87).sqrt()'
71
+ sage: preparse('15.10.sqrt()')
72
+ "RealNumber('15.10').sqrt()"
73
+
74
+ Note that calling methods on int literals in pure Python is a syntax
75
+ error, but Sage allows this for Sage integers and reals, because users
76
+ frequently request it::
77
+
78
+ sage: eval('4.__add__(3)')
79
+ Traceback (most recent call last):
80
+ ...
81
+ SyntaxError: invalid ...
82
+
83
+ Symbolic functional notation::
84
+
85
+ sage: # needs sage.symbolic
86
+ sage: a = 10; f(theta, beta) = theta + beta; b = x^2 + theta
87
+ sage: f
88
+ (theta, beta) |--> beta + theta
89
+ sage: a
90
+ 10
91
+ sage: b
92
+ x^2 + theta
93
+ sage: f(theta,theta)
94
+ 2*theta
95
+
96
+ sage: a = 5; f(x,y) = x*y*sqrt(a) # needs sage.symbolic
97
+ sage: f # needs sage.symbolic
98
+ (x, y) |--> sqrt(5)*x*y
99
+
100
+ This involves an =-, but should still be turned into a symbolic
101
+ expression::
102
+
103
+ sage: preparse('a(x) =- 5')
104
+ '__tmp__=var("x"); a = symbolic_expression(- Integer(5)).function(x)'
105
+ sage: f(x)=-x # needs sage.symbolic
106
+ sage: f(10) # needs sage.symbolic
107
+ -10
108
+
109
+ This involves -=, which should not be turned into a symbolic
110
+ expression (of course a(x) is not an identifier, so this will never be
111
+ valid)::
112
+
113
+ sage: preparse('a(x) -= 5')
114
+ 'a(x) -= Integer(5)'
115
+
116
+ Raw literals:
117
+
118
+ Raw literals are not preparsed, which can be useful from an efficiency
119
+ point of view. In Sage raw integer and floating literals are followed
120
+ by an"r" (or "R") for raw, meaning not preparsed.
121
+
122
+ We create a raw integer::
123
+
124
+ sage: a = 393939r
125
+ sage: a
126
+ 393939
127
+ sage: type(a)
128
+ <class 'int'>
129
+
130
+ We create a raw float::
131
+
132
+ sage: z = 1.5949r
133
+ sage: z
134
+ 1.5949
135
+ sage: type(z)
136
+ <class 'float'>
137
+
138
+ You can also use an upper case letter::
139
+
140
+ sage: z = 3.1415R
141
+ sage: z
142
+ 3.1415
143
+ sage: type(z)
144
+ <class 'float'>
145
+
146
+ This next example illustrates how raw literals can be very useful in
147
+ certain cases. We make a list of even integers up to 10000::
148
+
149
+ sage: v = [ 2*i for i in range(10000)]
150
+
151
+ This takes a noticeable fraction of a second (e.g., 0.25
152
+ seconds). After preparsing, what Python is really executing is the
153
+ following::
154
+
155
+ sage: preparse('v = [ 2*i for i in range(10000)]')
156
+ 'v = [ Integer(2)*i for i in range(Integer(10000))]'
157
+
158
+ If instead we use a raw 2 we get execution that is *instant* (0.00
159
+ seconds)::
160
+
161
+ sage: v = [ 2r * i for i in range(10000r)]
162
+
163
+ Behind the scenes what happens is the following::
164
+
165
+ sage: preparse('v = [ 2r * i for i in range(10000r)]')
166
+ 'v = [ 2 * i for i in range(10000)]'
167
+
168
+ .. WARNING::
169
+
170
+ The results of the above two expressions are different. The
171
+ first one computes a list of Sage integers, whereas the second
172
+ creates a list of Python integers. Python integers are typically
173
+ much more efficient than Sage integers when they are very small;
174
+ large Sage integers are much more efficient than Python integers,
175
+ since they are implemented using the GMP C library.
176
+
177
+ F-Strings (`PEP 498 <https://www.python.org/dev/peps/pep-0498/>`_):
178
+
179
+ Expressions embedded within F-strings are preparsed::
180
+
181
+ sage: f'{1/3}'
182
+ '1/3'
183
+ sage: f'{2^3}'
184
+ '8'
185
+ sage: x = 20
186
+ sage: f'{x} in binary is: {x:08b}'
187
+ '20 in binary is: 00010100'
188
+ sage: f'{list(map(lambda x: x^2, [1, 2, .., 5]))}'
189
+ '[1, 4, 9, 16, 25]'
190
+
191
+ Note that the format specifier is not preparsed. Expressions within it,
192
+ however, are::
193
+
194
+ sage: f'{x:10r}'
195
+ Traceback (most recent call last):
196
+ ...
197
+ ValueError: Unknown format code 'r' for object of type 'int'
198
+ sage: f'{x:{10r}}'
199
+ ' 20'
200
+
201
+ Nested F-strings are also supported::
202
+
203
+ sage: f'{ f"{ 1/3 + 1/6 }" }'
204
+ '1/2'
205
+ sage: f'''1{ f"2{ f'4{ 2^3 }4' }2" }1'''
206
+ '1248421'
207
+
208
+ AUTHORS:
209
+
210
+ - William Stein (2006-02-19): fixed bug when loading .py files
211
+
212
+ - William Stein (2006-03-09): fixed crash in parsing exponentials; precision of
213
+ real literals now determined by digits of input (like Mathematica)
214
+
215
+ - Joe Wetherell (2006-04-14): added MAGMA-style constructor preparsing
216
+
217
+ - Bobby Moretti (2007-01-25): added preliminary function assignment notation
218
+
219
+ - Robert Bradshaw (2007-09-19): added strip_string_literals, containing_block
220
+ utility functions. Arrr!; added [1,2,..,n] notation
221
+
222
+ - Robert Bradshaw (2008-01-04): implicit multiplication (off by default)
223
+
224
+ - Robert Bradshaw (2008-09-23): factor out constants
225
+
226
+ - Robert Bradshaw (2009-01): simplify preparser by making it modular and using
227
+ regular expressions; bug fixes, complex numbers, and binary input
228
+ """
229
+
230
+ # ****************************************************************************
231
+ # Copyright (C) 2006 William Stein <wstein@gmail.com>
232
+ #
233
+ # This program is free software: you can redistribute it and/or modify
234
+ # it under the terms of the GNU General Public License as published by
235
+ # the Free Software Foundation, either version 2 of the License, or
236
+ # (at your option) any later version.
237
+ # https://www.gnu.org/licenses/
238
+ # ****************************************************************************
239
+
240
+ import re
241
+ from pathlib import Path
242
+ from types import SimpleNamespace
243
+
244
+ from sage.repl.load import load_wrap
245
+
246
+ implicit_mul_level = False
247
+ numeric_literal_prefix = '_sage_const_'
248
+
249
+
250
+ def implicit_multiplication(level=None):
251
+ r"""
252
+ Turn implicit multiplication on or off, optionally setting a
253
+ specific ``level``.
254
+
255
+ INPUT:
256
+
257
+ - ``level`` -- boolean or integer (default: 5); how aggressive to be in
258
+ placing \*'s
259
+
260
+ - 0 -- Do nothing
261
+ - 1 -- Numeric followed by alphanumeric
262
+ - 2 -- Closing parentheses followed by alphanumeric
263
+ - 3 -- Spaces between alphanumeric
264
+ - 10 -- Adjacent parentheses (may mangle call statements)
265
+
266
+ OUTPUT: the current ``level`` if no argument is given
267
+
268
+ EXAMPLES::
269
+
270
+ sage: implicit_multiplication(True)
271
+ sage: implicit_multiplication()
272
+ 5
273
+ sage: preparse('2x')
274
+ 'Integer(2)*x'
275
+ sage: implicit_multiplication(False)
276
+ sage: preparse('2x')
277
+ '2x'
278
+
279
+ Note that the `IPython automagic
280
+ <https://ipython.readthedocs.io/en/stable/interactive/magics.html#magic-automagic>`_
281
+ feature cannot be used if ``level >= 3``::
282
+
283
+ sage: implicit_multiplication(3)
284
+ sage: preparse('cd Documents')
285
+ 'cd*Documents'
286
+ sage: implicit_multiplication(2)
287
+ sage: preparse('cd Documents')
288
+ 'cd Documents'
289
+ sage: implicit_multiplication(False)
290
+
291
+ In this case, one can use the explicit syntax for IPython magics such as
292
+ ``%cd Documents``.
293
+ """
294
+ global implicit_mul_level
295
+ if level is None:
296
+ return implicit_mul_level
297
+ if level is True:
298
+ implicit_mul_level = 5
299
+ else:
300
+ implicit_mul_level = level
301
+
302
+
303
+ def isalphadigit_(s) -> bool:
304
+ """
305
+ Return ``True`` if ``s`` is a non-empty string of alphabetic characters
306
+ or a non-empty string of digits or just a single ``_``
307
+
308
+ EXAMPLES::
309
+
310
+ sage: from sage.repl.preparse import isalphadigit_
311
+ sage: isalphadigit_('abc')
312
+ True
313
+ sage: isalphadigit_('123')
314
+ True
315
+ sage: isalphadigit_('_')
316
+ True
317
+ sage: isalphadigit_('a123')
318
+ False
319
+ """
320
+ return s.isalpha() or s.isdigit() or s == "_"
321
+
322
+
323
+ in_single_quote = False
324
+ in_double_quote = False
325
+ in_triple_quote = False
326
+
327
+
328
+ def in_quote() -> bool:
329
+ return in_single_quote or in_double_quote or in_triple_quote
330
+
331
+
332
+ class QuoteStack:
333
+ """The preserved state of parsing in :func:`strip_string_literals`."""
334
+
335
+ def __init__(self):
336
+ """
337
+ Create a new, empty QuoteStack.
338
+
339
+ EXAMPLES::
340
+
341
+ sage: qs = sage.repl.preparse.QuoteStack()
342
+ sage: len(qs)
343
+ 0
344
+ """
345
+ self._stack = [] # list of QuoteStackFrame
346
+ self._single_quote_safe = True
347
+ self._double_quote_safe = True
348
+
349
+ def __len__(self):
350
+ """
351
+ Return the number of frames currently on the stack.
352
+
353
+ EXAMPLES::
354
+
355
+ sage: qs = sage.repl.preparse.QuoteStack(); len(qs)
356
+ 0
357
+ sage: qs.push(sage.repl.preparse.QuoteStackFrame("'")); len(qs)
358
+ 1
359
+ sage: qs.pop()
360
+ QuoteStackFrame(...)
361
+ sage: len(qs)
362
+ 0
363
+ """
364
+ return len(self._stack)
365
+
366
+ def __repr__(self):
367
+ """
368
+ Return a string representation of the stack's contents.
369
+
370
+ EXAMPLES::
371
+
372
+ sage: qs = sage.repl.preparse.QuoteStack(); qs
373
+ []
374
+ sage: qs.push(sage.repl.preparse.QuoteStackFrame('"')); qs
375
+ [QuoteStackFrame(...delim='"'...)]
376
+ sage: qs.push(sage.repl.preparse.QuoteStackFrame("'")); qs
377
+ [QuoteStackFrame(...delim='"'...), QuoteStackFrame(...delim="'"...)]
378
+ """
379
+ return repr(self._stack)
380
+
381
+ def peek(self):
382
+ """
383
+ Get the frame at the top of the stack or None if empty.
384
+
385
+ EXAMPLES::
386
+
387
+ sage: qs = sage.repl.preparse.QuoteStack()
388
+ sage: qs.peek()
389
+ sage: qs.push(sage.repl.preparse.QuoteStackFrame('"'))
390
+ sage: qs.peek()
391
+ QuoteStackFrame(...delim='"'...)
392
+ """
393
+ return self._stack[-1] if self._stack else None
394
+
395
+ def pop(self):
396
+ """
397
+ Remove and return the frame that was most recently added to the stack.
398
+
399
+ Raise an :exc:`IndexError` if the stack is empty.
400
+
401
+ EXAMPLES::
402
+
403
+ sage: qs = sage.repl.preparse.QuoteStack()
404
+ sage: qs.pop()
405
+ Traceback (most recent call last):
406
+ ...
407
+ IndexError: ...
408
+ sage: qs.push(sage.repl.preparse.QuoteStackFrame('"'))
409
+ sage: qs.pop()
410
+ QuoteStackFrame(...delim='"'...)
411
+ """
412
+ return self._stack.pop()
413
+
414
+ def push(self, frame):
415
+ """
416
+ Add a frame to the stack.
417
+
418
+ If the frame corresponds to an F-string, its delimiter is marked as no
419
+ longer being a :meth:`safe_delimiter`.
420
+
421
+ EXAMPLES::
422
+
423
+ sage: qs = sage.repl.preparse.QuoteStack()
424
+ sage: qs.push(sage.repl.preparse.QuoteStackFrame("'"))
425
+ sage: len(qs)
426
+ 1
427
+ """
428
+ self._stack.append(frame)
429
+ if frame.f_string:
430
+ if frame.delim == "'":
431
+ self._single_quote_safe = False
432
+ elif frame.delim == '"':
433
+ self._double_quote_safe = False
434
+
435
+ def safe_delimiter(self):
436
+ """
437
+ Return a string delimiter that may be safely inserted into the code
438
+ output by :func:`strip_string_literals`, if any.
439
+
440
+ ``'`` is preferred over ``"``. The triple-quoted versions are never
441
+ returned since by the time they would be chosen, they would also be invalid.
442
+ ``'''`` cannot, for example, appear within an F-string delimited by ``'``.
443
+
444
+ Once marked unsafe, a delimiter is never made safe again, even after the
445
+ stack frame that used it is popped. It may no longer be applicable to parsing,
446
+ but it appears somewhere in the processed code, so it is not safe to insert
447
+ just anywhere. A future enhancement could be to map ranges in the processed
448
+ code to the delimiter(s) that would be safe to insert there.
449
+
450
+ EXAMPLES::
451
+
452
+ sage: from sage.repl.preparse import QuoteStack, QuoteStackFrame
453
+ sage: s = QuoteStack()
454
+ sage: s.safe_delimiter()
455
+ "'"
456
+ sage: s.push(QuoteStackFrame("'"))
457
+ sage: s.safe_delimiter()
458
+ "'"
459
+ sage: s.pop()
460
+ QuoteStackFrame(...)
461
+ sage: s.push(QuoteStackFrame("'", f_string=True))
462
+ sage: s.safe_delimiter()
463
+ '"'
464
+ sage: s.push(QuoteStackFrame('"', f_string=True))
465
+ sage: s.safe_delimiter() is None
466
+ True
467
+ """
468
+ if self._single_quote_safe:
469
+ return "'"
470
+ if self._double_quote_safe:
471
+ return '"'
472
+ return None
473
+
474
+
475
+ class QuoteStackFrame(SimpleNamespace):
476
+ """
477
+ The state of a single level of a string literal being parsed.
478
+
479
+ Only F-strings have more than one level.
480
+ """
481
+
482
+ def __init__(self, delim, raw=False, f_string=False, braces=0, parens=0, brackets=0,
483
+ fmt_spec=False, nested_fmt_spec=False):
484
+ """
485
+ Create a new QuoteStackFrame.
486
+
487
+ INPUT:
488
+
489
+ - ``delim`` -- string; the quote character(s) used: ``'``, ``"``, ``'''``, or ``\"\"\"``
490
+ - ``raw`` -- boolean (default: ``False``); whether we are in a raw string
491
+ - ``f_string`` -- boolean (default: ``False``); whether we are in an F-string
492
+ - ``braces`` -- integer (default: 0); in an F-string,
493
+ how many unclosed ``{``'s have we encountered?
494
+ - ``parens`` -- integer (default: 0); in a replacement section of an F-string
495
+ (``braces > 0``), how many unclosed ``(``'s have we encountered?
496
+ - ``brackets`` -- integer (default: 0); in a replacement section of an F-string
497
+ (``braces > 0``), how many unclosed ``[``'s have we encountered?
498
+ - ``fmt_spec`` -- boolean (default: ``False``); in the format specifier portion of a
499
+ replacement section?
500
+ - ``nested_fmt_spec`` -- boolean (default: ``False``); in a nested format specifier?
501
+ For example, the ``X`` in ``f'{value:{width:X}}'``. Only one level of nesting
502
+ is currently allowed (as of Python 3.8).
503
+
504
+ EXAMPLES::
505
+
506
+ sage: qsf = sage.repl.preparse.QuoteStackFrame("'"); qsf
507
+ QuoteStackFrame(braces=0, brackets=0, delim="'", f_string=False, fmt_spec=False, nested_fmt_spec=False, parens=0, raw=False)
508
+ """
509
+ self.braces = braces
510
+ self.brackets = brackets
511
+ self.delim = delim
512
+ self.f_string = f_string
513
+ self.fmt_spec = fmt_spec
514
+ self.nested_fmt_spec = nested_fmt_spec
515
+ self.parens = parens
516
+ self.raw = raw
517
+
518
+
519
+ ssl_search_chars = re.compile(r'[()\[\]\'"#:{}]')
520
+
521
+
522
+ def strip_string_literals(code, state=None):
523
+ r"""
524
+ Return a string with all literal quotes replaced with labels and
525
+ a dictionary of labels for re-substitution.
526
+
527
+ This makes parsing easier.
528
+
529
+ INPUT:
530
+
531
+ - ``code`` -- string; the input
532
+
533
+ - ``state`` -- a :class:`QuoteStack` (default: ``None``); state with which to
534
+ continue processing, e.g., across multiple calls to this function
535
+
536
+ OUTPUT:
537
+
538
+ - a 3-tuple of the processed code, the dictionary of labels, and
539
+ any accumulated state
540
+
541
+ EXAMPLES::
542
+
543
+ sage: from sage.repl.preparse import strip_string_literals
544
+ sage: s, literals, state = strip_string_literals(r'''['a', "b", 'c', "d\""]''')
545
+ sage: s
546
+ '[%(L1)s, %(L2)s, %(L3)s, %(L4)s]'
547
+ sage: literals
548
+ {'L1': "'a'", 'L2': '"b"', 'L3': "'c'", 'L4': '"d\\""'}
549
+ sage: print(s % literals)
550
+ ['a', "b", 'c', "d\""]
551
+ sage: print(strip_string_literals(r'-"\\\""-"\\"-')[0])
552
+ -%(L1)s-%(L2)s-
553
+
554
+ Triple-quotes are handled as well::
555
+
556
+ sage: s, literals, state = strip_string_literals("[a, '''b''', c, '']")
557
+ sage: s
558
+ '[a, %(L1)s, c, %(L2)s]'
559
+ sage: print(s % literals)
560
+ [a, '''b''', c, '']
561
+
562
+ Comments are substitute too::
563
+
564
+ sage: s, literals, state = strip_string_literals("code '#' # ccc 't'"); s
565
+ 'code %(L1)s #%(L2)s'
566
+ sage: s % literals
567
+ "code '#' # ccc 't'"
568
+
569
+ A state is returned so one can break strings across multiple calls to
570
+ this function::
571
+
572
+ sage: s, literals, state = strip_string_literals('s = "some'); s
573
+ 's = %(L1)s'
574
+ sage: s, literals, state = strip_string_literals('thing" * 5', state); s
575
+ '%(L1)s * 5'
576
+
577
+ TESTS:
578
+
579
+ Even for raw strings, a backslash can escape a following quote::
580
+
581
+ sage: s, literals, state = strip_string_literals(r"r'somethin\' funny'"); s
582
+ 'r%(L1)s'
583
+ sage: dep_regex = r'^ *(?:(?:cimport +([\w\. ,]+))|(?:from +(\w+) +cimport)|(?:include *[\'"]([^\'"]+)[\'"])|(?:cdef *extern *from *[\'"]([^\'"]+)[\'"]))' # Issue 5821
584
+
585
+ Some extra tests for escaping with odd/even numbers of backslashes::
586
+
587
+ sage: s, literals, state = strip_string_literals(r"'somethin\\' 'funny'"); s
588
+ '%(L1)s %(L2)s'
589
+ sage: literals
590
+ {'L1': "'somethin\\\\'", 'L2': "'funny'"}
591
+ sage: s, literals, state = strip_string_literals(r"'something\\\' funny'"); s
592
+ '%(L1)s'
593
+ sage: literals
594
+ {'L1': "'something\\\\\\' funny'"}
595
+
596
+ Braces do not do anything special in normal strings::
597
+
598
+ sage: s, literals, state = strip_string_literals("'before{during}after'"); s
599
+ '%(L1)s'
600
+ sage: literals
601
+ {'L1': "'before{during}after'"}
602
+
603
+ But they are treated special in F-strings::
604
+
605
+ sage: s, literals, state = strip_string_literals("f'before{during}after'"); s
606
+ 'f%(L1)s{during}%(L2)s'
607
+ sage: literals
608
+ {'L1': "'before", 'L2': "after'"}
609
+
610
+ '#' is not handled specially inside a replacement section
611
+ (Python will not allow it anyways)::
612
+
613
+ sage: s, literals, state = strip_string_literals("f'#before {#during}' #after"); s
614
+ 'f%(L1)s{#during}%(L2)s #%(L3)s'
615
+ sage: literals
616
+ {'L1': "'#before ", 'L2': "'", 'L3': 'after'}
617
+
618
+ '{{' and '}}' escape sequences only work in the literal portion of an F-string::
619
+
620
+ sage: s, literals, state = strip_string_literals("f'A{{B}}C}}D{{'"); s
621
+ 'f%(L1)s'
622
+ sage: literals
623
+ {'L1': "'A{{B}}C}}D{{'"}
624
+ sage: s, literals, state = strip_string_literals("f'{ A{{B}}C }'"); s
625
+ 'f%(L1)s{ A{{B}}C }%(L2)s'
626
+ sage: literals
627
+ {'L1': "'", 'L2': "'"}
628
+
629
+ Nested braces in the replacement section (such as for a dict literal)::
630
+
631
+ sage: s, literals, state = strip_string_literals(''' f'{ {"x":1, "y":2} }' '''); s
632
+ ' f%(L1)s{ {%(L2)s:1, %(L3)s:2} }%(L4)s '
633
+ sage: literals
634
+ {'L1': "'", 'L2': '"x"', 'L3': '"y"', 'L4': "'"}
635
+
636
+ Format specifier treated as literal except for braced sections within::
637
+
638
+ sage: s, literals, state = strip_string_literals("f'{value:width}'"); s
639
+ 'f%(L1)s{value:%(L2)s}%(L3)s'
640
+ sage: literals['L2']
641
+ 'width'
642
+ sage: s, literals, state = strip_string_literals("f'{value:{width}}'"); s
643
+ 'f%(L1)s{value:%(L2)s{width}%(L3)s}%(L4)s'
644
+ sage: (literals['L2'], literals['L3']) # empty; not ideal, but not harmful
645
+ ('', '')
646
+
647
+ Nested format specifiers -- inside a braced section in the main format
648
+ specifier -- are treated as literals.
649
+ (Python does not allow any deeper nesting.)::
650
+
651
+ sage: s, literals, state = strip_string_literals("f'{value:{width:10}}'"); s
652
+ 'f%(L1)s{value:%(L2)s{width:%(L3)s}%(L4)s}%(L5)s'
653
+ sage: literals['L3']
654
+ '10'
655
+
656
+ A colon inside parentheses does not start the format specifier in order to
657
+ allow lambdas. (Python requires lambdas in F-strings to be in parentheses.)::
658
+
659
+ sage: s, literals, state = strip_string_literals("f'{(lambda x: x^2)(4)}'"); s
660
+ 'f%(L1)s{(lambda x: x^2)(4)}%(L2)s'
661
+
662
+ Similarly, a colon inside brackets does not start the format specifier in order
663
+ to allow slices::
664
+
665
+ sage: s, literals, state = strip_string_literals("f'{[0, 1, 2, 3][1:3]}'"); s
666
+ 'f%(L1)s{[0, 1, 2, 3][1:3]}%(L2)s'
667
+
668
+ 'r' and 'f' can be mixed to create raw F-strings::
669
+
670
+ sage: s, literals, stack = strip_string_literals("'"); stack.peek()
671
+ QuoteStackFrame(...f_string=False...raw=False...)
672
+ sage: s, literals, stack = strip_string_literals("f'"); stack.peek()
673
+ QuoteStackFrame(...f_string=True...raw=False...)
674
+ sage: s, literals, stack = strip_string_literals("r'"); stack.peek()
675
+ QuoteStackFrame(...f_string=False...raw=True...)
676
+ sage: s, literals, stack = strip_string_literals("rf'"); stack.peek()
677
+ QuoteStackFrame(...f_string=True...raw=True...)
678
+ sage: s, literals, stack = strip_string_literals("fr'"); stack.peek()
679
+ QuoteStackFrame(...f_string=True...raw=True...)
680
+ sage: s, literals, stack = strip_string_literals("FR'"); stack.peek()
681
+ QuoteStackFrame(...f_string=True...raw=True...)
682
+
683
+ Verify that state gets carried over correctly between calls with F-strings::
684
+
685
+ sage: s, lit = [None] * 10, [None] * 10
686
+ sage: s[0], lit[0], stack = strip_string_literals(''); stack
687
+ []
688
+ sage: s[1], lit[1], stack = strip_string_literals(" f'", stack); stack
689
+ [QuoteStackFrame(braces=0, brackets=0, delim="'", f_string=True, fmt_spec=False, nested_fmt_spec=False, parens=0, raw=False)]
690
+ sage: s[2], lit[2], stack = strip_string_literals('{', stack); stack
691
+ [QuoteStackFrame(...braces=1...)]
692
+ sage: s[3], lit[3], stack = strip_string_literals('r"abc', stack); stack
693
+ [QuoteStackFrame(...), QuoteStackFrame(...delim='"'...raw=True)]
694
+ sage: s[4], lit[4], stack = strip_string_literals('".upper(', stack); stack
695
+ [QuoteStackFrame(...parens=1...)]
696
+ sage: s[5], lit[5], stack = strip_string_literals(')[1:', stack); stack
697
+ [QuoteStackFrame(...brackets=1...parens=0...)]
698
+ sage: s[6], lit[6], stack = strip_string_literals(']:', stack); stack
699
+ [QuoteStackFrame(...brackets=0...fmt_spec=True...)]
700
+ sage: s[7], lit[7], stack = strip_string_literals('{width:1', stack); stack
701
+ [QuoteStackFrame(braces=2...nested_fmt_spec=True...)]
702
+ sage: s[8], lit[8], stack = strip_string_literals('0}', stack); stack
703
+ [QuoteStackFrame(braces=1...nested_fmt_spec=False...)]
704
+ sage: s[9], lit[9], stack = strip_string_literals("}' ", stack); stack
705
+ []
706
+ sage: s_broken_up = "".join(si % liti for si, liti in zip(s, lit)); s_broken_up
707
+ ' f\'{r"abc".upper()[1:]:{width:10}}\' '
708
+
709
+ Make sure the end result is the same whether broken up into multiple calls
710
+ or processed all at once::
711
+
712
+ sage: s, lit, state = strip_string_literals(''' f'{r"abc".upper()[1:]:{width:10}}' ''')
713
+ sage: s_one_time = s % lit; s_one_time
714
+ ' f\'{r"abc".upper()[1:]:{width:10}}\' '
715
+ sage: s_broken_up == s_one_time
716
+ True
717
+ """
718
+ new_code = []
719
+ literals = {}
720
+ counter = 0 # to assign unique label numbers
721
+ start = 0 # characters before this point have been added to new_code or literals
722
+ q = 0 # current search position in code string
723
+ state = state or QuoteStack()
724
+ quote = state.peek()
725
+
726
+ def in_literal():
727
+ """In the literal portion of a string?"""
728
+ if not quote:
729
+ return False
730
+ if not quote.f_string or not quote.braces or quote.nested_fmt_spec:
731
+ return True
732
+ return quote.fmt_spec and quote.braces == 1
733
+
734
+ match = ssl_search_chars.search(code)
735
+ while match:
736
+ q = match.start()
737
+ orig_q = q
738
+ ch = match.group()
739
+
740
+ # Just keep track of parentheses/brackets inside F-string replacement sections.
741
+ if ch == '(':
742
+ if quote and quote.braces:
743
+ quote.parens += 1
744
+ elif ch == ')':
745
+ if quote and quote.braces and quote.parens > 0:
746
+ quote.parens -= 1
747
+ elif ch == '[':
748
+ if quote and quote.braces:
749
+ quote.brackets += 1
750
+ elif ch == ']':
751
+ if quote and quote.braces and quote.brackets > 0:
752
+ quote.brackets -= 1
753
+
754
+ elif ch == "'" or ch == '"':
755
+ if in_literal():
756
+ # Deal with escaped quotes (odd number of backslashes preceding).
757
+ escaped = False
758
+ if q > 0 and code[q - 1] == '\\':
759
+ k = 2
760
+ while q >= k and code[q - k] == '\\':
761
+ k += 1
762
+ if not k % 2:
763
+ escaped = True
764
+ # Check for end of quote.
765
+ if not escaped and code[q:q + len(quote.delim)] == quote.delim:
766
+ counter += 1
767
+ label = "L%s" % counter
768
+ literals[label] = code[start:q + len(quote.delim)]
769
+ new_code.append("%%(%s)s" % label)
770
+ q += len(quote.delim)
771
+ start = q
772
+ state.pop()
773
+ quote = state.peek()
774
+ else:
775
+ # Check prefixes for raw or F-string.
776
+ if q > 0 and code[q - 1] in 'rR':
777
+ raw = True
778
+ f_string = q > 1 and code[q - 2] in 'fF'
779
+ elif q > 0 and code[q - 1] in 'fF':
780
+ f_string = True
781
+ raw = q > 1 and code[q - 2] in 'rR'
782
+ else:
783
+ raw = f_string = False
784
+ # Short or long string?
785
+ if len(code) >= q + 3 and (code[q + 1] == ch == code[q + 2]):
786
+ delim = ch * 3
787
+ else:
788
+ delim = ch
789
+ # Now inside quotes.
790
+ quote = QuoteStackFrame(delim, raw, f_string)
791
+ state.push(quote)
792
+ new_code.append(code[start:q].replace('%', '%%'))
793
+ start = q
794
+ q += len(delim)
795
+
796
+ elif ch == '#':
797
+ if not quote:
798
+ # It is a comment. Treat everything before as code and everything
799
+ # afterward up to the next newline as a comment literal.
800
+ newline = code.find('\n', q)
801
+ if newline == -1:
802
+ newline = len(code)
803
+ counter += 1
804
+ label = "L%s" % counter
805
+ literals[label] = code[q + 1:newline]
806
+ new_code.append(code[start:q].replace('%', '%%'))
807
+ new_code.append("#%%(%s)s" % label)
808
+ start = q = newline
809
+
810
+ elif ch == ':':
811
+ if quote and not quote.parens and not quote.brackets:
812
+ handle_colon = False
813
+ if quote.braces == 1:
814
+ # In a replacement section but outside of any nested braces or
815
+ # parentheses, the colon signals the beginning of the format specifier.
816
+ quote.fmt_spec = True
817
+ handle_colon = True
818
+ elif quote.fmt_spec:
819
+ # Already in the format specifier, so this must be a nested specifier.
820
+ quote.nested_fmt_spec = True
821
+ handle_colon = True
822
+ if handle_colon:
823
+ # Treat the preceding substring and the colon itself as code.
824
+ new_code.append(code[start:q + 1].replace('%', '%%'))
825
+ start = q + 1
826
+
827
+ elif ch == '{' or ch == '}':
828
+ if quote and quote.f_string:
829
+ # Skip over {{ and }} escape sequences outside of replacement sections.
830
+ if not quote.braces and q + 1 < len(code) and code[q + 1] == ch:
831
+ q += 2
832
+ else:
833
+ # Handle the substring preceding the brace.
834
+ if in_literal():
835
+ counter += 1
836
+ label = "L%s" % counter
837
+ literals[label] = code[start:q]
838
+ new_code.append("%%(%s)s" % label)
839
+ else:
840
+ new_code.append(code[start:q].replace('%', '%%'))
841
+ # Treat the brace itself as code.
842
+ new_code.append(ch)
843
+ if ch == '{':
844
+ quote.braces += 1
845
+ else:
846
+ if quote.braces > 0:
847
+ quote.braces -= 1
848
+ # We can no longer be in a nested format specifier following a }.
849
+ quote.nested_fmt_spec = False
850
+ start = q + 1
851
+
852
+ # Move to the next character if we have not already moved elsewhere.
853
+ if q == orig_q:
854
+ q += 1
855
+
856
+ # Re-prime the loop.
857
+ match = ssl_search_chars.search(code, q)
858
+
859
+ # Handle the remainder of the string.
860
+ if in_literal():
861
+ counter += 1
862
+ label = "L%s" % counter
863
+ literals[label] = code[start:]
864
+ new_code.append("%%(%s)s" % label)
865
+ else:
866
+ new_code.append(code[start:].replace('%', '%%'))
867
+
868
+ return "".join(new_code), literals, state
869
+
870
+
871
+ def containing_block(code, idx, delimiters=['()', '[]', '{}'], require_delim=True):
872
+ """
873
+ Find the code block given by balanced delimiters that contains the position ``idx``.
874
+
875
+ INPUT:
876
+
877
+ - ``code`` -- string
878
+
879
+ - ``idx`` -- integer; a starting position
880
+
881
+ - ``delimiters`` -- list of strings (default: ['()', '[]',
882
+ '{}']); the delimiters to balance. A delimiter must be a single
883
+ character and no character can at the same time be opening and
884
+ closing delimiter.
885
+
886
+ - ``require_delim`` -- boolean (default: ``True``); whether to raise
887
+ a :exc:`SyntaxError` if delimiters are present. If the delimiters are
888
+ unbalanced, an error will be raised in any case.
889
+
890
+ OUTPUT:
891
+
892
+ - a 2-tuple ``(a,b)`` of integers, such that ``code[a:b]`` is
893
+ delimited by balanced delimiters, ``a<=idx<b``, and ``a``
894
+ is maximal and ``b`` is minimal with that property. If that
895
+ does not exist, a :exc:`SyntaxError` is raised.
896
+
897
+ - If ``require_delim`` is false and ``a,b`` as above can not be
898
+ found, then ``0, len(code)`` is returned.
899
+
900
+ EXAMPLES::
901
+
902
+ sage: from sage.repl.preparse import containing_block
903
+ sage: s = "factor(next_prime(L[5]+1))"
904
+ sage: s[22]
905
+ '+'
906
+ sage: start, end = containing_block(s, 22)
907
+ sage: start, end
908
+ (17, 25)
909
+ sage: s[start:end]
910
+ '(L[5]+1)'
911
+ sage: s[20]
912
+ '5'
913
+ sage: start, end = containing_block(s, 20); s[start:end]
914
+ '[5]'
915
+ sage: start, end = containing_block(s, 20, delimiters=['()']); s[start:end]
916
+ '(L[5]+1)'
917
+ sage: start, end = containing_block(s, 10); s[start:end]
918
+ '(next_prime(L[5]+1))'
919
+
920
+ TESTS::
921
+
922
+ sage: containing_block('((a{))',0)
923
+ Traceback (most recent call last):
924
+ ...
925
+ SyntaxError: unbalanced delimiters
926
+ sage: containing_block('((a{))',1)
927
+ Traceback (most recent call last):
928
+ ...
929
+ SyntaxError: unbalanced delimiters
930
+ sage: containing_block('((a{))',2)
931
+ Traceback (most recent call last):
932
+ ...
933
+ SyntaxError: unbalanced delimiters
934
+ sage: containing_block('((a{))',3)
935
+ Traceback (most recent call last):
936
+ ...
937
+ SyntaxError: unbalanced delimiters
938
+ sage: containing_block('((a{))',4)
939
+ Traceback (most recent call last):
940
+ ...
941
+ SyntaxError: unbalanced delimiters
942
+ sage: containing_block('((a{))',5)
943
+ Traceback (most recent call last):
944
+ ...
945
+ SyntaxError: unbalanced delimiters
946
+ sage: containing_block('(()()',1)
947
+ (1, 3)
948
+ sage: containing_block('(()()',3)
949
+ (3, 5)
950
+ sage: containing_block('(()()',4)
951
+ (3, 5)
952
+ sage: containing_block('(()()',0)
953
+ Traceback (most recent call last):
954
+ ...
955
+ SyntaxError: unbalanced delimiters
956
+ sage: containing_block('(()()',0, require_delim=False)
957
+ (0, 5)
958
+ sage: containing_block('((})()',1, require_delim=False)
959
+ (0, 6)
960
+ sage: containing_block('abc',1, require_delim=False)
961
+ (0, 3)
962
+ """
963
+ openings = "".join(d[0] for d in delimiters)
964
+ closings = "".join(d[-1] for d in delimiters)
965
+ levels = [0] * len(openings)
966
+ p = 0
967
+ start = idx
968
+ while start >= 0:
969
+ if code[start] in openings:
970
+ p = openings.index(code[start])
971
+ levels[p] -= 1
972
+ if levels[p] == -1:
973
+ break
974
+ elif code[start] in closings and start < idx:
975
+ p = closings.index(code[start])
976
+ levels[p] += 1
977
+ start -= 1
978
+ if start == -1:
979
+ if require_delim:
980
+ raise SyntaxError("unbalanced or missing delimiters")
981
+ else:
982
+ return 0, len(code)
983
+ if levels.count(0) != len(levels) - 1:
984
+ if require_delim:
985
+ raise SyntaxError("unbalanced delimiters")
986
+ else:
987
+ return 0, len(code)
988
+ p0 = p
989
+ # We now have levels[p0]==-1. We go to the right hand side
990
+ # till we find a closing delimiter of type p0 that makes
991
+ # levels[p0]==0.
992
+ end = idx
993
+ while end < len(code):
994
+ if code[end] in closings:
995
+ p = closings.index(code[end])
996
+ levels[p] += 1
997
+ if p == p0 and levels[p] == 0:
998
+ break
999
+ elif code[end] in openings and end > idx:
1000
+ p = openings.index(code[end])
1001
+ levels[p] -= 1
1002
+ end += 1
1003
+ if levels.count(0) != len(levels):
1004
+ # This also occurs when end==len(code) without finding a closing delimiter
1005
+ if require_delim:
1006
+ raise SyntaxError("unbalanced delimiters")
1007
+ else:
1008
+ return 0, len(code)
1009
+ return start, end + 1
1010
+
1011
+
1012
+ def parse_ellipsis(code, preparse_step=True):
1013
+ """
1014
+ Preparses [0,2,..,n] notation.
1015
+
1016
+ INPUT:
1017
+
1018
+ - ``code`` -- string
1019
+
1020
+ - ``preparse_step`` -- boolean (default: ``True``)
1021
+
1022
+ OUTPUT: string
1023
+
1024
+ EXAMPLES::
1025
+
1026
+ sage: from sage.repl.preparse import parse_ellipsis
1027
+ sage: parse_ellipsis("[1,2,..,n]")
1028
+ '(ellipsis_range(1,2,Ellipsis,n))'
1029
+ sage: parse_ellipsis("for i in (f(x) .. L[10]):")
1030
+ 'for i in (ellipsis_iter(f(x) ,Ellipsis, L[10])):'
1031
+ sage: [1.0..2.0] # needs sage.rings.real_mpfr
1032
+ [1.00000000000000, 2.00000000000000]
1033
+
1034
+ TESTS:
1035
+
1036
+ Check that nested ellipsis is processed correctly (:issue:`17378`)::
1037
+
1038
+ sage: preparse('[1,..,2,..,len([1..3])]')
1039
+ '(ellipsis_range(Integer(1),Ellipsis,Integer(2),Ellipsis,len((ellipsis_range(Integer(1),Ellipsis,Integer(3))))))'
1040
+ """
1041
+ ix = code.find('..')
1042
+ while ix != -1:
1043
+ if ix == 0:
1044
+ raise SyntaxError("cannot start line with ellipsis")
1045
+ elif code[ix - 1] == '.':
1046
+ # '...' be valid Python in index slices
1047
+ code = code[:ix - 1] + "Ellipsis" + code[ix + 2:]
1048
+ elif len(code) >= ix + 3 and code[ix + 2] == '.':
1049
+ # '...' be valid Python in index slices
1050
+ code = code[:ix] + "Ellipsis" + code[ix + 3:]
1051
+ else:
1052
+ start_list, end_list = containing_block(code, ix, ['()', '[]'])
1053
+
1054
+ # search the current containing block for other '..' occurrences that may
1055
+ # be contained in proper subblocks. Those need to be processed before
1056
+ # we can deal with the present level of ellipses.
1057
+ ix = code.find('..', ix + 2, end_list)
1058
+ while ix != -1:
1059
+ if code[ix - 1] != '.' and code[ix + 2] != '.':
1060
+ start_list, end_list = containing_block(code, ix, ['()', '[]'])
1061
+ ix = code.find('..', ix + 2, end_list)
1062
+
1063
+ arguments = code[start_list + 1:end_list - 1].replace('...', ',Ellipsis,').replace('..', ',Ellipsis,')
1064
+ arguments = re.sub(r',\s*,', ',', arguments)
1065
+ if preparse_step:
1066
+ arguments = arguments.replace(';', ', step=')
1067
+ range_or_iter = 'range' if code[start_list] == '[' else 'iter'
1068
+ code = "%s(ellipsis_%s(%s))%s" % (code[:start_list],
1069
+ range_or_iter,
1070
+ arguments,
1071
+ code[end_list:])
1072
+ ix = code.find('..')
1073
+ return code
1074
+
1075
+
1076
+ def extract_numeric_literals(code):
1077
+ """
1078
+ Pulls out numeric literals and assigns them to global variables.
1079
+ This eliminates the need to re-parse and create the literals,
1080
+ e.g., during every iteration of a loop.
1081
+
1082
+ INPUT:
1083
+
1084
+ - ``code`` -- string; a block of code
1085
+
1086
+ OUTPUT:
1087
+
1088
+ - a (string, string:string dictionary) 2-tuple; the block with
1089
+ literals replaced by variable names and a mapping from names to
1090
+ the new variables
1091
+
1092
+ EXAMPLES::
1093
+
1094
+ sage: from sage.repl.preparse import extract_numeric_literals
1095
+ sage: code, nums = extract_numeric_literals("1.2 + 5")
1096
+ sage: print(code)
1097
+ _sage_const_1p2 + _sage_const_5
1098
+ sage: print(nums)
1099
+ {'_sage_const_1p2': "RealNumber('1.2')", '_sage_const_5': 'Integer(5)'}
1100
+
1101
+ sage: extract_numeric_literals("[1, 1.1, 1e1, -1e-1, 1.]")[0]
1102
+ '[_sage_const_1 , _sage_const_1p1 , _sage_const_1e1 , -_sage_const_1en1 , _sage_const_1p ]'
1103
+
1104
+ sage: extract_numeric_literals("[1.sqrt(), 1.2.sqrt(), 1r, 1.2r, R.1, R0.1, (1..5)]")[0]
1105
+ '[_sage_const_1 .sqrt(), _sage_const_1p2 .sqrt(), 1 , 1.2 , R.1, R0.1, (_sage_const_1 .._sage_const_5 )]'
1106
+ """
1107
+ return preparse_numeric_literals(code, True)
1108
+
1109
+
1110
+ all_num_regex = None
1111
+
1112
+
1113
+ def preparse_numeric_literals(code, extract=False, quotes="'"):
1114
+ """
1115
+ Preparse numerical literals into their Sage counterparts,
1116
+ e.g. Integer, RealNumber, and ComplexNumber.
1117
+
1118
+ INPUT:
1119
+
1120
+ - ``code`` -- string; a code block to preparse
1121
+
1122
+ - ``extract`` -- boolean (default: ``False``); whether to create
1123
+ names for the literals and return a dictionary of
1124
+ name-construction pairs
1125
+
1126
+ - ``quotes`` -- string (default: ``"'"``); used to surround string
1127
+ arguments to RealNumber and ComplexNumber. If ``None``, will rebuild
1128
+ the string using a list of its Unicode code-points.
1129
+
1130
+ OUTPUT:
1131
+
1132
+ - a string or (string, string:string dictionary) 2-tuple; the
1133
+ preparsed block and, if ``extract`` is True, the
1134
+ name-construction mapping
1135
+
1136
+ EXAMPLES::
1137
+
1138
+ sage: from sage.repl.preparse import preparse_numeric_literals
1139
+ sage: preparse_numeric_literals("5")
1140
+ 'Integer(5)'
1141
+ sage: preparse_numeric_literals("5j")
1142
+ "ComplexNumber(0, '5')"
1143
+ sage: preparse_numeric_literals("5jr")
1144
+ '5J'
1145
+ sage: preparse_numeric_literals("5l")
1146
+ '5'
1147
+ sage: preparse_numeric_literals("5L")
1148
+ '5'
1149
+ sage: preparse_numeric_literals("1.5")
1150
+ "RealNumber('1.5')"
1151
+ sage: preparse_numeric_literals("1.5j")
1152
+ "ComplexNumber(0, '1.5')"
1153
+ sage: preparse_numeric_literals(".5j")
1154
+ "ComplexNumber(0, '.5')"
1155
+ sage: preparse_numeric_literals("5e9j")
1156
+ "ComplexNumber(0, '5e9')"
1157
+ sage: preparse_numeric_literals("5.")
1158
+ "RealNumber('5.')"
1159
+ sage: preparse_numeric_literals("5.j")
1160
+ "ComplexNumber(0, '5.')"
1161
+ sage: preparse_numeric_literals("5.foo()")
1162
+ 'Integer(5).foo()'
1163
+ sage: preparse_numeric_literals("5.5.foo()")
1164
+ "RealNumber('5.5').foo()"
1165
+ sage: preparse_numeric_literals("5.5j.foo()")
1166
+ "ComplexNumber(0, '5.5').foo()"
1167
+ sage: preparse_numeric_literals("5j.foo()")
1168
+ "ComplexNumber(0, '5').foo()"
1169
+ sage: preparse_numeric_literals("1.exp()")
1170
+ 'Integer(1).exp()'
1171
+ sage: preparse_numeric_literals("1e+10")
1172
+ "RealNumber('1e+10')"
1173
+ sage: preparse_numeric_literals("0x0af")
1174
+ 'Integer(0x0af)'
1175
+ sage: preparse_numeric_literals("0x10.sqrt()")
1176
+ 'Integer(0x10).sqrt()'
1177
+ sage: preparse_numeric_literals('0o100')
1178
+ 'Integer(0o100)'
1179
+ sage: preparse_numeric_literals('0b111001')
1180
+ 'Integer(0b111001)'
1181
+ sage: preparse_numeric_literals('0xe')
1182
+ 'Integer(0xe)'
1183
+ sage: preparse_numeric_literals('0xEAR')
1184
+ '0xEA'
1185
+ sage: preparse_numeric_literals('0x1012Fae')
1186
+ 'Integer(0x1012Fae)'
1187
+ sage: preparse_numeric_literals('042')
1188
+ 'Integer(42)'
1189
+ sage: preparse_numeric_literals('000042')
1190
+ 'Integer(42)'
1191
+
1192
+ Test underscores as digit separators (PEP 515,
1193
+ https://www.python.org/dev/peps/pep-0515/)::
1194
+
1195
+ sage: preparse_numeric_literals('123_456')
1196
+ 'Integer(123_456)'
1197
+ sage: preparse_numeric_literals('123_456.78_9_0')
1198
+ "RealNumber('123_456.78_9_0')"
1199
+ sage: preparse_numeric_literals('0b11_011')
1200
+ 'Integer(0b11_011)'
1201
+ sage: preparse_numeric_literals('0o76_321')
1202
+ 'Integer(0o76_321)'
1203
+ sage: preparse_numeric_literals('0xaa_aaa')
1204
+ 'Integer(0xaa_aaa)'
1205
+ sage: preparse_numeric_literals('1_3.2_5e-2_2')
1206
+ "RealNumber('1_3.2_5e-2_2')"
1207
+
1208
+ sage: for f in ["1_1.", "11_2.", "1.1_1", "1_1.1_1", ".1_1", ".1_1e1_1", ".1e1_1",
1209
+ ....: "1e12_3", "1_1e1_1", "1.1_3e1_2", "1_1e1_1", "1e1", "1.e1_1",
1210
+ ....: "1.0", "1_1.0"]:
1211
+ ....: preparse_numeric_literals(f)
1212
+ ....: assert preparse(f) == preparse_numeric_literals(f), f
1213
+ "RealNumber('1_1.')"
1214
+ "RealNumber('11_2.')"
1215
+ "RealNumber('1.1_1')"
1216
+ "RealNumber('1_1.1_1')"
1217
+ "RealNumber('.1_1')"
1218
+ "RealNumber('.1_1e1_1')"
1219
+ "RealNumber('.1e1_1')"
1220
+ "RealNumber('1e12_3')"
1221
+ "RealNumber('1_1e1_1')"
1222
+ "RealNumber('1.1_3e1_2')"
1223
+ "RealNumber('1_1e1_1')"
1224
+ "RealNumber('1e1')"
1225
+ "RealNumber('1.e1_1')"
1226
+ "RealNumber('1.0')"
1227
+ "RealNumber('1_1.0')"
1228
+
1229
+ Having consecutive underscores is not valid Python syntax, so
1230
+ it is not preparsed, and similarly with a trailing underscore::
1231
+
1232
+ sage: preparse_numeric_literals('123__45')
1233
+ '123__45'
1234
+ sage: 123__45
1235
+ Traceback (most recent call last):
1236
+ ...
1237
+ SyntaxError: invalid ...
1238
+
1239
+ sage: preparse_numeric_literals('3040_1_')
1240
+ '3040_1_'
1241
+ sage: 3040_1_
1242
+ Traceback (most recent call last):
1243
+ ...
1244
+ SyntaxError: invalid ...
1245
+
1246
+ Using the ``quotes`` parameter::
1247
+
1248
+ sage: preparse_numeric_literals('5j', quotes='"')
1249
+ 'ComplexNumber(0, "5")'
1250
+ sage: preparse_numeric_literals('3.14', quotes="'''")
1251
+ "RealNumber('''3.14''')"
1252
+ sage: preparse_numeric_literals('3.14', quotes=None)
1253
+ 'RealNumber(str().join(map(chr, [51, 46, 49, 52])))'
1254
+ sage: preparse_numeric_literals('5j', quotes=None)
1255
+ 'ComplexNumber(0, str().join(map(chr, [53])))'
1256
+ """
1257
+ literals = {}
1258
+ last = 0
1259
+ new_code = []
1260
+
1261
+ global all_num_regex
1262
+ if all_num_regex is None:
1263
+ hex_num = r"\b0x[0-9a-f]+(_[0-9a-f]+)*"
1264
+ oct_num = r"\b0o[0-7]+(_[0-7]+)*"
1265
+ bin_num = r"\b0b[01]+(_[01]+)*"
1266
+ # This is slightly annoying as floating point numbers may start
1267
+ # with a decimal point, but if they do the \b will not match.
1268
+ float_num = r"((\b\d+(_\d+)*([.](\d+(_\d+)*)?)?)|([.]\d+(_\d+)*))(e[-+]?\d+(_\d+)*)?"
1269
+ all_num = r"((%s)|(%s)|(%s)|(%s))(rj|rL|jr|Lr|j|L|r|)\b" % (hex_num, oct_num, bin_num, float_num)
1270
+ all_num_regex = re.compile(all_num, re.I)
1271
+
1272
+ for m in all_num_regex.finditer(code):
1273
+ start, end = m.start(), m.end()
1274
+ num = m.group(1)
1275
+ postfix = m.groups()[-1].upper()
1276
+
1277
+ if 'R' in postfix:
1278
+ postfix = postfix.replace('L', '')
1279
+ num_name = num_make = num + postfix.replace('R', '')
1280
+ elif 'L' in postfix:
1281
+ num_name = num_make = num + postfix.replace('L', '')
1282
+ else:
1283
+
1284
+ # The Sage preparser does extra things with numbers, which we need to handle here.
1285
+ if '.' in num:
1286
+ if start > 0 and num[0] == '.':
1287
+ if code[start - 1] == '.':
1288
+ # handle Ellipsis
1289
+ start += 1
1290
+ num = num[1:]
1291
+ elif re.match(r'[\w\])]', code[start - 1]):
1292
+ # handle R.0
1293
+ continue
1294
+ elif end < len(code) and num[-1] == '.':
1295
+ if re.match(r'[^\W\d]', code[end]):
1296
+ # handle 4.sqrt()
1297
+ end -= 1
1298
+ num = num[:-1]
1299
+ elif end < len(code) and code[end] == '.' and not postfix and re.match(r'\d+(_\d+)*$', num):
1300
+ # \b does not match after the . for floating point
1301
+ # two dots in a row would be an ellipsis
1302
+ if end + 1 == len(code) or code[end + 1] != '.':
1303
+ end += 1
1304
+ num += '.'
1305
+
1306
+ num_name = numeric_literal_prefix + num.replace('.', 'p').replace('-', 'n').replace('+', '')
1307
+
1308
+ if 'J' in postfix:
1309
+ if quotes:
1310
+ num_make = "ComplexNumber(0, %s%s%s)" % (quotes, num, quotes)
1311
+ else:
1312
+ code_points = list(map(ord, list(num)))
1313
+ num_make = "ComplexNumber(0, str().join(map(chr, %s)))" % code_points
1314
+ num_name += 'j'
1315
+ elif len(num) < 2 or num[1] in 'oObBxX':
1316
+ num_make = "Integer(%s)" % num
1317
+ elif '.' in num or 'e' in num or 'E' in num:
1318
+ if quotes:
1319
+ num_make = "RealNumber(%s%s%s)" % (quotes, num, quotes)
1320
+ else:
1321
+ code_points = list(map(ord, list(num)))
1322
+ num_make = "RealNumber(str().join(map(chr, %s)))" % code_points
1323
+ else:
1324
+ # Python 3 does not allow leading zeroes. Sage does, so just strip them out.
1325
+ # The number is still interpreted as decimal, not octal!
1326
+ num = re.sub(r'^0+', '', num)
1327
+ num_make = "Integer(%s)" % num
1328
+
1329
+ literals[num_name] = num_make
1330
+
1331
+ new_code.append(code[last:start])
1332
+ if extract:
1333
+ new_code.append(num_name + ' ')
1334
+ else:
1335
+ new_code.append(num_make)
1336
+ last = end
1337
+
1338
+ new_code.append(code[last:])
1339
+ code = ''.join(new_code)
1340
+ if extract:
1341
+ return code, literals
1342
+ else:
1343
+ return code
1344
+
1345
+
1346
+ def strip_prompts(line):
1347
+ r"""
1348
+ Remove leading sage: and >>> prompts so that pasting of examples
1349
+ from the documentation works.
1350
+
1351
+ INPUT:
1352
+
1353
+ - ``line`` -- string to process
1354
+
1355
+ OUTPUT: string stripped of leading prompts
1356
+
1357
+ EXAMPLES::
1358
+
1359
+ sage: from sage.repl.preparse import strip_prompts
1360
+ sage: strip_prompts("sage: 2 + 2")
1361
+ '2 + 2'
1362
+ sage: strip_prompts(">>> 3 + 2")
1363
+ '3 + 2'
1364
+ sage: strip_prompts(" 2 + 4")
1365
+ ' 2 + 4'
1366
+ """
1367
+ for prompt, length in [('sage:', 5), ('>>>', 3)]:
1368
+ if line.startswith(prompt):
1369
+ return line[length:].lstrip()
1370
+ return line
1371
+
1372
+
1373
+ def preparse_calculus(code):
1374
+ r"""
1375
+ Supports calculus-like function assignment, e.g., transforms::
1376
+
1377
+ f(x,y,z) = sin(x^3 - 4*y) + y^x
1378
+
1379
+ into::
1380
+
1381
+ __tmp__=var("x,y,z")
1382
+ f = symbolic_expression(sin(x**3 - 4*y) + y**x).function(x,y,z)
1383
+
1384
+ AUTHORS:
1385
+
1386
+ - Bobby Moretti
1387
+
1388
+ - Initial version - 02/2007
1389
+
1390
+ - William Stein
1391
+
1392
+ - Make variables become defined if they are not already defined.
1393
+
1394
+ - Robert Bradshaw
1395
+
1396
+ - Rewrite using regular expressions (01/2009)
1397
+
1398
+ EXAMPLES::
1399
+
1400
+ sage: preparse("f(x) = x^3-x")
1401
+ '__tmp__=var("x"); f = symbolic_expression(x**Integer(3)-x).function(x)'
1402
+ sage: preparse("f(u,v) = u - v")
1403
+ '__tmp__=var("u,v"); f = symbolic_expression(u - v).function(u,v)'
1404
+ sage: preparse("f(x) =-5")
1405
+ '__tmp__=var("x"); f = symbolic_expression(-Integer(5)).function(x)'
1406
+ sage: preparse("f(x) -= 5")
1407
+ 'f(x) -= Integer(5)'
1408
+ sage: preparse("f(x_1, x_2) = x_1^2 - x_2^2")
1409
+ '__tmp__=var("x_1,x_2"); f = symbolic_expression(x_1**Integer(2) - x_2**Integer(2)).function(x_1,x_2)'
1410
+
1411
+ For simplicity, this function assumes all statements begin and end
1412
+ with a semicolon::
1413
+
1414
+ sage: from sage.repl.preparse import preparse_calculus
1415
+ sage: preparse_calculus(";f(t,s)=t^2;")
1416
+ ';__tmp__=var("t,s"); f = symbolic_expression(t^2).function(t,s);'
1417
+ sage: preparse_calculus(";f( t , s ) = t^2;")
1418
+ ';__tmp__=var("t,s"); f = symbolic_expression(t^2).function(t,s);'
1419
+
1420
+ TESTS:
1421
+
1422
+ The arguments in the definition must be symbolic variables (:issue:`10747`)::
1423
+
1424
+ sage: preparse_calculus(";f(_sage_const_)=x;")
1425
+ Traceback (most recent call last):
1426
+ ...
1427
+ ValueError: argument names should be valid python identifiers
1428
+
1429
+ Although preparse_calculus returns something for f(1)=x, when
1430
+ preparsing a file an exception is raised because it is invalid python::
1431
+
1432
+ sage: preparse_calculus(";f(1)=x;")
1433
+ ';__tmp__=var("1"); f = symbolic_expression(x).function(1);'
1434
+
1435
+ sage: from sage.repl.preparse import preparse_file
1436
+ sage: preparse_file("f(1)=x")
1437
+ Traceback (most recent call last):
1438
+ ...
1439
+ ValueError: argument names should be valid python identifiers
1440
+
1441
+ sage: from sage.repl.preparse import preparse_file
1442
+ sage: preparse_file("f(x,1)=2")
1443
+ Traceback (most recent call last):
1444
+ ...
1445
+ ValueError: argument names should be valid python identifiers
1446
+
1447
+ Check support for unicode characters (:issue:`29278`)::
1448
+
1449
+ sage: preparse("μ(x) = x^2")
1450
+ '__tmp__=var("x"); μ = symbolic_expression(x**Integer(2)).function(x)'
1451
+
1452
+ Check that the parameter list can span multiple lines (:issue:`30928`)::
1453
+
1454
+ sage: preparse('''
1455
+ ....: f(a,
1456
+ ....: b,
1457
+ ....: c,
1458
+ ....: d) = a + b*2 + c*3 + d*4
1459
+ ....: ''')
1460
+ '\n__tmp__=var("a,b,c,d"); f = symbolic_expression(a + b*Integer(2) + c*Integer(3) + d*Integer(4)).function(a,b,c,d)\n'
1461
+
1462
+ Check that :issue:`30953` is fixed::
1463
+
1464
+ sage: preparse('''
1465
+ ....: f(x) = (x + (x*x) + # some comment with matching )
1466
+ ....: 1); f''')
1467
+ '\n__tmp__=var("x"); f = symbolic_expression((x + (x*x) + # some comment with matching )\n Integer(1))).function(x); f'
1468
+ """
1469
+ new_code = []
1470
+ last_end = 0
1471
+ # f ( vars ) = expr
1472
+ for m in re.finditer(r";(\s*)([^\W\d]\w*) *\(([^()]+)\) *= *([^;#=][^;]*)", code):
1473
+ ident, func, vars, expr = m.groups()
1474
+ # Semicolons are removed in order to allow the vars to span multiple lines.
1475
+ # (preparse having converted all \n into ;\n;)
1476
+ stripped_vars = [v.replace(';', '').strip() for v in vars.split(',')]
1477
+ # if the variable name starts with numeric_literal_prefix
1478
+ # the argument name for the symbolic expression is a numeric literal
1479
+ # such as f(2)=5
1480
+ if any(n.startswith(numeric_literal_prefix) for n in stripped_vars):
1481
+ raise ValueError("argument names should be valid python identifiers")
1482
+ vars = ','.join(stripped_vars)
1483
+
1484
+ new_code.append(code[last_end:m.start()])
1485
+ new_code.append(';%s__tmp__=var("%s"); %s = symbolic_expression(%s).function(%s)' %
1486
+ (ident, vars, func, expr, vars))
1487
+ last_end = m.end()
1488
+
1489
+ if last_end == 0:
1490
+ return code
1491
+
1492
+ new_code.append(code[m.end():])
1493
+ return ''.join(new_code)
1494
+
1495
+
1496
+ def preparse_generators(code):
1497
+ r"""
1498
+ Parse generator syntax, converting::
1499
+
1500
+ obj.<gen0,gen1,...,genN> = objConstructor(...)
1501
+
1502
+ into::
1503
+
1504
+ obj = objConstructor(..., names=("gen0", "gen1", ..., "genN"))
1505
+ (gen0, gen1, ..., genN,) = obj.gens()
1506
+
1507
+ and::
1508
+
1509
+ obj.<gen0,gen1,...,genN> = R[interior]
1510
+
1511
+ into::
1512
+
1513
+ obj = R[interior]; (gen0, gen1, ..., genN,) = obj.gens()
1514
+
1515
+ INPUT:
1516
+
1517
+ - ``code`` -- string
1518
+
1519
+ OUTPUT: string
1520
+
1521
+ LIMITATIONS:
1522
+
1523
+ - The entire constructor *must* be on one line.
1524
+
1525
+ AUTHORS:
1526
+
1527
+ - 2006-04-14: Joe Wetherell (jlwether@alum.mit.edu)
1528
+
1529
+ - Initial version.
1530
+
1531
+ - 2006-04-17: William Stein
1532
+
1533
+ - Improvements to allow multiple statements.
1534
+
1535
+ - 2006-05-01: William
1536
+
1537
+ - Fix bug that Joe found.
1538
+
1539
+ - 2006-10-31: William
1540
+
1541
+ - Fix so obj does not have to be mutated.
1542
+
1543
+ - 2009-01-27: Robert Bradshaw
1544
+
1545
+ - Rewrite using regular expressions
1546
+
1547
+ TESTS::
1548
+
1549
+ sage: from sage.repl.preparse import preparse, preparse_generators
1550
+
1551
+ Vanilla::
1552
+
1553
+ sage: preparse("R.<x> = ZZ['x']")
1554
+ "R = ZZ['x']; (x,) = R._first_ngens(1)"
1555
+ sage: preparse("R.<x,y> = ZZ['x,y']")
1556
+ "R = ZZ['x,y']; (x, y,) = R._first_ngens(2)"
1557
+
1558
+ No square brackets::
1559
+
1560
+ sage: preparse("R.<x> = PolynomialRing(ZZ, 'x')")
1561
+ "R = PolynomialRing(ZZ, 'x', names=('x',)); (x,) = R._first_ngens(1)"
1562
+ sage: preparse("R.<x,y> = PolynomialRing(ZZ, 'x,y')")
1563
+ "R = PolynomialRing(ZZ, 'x,y', names=('x', 'y',)); (x, y,) = R._first_ngens(2)"
1564
+
1565
+ Names filled in::
1566
+
1567
+ sage: preparse("R.<x> = ZZ[]")
1568
+ "R = ZZ['x']; (x,) = R._first_ngens(1)"
1569
+ sage: preparse("R.<x,y> = ZZ[]")
1570
+ "R = ZZ['x, y']; (x, y,) = R._first_ngens(2)"
1571
+
1572
+ Names given not the same as generator names::
1573
+
1574
+ sage: preparse("R.<x> = ZZ['y']")
1575
+ "R = ZZ['y']; (x,) = R._first_ngens(1)"
1576
+ sage: preparse("R.<x,y> = ZZ['u,v']")
1577
+ "R = ZZ['u,v']; (x, y,) = R._first_ngens(2)"
1578
+
1579
+ Number fields::
1580
+
1581
+ sage: preparse("K.<a> = QQ[2^(1/3)]")
1582
+ 'K = QQ[Integer(2)**(Integer(1)/Integer(3))]; (a,) = K._first_ngens(1)'
1583
+ sage: preparse("K.<a, b> = QQ[2^(1/3), 2^(1/2)]")
1584
+ 'K = QQ[Integer(2)**(Integer(1)/Integer(3)), Integer(2)**(Integer(1)/Integer(2))]; (a, b,) = K._first_ngens(2)'
1585
+
1586
+ Just the .<> notation::
1587
+
1588
+ sage: preparse("R.<x> = ZZx")
1589
+ 'R = ZZx; (x,) = R._first_ngens(1)'
1590
+ sage: preparse("R.<x, y> = a+b")
1591
+ 'R = a+b; (x, y,) = R._first_ngens(2)'
1592
+ sage: preparse("A.<x,y,z>=FreeAlgebra(ZZ,3)")
1593
+ "A = FreeAlgebra(ZZ,Integer(3), names=('x', 'y', 'z',)); (x, y, z,) = A._first_ngens(3)"
1594
+
1595
+ Ensure we do not eat too much::
1596
+
1597
+ sage: preparse("R.<x, y> = ZZ;2")
1598
+ 'R = ZZ; (x, y,) = R._first_ngens(2);Integer(2)'
1599
+ sage: preparse("R.<x, y> = ZZ['x,y'];2")
1600
+ "R = ZZ['x,y']; (x, y,) = R._first_ngens(2);Integer(2)"
1601
+ sage: preparse("F.<b>, f, g = S.field_extension()")
1602
+ "F, f, g = S.field_extension(names=('b',)); (b,) = F._first_ngens(1)"
1603
+
1604
+ For simplicity, this function assumes all statements begin and end
1605
+ with a semicolon::
1606
+
1607
+ sage: preparse_generators("; R.<x>=ZZ[];")
1608
+ "; R = ZZ['x']; (x,) = R._first_ngens(1);"
1609
+
1610
+ See :issue:`16731`::
1611
+
1612
+ sage: preparse_generators('R.<x> = ')
1613
+ 'R.<x> = '
1614
+
1615
+ Check support for unicode characters (:issue:`29278`)::
1616
+
1617
+ sage: preparse('Ω.<λ,μ> = QQ[]')
1618
+ "Ω = QQ['λ, μ']; (λ, μ,) = Ω._first_ngens(2)"
1619
+
1620
+ Check that :issue:`30953` is fixed::
1621
+
1622
+ sage: preparse('''
1623
+ ....: K.<a> = QuadraticField(2 +
1624
+ ....: 1)''')
1625
+ "\nK = QuadraticField(Integer(2) +\n Integer(1), names=('a',)); (a,) = K._first_ngens(1)"
1626
+ sage: preparse('''
1627
+ ....: K.<a> = QuadraticField(2 + (1 + 1) + # some comment
1628
+ ....: 1)''')
1629
+ "\nK = QuadraticField(Integer(2) + (Integer(1) + Integer(1)) + # some comment\n Integer(1), names=('a',)); (a,) = K._first_ngens(1)"
1630
+ sage: preparse('''
1631
+ ....: K.<a> = QuadraticField(2) # some comment''')
1632
+ "\nK = QuadraticField(Integer(2), names=('a',)); (a,) = K._first_ngens(1)# some comment"
1633
+ """
1634
+ new_code = []
1635
+ last_end = 0
1636
+ # obj .< gens > , other = constructor
1637
+ for m in re.finditer(r";(\s*)([^\W\d]\w*)\.<([^>]+)> *((?:,[\w, ]+)?)= *([^;]+)", code):
1638
+ ident, obj, gens, other_objs, constructor = m.groups()
1639
+ gens = [v.strip() for v in gens.split(',')]
1640
+ constructor = constructor.rstrip()
1641
+ if len(constructor) == 0:
1642
+ pass # SyntaxError will be raised by Python later
1643
+ elif constructor[-1] == ')':
1644
+ if '(' not in constructor:
1645
+ raise SyntaxError("mismatched ')'")
1646
+ opening = constructor.rindex('(')
1647
+ # Only use comma if there are already arguments to the constructor
1648
+ comma = ', ' if constructor[opening + 1:-1].strip() else ''
1649
+ names = "('%s',)" % "', '".join(gens)
1650
+ constructor = constructor[:-1] + comma + "names=%s)" % names
1651
+ elif constructor[-1] == ']':
1652
+ # Could be nested.
1653
+ if '[' not in constructor:
1654
+ raise SyntaxError("mismatched ']'")
1655
+ opening = constructor.rindex('[')
1656
+ closing = constructor.index(']', opening)
1657
+ if not constructor[opening + 1:closing].strip():
1658
+ names = "'" + ', '.join(gens) + "'"
1659
+ constructor = constructor[:opening + 1] + names + constructor[closing:]
1660
+ else:
1661
+ pass
1662
+ gens_tuple = "(%s,)" % ', '.join(gens)
1663
+ new_code.append(code[last_end:m.start()])
1664
+ new_code.append(";%s%s%s = %s; %s = %s._first_ngens(%s)" %
1665
+ (ident, obj, other_objs, constructor, gens_tuple, obj, len(gens)))
1666
+ last_end = m.end()
1667
+
1668
+ if last_end == 0:
1669
+ return code
1670
+
1671
+ new_code.append(code[m.end():])
1672
+ return ''.join(new_code)
1673
+
1674
+
1675
+ quote_state = None
1676
+
1677
+
1678
+ def preparse(line, reset=True, do_time=False, ignore_prompts=False,
1679
+ numeric_literals=True):
1680
+ r"""
1681
+ Preparse a line of input.
1682
+
1683
+ INPUT:
1684
+
1685
+ - ``line`` -- string
1686
+
1687
+ - ``reset`` -- boolean (default: ``True``)
1688
+
1689
+ - ``do_time`` -- boolean (default: ``False``)
1690
+
1691
+ - ``ignore_prompts`` -- boolean (default: ``False``)
1692
+
1693
+ - ``numeric_literals`` -- boolean (default: ``True``)
1694
+
1695
+ OUTPUT: string
1696
+
1697
+ EXAMPLES::
1698
+
1699
+ sage: preparse("ZZ.<x> = ZZ['x']")
1700
+ "ZZ = ZZ['x']; (x,) = ZZ._first_ngens(1)"
1701
+ sage: preparse("ZZ.<x> = ZZ['y']")
1702
+ "ZZ = ZZ['y']; (x,) = ZZ._first_ngens(1)"
1703
+ sage: preparse("ZZ.<x,y> = ZZ[]")
1704
+ "ZZ = ZZ['x, y']; (x, y,) = ZZ._first_ngens(2)"
1705
+ sage: preparse("ZZ.<x,y> = ZZ['u,v']")
1706
+ "ZZ = ZZ['u,v']; (x, y,) = ZZ._first_ngens(2)"
1707
+ sage: preparse("ZZ.<x> = QQ[2^(1/3)]")
1708
+ 'ZZ = QQ[Integer(2)**(Integer(1)/Integer(3))]; (x,) = ZZ._first_ngens(1)'
1709
+ sage: QQ[2^(1/3)] # needs sage.rings.number_field sage.symbolic
1710
+ Number Field in a with defining polynomial x^3 - 2 with a = 1.259921049894873?
1711
+
1712
+ sage: preparse("a^b")
1713
+ 'a**b'
1714
+ sage: preparse("a^^b")
1715
+ 'a^b'
1716
+ sage: 8^1
1717
+ 8
1718
+ sage: 8^^1
1719
+ 9
1720
+ sage: 9^^1
1721
+ 8
1722
+
1723
+ sage: preparse("A \\ B")
1724
+ 'A * BackslashOperator() * B'
1725
+ sage: preparse("A^2 \\ B + C")
1726
+ 'A**Integer(2) * BackslashOperator() * B + C'
1727
+ sage: preparse("a \\ b \\") # There is really only one backslash here, it is just being escaped.
1728
+ 'a * BackslashOperator() * b \\'
1729
+
1730
+ sage: preparse("time R.<x> = ZZ[]", do_time=True)
1731
+ '__time__ = cputime(); __wall__ = walltime(); R = ZZ[\'x\']; print("Time: CPU {:.2f} s, Wall: {:.2f} s".format(cputime(__time__), walltime(__wall__))); (x,) = R._first_ngens(1)'
1732
+
1733
+ TESTS:
1734
+
1735
+ Check support for unicode characters (:issue:`29278`)::
1736
+
1737
+ sage: preparse("Ω.0")
1738
+ 'Ω.gen(0)'
1739
+
1740
+ Check support for backslash line continuation (:issue:`30928`)::
1741
+
1742
+ sage: preparse("f(x) = x \\\n+ 1")
1743
+ '__tmp__=var("x"); f = symbolic_expression(x + Integer(1)).function(x)'
1744
+
1745
+ Check that multi-line strings starting with a comment are still preparsed
1746
+ (:issue:`31043`)::
1747
+
1748
+ sage: print(preparse('''# some comment
1749
+ ....: f(x) = x + 1'''))
1750
+ # some comment
1751
+ __tmp__=var("x"); f = symbolic_expression(x + Integer(1)).function(x)
1752
+
1753
+ TESTS::
1754
+
1755
+ sage: from sage.repl.preparse import preparse
1756
+ sage: lots_of_numbers = "[%s]" % ", ".join(str(i) for i in range(3000))
1757
+ sage: _ = preparse(lots_of_numbers)
1758
+ sage: print(preparse("type(100r), type(100)"))
1759
+ type(100), type(Integer(100))
1760
+ """
1761
+ global quote_state
1762
+ if reset:
1763
+ quote_state = None
1764
+
1765
+ L = line.lstrip()
1766
+
1767
+ if L.startswith('...'):
1768
+ i = line.find('...')
1769
+ return line[:i + 3] + preparse(line[i + 3:], reset=reset,
1770
+ do_time=do_time, ignore_prompts=ignore_prompts)
1771
+
1772
+ if ignore_prompts:
1773
+ # Get rid of leading sage: and >>> so that pasting of examples from
1774
+ # the documentation works.
1775
+ line = strip_prompts(line)
1776
+
1777
+ # This part handles lines with semi-colons all at once
1778
+ # Then can also handle multiple lines more efficiently, but
1779
+ # that optimization can be done later.
1780
+ L, literals, quote_state = strip_string_literals(line, quote_state)
1781
+
1782
+ # Ellipsis Range
1783
+ # [1..n]
1784
+ try:
1785
+ L = parse_ellipsis(L, preparse_step=False)
1786
+ except SyntaxError:
1787
+ pass
1788
+
1789
+ if implicit_mul_level:
1790
+ # Implicit Multiplication
1791
+ # 2x -> 2*x
1792
+ L = implicit_mul(L, level=implicit_mul_level)
1793
+
1794
+ if numeric_literals:
1795
+ # Wrapping
1796
+ # 1 + 0.5 -> Integer(1) + RealNumber('0.5')
1797
+ L = preparse_numeric_literals(L, quotes=quote_state.safe_delimiter())
1798
+
1799
+ # Generators
1800
+ # R.0 -> R.gen(0)
1801
+ L = re.sub(r'(\b[^\W\d]\w*|[)\]])\.(\d+)', r'\1.gen(\2)', L)
1802
+
1803
+ # Use ^ for exponentiation and ^^ for xor
1804
+ # (A side effect is that **** becomes xor as well.)
1805
+ L = L.replace('^', '**').replace('****', '^')
1806
+
1807
+ # Combine lines that use backslash continuation
1808
+ L = L.replace('\\\n', '')
1809
+
1810
+ # Make it easy to match statement ends
1811
+ ends = []
1812
+ counta = 0
1813
+ countb = 0
1814
+ for i in range(len(L)):
1815
+ if L[i] in ('[', ']'):
1816
+ counta += 1 if L[i] == '[' else -1
1817
+ elif L[i] in ('(', ')'):
1818
+ countb += 1 if L[i] == '(' else -1
1819
+ elif L[i] in ('\n', '#'):
1820
+ if counta == countb == 0:
1821
+ ends.append(i)
1822
+ while ends:
1823
+ i = ends.pop()
1824
+ L = L[:i] + ';%s;' % L[i] + L[i + 1:]
1825
+ L = ';' + L + ';'
1826
+
1827
+ if do_time:
1828
+ # Separate time statement
1829
+ L = re.sub(r';(\s*)time +(\w)', r';time;\1\2', L)
1830
+
1831
+ # Construction with generators
1832
+ # R.<...> = obj()
1833
+ # R.<...> = R[]
1834
+ L = preparse_generators(L)
1835
+
1836
+ # Calculus functions
1837
+ # f(x,y) = x^3 - sin(y)
1838
+ L = preparse_calculus(L)
1839
+
1840
+ # Backslash
1841
+ L = re.sub(r'''\\\s*([^\t ;#])''', r' * BackslashOperator() * \1', L)
1842
+
1843
+ if do_time:
1844
+ # Time keyword
1845
+ L = re.sub(r';time;(\s*)(\S[^;\n]*)',
1846
+ r';\1__time__ = cputime(); __wall__ = walltime(); \2; print(' +
1847
+ r'"Time: CPU {:.2f} s, Wall: {:.2f} s".format(cputime(__time__), walltime(__wall__)))',
1848
+ L, flags=re.MULTILINE)
1849
+
1850
+ # Remove extra ;'s
1851
+ L = L.replace(';#;', '#')
1852
+ L = L.replace(';\n;', '\n')[1:-1]
1853
+
1854
+ return L % literals
1855
+
1856
+
1857
+ ######################################################
1858
+ # Apply the preparser to an entire file
1859
+ ######################################################
1860
+
1861
+ def preparse_file(contents, globals=None, numeric_literals=True):
1862
+ """
1863
+ Preparse ``contents`` which is input from a file such as ``.sage`` files.
1864
+
1865
+ Special attentions are given to numeric literals and load/attach
1866
+ file directives.
1867
+
1868
+ .. NOTE:: Temporarily, if @parallel is in the input, then
1869
+ numeric_literals is always set to False.
1870
+
1871
+ INPUT:
1872
+
1873
+ - ``contents`` -- string
1874
+
1875
+ - ``globals`` -- dictionary or ``None`` (default: ``None``); if given, then
1876
+ arguments to load/attach are evaluated in the namespace of this dictionary
1877
+
1878
+ - ``numeric_literals`` -- boolean (default: ``True``); whether to factor
1879
+ out wrapping of integers and floats, so they do not get created
1880
+ repeatedly inside loops
1881
+
1882
+ OUTPUT: string
1883
+
1884
+ TESTS::
1885
+
1886
+ sage: from sage.repl.preparse import preparse_file
1887
+ sage: lots_of_numbers = "[%s]" % ", ".join(str(i) for i in range(3000))
1888
+ sage: _ = preparse_file(lots_of_numbers)
1889
+ sage: print(preparse_file("type(100r), type(100)"))
1890
+ _sage_const_100 = Integer(100)
1891
+ type(100 ), type(_sage_const_100 )
1892
+
1893
+ Check that :issue:`4545` is fixed::
1894
+
1895
+ sage: file_contents = '''
1896
+ ....: @parallel(8)
1897
+ ....: def func(p):
1898
+ ....: t = cputime()
1899
+ ....: M = ModularSymbols(p^2,sign=1)
1900
+ ....: w = M.atkin_lehner_operator(p)
1901
+ ....: K = (w-1).kernel()'''
1902
+ sage: t = tmp_filename(ext='.sage')
1903
+ sage: with open(t, 'w') as file:
1904
+ ....: file.write(file_contents)
1905
+ 137
1906
+ sage: load(t)
1907
+ sage: sorted(list(func([11,17]))) # needs sage.modular
1908
+ [(((11,), {}), None), (((17,), {}), None)]
1909
+ """
1910
+ if not isinstance(contents, str):
1911
+ raise TypeError("contents must be a string")
1912
+
1913
+ if globals is None:
1914
+ globals = {}
1915
+
1916
+ if numeric_literals:
1917
+ contents, literals, state = strip_string_literals(contents)
1918
+ contents, nums = extract_numeric_literals(contents)
1919
+ contents = contents % literals
1920
+ if nums:
1921
+ # Stick the assignments at the top, trying not to shift
1922
+ # the lines down.
1923
+ ix = contents.find('\n')
1924
+ if ix == -1:
1925
+ ix = len(contents)
1926
+ if not re.match(r"^ *(#.*)?$", contents[:ix]):
1927
+ contents = "\n" + contents
1928
+ assignments = ["%s = %s" % x for x in nums.items()]
1929
+ # the preparser recurses on semicolons, so we only attempt
1930
+ # to preserve line numbers if there are a few
1931
+ if len(assignments) < 500:
1932
+ contents = "; ".join(assignments) + contents
1933
+ else:
1934
+ contents = "\n".join(assignments) + "\n\n" + contents
1935
+
1936
+ start = 0
1937
+ lines_out = []
1938
+ preparse_opts = dict(do_time=True, ignore_prompts=False, numeric_literals=not numeric_literals)
1939
+ for m in re.finditer(r'^(\s*)(load|attach) ([^(].*)$', contents, re.MULTILINE):
1940
+ # Preparse contents prior to the load/attach.
1941
+ lines_out += preparse(contents[start:m.start()], **preparse_opts).splitlines()
1942
+ # Wrap the load/attach itself.
1943
+ lines_out.append(m.group(1) + load_wrap(m.group(3), m.group(2) == 'attach'))
1944
+ # Further preparsing should start after this load/attach line.
1945
+ start = m.end()
1946
+ # Preparse the remaining contents.
1947
+ lines_out += preparse(contents[start:], **preparse_opts).splitlines()
1948
+
1949
+ return '\n'.join(lines_out)
1950
+
1951
+
1952
+ def implicit_mul(code, level=5):
1953
+ r"""
1954
+ Insert \*'s to make implicit multiplication explicit.
1955
+
1956
+ INPUT:
1957
+
1958
+ - ``code`` -- string; the code with missing \*'s
1959
+
1960
+ - ``level`` -- integer (default: 5); see :func:`implicit_multiplication`
1961
+ for a list
1962
+
1963
+ OUTPUT: string
1964
+
1965
+ EXAMPLES::
1966
+
1967
+ sage: from sage.repl.preparse import implicit_mul
1968
+ sage: implicit_mul('(2x^2-4x+3)a0')
1969
+ '(2*x^2-4*x+3)*a0'
1970
+ sage: implicit_mul('a b c in L')
1971
+ 'a*b*c in L'
1972
+ sage: implicit_mul('1r + 1e3 + 5exp(2)')
1973
+ '1r + 1e3 + 5*exp(2)'
1974
+ sage: implicit_mul('f(a)(b)', level=10)
1975
+ 'f(a)*(b)'
1976
+
1977
+ TESTS:
1978
+
1979
+ Check handling of Python 3 keywords (:issue:`29391`)::
1980
+
1981
+ sage: implicit_mul('nonlocal a')
1982
+ 'nonlocal a'
1983
+
1984
+ Although these are not keywords in Python 3, we explicitly avoid implicit
1985
+ multiplication in these cases because the error message will be more
1986
+ helpful (:issue:`29391`)::
1987
+
1988
+ sage: implicit_mul('print 2')
1989
+ 'print 2'
1990
+ sage: implicit_mul('exec s')
1991
+ 'exec s'
1992
+
1993
+ Check support for unicode characters (:issue:`29278`)::
1994
+
1995
+ sage: implicit_mul('3λ')
1996
+ '3*λ'
1997
+
1998
+ Check support for complex literals (:issue:`30477`)::
1999
+
2000
+ sage: implicit_mul('2r-1JR')
2001
+ '2r-1JR'
2002
+ sage: implicit_mul('1E3 + 0.3E-3rj')
2003
+ '1e3 + 0.3e-3rj'
2004
+ """
2005
+ from keyword import iskeyword
2006
+ keywords_py2 = ['print', 'exec']
2007
+
2008
+ def re_no_keyword(pattern, code):
2009
+ for _ in range(2): # do it twice in because matches do not overlap
2010
+ for m in reversed(list(re.finditer(pattern, code))):
2011
+ left, right = m.groups()
2012
+ if not iskeyword(left) and not iskeyword(right) \
2013
+ and left not in keywords_py2:
2014
+ code = "%s%s*%s%s" % (code[:m.start()],
2015
+ left,
2016
+ right,
2017
+ code[m.end():])
2018
+ return code
2019
+
2020
+ code, literals, state = strip_string_literals(code)
2021
+ if level >= 1:
2022
+ no_mul_token = " '''_no_mult_token_''' "
2023
+ code = re.sub(r'\b0x', r'0%sx' % no_mul_token, code) # hex digits
2024
+ code = re.sub(r'( *)time ', r'\1time %s' % no_mul_token, code) # first word may be magic 'time'
2025
+ code = re.sub(r'\b(\d+(?:\.\d+)?(?:e\d+)?)(rj?\b|j?r\b)', r'\1%s\2' % no_mul_token, code, flags=re.I) # exclude such things as 10r, 10rj, 10jr
2026
+ code = re.sub(r'\b(\d+(?:\.\d+)?)e([-\d])', r'\1%se%s\2' % (no_mul_token, no_mul_token), code, flags=re.I) # exclude such things as 1e5
2027
+ code = re_no_keyword(r'\b((?:\d+(?:\.\d+)?)|(?:%s[0-9eEpn]*\b)) *([^\W\d(]\w*)\b' % numeric_literal_prefix, code)
2028
+ if level >= 2:
2029
+ code = re.sub(r'(\%\(L\d+\))s', r'\1%ss%s' % (no_mul_token, no_mul_token), code) # literal strings
2030
+ code = re_no_keyword(r'(\)) *(\w+)', code)
2031
+ if level >= 3:
2032
+ code = re_no_keyword(r'(\w+) +(\w+)', code)
2033
+ if level >= 10:
2034
+ code = re.sub(r'\) *\(', ')*(', code)
2035
+ code = code.replace(no_mul_token, '')
2036
+ return code % literals
2037
+
2038
+
2039
+ def _strip_quotes(s):
2040
+ """
2041
+ Strip one set of outer quotes.
2042
+
2043
+ INPUT:
2044
+
2045
+ - ``s`` -- string
2046
+
2047
+ OUTPUT: string with any single and double quotes on either side of ``s``
2048
+ removed
2049
+
2050
+ EXAMPLES:
2051
+
2052
+ Both types of quotes work::
2053
+
2054
+ sage: import sage.repl.preparse
2055
+ sage: sage.repl.preparse._strip_quotes('"foo.sage"')
2056
+ 'foo.sage'
2057
+ sage: sage.repl.preparse._strip_quotes("'foo.sage'")
2058
+ 'foo.sage'
2059
+
2060
+ The only thing that is stripped is at most one set of outer quotes::
2061
+
2062
+ sage: sage.repl.preparse._strip_quotes('""foo".sage""')
2063
+ '"foo".sage"'
2064
+ """
2065
+ if not s:
2066
+ return s
2067
+ if s[0] in ["'", '"']:
2068
+ s = s[1:]
2069
+ if s[-1] in ["'", '"']:
2070
+ s = s[:-1]
2071
+ return s
2072
+
2073
+
2074
+ def handle_encoding_declaration(contents, out):
2075
+ r"""
2076
+ Find a PEP 263-style Python encoding declaration in the first or
2077
+ second line of ``contents``. If found, output it to ``out`` and return
2078
+ ``contents`` without the encoding line; otherwise output a default
2079
+ UTF-8 declaration and return ``contents``.
2080
+
2081
+ EXAMPLES::
2082
+
2083
+ sage: from sage.repl.preparse import handle_encoding_declaration
2084
+ sage: import sys
2085
+ sage: c1='# -*- coding: latin-1 -*-\nimport os, sys\n...'
2086
+ sage: c2='# -*- coding: iso-8859-15 -*-\nimport os, sys\n...'
2087
+ sage: c3='# -*- coding: ascii -*-\nimport os, sys\n...'
2088
+ sage: c4='import os, sys\n...'
2089
+ sage: handle_encoding_declaration(c1, sys.stdout)
2090
+ # -*- coding: latin-1 -*-
2091
+ 'import os, sys\n...'
2092
+ sage: handle_encoding_declaration(c2, sys.stdout)
2093
+ # -*- coding: iso-8859-15 -*-
2094
+ 'import os, sys\n...'
2095
+ sage: handle_encoding_declaration(c3, sys.stdout)
2096
+ # -*- coding: ascii -*-
2097
+ 'import os, sys\n...'
2098
+ sage: handle_encoding_declaration(c4, sys.stdout)
2099
+ # -*- coding: utf-8 -*-
2100
+ 'import os, sys\n...'
2101
+
2102
+ TESTS:
2103
+
2104
+ These are some of the tests listed in PEP 263::
2105
+
2106
+ sage: contents = '#!/usr/bin/python\n# -*- coding: latin-1 -*-\nimport os, sys'
2107
+ sage: handle_encoding_declaration(contents, sys.stdout)
2108
+ # -*- coding: latin-1 -*-
2109
+ '#!/usr/bin/python\nimport os, sys'
2110
+
2111
+ sage: contents = '# This Python file uses the following encoding: utf-8\nimport os, sys'
2112
+ sage: handle_encoding_declaration(contents, sys.stdout)
2113
+ # This Python file uses the following encoding: utf-8
2114
+ 'import os, sys'
2115
+
2116
+ sage: contents = '#!/usr/local/bin/python\n# coding: latin-1\nimport os, sys'
2117
+ sage: handle_encoding_declaration(contents, sys.stdout)
2118
+ # coding: latin-1
2119
+ '#!/usr/local/bin/python\nimport os, sys'
2120
+
2121
+ Two hash marks are okay; this shows up in SageTeX-generated scripts::
2122
+
2123
+ sage: contents = '## -*- coding: utf-8 -*-\nimport os, sys\nprint(x)'
2124
+ sage: handle_encoding_declaration(contents, sys.stdout)
2125
+ ## -*- coding: utf-8 -*-
2126
+ 'import os, sys\nprint(x)'
2127
+
2128
+ When the encoding declaration does not match the specification, we
2129
+ spit out a default UTF-8 encoding.
2130
+
2131
+ Incorrect coding line::
2132
+
2133
+ sage: contents = '#!/usr/local/bin/python\n# latin-1\nimport os, sys'
2134
+ sage: handle_encoding_declaration(contents, sys.stdout)
2135
+ # -*- coding: utf-8 -*-
2136
+ '#!/usr/local/bin/python\n# latin-1\nimport os, sys'
2137
+
2138
+ Encoding declaration not on first or second line::
2139
+
2140
+ sage: contents ='#!/usr/local/bin/python\n#\n# -*- coding: latin-1 -*-\nimport os, sys'
2141
+ sage: handle_encoding_declaration(contents, sys.stdout)
2142
+ # -*- coding: utf-8 -*-
2143
+ '#!/usr/local/bin/python\n#\n# -*- coding: latin-1 -*-\nimport os, sys'
2144
+
2145
+ We do not check for legal encoding names; that is Python's job::
2146
+
2147
+ sage: contents ='#!/usr/local/bin/python\n# -*- coding: utf-42 -*-\nimport os, sys'
2148
+ sage: handle_encoding_declaration(contents, sys.stdout)
2149
+ # -*- coding: utf-42 -*-
2150
+ '#!/usr/local/bin/python\nimport os, sys'
2151
+
2152
+ .. NOTE::
2153
+
2154
+ - :pep:`263` says that Python will interpret a UTF-8
2155
+ byte order mark as a declaration of UTF-8 encoding, but I do not
2156
+ think we do that; this function only sees a Python string so it
2157
+ cannot account for a BOM.
2158
+
2159
+ - We default to UTF-8 encoding even though PEP 263 says that
2160
+ Python files should default to ASCII.
2161
+
2162
+ - Also see https://docs.python.org/ref/encodings.html.
2163
+
2164
+ AUTHORS:
2165
+
2166
+ - Lars Fischer
2167
+ - Dan Drake (2010-12-08, rewrite for :issue:`10440`)
2168
+ """
2169
+ lines = contents.splitlines()
2170
+ for num, line in enumerate(lines[:2]):
2171
+ if re.search(r"coding[:=]\s*([-\w.]+)", line):
2172
+ out.write(line + '\n')
2173
+ return '\n'.join(lines[:num] + lines[(num + 1):])
2174
+
2175
+ # If we did not find any encoding hints, use explicit utf-8.
2176
+ # According to PEP 3120, this could be omitted.
2177
+ out.write("# -*- coding: utf-8 -*-\n")
2178
+ return contents
2179
+
2180
+
2181
+ def preparse_file_named_to_stream(name, out):
2182
+ r"""
2183
+ Preparse file named \code{name} (presumably a .sage file), outputting to
2184
+ stream \code{out}.
2185
+ """
2186
+ name = Path(name).resolve()
2187
+ with name.open() as f:
2188
+ contents = f.read()
2189
+ contents = handle_encoding_declaration(contents, out)
2190
+ out.write('# ' + '#' * 68 + '\n')
2191
+ out.write(f'# This file was *autogenerated* from the file {name}.\n')
2192
+ out.write('# ' + '#' * 68 + '\n')
2193
+ out.write(preparse_file(contents))
2194
+
2195
+
2196
+ def preparse_file_named(name) -> Path:
2197
+ r"""
2198
+ Preparse file named ``name`` (presumably a ``.sage`` file),
2199
+ outputting to a temporary file.
2200
+
2201
+ This returns the temporary file as a :class:`Path` object.
2202
+
2203
+ EXAMPLES::
2204
+
2205
+ sage: from sage.repl.preparse import preparse_file_named
2206
+ sage: tmpf = tmp_filename(ext='.sage')
2207
+ sage: with open(tmpf, 'w') as f:
2208
+ ....: out = f.write("a = 2")
2209
+ sage: preparse_file_named(tmpf)
2210
+ PosixPath('...sage.py')
2211
+ """
2212
+ from sage.misc.temporary_file import tmp_filename
2213
+ name = Path(name)
2214
+ assert name.suffix == '.sage'
2215
+ tmpfilename = Path(tmp_filename(name.stem, ext='.sage.py'))
2216
+ with tmpfilename.open('w') as out:
2217
+ preparse_file_named_to_stream(name, out)
2218
+ return tmpfilename