diff --git a/editor/datamodel.py b/editor/datamodel.py index f9f70541..18210c44 100644 --- a/editor/datamodel.py +++ b/editor/datamodel.py @@ -10,7 +10,7 @@ class Expression: def __init__(self): - self.id = get_id() + self.id = None class ValueHolder(Expression): @@ -38,6 +38,7 @@ def __repr__(self): return super().__repr__() + class Pair(Expression): def __init__(self, first: Expression, rest: Expression): import log @@ -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]" diff --git a/editor/evaluate_apply.py b/editor/evaluate_apply.py index ebd5ff41..71fe4a44 100644 --- a/editor/evaluate_apply.py +++ b/editor/evaluate_apply.py @@ -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}]") @@ -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: diff --git a/editor/execution.py b/editor/execution.py index 33f690b6..5a0c09d2 100644 --- a/editor/execution.py +++ b/editor/execution.py @@ -3,6 +3,7 @@ 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 @@ -10,12 +11,12 @@ 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: @@ -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() diff --git a/editor/log.py b/editor/log.py index 2015355a..d90b676d 100644 --- a/editor/log.py +++ b/editor/log.py @@ -20,6 +20,20 @@ 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 @@ -27,6 +41,11 @@ def __init__(self, base_expr: Expression = None, true_base_expr: Expression = No 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) \ @@ -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 @@ -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) @@ -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 @@ -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 @@ -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 @@ -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 = [] @@ -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): @@ -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)) @@ -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) diff --git a/editor/runtime_limiter.py b/editor/runtime_limiter.py index 7d00489c..a8a983b9 100644 --- a/editor/runtime_limiter.py +++ b/editor/runtime_limiter.py @@ -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()) @@ -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) diff --git a/editor/special_forms.py b/editor/special_forms.py index 286c692f..29d410d9 100644 --- a/editor/special_forms.py +++ b/editor/special_forms.py @@ -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]) @@ -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) @@ -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]) @@ -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) @@ -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)}.") @@ -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:] = [] @@ -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) @@ -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])