passagemath-repl 10.5.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.
- passagemath_repl-10.5.1.data/scripts/sage-cachegrind +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-callgrind +16 -0
- passagemath_repl-10.5.1.data/scripts/sage-cleaner +230 -0
- passagemath_repl-10.5.1.data/scripts/sage-coverage +327 -0
- passagemath_repl-10.5.1.data/scripts/sage-eval +14 -0
- passagemath_repl-10.5.1.data/scripts/sage-fixdoctests +710 -0
- passagemath_repl-10.5.1.data/scripts/sage-inline-fortran +12 -0
- passagemath_repl-10.5.1.data/scripts/sage-ipynb2rst +50 -0
- passagemath_repl-10.5.1.data/scripts/sage-ipython +16 -0
- passagemath_repl-10.5.1.data/scripts/sage-massif +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-notebook +267 -0
- passagemath_repl-10.5.1.data/scripts/sage-omega +25 -0
- passagemath_repl-10.5.1.data/scripts/sage-preparse +302 -0
- passagemath_repl-10.5.1.data/scripts/sage-run +27 -0
- passagemath_repl-10.5.1.data/scripts/sage-run-cython +10 -0
- passagemath_repl-10.5.1.data/scripts/sage-runtests +9 -0
- passagemath_repl-10.5.1.data/scripts/sage-startuptime.py +163 -0
- passagemath_repl-10.5.1.data/scripts/sage-valgrind +34 -0
- passagemath_repl-10.5.1.dist-info/METADATA +77 -0
- passagemath_repl-10.5.1.dist-info/RECORD +162 -0
- passagemath_repl-10.5.1.dist-info/WHEEL +5 -0
- passagemath_repl-10.5.1.dist-info/top_level.txt +1 -0
- sage/all__sagemath_repl.py +119 -0
- sage/doctest/__init__.py +4 -0
- sage/doctest/__main__.py +236 -0
- sage/doctest/all.py +4 -0
- sage/doctest/check_tolerance.py +261 -0
- sage/doctest/control.py +1727 -0
- sage/doctest/external.py +534 -0
- sage/doctest/fixtures.py +383 -0
- sage/doctest/forker.py +2665 -0
- sage/doctest/marked_output.py +102 -0
- sage/doctest/parsing.py +1708 -0
- sage/doctest/parsing_test.py +79 -0
- sage/doctest/reporting.py +733 -0
- sage/doctest/rif_tol.py +124 -0
- sage/doctest/sources.py +1657 -0
- sage/doctest/test.py +584 -0
- sage/doctest/tests/1second.rst +4 -0
- sage/doctest/tests/99seconds.rst +4 -0
- sage/doctest/tests/abort.rst +5 -0
- sage/doctest/tests/atexit.rst +7 -0
- sage/doctest/tests/fail_and_die.rst +6 -0
- sage/doctest/tests/initial.rst +15 -0
- sage/doctest/tests/interrupt.rst +7 -0
- sage/doctest/tests/interrupt_diehard.rst +14 -0
- sage/doctest/tests/keyboardinterrupt.rst +11 -0
- sage/doctest/tests/longtime.rst +5 -0
- sage/doctest/tests/nodoctest +5 -0
- sage/doctest/tests/random_seed.rst +4 -0
- sage/doctest/tests/show_skipped.rst +18 -0
- sage/doctest/tests/sig_on.rst +9 -0
- sage/doctest/tests/simple_failure.rst +8 -0
- sage/doctest/tests/sleep_and_raise.rst +106 -0
- sage/doctest/tests/tolerance.rst +31 -0
- sage/doctest/util.py +750 -0
- sage/interfaces/cleaner.py +48 -0
- sage/interfaces/quit.py +163 -0
- sage/misc/all__sagemath_repl.py +51 -0
- sage/misc/banner.py +235 -0
- sage/misc/benchmark.py +221 -0
- sage/misc/classgraph.py +134 -0
- sage/misc/copying.py +22 -0
- sage/misc/cython.py +694 -0
- sage/misc/dev_tools.py +745 -0
- sage/misc/edit_module.py +304 -0
- sage/misc/explain_pickle.py +3079 -0
- sage/misc/gperftools.py +361 -0
- sage/misc/inline_fortran.py +212 -0
- sage/misc/messaging.py +86 -0
- sage/misc/pager.py +21 -0
- sage/misc/profiler.py +179 -0
- sage/misc/python.py +70 -0
- sage/misc/remote_file.py +53 -0
- sage/misc/sage_eval.py +249 -0
- sage/misc/sage_input.py +3621 -0
- sage/misc/sagedoc.py +1742 -0
- sage/misc/sh.py +38 -0
- sage/misc/trace.py +90 -0
- sage/repl/__init__.py +16 -0
- sage/repl/all.py +15 -0
- sage/repl/attach.py +625 -0
- sage/repl/configuration.py +186 -0
- sage/repl/display/__init__.py +1 -0
- sage/repl/display/fancy_repr.py +354 -0
- sage/repl/display/formatter.py +318 -0
- sage/repl/display/jsmol_iframe.py +290 -0
- sage/repl/display/pretty_print.py +153 -0
- sage/repl/display/util.py +163 -0
- sage/repl/image.py +302 -0
- sage/repl/inputhook.py +91 -0
- sage/repl/interface_magic.py +298 -0
- sage/repl/interpreter.py +854 -0
- sage/repl/ipython_extension.py +593 -0
- sage/repl/ipython_kernel/__init__.py +1 -0
- sage/repl/ipython_kernel/__main__.py +4 -0
- sage/repl/ipython_kernel/all_jupyter.py +10 -0
- sage/repl/ipython_kernel/install.py +301 -0
- sage/repl/ipython_kernel/interact.py +278 -0
- sage/repl/ipython_kernel/kernel.py +217 -0
- sage/repl/ipython_kernel/widgets.py +466 -0
- sage/repl/ipython_kernel/widgets_sagenb.py +587 -0
- sage/repl/ipython_tests.py +163 -0
- sage/repl/load.py +326 -0
- sage/repl/preparse.py +2218 -0
- sage/repl/prompts.py +90 -0
- sage/repl/rich_output/__init__.py +4 -0
- sage/repl/rich_output/backend_base.py +648 -0
- sage/repl/rich_output/backend_doctest.py +316 -0
- sage/repl/rich_output/backend_emacs.py +151 -0
- sage/repl/rich_output/backend_ipython.py +596 -0
- sage/repl/rich_output/buffer.py +311 -0
- sage/repl/rich_output/display_manager.py +829 -0
- sage/repl/rich_output/example.avi +0 -0
- sage/repl/rich_output/example.canvas3d +1 -0
- sage/repl/rich_output/example.dvi +0 -0
- sage/repl/rich_output/example.flv +0 -0
- sage/repl/rich_output/example.gif +0 -0
- sage/repl/rich_output/example.jpg +0 -0
- sage/repl/rich_output/example.mkv +0 -0
- sage/repl/rich_output/example.mov +0 -0
- sage/repl/rich_output/example.mp4 +0 -0
- sage/repl/rich_output/example.ogv +0 -0
- sage/repl/rich_output/example.pdf +0 -0
- sage/repl/rich_output/example.png +0 -0
- sage/repl/rich_output/example.svg +54 -0
- sage/repl/rich_output/example.webm +0 -0
- sage/repl/rich_output/example.wmv +0 -0
- sage/repl/rich_output/example_jmol.spt.zip +0 -0
- sage/repl/rich_output/example_wavefront_scene.mtl +7 -0
- sage/repl/rich_output/example_wavefront_scene.obj +17 -0
- sage/repl/rich_output/output_basic.py +391 -0
- sage/repl/rich_output/output_browser.py +103 -0
- sage/repl/rich_output/output_catalog.py +54 -0
- sage/repl/rich_output/output_graphics.py +320 -0
- sage/repl/rich_output/output_graphics3d.py +345 -0
- sage/repl/rich_output/output_video.py +231 -0
- sage/repl/rich_output/preferences.py +432 -0
- sage/repl/rich_output/pretty_print.py +339 -0
- sage/repl/rich_output/test_backend.py +201 -0
- sage/repl/user_globals.py +214 -0
- sage/tests/all.py +0 -0
- sage/tests/all__sagemath_repl.py +3 -0
- sage/tests/article_heuberger_krenn_kropf_fsm-in-sage.py +630 -0
- sage/tests/arxiv_0812_2725.py +351 -0
- sage/tests/benchmark.py +1925 -0
- sage/tests/book_schilling_zabrocki_kschur_primer.py +795 -0
- sage/tests/book_stein_ent.py +651 -0
- sage/tests/book_stein_modform.py +558 -0
- sage/tests/cmdline.py +796 -0
- sage/tests/combinatorial_hopf_algebras.py +52 -0
- sage/tests/finite_poset.py +623 -0
- sage/tests/functools_partial_src.py +27 -0
- sage/tests/gosper-sum.py +218 -0
- sage/tests/lazy_imports.py +28 -0
- sage/tests/modular_group_cohomology.py +80 -0
- sage/tests/numpy.py +21 -0
- sage/tests/parigp.py +76 -0
- sage/tests/startup.py +27 -0
- sage/tests/symbolic-series.py +76 -0
- sage/tests/sympy.py +16 -0
- 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
|