Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sagemath
GitHub Repository: sagemath/sagelib
Path: blob/master/sage/misc/interpreter.py
4045 views
1
"""
2
Preparses input from the interpreter
3
4
Modified input.
5
6
-- All ^'s (not in strings) are replaced by **'s.
7
8
-- If M is a variable and i an integer,
9
then M.i is replaced by M.gen(i), so generators can be
10
accessed as in MAGMA.
11
12
-- quit alone on a line quits.
13
14
-- load to load in scripts
15
16
-- Most int literals n are replaced by ZZ(n) Thus 2/3 is a rational
17
number. If they are in []'s right after a valid identifier they
18
aren't replaced.
19
20
-- real literals get wrapped in "RR" (with a precision)
21
22
-- the R.<x,y,z> = ... notation
23
24
TODO:
25
26
I have no plans for anything further, except to improve the
27
robustness of the above. Preparsing may work incorrectly for
28
multi-line input lines in some cases; this will be fixed.
29
30
All other input is processed normally.
31
32
It automatically converts *most* integer literals to Sage Integer's and
33
decimal literals to Sage Reals. It does not convert indexes into
34
1-d arrays, since those have to be ints.
35
36
I also extended the load command so it *really* works exactly
37
like the Sage interpreter, so e.g., ^ for exponentiation is
38
allowed. Also files being loaded can themselves load other files.
39
Finally, I added an "attach" command, e.g.,
40
attach 'file'
41
that works kind of like attach in MAGMA. Whenever you enter a blank
42
line in the Sage interpreter, *all* attached files that have changed
43
are automatically reloaded. Moreover, the attached files work according
44
to the Sage interpreter rules, i.e., ^ --> **, etc.
45
46
I also fixed it so ^ is not replaced by ** inside strings.
47
48
Finally, I added back the M.n notation for the n-th generator
49
of object M, again like in MAGMA.
50
51
EXAMPLE::
52
53
sage: 2/3
54
2/3
55
sage: type(2/3)
56
<type 'sage.rings.rational.Rational'>
57
sage: a = 49928420832092
58
sage: type(a)
59
<type 'sage.rings.integer.Integer'>
60
sage: a.factor()
61
2^2 * 11 * 1134736837093
62
sage: v = [1,2,3]
63
sage: type(v[0])
64
<type 'sage.rings.integer.Integer'>
65
66
If we don't make potential list indices int's, then lots of stuff
67
breaks, or users have to type v[int(7)], which is insane.
68
A fix would be to only not make what's in the brackets an
69
Integer if what's before the bracket is a valid identifier,
70
so the w = [5] above would work right.
71
::
72
73
sage: s = "x^3 + x + 1"
74
sage: s
75
'x^3 + x + 1'
76
sage: pari(s)
77
x^3 + x + 1
78
sage: f = pari(s)
79
sage: f^2
80
x^6 + 2*x^4 + 2*x^3 + x^2 + 2*x + 1
81
sage: V = VectorSpace(QQ,3)
82
sage: V.0
83
(1, 0, 0)
84
sage: V.1
85
(0, 1, 0)
86
sage: s = "This. Is. It."
87
sage: print s
88
This. Is. It.
89
"""
90
91
#*****************************************************************************
92
# Copyright (C) 2004 William Stein <[email protected]>
93
#
94
# Distributed under the terms of the GNU General Public License (GPL)
95
#
96
# http://www.gnu.org/licenses/
97
#*****************************************************************************
98
import IPython.ipapi
99
_ip = IPython.ipapi.get()
100
101
__author__ = 'William Stein <[email protected]> et al.'
102
__license__ = 'GPL'
103
104
import os
105
import log
106
import re
107
108
import remote_file
109
110
from IPython.iplib import InteractiveShell
111
112
import preparser_ipython
113
from preparser import preparse_file, load_wrap, modified_attached_files, attached_files
114
115
import cython
116
117
# IPython has a prefilter() function that analyzes each input line. We redefine
118
# it here to first pre-process certain forms of input
119
120
# The prototype of any alternate prefilter must be like this one (the name
121
# doesn't matter):
122
# - line is a string containing the user input line.
123
# - continuation is a parameter which tells us if we are processing a first line of
124
# user input or the second or higher of a multi-line statement.
125
126
127
128
def load_startup_file(file):
129
if os.path.exists(file):
130
X = do_prefilter_paste('load "%s"'%file,False)
131
_ip.runlines(X)
132
if os.path.exists('attach.sage'):
133
X = do_prefilter_paste('attach "attach.sage"',False)
134
_ip.runlines(X)
135
136
137
def do_prefilter_paste(line, continuation):
138
"""
139
Alternate prefilter for input.
140
141
INPUT:
142
143
- ``line`` -- a single line; must *not* have any newlines in it
144
- ``continuation`` -- whether the input line is really part
145
of the previous line, because of open parens or backslash.
146
"""
147
if '\n' in line:
148
raise RuntimeError, "bug in function that calls do_prefilter_paste -- there can be no newlines in the input"
149
150
# This is so it's OK to have lots of blank space at the
151
# beginning of any non-continuation line.
152
153
if continuation:
154
# strip ...'s that appear in examples
155
L = line.lstrip()
156
if L[:3] == '...':
157
line = L[3:]
158
else:
159
line = line.lstrip()
160
161
line = line.rstrip()
162
163
# Process attached files.
164
for F in modified_attached_files():
165
# We attach the files again instead of loading them,
166
# to preserve tracebacks or efficiency according
167
# to the settings of load_attach_mode().
168
_ip.runlines(load_wrap(F, attach=True))
169
170
# Get rid of leading sage: prompts so that pasting of examples
171
# from the documentation works. This is like MAGMA's
172
# SetLinePrompt(false).
173
for prompt in ['sage:', '>>>']:
174
if not continuation:
175
while True:
176
strip = False
177
if line[:3] == prompt:
178
line = line[3:].lstrip()
179
strip = True
180
elif line[:5] == prompt:
181
line = line[5:].lstrip()
182
strip = True
183
if not strip:
184
break
185
else:
186
line = line.lstrip()
187
188
# 'quit' alone on a line to quit.
189
if line.lower() in ['quit', 'exit', 'quit;', 'exit;']:
190
line = '%quit'
191
192
# An interactive load command, like iload in MAGMA.
193
if line[:6] == 'iload ':
194
try:
195
name = str(eval(line[6:]))
196
except:
197
name = str(line[6:].strip())
198
try:
199
F = open(name)
200
except IOError:
201
raise ImportError, 'Could not open file "%s"'%name
202
203
print 'Interactively loading "%s"'%name
204
n = len(__IPYTHON__.input_hist)
205
for L in F.readlines():
206
L = L.rstrip()
207
Llstrip = L.lstrip()
208
raw_input('sage: %s'%L.rstrip())
209
__IPYTHON__.input_hist_raw.append(L)
210
if Llstrip[:5] == 'load ' or Llstrip[:7] == 'attach ' \
211
or Llstrip[:6] == 'iload ':
212
log.offset -= 1
213
L = do_prefilter_paste(L, False)
214
if len(L.strip()) > 0:
215
_ip.runlines(L)
216
L = ''
217
else:
218
L = preparser_ipython.preparse_ipython(L, not continuation)
219
__IPYTHON__.input_hist.append(L)
220
__IPYTHON__.push(L)
221
log.offset += 1
222
return ''
223
224
225
#################################################################
226
# load and attach commands
227
#################################################################
228
for cmd in ['load', 'attach']:
229
if line.lstrip().startswith(cmd+' '):
230
j = line.find(cmd+' ')
231
s = line[j+len(cmd)+1:].strip()
232
if not s.startswith('('):
233
line = ' '*j + load_wrap(s, cmd=='attach')
234
235
if len(line) > 0:
236
line = preparser_ipython.preparse_ipython(line, not continuation)
237
238
return line
239
240
def load_cython(name):
241
cur = os.path.abspath(os.curdir)
242
try:
243
mod, dir = cython.cython(name, compile_message=True, use_cache=True)
244
except (IOError, OSError, RuntimeError), msg:
245
print "Error compiling cython file:\n%s"%msg
246
return ''
247
import sys
248
sys.path.append(dir)
249
return 'from %s import *'%mod
250
251
def handle_encoding_declaration(contents, out):
252
"""Find a PEP 263-style Python encoding declaration in the first or
253
second line of `contents`. If found, output it to `out` and return
254
`contents` without the encoding line; otherwise output a default
255
UTF-8 declaration and return `contents`.
256
257
EXAMPLES::
258
259
sage: from sage.misc.interpreter import handle_encoding_declaration
260
sage: import sys
261
sage: c1='# -*- coding: latin-1 -*-\nimport os, sys\n...'
262
sage: c2='# -*- coding: iso-8859-15 -*-\nimport os, sys\n...'
263
sage: c3='# -*- coding: ascii -*-\nimport os, sys\n...'
264
sage: c4='import os, sys\n...'
265
sage: handle_encoding_declaration(c1, sys.stdout)
266
# -*- coding: latin-1 -*-
267
'import os, sys\n...'
268
sage: handle_encoding_declaration(c2, sys.stdout)
269
# -*- coding: iso-8859-15 -*-
270
'import os, sys\n...'
271
sage: handle_encoding_declaration(c3, sys.stdout)
272
# -*- coding: ascii -*-
273
'import os, sys\n...'
274
sage: handle_encoding_declaration(c4, sys.stdout)
275
# -*- coding: utf-8 -*-
276
'import os, sys\n...'
277
278
TESTS:
279
280
These are some of the tests listed in PEP 263::
281
282
sage: contents = '#!/usr/bin/python\n# -*- coding: latin-1 -*-\nimport os, sys'
283
sage: handle_encoding_declaration(contents, sys.stdout)
284
# -*- coding: latin-1 -*-
285
'#!/usr/bin/python\nimport os, sys'
286
287
sage: contents = '# This Python file uses the following encoding: utf-8\nimport os, sys'
288
sage: handle_encoding_declaration(contents, sys.stdout)
289
# This Python file uses the following encoding: utf-8
290
'import os, sys'
291
292
sage: contents = '#!/usr/local/bin/python\n# coding: latin-1\nimport os, sys'
293
sage: handle_encoding_declaration(contents, sys.stdout)
294
# coding: latin-1
295
'#!/usr/local/bin/python\nimport os, sys'
296
297
Two hash marks are okay; this shows up in SageTeX-generated scripts::
298
299
sage: contents = '## -*- coding: utf-8 -*-\nimport os, sys\nprint x'
300
sage: handle_encoding_declaration(contents, sys.stdout)
301
## -*- coding: utf-8 -*-
302
'import os, sys\nprint x'
303
304
When the encoding declaration doesn't match the specification, we
305
spit out a default UTF-8 encoding.
306
307
Incorrect coding line::
308
309
sage: contents = '#!/usr/local/bin/python\n# latin-1\nimport os, sys'
310
sage: handle_encoding_declaration(contents, sys.stdout)
311
# -*- coding: utf-8 -*-
312
'#!/usr/local/bin/python\n# latin-1\nimport os, sys'
313
314
Encoding declaration not on first or second line::
315
316
sage: contents ='#!/usr/local/bin/python\n#\n# -*- coding: latin-1 -*-\nimport os, sys'
317
sage: handle_encoding_declaration(contents, sys.stdout)
318
# -*- coding: utf-8 -*-
319
'#!/usr/local/bin/python\n#\n# -*- coding: latin-1 -*-\nimport os, sys'
320
321
We don't check for legal encoding names; that's Python's job::
322
323
sage: contents ='#!/usr/local/bin/python\n# -*- coding: utf-42 -*-\nimport os, sys'
324
sage: handle_encoding_declaration(contents, sys.stdout)
325
# -*- coding: utf-42 -*-
326
'#!/usr/local/bin/python\nimport os, sys'
327
328
329
NOTES:
330
331
- PEP 263: http://www.python.org/dev/peps/pep-0263/
332
- PEP 263 says that Python will interpret a UTF-8 byte order mark
333
as a declaration of UTF-8 encoding, but I don't think we do
334
that; this function only sees a Python string so it can't
335
account for a BOM.
336
- We default to UTF-8 encoding even though PEP 263 says that
337
Python files should default to ASCII.
338
- Also see http://docs.python.org/ref/encodings.html.
339
340
AUTHORS:
341
342
- Lars Fischer
343
- Dan Drake (2010-12-08, rewrite for ticket #10440)
344
"""
345
lines = contents.splitlines()
346
for num, line in enumerate(lines[:2]):
347
if re.search(r"coding[:=]\s*([-\w.]+)", line):
348
out.write(line + '\n')
349
return '\n'.join(lines[:num] + lines[(num+1):])
350
351
# If we didn't find any encoding hints, use utf-8. This is not in
352
# conformance with PEP 263, which says that Python files default to
353
# ascii encoding.
354
out.write("# -*- coding: utf-8 -*-\n")
355
return contents
356
357
def preparse_file_named_to_stream(name, out):
358
r"""
359
Preparse file named \code{name} (presumably a .sage file), outputting to
360
stream \code{out}.
361
"""
362
name = os.path.abspath(name)
363
dir, _ = os.path.split(name)
364
cur = os.path.abspath(os.curdir)
365
os.chdir(dir)
366
contents = open(name).read()
367
contents = handle_encoding_declaration(contents, out)
368
parsed = preparse_file(contents)
369
os.chdir(cur)
370
out.write("# -*- encoding: utf-8 -*-\n")
371
out.write('#'*70+'\n')
372
out.write('# This file was *autogenerated* from the file %s.\n' % name)
373
out.write('#'*70+'\n')
374
out.write(parsed)
375
376
def preparse_file_named(name):
377
r"""
378
Preparse file named \code{name} (presumably a .sage file), outputting to a
379
temporary file. Returns name of temporary file.
380
"""
381
import sage.misc.misc
382
name = os.path.abspath(name)
383
tmpfilename = os.path.abspath(sage.misc.misc.tmp_filename(name) + ".py")
384
out = open(tmpfilename,'w')
385
preparse_file_named_to_stream(name, out)
386
out.close()
387
return tmpfilename
388
389
def sage_prefilter(self, block, continuation):
390
"""
391
Sage's prefilter for input. Given a string block (usually a
392
line), return the preparsed version of it.
393
394
INPUT:
395
396
- block -- string (usually a single line, but not always)
397
- continuation -- whether or not this line is a continuation.
398
"""
399
try:
400
block2 = ''
401
first = True
402
B = block.split('\n')
403
for i in range(len(B)):
404
L = B[i]
405
M = do_prefilter_paste(L, continuation or (not first))
406
first = False
407
# The L[:len(L)-len(L.lstrip())] business here preserves
408
# the whitespace at the beginning of L.
409
if block2 != '':
410
block2 += '\n'
411
lstrip = L.lstrip()
412
if lstrip[:5] == 'sage:' or lstrip[:3] == '>>>' or i==0:
413
block2 += M
414
else:
415
block2 += L[:len(L)-len(lstrip)] + M
416
417
except None:
418
419
print "WARNING: An error occurred in the Sage parser while"
420
print "parsing the following block:"
421
print block
422
print "Please report this as a bug (include the output of typing '%hist')."
423
block2 = block
424
425
return InteractiveShell._prefilter(self, block2, continuation)
426
427
428
import sage.server.support
429
def embedded():
430
return sage.server.support.EMBEDDED_MODE
431
432
ipython_prefilter = InteractiveShell.prefilter
433
do_preparse=True
434
def preparser(on=True):
435
"""
436
Turn on or off the Sage preparser.
437
438
INPUT:
439
440
- ``on`` -- bool (default: True) if True turn on preparsing; if False, turn it off.
441
442
EXAMPLES::
443
444
sage: 2/3
445
2/3
446
sage: preparser(False)
447
sage: 2/3 # not tested since doctests are always preparsed
448
0
449
sage: preparser(True)
450
sage: 2^3
451
8
452
"""
453
global do_preparse
454
if on:
455
do_preparse = True
456
InteractiveShell.prefilter = sage_prefilter
457
else:
458
do_preparse = False
459
InteractiveShell.prefilter = ipython_prefilter
460
461
462
import sagedoc
463
import sageinspect
464
import IPython.OInspect
465
IPython.OInspect.getdoc = sageinspect.sage_getdoc #sagedoc.my_getdoc
466
IPython.OInspect.getsource = sagedoc.my_getsource
467
IPython.OInspect.getargspec = sageinspect.sage_getargspec
468
469
#We monkey-patch IPython to disable the showing of plots
470
#when doing introspection on them. This fixes Trac #2163.
471
old_pinfo = IPython.OInspect.Inspector.pinfo
472
def sage_pinfo(self, *args, **kwds):
473
"""
474
A wrapper around IPython.OInspect.Inspector.pinfo which turns
475
off show_default before it is called and then sets it back
476
to its previous value.
477
478
Since this requires an IPython shell to test and the doctests aren't,
479
run under IPython, we cannot add doctests for this function.
480
"""
481
from sage.plot.all import show_default
482
old_value = show_default()
483
show_default(False)
484
485
result = old_pinfo(self, *args, **kwds)
486
487
show_default(old_value)
488
return result
489
IPython.OInspect.Inspector.pinfo = sage_pinfo
490
491
import __builtin__
492
_prompt = 'sage'
493
494
def set_sage_prompt(s):
495
global _prompt
496
_prompt = str(s)
497
498
def sage_prompt():
499
log.update()
500
return '%s'%_prompt
501
502
__builtin__.sage_prompt = sage_prompt
503
504
505
506
#######################################
507
#
508
def load_a_file(argstr, globals):
509
s = open(argstr).read()
510
return preparse_file(s, globals=globals)
511
512
513