1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262
|
#!${PYTHON_EXECUTABLE}
#
# \ingroup pythoncore
#
# \file
# Command line interpreter for Cadabra, written in Python.
#
# cadabra2 [-d] [filename]
#
# Running with the '-d' flag runs cadabra under gdb, so that
# one can generate back traces etc.
import sys
import site
from code import InteractiveConsole
import code
import re
import readline
import rlcompleter
import os
# Make sure we can find the cadabra2 python module; is always
# installed in PYTHON_SITE_PATH
install_prefix=os.path.realpath(sys.argv[0])
install_prefix=install_prefix.replace(os.sep+'bin'+os.sep+'cadabra2', '${PYTHON_SITE_PATH_REL}')
# print("Module path ", install_prefix)
sys.path.append(install_prefix)
from cadabra2 import *
class FileCacher:
"Cache the stdout text so we can analyze it before returning it"
def __init__(self): self.reset()
def reset(self): self.out = []
def write(self,line): self.out.append(line)
def flush(self):
# output = '\n'.join(self.out)
output=self.out
self.reset()
return output
def findDefaults():
for d in sys.path:
filepath = os.path.join(d, "cadabra2_defaults.py")
if os.path.isfile(filepath):
return filepath
return None
class Shell(InteractiveConsole):
"Wrapper around Python that can filter input/output to the shell"
def __init__(self):
self.stdout = sys.stdout
self.cache = FileCacher()
# Variables to keep track of multi-line parsing info.
self.indent = "";
self.lhs = "";
self.rhs = "";
self.operator = "";
InteractiveConsole.__init__(self)
return
# If the object to be displayed is an Ex (add Property), print it
# using the human-readable str (FIXME: add other printers). If not,
# pass it on to the previously existing display hook.
def _displayhook(self, arg):
if isinstance(arg, Ex):
pass
#print(str(arg))
elif isinstance(arg, Property):
pass
#print(str(arg))
else:
self.remember_display_hook(arg)
# Setup hooks for pretty printing.
def set_display(self):
self.remember_display_hook = sys.displayhook
sys.displayhook = self._displayhook
def unset_display(self):
sys.displayhook = self.remember_display_hook
def get_output(self): sys.stdout = self.cache
def return_output(self): sys.stdout = self.stdout
# Detect Cadabra expression statements and rewrite to Python form.
#
# Lines containing ':=' are interpreted as expression declarations.
# Lines containing '::' are interpreted as property declarations.
#
# These need to end on '.', ':' or ';'. If not, keep track of the
# input so far and store that in self.lhs, self.operator, self.rhs, and
# then return an empty string.
#
# TODO: make ';' at the end of '::' line result the print statement printing
# property objects using their readable form; addresses one issue report).
def preprocess(self, line, shell):
# print '='+line+"=="
imatch = re.search('([\s]*)([^\s].*[^\s])([\s]*)', line)
if imatch:
indent_line=imatch.group(1)
end_of_line=imatch.group(3)
else:
indent_line=""
end_of_line="\n"
line_stripped=line.rstrip().lstrip()
# Do not do anything with comment lines.
if len(line_stripped)>0 and line_stripped[0]=='#':
return line
# Bare ';' gets replaced with 'display(_)'.
if line_stripped==';':
return indent_line+"display(_)\n"
lastchar = line_stripped[-1:]
if lastchar=='.' or lastchar==';' or lastchar==':':
if self.lhs!="":
line_stripped=line_stripped[:-1]
self.rhs += line_stripped
rewrite = self.indent + self.lhs + ' = Ex(r"' + self.rhs+'")'
if self.operator==':=':
if rewrite[-1]!=';':
rewrite+=';'
rewrite += " _="+self.lhs
if lastchar!='.' and len(self.indent)==0:
rewrite += "; display("+self.lhs+")"
rewrite = rewrite+"\n"
self.indent=""
self.lhs=""
self.operator=""
self.rhs=""
return rewrite
else:
# If we are a Cadabra continuation, add to the rhs without further processing
# and return an empty line immediately.
if self.lhs!="":
self.rhs += line_stripped
return ""
# Add '__cdbkernel__' as first argument of post_process if it doesn't have that already.
line_stripped=re.sub(r'def post_process\(([^_])', r'def post_process(__cdbkernel__, \1', line_stripped)
# Replace $...$ with Ex(...).
line_stripped=re.sub(r'\$([^\$]*)\$', r'Ex(r"\1", False)', line_stripped)
# Replace 'converge(ex):' with 'ex.reset(); while ex.changed():' properly indented.
imatch = re.match(r'([ ]*)converge\(([^\)]*)\):', line_stripped)
if imatch:
ret = indent_line+imatch.group(1)+imatch.group(2)+".reset()\n"
ret += indent_line+"_="+imatch.group(2)+"\n"
ret += indent_line+imatch.group(1)+"while "+imatch.group(2)+".changed():\n"
return ret
found = line_stripped.find(':=')
if found>0:
# If the last character is not a Cadabra terminator, start a capture process.
if lastchar!='.' and lastchar!=';' and lastchar!=':':
self.indent = indent_line
self.lhs = line_stripped[:found]
self.operator = ':='
self.rhs = line_stripped[found+2:]
return ""
else:
rewrite = indent_line + line_stripped[:found] + ' = Ex(r"' + line_stripped[found+2:-1]+'")'
objname=line_stripped[:found]
rewrite = rewrite + "; _="+objname
if lastchar!='.' and len(indent_line)==0:
rewrite = rewrite + "; display(" + objname+")"
line=rewrite
else:
# Is it a property declaration?
found = line_stripped.find('::')
if found>0:
match = re.search('([a-zA-Z]*)(.*)[;\.:]*', line_stripped[found+2:])
if match:
if len(match.group(2))>0: # declaration with arguments
last = match.group(2)[1:-1]
if len(last)>0 and last[-1]==')':
last=last[:-1]
rewrite = indent_line + "__cdbtmp__ = "+match.group(1)+'(Ex(r"'+line_stripped[:found]+'"), Ex(r"""'+last+' """) )'
else:
rewrite = indent_line + "__cdbtmp__ = "+line_stripped[found+2:]+'(Ex(r"'+line_stripped[:found]+'"))'
objname="__cdbtmp__"
# print the expression if we are at top level (not in a function) and the last char is not '.'
if lastchar!='.' and len(indent_line)==0:
rewrite = rewrite + "; display(" + objname+")"
line=rewrite
else:
print("inconsistent") # property names can only contain letters
else:
line=indent_line+line_stripped
if lastchar==';' and shell==False:
line+=" display(_)"
return line+end_of_line
def push(self,line):
line = self.preprocess(line, True)
if self.lhs == "":
# print('executing: ')
# print(line)
# 'line' may actually be multiple lines.
# Now feed the pre-parsed input to Python.
self.get_output()
sys.ps1='> '
for single in line.splitlines():
ret=InteractiveConsole.push(self, single)
self.return_output()
output = self.cache.flush()
for line in output:
sys.stdout.write(line)
return ret
else:
# Preprocessing has detected an unfinished Cadabra line;
# switch the prompt to indicate Cadabra continuation, and
# do not feed the line to Python yet.
sys.ps1='| '
return ""
if __name__ == '__main__':
sh = Shell()
sys.ps1='> '
sys.ps2='. '
readline.set_completer(rlcompleter.Completer(locals()).complete)
readline.parse_and_bind("tab: complete")
if len(sys.argv)>1:
if '-d' in sys.argv:
#rs = "lldb -ex r --args ${PYTHON_EXECUTABLE} "+sys.argv[0];
rs = "gdb -q -ex r --args ${PYTHON_EXECUTABLE} "+sys.argv[0];
for a in sys.argv[1:]:
if a!='-d':
rs += " "+a
#print('executing '+rs)
os.system(rs)
else:
with open(findDefaults()) as f:
code = compile(f.read(), "cadabra2_defaults.py", 'exec')
exec(code)
sh2 = InteractiveConsole()
with open(sys.argv[1]) as f:
collect=""
for line in f:
sline=line.strip()
# if len(sline)>0 and sline[0]!='#':
collect += sh.preprocess(line, False)
#print "----\n"+collect+"----\n"
cmp = compile(collect, sys.argv[1], 'exec')
exec(cmp)
# would be nice to be able to continue from here on the command line, but that requires
# pulling in the right locals/globals
# sh.interact(banner='Info at https://cadabra.phi-sci.com/\nAvailable under the terms of the GNU General Public License v3\n', locals(), globals())
else:
sh.runsource("import os; import sys; install_prefix=os.path.realpath(sys.argv[0]); install_prefix=install_prefix.replace('/bin/cadabra2','${PYTHON_SITE_PATH_REL}'); sys.path.append(install_prefix); print('Cadabra @CADABRA_VERSION_MAJOR@.@CADABRA_VERSION_MINOR@.@CADABRA_VERSION_PATCH@ (build @CADABRA_VERSION_BUILD@ dated @CADABRA_VERSION_DATE@)'); print ('Copyright (C) @COPYRIGHT_YEARS@ Kasper Peeters <kasper.peeters@phi-sci.com>'); f=open('${PYTHON_SITE_PATH}/cadabra2_defaults.py'); code=compile(f.read(), 'cadabra2_defaults.py', 'exec'); exec(code); f.close(); print('Using SymPy version '+sympy.__version__);")
sh.interact(banner='Info at https://cadabra.science/\nAvailable under the terms of the GNU General Public License v3\n')
|