Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Faster logging #153

Merged
merged 14 commits into from Apr 15, 2019
4 changes: 3 additions & 1 deletion editor/datamodel.py
Expand Up @@ -10,7 +10,7 @@

class Expression:
def __init__(self):
self.id = get_id()
self.id = None


class ValueHolder(Expression):
Expand Down Expand Up @@ -38,6 +38,7 @@ def __repr__(self):
return super().__repr__()



class Pair(Expression):
def __init__(self, first: Expression, rest: Expression):
import log
Expand Down Expand Up @@ -100,6 +101,7 @@ def __init__(self, expr: Expression, frame: 'Frame'):
self.expr = expr
self.frame = frame
self.targets = []
self.id = get_id()

def __repr__(self):
return "#[promise]"
Expand Down
9 changes: 3 additions & 6 deletions editor/evaluate_apply.py
Expand Up @@ -83,11 +83,7 @@ def evaluate(expr: Expression, frame: Frame, gui_holder: log.Holder,
if depth > RECURSION_LIMIT:
raise OutOfMemoryError("Debugger ran out of memory due to excessively deep recursion.")

if isinstance(gui_holder.expression, Expression):
visual_expression = log.VisualExpression(expr)
gui_holder.link_visual(visual_expression)
else:
visual_expression = gui_holder.expression
visual_expression = gui_holder.expression

if log_stack:
log.logger.eval_stack.append(f"{repr(expr)} [frame = {frame.id}]")
Expand Down Expand Up @@ -122,7 +118,8 @@ def evaluate(expr: Expression, frame: Frame, gui_holder: log.Holder,
if isinstance(out, Thunk):
expr, frame = out.expr, out.frame
thunks.append(out)
out.gui_holder.evaluate()
if out.gui_holder.state != log.HolderState.EVALUATING:
out.gui_holder.evaluate()
if log.logger.show_thunks:
gui_holder = out.gui_holder
else:
Expand Down
12 changes: 8 additions & 4 deletions editor/execution.py
Expand Up @@ -3,19 +3,20 @@
from datamodel import Undefined, Pair
from evaluate_apply import evaluate
from graphics import Canvas
from helper import pair_to_list
from log import Holder, Root
from execution_parser import get_expression
from lexer import TokenBuffer
from runtime_limiter import TimeLimitException
from scheme_exceptions import SchemeError, ParseError

MAX_TRACEBACK_LENGTH = 20
MAX_AUTODRAW_LENGTH = 50


def string_exec(strings, out, visualize_tail_calls, global_frame=None):
import log


empty = False

if global_frame is None:
Expand Down Expand Up @@ -50,9 +51,12 @@ def string_exec(strings, out, visualize_tail_calls, global_frame=None):
res = evaluate(expr, global_frame, holder)
if res is not Undefined:
out(res)
if not log.logger.fragile and log.logger.autodraw and isinstance(res, Pair):
log.logger.raw_out("AUTODRAW" +
json.dumps([log.logger.i, log.logger.heap.record(res)]) + "\n")
if not log.logger.fragile and log.logger.autodraw:
try:
log.logger.raw_out("AUTODRAW" +
json.dumps([log.logger.i, log.logger.heap.record(res)]) + "\n")
except RecursionError:
pass
except (SchemeError, ZeroDivisionError, RecursionError, ValueError) as e:
if isinstance(e, ParseError):
log.logger.new_expr()
Expand Down
75 changes: 33 additions & 42 deletions editor/log.py
Expand Up @@ -20,13 +20,32 @@ class HolderState(Enum):
APPLYING = 4


class FakeObj:
def __getattr__(self, item):
return fake_obj

def __getitem__(self, item):
return fake_obj

def __call__(self, *args, **kwargs):
return fake_obj


fake_obj = FakeObj()


class VisualExpression:
def __init__(self, base_expr: Expression = None, true_base_expr: Expression = None):
self.display_value = base_expr
self.base_expr = base_expr if true_base_expr is None else true_base_expr
self.value: Expression = None
self.children: List[Holder] = []
self.id = get_id()

if logger.op_count >= OP_LIMIT:
self.children = fake_obj
return

if base_expr is None:
return
if isinstance(base_expr, ValueHolder) \
Expand All @@ -41,16 +60,14 @@ def __init__(self, base_expr: Expression = None, true_base_expr: Expression = No
except OperandDeduceError:
self.set_entries([base_expr.first, base_expr.rest])
else:
raise NotImplementedError(base_expr)
raise NotImplementedError(base_expr, type(base_expr))

def set_entries(self, expressions: List[Expression]):
def set_entries(self, expressions: Union[List[Expression], List['VisualExpression']]):
self.value = None
self.children = [Holder(expression, self) for expression in expressions]
if expressions and isinstance(expressions[0], VisualExpression):
if self.id in logger.node_cache:
if isinstance(logger.node_cache[self.id], StaticNode):
curr_transition = HolderState[logger.node_cache[self.id].transition_type]
elif logger.node_cache[self.id].transitions:
if logger.node_cache[self.id].transitions:
curr_transition = HolderState[logger.node_cache[self.id].transitions[-1][-1]]
else:
return self
Expand All @@ -65,16 +82,10 @@ def __repr__(self):

class Holder:
def __init__(self, expr: Expression, parent: VisualExpression):
self.expression: Union[Expression, VisualExpression] = expr
self.expression: VisualExpression = VisualExpression(expr) if isinstance(expr, Expression) else expr
self.state = HolderState.UNEVALUATED
self.parent = parent

def link_visual(self, expr: VisualExpression):
self.expression = expr
if self.parent is not None and self.parent.id in logger.node_cache:
logger.node_cache[self.parent.id].modify(self.parent, HolderState.EVALUATING)
return expr

def evaluate(self):
self.state = HolderState.EVALUATING
announce("Evaluating", self, Root.root)
Expand Down Expand Up @@ -131,7 +142,7 @@ def __init__(self):

self.show_thunks = True

self.node_cache: Dict[str, Union[StaticNode, FatNode]] = {} # a cache of visual expressions
self.node_cache: Dict[str, Node] = {} # a cache of visual expressions
self.export_states = [] # all the nodes generated in the current evaluation, in exported form
self.roots = [] # the root node of each expr we are currently evaluating

Expand Down Expand Up @@ -165,8 +176,8 @@ def new_query(self, global_frame: 'StoredFrame'=None, curr_i=0, curr_f=0):
self.export_states = []
self.frame_updates = []
self.global_frame = global_frame
self.op_count = 0
self.graphics_open = False
self.op_count = 0

def get_canvas(self) -> 'graphics.Canvas':
self.graphics_open = True
Expand Down Expand Up @@ -218,14 +229,10 @@ def frame_create(self, frame: 'evaluate_apply.Frame'):
def frame_store(self, frame: 'evaluate_apply.Frame', name: str, value: Expression):
self.frame_lookup[id(frame)].bind(name, value)

def new_node(self, expr: Union[Expression, VisualExpression], transition_type: HolderState):
if isinstance(expr, Expression):
key = get_id()
self.node_cache[key] = StaticNode(expr, transition_type)
return key
def new_node(self, expr: VisualExpression, transition_type: HolderState):
if expr.id in self.node_cache:
return self.node_cache[expr.id].modify(expr, transition_type, force=True)
node = FatNode(expr, transition_type)
node = Node(expr, transition_type)
self.node_cache[node.id] = node
return node.id

Expand All @@ -235,22 +242,7 @@ def log_op(self):
return self.op_count < OP_LIMIT


class StaticNode:
def __init__(self, expr: Expression, transition_type: HolderState):
self.expr = expr
self.transition_type = transition_type

def export(self):
return {
"transitions": [(0, self.transition_type.name)],
"strs": [(0, repr(self.expr))],
"parent_strs": [(0, repr(self.expr))],
"children": [(0, [])],
"static": True
}


class FatNode:
class Node:
def __init__(self, expr: VisualExpression, transition_type: HolderState):
self.transitions = []
self.str = []
Expand All @@ -260,7 +252,7 @@ def __init__(self, expr: VisualExpression, transition_type: HolderState):
self.modify(expr, transition_type)

@limited
def modify(self, expr: Union[Expression, VisualExpression], transition_type: HolderState):
def modify(self, expr: VisualExpression, transition_type: HolderState):
if not self.transitions or self.transitions[-1][1] != transition_type.name:
self.transitions.append((logger.i, transition_type.name))
if not self.str or self.str[-1][1] != repr(expr):
Expand All @@ -269,17 +261,14 @@ def modify(self, expr: Union[Expression, VisualExpression], transition_type: Hol
while self.children and self.children[-1][0] == logger.i:
self.children.pop()

if isinstance(expr, VisualExpression) and expr.value is None:
if expr.value is None:
self.children.append(
(logger.i,
[logger.new_node(child.expression, child.state) for child in expr.children]))
else:
self.children.append((logger.i, []))

if isinstance(expr, VisualExpression):
new_base_str = repr(expr.base_expr)
else:
new_base_str = expr
new_base_str = repr(expr.base_expr)

if not self.base_str or self.base_str[-1][1] != new_base_str:
self.base_str.append((logger.i, new_base_str))
Expand Down Expand Up @@ -355,6 +344,8 @@ def modify(self, id):
def record(self, expr: Expression) -> 'Heap.HeapKey':
if isinstance(expr, evaluate_apply.Thunk):
return False, "thunk"
if expr.id is None:
expr.id = get_id()
if expr.id not in self.prev and expr.id not in self.curr:
if isinstance(expr, ValueHolder):
return False, repr(expr)
Expand Down
14 changes: 12 additions & 2 deletions editor/runtime_limiter.py
Expand Up @@ -2,16 +2,24 @@
import threading
import time

import log
from scheme_exceptions import TerminatedError

class OperationCanceledException(Exception): pass
class TimeLimitException(Exception): pass

class OperationCanceledException(Exception):
pass


class TimeLimitException(Exception):
pass


def limiter(raise_exception, lim, func, *args):
is_event = isinstance(lim, threading.Event)
lim_is_set = lim.is_set if is_event else None # For performance
gettime = time.time # For performance
end = (gettime() + lim) if not is_event else None

def tracer(*args):
if lim_is_set() if is_event else gettime() > end:
raise_exception(OperationCanceledException() if is_event else TimeLimitException())
Expand All @@ -24,9 +32,11 @@ def tracer(*args):
finally:
sys.settrace(sys_tracer)


def scheme_limiter(*args, **kwargs):
def raise_(e): # Translate to scheme exception and throw
if isinstance(e, OperationCanceledException):
e = TerminatedError
raise e

return limiter(raise_, *args, **kwargs)
10 changes: 1 addition & 9 deletions editor/special_forms.py
Expand Up @@ -54,7 +54,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
new_frame.assign(self.var_param, make_list(operands[len(self.params):]))

out = None
# noinspection PyTypeChecker
gui_holder.expression.set_entries(
[VisualExpression(expr, gui_holder.expression.display_value) for expr in body])

Expand All @@ -70,7 +69,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
new_frame.assign(return_symbol, out)

if not self.evaluates_operands:
# noinspection PyTypeChecker
gui_holder.expression.set_entries([VisualExpression(out, gui_holder.expression.display_value)])
out = evaluate(out, frame, gui_holder.expression.children[i], True)

Expand Down Expand Up @@ -266,7 +264,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder):
raise OperandDeduceError(f"Unable to evaluate clause of cond, as {cond} is not a Pair.")
expanded = pair_to_list(cond)
cond_holder = gui_holder.expression.children[cond_i + 1]
cond_holder.link_visual(VisualExpression(cond))
eval_condition = SingletonTrue
if not isinstance(expanded[0], Symbol) or expanded[0].value != "else":
eval_condition = evaluate(expanded[0], frame, cond_holder.expression.children[0])
Expand Down Expand Up @@ -311,7 +308,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder):

new_frame = Frame("anonymous let", frame)

gui_holder.expression.children[1].link_visual(VisualExpression(bindings))
bindings_holder = gui_holder.expression.children[1]

bindings = pair_to_list(bindings)
Expand All @@ -320,7 +316,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder):
if not isinstance(binding, Pair):
raise OperandDeduceError(f"Expected binding to be a Pair, not {binding}.")
binding_holder = bindings_holder.expression.children[i]
binding_holder.link_visual(VisualExpression(binding))
binding = pair_to_list(binding)
if len(binding) != 2:
raise OperandDeduceError(f"Expected binding to be of length 2, not {len(binding)}.")
Expand Down Expand Up @@ -375,8 +370,7 @@ def quasiquote_evaluate(cls, expr: Expression, frame: Frame, gui_holder: Holder,
is_well_formed = not any(map(
lambda x: isinstance(x, Symbol) and x.value in ["unquote", "quasiquote", "unquote-splicing"], lst))

visual_expression = VisualExpression(expr)
gui_holder.link_visual(visual_expression)
visual_expression = gui_holder.expression
if not is_well_formed:
visual_expression.children[2:] = []

Expand Down Expand Up @@ -437,7 +431,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
code = "(begin-noexcept" + "\n".join(file.readlines()) + "\n)"
buffer = TokenBuffer([code])
expr = get_expression(buffer)
# noinspection PyTypeChecker
gui_holder.expression.set_entries([VisualExpression(expr, gui_holder.expression.display_value)])
gui_holder.apply()
return evaluate(expr, frame, gui_holder.expression.children[0], True)
Expand Down Expand Up @@ -477,7 +470,6 @@ def execute(self, operands: List[Expression], frame: Frame, gui_holder: Holder,
return operand.expr
if logger.fragile:
raise IrreversibleOperationError()
# noinspection PyTypeChecker
gui_holder.expression.set_entries([VisualExpression(operand.expr, gui_holder.expression.display_value)])
gui_holder.apply()
evaluated = evaluate(operand.expr, operand.frame, gui_holder.expression.children[0])
Expand Down