'''Python bindings for libcyrt.so.'''
from ._cyrtbindings import *
from ...generic.eval import control, trace
from ....interpreter import flags as _flags
from .... import exceptions
from . import fingerprint
from ... import InfoTable as _backends_InfoTable
from ... import Node as _backends_Node
import logging, sys
logger = logging.getLogger(__name__)
_backends_Node.register(Node)
_backends_InfoTable.register(InfoTable)
[docs]
def make_node(info, *args, **kwds):
info = getattr(info, 'info', info)
partial_info = kwds.pop('partial_info', None)
target = kwds.pop('target', None)
target = getattr(target, 'target', target)
args = [Arg(arg) for arg in args]
return Node.create(info, args, target, bool(partial_info))
[docs]
class StepCounter(object):
'''
The step counter of the generic evaluator over a C++ runtime state.
Evaluator.set_global_step_limit and the telemetry read the counter of the
Python backend (generic.eval.stepcounter.StepCounter) as
``rts.stepcounter``; this object answers for the C++ state through its
fields ``steps_total`` and ``step_limit``. The count and the limit are
relative to the last reset (``_step_base`` of the state), and the totals
of the state keep every step. A limit at or below the count raises
E_TERMINATE at once, as the counter of the Python backend does; else the
scheduler returns E_TERMINATE after the step that reaches the limit
(cyrt/state/rts.hpp), and generate_values raises it.
A view: ``rts.stepcounter`` makes one per access, and the state holds no
counter. A counter kept by the state made a reference cycle, which
delayed the destruction of the state of evaluator.single_step until the
cyclic collector ran; a live state is a root of the collector, so the
history of a search stayed reachable (test_repl_eval_under_stress of
unit_goals.py grew with the square of its length).
'''
def __init__(self, rts):
self._rts = rts
@property
def global_count(self):
'''The steps taken since the last reset.'''
return self._rts.steps_total - self._rts._step_base
@property
def global_limit(self):
limit = self._rts.step_limit
return float('inf') if limit == NOLIMIT else limit - self._rts._step_base
@global_limit.setter
def global_limit(self, limit):
if limit is None:
self._rts.step_limit = NOLIMIT
return
limit = int(limit)
if self.global_count >= limit:
raise control.E_TERMINATE()
self._rts.step_limit = self._rts._step_base + limit
[docs]
def reset_global(self):
self._rts._step_base = self._rts.steps_total
[docs]
class RuntimeState(RuntimeStateBase):
def __init__(self, interp, goal=None):
istate = interp.backend.get_interpreter_state(interp)
self.tracing = interp.flags['trace']
limit = interp.flags['stack_limit']
self.stack_limit = NOLIMIT if limit is None else int(limit)
# The rotation cadence (see cyrt/ticker.hpp): zero steps select time
# mode with the quantum in nanoseconds.
mode, value = _flags.parse_rotation(interp.flags['rotation'])
self.rotation_steps = value if mode == 'steps' else 0
self.rotation_quantum_ns = value if mode == 'time' else 0
# The checker of the run-time invariants of the Fair Scheme (the flag
# ``checker``; cyrt/checker.hpp).
self.checker = bool(interp.flags['checker'])
RuntimeStateBase.__init__(
self, istate, goal, self.tracing, self.stack_limit
, self.rotation_steps, self.rotation_quantum_ns, self.checker
)
# The count of the stepper starts here (StepCounter.reset_global).
self._step_base = 0
@property
def stepcounter(self):
'''The step counter of the generic evaluator: a view of this state.'''
return StepCounter(self)
[docs]
def single_step(self, node):
'''
One rewrite step at the root of ``node`` (see evaluator.single_step).
A step that reports an error raises it as the evaluation would.
'''
try:
RuntimeStateBase.single_step(self, node)
except EvaluationError as err:
raise exceptions.EvaluationError(str(err))
[docs]
def generate_values(self):
'''
Generates the values of the goal.
The Curry program writes to the C standard output, and Python writes to
``sys.stdout``. Both buffers share one file descriptor. Python's buffer
is flushed before the scheduler runs, and the C buffer when the scheduler
returns (see the ``next`` binding), so the output keeps its order.
The step limit of the stepper (``stepcounter``) ends the evaluation with
E_TERMINATE, the flow-control exception of the generic evaluator, as on
the Python backend.
'''
try:
while True:
_flush_stdout()
result = self.next()
if result is None:
return
yield result
except EvaluationError as err:
raise exceptions.EvaluationError(str(err))
except EvaluationSuspended:
raise exceptions.EvaluationSuspended()
except StepLimitReached:
raise control.E_TERMINATE()
def _flush_stdout():
# A closed or missing stdout is legal: some IO tests close it to provoke an
# error from the program.
try:
sys.stdout.flush()
except (AttributeError, OSError, ValueError):
pass