| ''' |
| Copyright (c) 2014 Martin Kloesch |
| All rights reserved. This program and the accompanying materials |
| are made available under the terms of the Eclipse Public License v1.0 |
| which accompanies this distribution, and is available at |
| http://www.eclipse.org/legal/epl-v10.html |
| |
| Contributors: |
| * Martin Kloesch - initial API and implementation |
| * Christian Pontesegger - stripped most parts to simply trace and relay to java |
| ''' |
| |
| # Python std library imports |
| import sys |
| import __main__ |
| import re |
| import ast |
| from pygments.lexers._scilab_builtins import variables_kw |
| |
| # : Regular expression if we are dealing with internal module. |
| _pyease_INTERNAL_CHECKER = re.compile(r"^<.+>$") |
| |
| class _pyease_PyFrame: |
| ''' |
| Python implementation of IPyFrame used for exchanging frame data |
| with eclipse. |
| |
| Simply wraps standard python frame to more easily usable format. |
| |
| Python frame documentation is available here: |
| https://docs.python.org/3/reference/datamodel.html#types |
| ''' |
| def __init__(self, frame): |
| ''' |
| Constructor only stores frame to member. |
| |
| :param frame: Python frame to be converted. |
| ''' |
| self._frame = frame |
| |
| def getFilename(self): |
| ''' |
| Returns the filename of the frame. |
| |
| If no frame was set, a dummy value will be returned. |
| |
| :returns: Filename of frame or dummy value. |
| ''' |
| if self._frame: |
| return self._frame.f_code.co_filename |
| else: |
| return "__no_frame__" |
| |
| def getLineNumber(self): |
| ''' |
| Returns the linenumber of the frame. |
| |
| If no frame was set, -1 is returned. |
| |
| :returns: Line number of frame or -1. |
| ''' |
| if self._frame: |
| return self._frame.f_lineno |
| else: |
| return -1 |
| |
| def getParent(self): |
| ''' |
| Returns the parent of the frame. |
| |
| If no frame was set or the frame does not have a parent |
| `None` is returned. |
| |
| :returns: Parent frame in stack or `None` |
| ''' |
| if self._frame: |
| return _pyease_PyFrame(self._frame.f_back) |
| else: |
| return None |
| |
| def getVariable(self, name): |
| ''' |
| Get a variable from the current frame. |
| |
| :param: name: variable name to look up |
| :return: variable or <code>null</code> |
| ''' |
| if (self._frame): |
| if name in self._frame.f_locals: |
| return self._frame.f_locals[name] |
| |
| if name in self._frame.f_globals: |
| return self._frame.f_globals[name] |
| |
| return None |
| |
| def getVariables(self): |
| ''' |
| Get variables visible from current frame. |
| |
| :return: variableName -> variableContent |
| ''' |
| variables = {} |
| if (self._frame): |
| variables.update(self._frame.f_globals) |
| variables.update(self._frame.f_locals) |
| |
| return variables |
| |
| def setVariable(self, name, value): |
| if (self._frame): |
| if name in self._frame.f_locals: |
| self._frame.f_locals[name] = value |
| |
| elif name in self._frame.f_globals: |
| self._frame.f_locals[name] = value |
| |
| def toString(self): |
| return str(self.getFilename()) + ": #" + str(self.getLineNumber()) |
| |
| class Java: |
| implements = ['org.eclipse.ease.lang.python.debugger.IPyFrame'] |
| |
| |
| class _pyease_CodeTracer: |
| ''' |
| Eclipse Debugger class. |
| ''' |
| _debugger = None |
| _framework_variables = {} |
| |
| def __init__(self): |
| sys.settrace(self.trace_dispatch) |
| |
| def set_debugger(self, debugger): |
| ''' |
| Setter method for self._debugger. |
| |
| :param org.eclipse.ease.lang.python.debugger.PythonDebugger debugger: |
| PythonDebugger object to handling communication with Eclipse. |
| ''' |
| self._debugger = debugger |
| |
| def trace_dispatch(self, frame, event, arg): |
| ''' |
| Method called each time a new frame is reached in execution. |
| <p> |
| Performs prefiltering and dispatches the data to Eclipse. |
| |
| :param frame: New execution frame. |
| :param event: ignored. |
| :param arg: ignored. |
| :see: sys.settrace |
| ''' |
| |
| if not self.ignore_frame(frame): |
| if self._debugger: |
| if event == "return": |
| self._debugger.traceDispatch(_pyease_PyFrame(frame.f_back), event) |
| else: |
| self._debugger.traceDispatch(_pyease_PyFrame(frame), event) |
| |
| return self.trace_dispatch |
| |
| def run(self, script, filename): |
| ''' |
| Executes the file given using the bdb.Bdb.run method. |
| ''' |
| code = "{}\n".format(script.getCode()) |
| |
| ast_ = ast.parse(code, filename, 'exec') |
| final_expr = None |
| for field_ in ast.iter_fields(ast_): |
| if 'body' != field_[0]: |
| continue |
| |
| if len(field_[1]) > 0: |
| if isinstance(field_[1][-1], ast.Expr): |
| final_expr = ast.Expression() |
| popped = field_[1].pop(len(field_[1]) - 1); # Jython pop() will not accept -1 as parameter |
| final_expr.body = popped.value |
| |
| return_value = None |
| exec(compile(ast_, filename, 'exec'), globals()) |
| if final_expr: |
| return_value = eval(compile(final_expr, filename, 'eval'), None) |
| |
| return return_value |
| |
| # exec(compiledCode, g) |
| |
| def ignore_frame(self, frame, first=True): |
| ''' |
| Utility method to check if a frame should be ignored. |
| |
| Current reasons to ignore a frame: |
| * It is the top entry of the stack and it is a standard module. |
| e.g. <string> |
| * It is part of the py4j library or has py4j in its call chain. |
| |
| Recursively checks trace until at bottom of stack. |
| |
| :param frame: Frame to check if it should be ignored. |
| :param first: Flag to signalize if it is the first call. |
| :returns: `True` if the frame should be ignored. |
| ''' |
| # End of frame |
| if not frame: |
| return False |
| |
| # ignore frames that originate from the self.run() method |
| if first: |
| if frame.f_code.co_filename == "(none)": |
| return True |
| |
| # Check if we are in the standard modules |
| if _pyease_INTERNAL_CHECKER.match(frame.f_code.co_filename): |
| # Only ignore standard module if its the top of the stack |
| return first |
| |
| # TODO: Think of better way to identify py4j library |
| if 'py4j' in frame.f_code.co_filename.lower(): |
| return True |
| |
| # Check parent in stack |
| return self.ignore_frame(frame.f_back, False) |
| |
| |
| class Java: |
| implements = ['org.eclipse.ease.lang.python.debugger.ICodeTracer'] |
| |
| |
| # Set up connection between eclipse and python |
| _pyease_eclipse_python_debugger = _pyease_CodeTracer() |
| _pyease_eclipse_python_debugger.set_debugger(_pyease_debugger) |
| _pyease_debugger.setCodeTracer(_pyease_eclipse_python_debugger) |