source: sage/interfaces/expect.py @ 2:a572d77184a3

Revision 2:a572d77184a3, 20.8 KB checked in by tornaria@…, 7 years ago (diff)

[project @ patch to sage-1.0.0]

Line 
1"""
2Common Interface Functionality
3
4See the examples in the other sections for how to use specific
5interfaces.  The interface classes all derive from the generic
6interface that is described in this section.
7"""
8
9#*****************************************************************************
10#       Copyright (C) 2005 William Stein <wstein@ucsd.edu>
11#
12#  Distributed under the terms of the GNU General Public License (GPL)
13#
14#    This code is distributed in the hope that it will be useful,
15#    but WITHOUT ANY WARRANTY; without even the implied warranty of
16#    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
17#    General Public License for more details.
18#
19#  The full text of the GPL is available at:
20#
21#                  http://www.gnu.org/licenses/
22#*****************************************************************************
23
24import os
25import pexpect
26import weakref
27import time
28from sage.ext.sage_object import SageObject
29from sage.structure.element import Element_cmp_
30from sage.misc.sage_eval import sage_eval
31
32import quit
33
34import sage.rings.coerce as coerce
35from sage.misc.misc import SAGE_ROOT, verbose, SAGE_TMP_INTERFACE
36from sage.structure.element import RingElement
37BAD_SESSION = -2
38
39failed_to_start = []
40
41tmp='%s/tmp'%SAGE_TMP_INTERFACE
42
43class Expect(SageObject):
44    def __init__(self, name, prompt, command=None, server=None, maxread=100000, 
45                 script_subdirectory="", restart_on_ctrlc=False,
46                 verbose_start=False, init_code=[], max_startup_time=30,
47                 logfile = None, eval_using_file_cutoff=0):
48
49        self.__is_remote = False
50        if command == None:
51            command = name
52        if server != None:
53            command = "ssh -t %s %s"%(server, command)
54            self.__is_remote = True
55            eval_using_file_cutoff = 0  # don't allow this!
56            #print command
57            self._server = server
58        self.__maxread = maxread
59        self._eval_using_file_cutoff = eval_using_file_cutoff
60        self.__script_subdirectory = script_subdirectory
61        self.__name = name
62        self.__coerce_name = '_' + name + '_'
63        self.__command = command
64        self._prompt = prompt
65        self._restart_on_ctrlc = restart_on_ctrlc
66        self.__verbose_start = verbose_start
67        if script_subdirectory == None:
68            self.__path = '.'
69        else:
70            self.__path = '%s/data/extcode/%s/%s'%(SAGE_ROOT,self.__name, self.__script_subdirectory)
71        self.__initialized = False
72        self.__seq = -1
73        self._expect = None
74        self._session_number = 0
75        self.__init_code = init_code
76        self.__max_startup_time = max_startup_time
77        if isinstance(logfile, str):
78            logfile = open(logfile,'w')
79        self.__logfile = logfile
80        quit.expect_objects.append(weakref.ref(self))
81        self._available_vars = []
82
83    def is_remote(self):
84        return self.__is_remote
85
86    def is_local(self):
87        return not self.__is_remote
88
89    def user_dir(self):
90        return self.__path
91
92    def __repr__(self):
93        return self.__name.capitalize()
94
95    def _change_prompt(self, prompt):
96        self._prompt = prompt
97
98    def _temp_file(self, x):
99        T = self.__path + "/tmp/"
100        if not os.path.exists(T):
101            os.makedirs(T)
102        return T + str(x)
103
104    def name(self):
105        return self.__name
106
107    def path(self):
108        return self.__path
109
110    def expect(self):
111        if self._expect is None:
112            self._start()
113        return self._expect
114
115    def interact(self):
116        r"""
117        This allows you to interactively interact with the child
118        interpreter.  Press Ctrl-] to exit and return to SAGE.
119
120        {\em This function does not work very well. }
121
122        \note{This is completely different than the console() member
123        function.  The console function opens a new copy of the child
124        interepreter, whereas the interact function gives you
125        interactive access to the interpreter that is being used by
126        SAGE.}
127        """
128        if not hasattr(self, '__expect'):
129            self._start()
130        self._eval_line('')
131        self._eval_line('')
132        self._expect.interact()
133        self._eval_line('')
134        self._eval_line('')
135
136    def pid(self):
137        if self._expect is None:
138            self._start()
139        return self._expect.pid
140
141    def _start(self, alt_message=None):
142        self.quit()  # in case one is already running
143        global failed_to_start
144        if self.__name in failed_to_start:
145            if alt_message:
146                raise RuntimeError, alt_message
147            else:
148                raise RuntimeError, 'Unable to start %s (%s failed to start during this SAGE session; not attempting to start again)'%(self.__name, self.__name)
149       
150        self._session_number += 1
151        current_path = os.path.abspath('.')
152        dir = self.__path
153        if not os.path.exists(dir):
154            os.makedirs(dir)
155        os.chdir(dir)
156        if self.__verbose_start:
157            print "Starting %s"%self.__command.split()[0]
158        try:
159            self._expect = pexpect.spawn(self.__command, logfile=self.__logfile)
160        except pexpect.ExceptionPexpect:
161            self._expect = None
162            self._session_number = BAD_SESSION
163            failed_to_start.append(self.__name)
164            raise RuntimeError, "Unable to start %s because the command '%s' failed."%(self.__name, self.__command)
165        os.chdir(current_path)
166        self._expect.timeout = self.__max_startup_time
167        #self._expect.setmaxread(self.__maxread)
168        self._expect.maxread = self.__maxread
169        self._expect.delaybeforesend = 0
170        try:
171            self._expect.expect(self._prompt)
172        except pexpect.TIMEOUT:
173            self._expect = None
174            self._session_number = BAD_SESSION
175            failed_to_start.append(self.__name)
176            raise RuntimeError, "Unable to start %s"%self.__name
177        self._expect.timeout = 9999999  # don't make this bigger, or it breaks on OS X
178        for X in self.__init_code:
179            self.eval(X)
180
181    def clear_prompts(self):
182        while True:
183            try:
184                self._expect.expect(self._prompt, timeout=0.1)
185            except pexpect.TIMEOUT:
186                return
187       
188    def __del__(self):
189        if self._expect is None:
190            return
191        try:
192            self.quit()
193        except:
194            pass
195        # The following programs around a bug in pexpect.
196        def dummy(): pass
197        try:
198            self._expect.close = dummy
199        except AttributeError:
200            pass
201
202    def quit(self):
203        if self._expect is None:
204            return
205        # Send a kill -9 to the process *group*.
206        # this is *very useful* when external binaries are started up
207        # by shell scripts, and killing the shell script doesn't
208        # kill the binary.
209        try:
210            os.killpg(self._expect.pid, 9)
211        except OSError:
212            # this is OK, it just means the process was already dead.
213            pass 
214
215    def _quit_string(self):
216        return 'quit'
217
218    def _read_in_file_command(self, filename):
219        raise NotImplementedError
220
221    def _eval_line_using_file(self, line, tmp):
222        F = open(tmp, 'w')
223        F.write(line)
224        F.close()
225        s = self._eval_line(self._read_in_file_command(tmp), allow_use_file=False)
226        return s
227
228    def _eval_line(self, line, allow_use_file=True):
229        #if line.find('\n') != -1:
230        #    raise ValueError, "line must not contain any newlines"
231        if allow_use_file and self._eval_using_file_cutoff and len(line) > self._eval_using_file_cutoff:
232            return self._eval_line_using_file(line, tmp)
233        try:
234            if self._expect is None:
235                self._start()
236            E = self._expect
237            try:
238                E.sendline(line)
239            except OSError:
240                return RuntimeError, "Error evaluating %s in %s"%(line, self)
241            if len(line)>0:
242                try:
243                    E.expect(self._prompt)
244                except pexpect.EOF, msg:
245                    if self._quit_string() in line:
246                        # we expect to get an EOF if we're quitting.
247                        return ''
248                    print "** %s crashed or quit executing '%s' **"%(self, line)
249                    print "Restarting %s and trying again"%self
250                    self._start()
251                    if line != '':
252                        return self._eval_line(line, allow_use_file=allow_use_file)
253                    else:
254                        return ''
255                    #raise RuntimeError, "%s crashed executing %s"%(self, line)
256                out = E.before
257            else:
258                out = '\n\r'
259        except KeyboardInterrupt:
260            self._keyboard_interrupt()
261            raise KeyboardInterrupt, "Ctrl-c pressed while running %s"%self
262        i = out.find("\n")
263        j = out.rfind("\r")
264        return out[i+1:j].replace('\r\n','\n')
265
266    def _keyboard_interrupt(self):
267        print "Interrupting %s..."%self
268        if self._restart_on_ctrlc:
269            try:
270                self._expect.close(force=1)
271            except pexpect.ExceptionPexpect:
272                print "WARNING: -- unable to kill %s. You may have to do so manually."%self
273                pass
274            self._start()
275            raise KeyboardInterrupt, "Restarting %s (WARNING: all variables defined in previous session are now invalid)"%self
276        else:
277            self._expect.sendline(chr(3))  # send ctrl-c
278            self._expect.expect(self._prompt)
279            self._expect.expect(self._prompt)
280            raise KeyboardInterrupt, "Ctrl-c pressed while running %s"%self
281
282    def eval(self, code):
283        try:
284            return '\n'.join([self._eval_line(L) for L in str(code).split('\n')])
285        except KeyboardInterrupt:
286            self._keyboard_interrupt()
287        except TypeError, s:
288            return 'error evaluating "%s":\n%s'%(code,s)
289
290    def _coerce_(self, x):
291        return self(x)
292       
293    def __call__(self, x):
294        r"""
295        Create a new object in self from x.
296
297        The object X returned can be used like any SAGE object, and
298        wraps an object in self.  The standard arithmetic operators
299        work.  Morever if foo is a function then
300                      X.foo(y,z,...)
301        calls foo(X, y, z, ...) and returns the corresponding object.
302        """
303        cls = self._object_class()
304       
305        if isinstance(x, cls) and x.parent() is self:
306            return x
307
308        elif (not isinstance(x, ExpectElement) and hasattr(x, self.__coerce_name)) or  \
309                (isinstance(x, ExpectElement) and x.hasattr(self.__coerce_name)):
310            return getattr(x, self.__coerce_name)(self)
311
312        elif isinstance(x, (list, tuple)):
313            A = []
314            for v in x:
315                if isinstance(v, cls):
316                    A.append(v.name())
317                else:
318                    A.append(str(v))
319            X = ','.join(A)
320            return self.new('%s%s%s'%(self._left_list_delim(), X, self._right_list_delim()))
321
322        return cls(self, x)
323
324    def new(self, code):
325        return self(code)
326
327    ###################################################################
328    # these should all be appropriately overloaded by the derived class
329    ###################################################################
330   
331    def _left_list_delim(self):
332        return "["
333
334    def _right_list_delim(self):
335        return "]"
336
337    def _assign_symbol(self):
338        return "="
339   
340    def _equality_symbol(self):
341        return "=="
342
343    # These below could easily be wrong and it not be obvious, so we require
344    # they be overloaded.  The above would obviously bomb if not correct.
345    def _true_symbol(self):
346        raise NotImplementedError
347
348    def _false_symbol(self):
349        raise NotImplementedError
350
351    ############################################################
352    #         Functions for working with variables.
353    #  The first three must be overloaded by derived classes,
354    #  and the definition depends a lot on the class.  But
355    #  the functionality one gets from this is very nice.
356    ############################################################
357   
358    def set(self, var, value):
359        """
360        Set the variable var to the given value.
361        """
362        cmd = '%s%s%s;'%(var,self._assign_symbol(), value)       
363        self.eval(cmd)
364
365    def get(self, var):
366        """
367        Get the value of the variable var.
368        """
369        return self.eval(var)
370
371    def get_using_file(self, var):
372        return self.get(var)
373
374    def clear(self, var):
375        """
376        Clear the variable named var.
377        """
378        self._available_vars.append(var)
379   
380    def _next_var_name(self):
381        if len(self._available_vars) != 0:
382            v = self._available_vars[0]
383            del self._available_vars[0]
384            return v
385        self.__seq += 1
386        return "sage%s"%self.__seq
387
388    def _create(self, value):
389        name = self._next_var_name()
390        self.set(name, value)
391        return name
392
393    def _object_class(self):
394        return ExpectElement
395
396    def function_call(self, function, args=[]):
397        if function == '':
398            raise ValueError, "function name must be nonempty"
399        if function[:2] == "__":
400            raise AttributeError
401        if not isinstance(args, list):
402            args = [args]
403        for i in range(len(args)):
404            if not isinstance(args[i], ExpectElement):
405                args[i] = self.new(args[i])
406        return self.new("%s(%s)"%(function, ",".join([s.name() for s in args])))
407           
408    def call(self, function_name, *args):
409        return self.function_call(function_name, args)
410
411    def _contains(self, v1, v2):
412        raise NotImplementedError
413
414    def _is_true_string(self, s):
415        raise NotImplementedError
416   
417    def __getattr__(self, attrname):
418        if attrname[:1] == "_":
419            raise AttributeError
420        return ExpectFunction(self, attrname)
421
422    def __cmp__(self, other):
423        if self is other:
424            return 0
425        return -1
426
427    def console(self):
428        raise NotImplementedError
429   
430
431class ExpectFunction(SageObject):
432    def __init__(self, parent, name):
433        self._parent = parent
434        self._name = name
435       
436    def __repr__(self):
437        return "Function %s"%self._name
438   
439    def __call__(self, *args):
440        return self._parent.function_call(self._name, list(args))
441       
442       
443class FunctionElement(SageObject):
444    def __init__(self, obj, name):
445        self._obj = obj
446        self._name = name
447
448    def __repr__(self):
449        return "member function %s"%self._name
450
451    def __call__(self, *args):
452        return self._obj.parent().function_call(self._name, [self._obj] + list(args))
453
454def is_ExpectElement(x):
455    return isinstance(x, ExpectElement)
456
457class ExpectElement(Element_cmp_, RingElement):
458    def __init__(self, parent, value, is_name=False):
459        RingElement.__init__(self, parent)
460        self._create = value
461        if parent is None: return     # means "invalid element"
462        if isinstance(value, str) and parent._eval_using_file_cutoff and \
463           parent._eval_using_file_cutoff > len(value):
464            self._get_using_file = True
465           
466        if is_name:
467            self._name = value
468        else:
469            try:
470                self._name = parent._create(value)
471            except (TypeError, KeyboardInterrupt, RuntimeError, ValueError), x:
472                self._session_number = -1
473                raise TypeError, x
474        self._session_number = parent._session_number
475
476    def __iter__(self):
477        for i in range(1, len(self)+1):
478            yield self[i]
479
480    def __len__(self):
481        raise NotImplementedError
482
483    def __reduce__(self):
484        return reduce_load, (self.parent(), self._reduce())
485
486    def _reduce(self):
487        return str(self)
488
489    def _r_action(self, x):   # used for coercion
490        raise AttributeError
491
492    def __call__(self, *args):
493        self._check_valid()               
494        P = self.parent()
495        return getattr(P, self.name())(*args)
496
497    def _cmp_(self, other):
498        #if not (isinstance(other, ExpectElement) and other.parent() is self.parent()):
499        #    return coerce.cmp(self, other)
500        P = self.parent()
501        if P.eval("%s < %s"%(self.name(), other.name())) == P._true_symbol():
502            return -1
503        elif P.eval("%s > %s"%(self.name(), other.name())) == P._true_symbol():
504            return 1
505        elif P.eval("%s %s %s"%(self.name(), P._equality_symbol(),
506                                 other.name())) == P._true_symbol():
507            return 0
508        else:
509            return -1  # everything is supposed to be comparable in Python, so we define
510                       # the comparison thus when no comparable in interfaced system.
511
512    def _matrix_(self, R):
513        raise NotImplementedError
514
515    def _vector_(self, R):
516        raise NotImplementedError
517
518    def _check_valid(self):
519        """
520        Check that this object is valid, i.e., the session in which
521        this object is defined is still running.  This is relevant for
522        interpreters that can't be interrupted via ctrl-C, hence get
523        restarted. 
524        """
525        try:
526            P = self.parent()
527            if P is None is None or P._session_number == BAD_SESSION or self._session_number == -1 or \
528                          (P._restart_on_ctrlc and P._session_number != self._session_number):
529                raise ValueError, "The %s session in which this object was defined is no longer running."%P.name()
530        except AttributeError:
531            raise ValueError, "The session in which this object was defined is no longer running."
532
533    def __contains__(self, x):
534        self._check_valid()               
535        P = self.parent()
536        if not isinstance(x, ExpectElement) or x.parent() != self.parent():
537            x = P.new(x)
538        t = P._contains(x.name(), self.name())
539        return P._is_true_string(t)
540
541    def __del__(self):
542        try:
543            self._check_valid()
544        except ValueError:
545            return
546        try:
547            if hasattr(self,'_name'):
548                P = self.parent()
549                if not (P is None):
550                    P.clear(self._name)
551        except RuntimeError, msg:    # needed to avoid infinite loops in some rare cases
552            #print msg
553            pass
554
555    def _sage_(self):
556        """
557        Attempt to return a SAGE version of this object.
558        """
559        return sage_eval(str(self))
560
561    def __repr__(self):
562        self._check_valid()
563        try:
564            if self._get_using_file:
565                return self.parent().get_using_file(self._name)
566        except AttributeError:
567            return self.parent().get(self._name)
568
569    def __getattr__(self, attrname):
570        self._check_valid()
571        return FunctionElement(self, attrname)
572
573    def hasattr(self, attrname):
574        """
575        Returns whether the given attribute is already defined by this object,
576        and in particular is not dynamically generated.
577        """
578        return not isinstance(getattr(self, attrname), FunctionElement)
579       
580
581    def __getitem__(self, n):
582        self._check_valid()
583        if not isinstance(n, tuple):
584            return self.parent().new('%s[%s]'%(self._name, n))
585        else:
586            return self.parent().new('%s[%s]'%(self._name, str(n)[1:-1]))
587   
588    def __int__(self):
589        return int(str(self))
590
591    def bool(self):
592        P = self.parent()
593        t = P._true_symbol()
594        cmd = '%s %s %s'%(self._name, P._equality_symbol(), t)
595        return P.eval(cmd) == t
596
597    def __long__(self):
598        return long(str(self))
599
600    def __float____(self):
601        return float(str(self))
602
603    def _integer_(self):
604        import sage.rings.all
605        return sage.rings.all.Integer(str(self))
606
607    def _rational_(self):
608        import sage.rings.all       
609        return sage.rings.all.Rational(str(self))
610
611    def name(self):
612        return self._name
613
614    def gen(self, n):
615        self._check_valid()
616        return self.parent().new('%s.%s'%(self._name, int(n)))
617
618    def _add_(self, right):
619        self._check_valid()       
620        return self.parent().new('%s + %s'%(self._name, right._name))
621       
622    def _sub_(self, right):
623        self._check_valid()       
624        return self.parent().new('%s - %s'%(self._name, right._name))
625
626    def _mul_(self, right):
627        self._check_valid()       
628        return self.parent().new('%s * %s'%(self._name, right._name))
629
630    def _div_(self, right):
631        self._check_valid()       
632        return self.parent().new('%s / %s'%(self._name, right._name))
633
634    def __pow__(self, n):
635        self._check_valid()
636        if isinstance(n, ExpectElement):
637            return self.parent().new('%s ^ %s'%(self._name,n._name))
638        else:
639            return self.parent().new('%s ^ %s'%(self._name,n))
640
641
642def reduce_load(parent, x):
643    return parent(x)
644
645import os
646def console(cmd):
647    os.system(cmd)
648
Note: See TracBrowser for help on using the repository browser.