From 4d1f9d0f3c57a16d095ab23c7ba01253c6df4880 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Thu, 19 Apr 2018 13:18:52 +0100 Subject: [PATCH 01/10] First cut at implementing `save`. Only the most basic tests though. --- src/sixtypical/analyzer.py | 51 ++++++++++++++++++++++++++++++++++-- src/sixtypical/ast.py | 5 ++++ src/sixtypical/parser.py | 6 ++++- tests/SixtyPical Analysis.md | 15 +++++++++++ tests/SixtyPical Syntax.md | 14 ++++++++++ 5 files changed, 88 insertions(+), 3 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 5c5c1fa..dc2333d 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -1,6 +1,6 @@ # encoding: UTF-8 -from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff +from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save from sixtypical.model import ( TYPE_BYTE, TYPE_WORD, TableType, BufferType, PointerType, VectorType, RoutineType, @@ -269,7 +269,7 @@ class Context(object): self._writeable.remove(ref) def set_writeable(self, *refs): - """Intended to be used for implementing analyzing `for`.""" + """Intended to be used for implementing analyzing `for`, but also used in `save`.""" for ref in refs: self._writeable.add(ref) @@ -292,6 +292,41 @@ class Context(object): self.assert_in_range(dest.index, dest.ref) self.set_written(dest.ref) + def extract(self, location): + """Used in `save`.""" + baton = ( + location, + location in self._touched, + self._range.get(location, None), + location in self._writeable, + ) + + if location in self._touched: + self._touched.remove(location) + self.set_unmeaningful(location) + self.set_writeable(location) + + return baton + + def re_introduce(self, baton): + """Used in `save`.""" + location, was_touched, was_range, was_writeable = baton + + if was_touched: + self._touched.add(location) + elif location in self._touched: + self._touched.remove(location) + + if was_range is not None: + self._range[location] = was_range + elif location in self._range: + del self._range[location] + + if was_writeable: + self._writeable.add(location) + elif location in self._writeable: + self._writeable.remove(location) + class Analyzer(object): @@ -385,6 +420,8 @@ class Analyzer(object): self.analyze_for(instr, context) elif isinstance(instr, WithInterruptsOff): self.analyze_block(instr.block, context) + elif isinstance(instr, Save): + self.analyze_save(instr, context) else: raise NotImplementedError @@ -730,3 +767,13 @@ class Analyzer(object): # after it is executed, we know the range of the loop variable. context.set_range(instr.dest, instr.final, instr.final) context.set_writeable(instr.dest) + + def analyze_save(self, instr, context): + if len(instr.locations) != 1: + raise NotImplementedError("Only 1 location in save is supported right now") + location = instr.locations[0] + + baton = context.extract(location) + self.analyze_block(instr.block, context) + # TODO assert no goto was encountered + context.re_introduce(baton) diff --git a/src/sixtypical/ast.py b/src/sixtypical/ast.py index afd2944..718636f 100644 --- a/src/sixtypical/ast.py +++ b/src/sixtypical/ast.py @@ -90,3 +90,8 @@ class For(Instr): class WithInterruptsOff(Instr): child_attrs = ('block',) + + +class Save(Instr): + value_attrs = ('locations',) + child_attrs = ('block',) diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 7ca2f69..e16cf66 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -1,6 +1,6 @@ # encoding: UTF-8 -from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff +from sixtypical.ast import Program, Defn, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save from sixtypical.model import ( TYPE_BIT, TYPE_BYTE, TYPE_WORD, RoutineType, VectorType, TableType, BufferType, PointerType, @@ -470,6 +470,10 @@ class Parser(object): self.scanner.expect("off") block = self.block() return WithInterruptsOff(self.scanner.line_number, block=block) + elif self.scanner.consume("save"): + locations = self.locexprs() + block = self.block() + return Save(self.scanner.line_number, locations=locations, block=block) elif self.scanner.consume("trash"): dest = self.locexpr() return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 4670794..f35d667 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1938,6 +1938,21 @@ initialized at the start of that loop. | } ? UnmeaningfulReadError: y +### save ### + +Basic neutral test. + + | routine main + | inputs a, x + | outputs a, x + | trashes z, n + | { + | save x { + | ld a, 0 + | } + | } + = ok + ### copy ### Can't `copy` from a memory location that isn't initialized. diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index 88bdc67..e2ef953 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -161,6 +161,20 @@ Basic "open-faced for" loops, up and down. | } = ok +Other blocks. + + | routine main trashes a, x, c, z, v { + | with interrupts off { + | save a, x, c { + | ld a, 0 + | } + | } + | save a, x, c { + | ld a, 0 + | } + | } + = ok + User-defined memory addresses of different types. | byte byt From 0f41857b393d5a6c1a7e0d8885f28bc87850243d Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Fri, 20 Apr 2018 13:36:08 +0100 Subject: [PATCH 02/10] Add two simple tests for save. Surprisingly, they both pass. --- tests/SixtyPical Analysis.md | 37 +++++++++++++++++++++++++++++++++++- 1 file changed, 36 insertions(+), 1 deletion(-) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index f35d667..f8d2f4f 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1940,19 +1940,54 @@ initialized at the start of that loop. ### save ### -Basic neutral test. +Basic neutral test, where the `save` makes no difference. | routine main | inputs a, x | outputs a, x | trashes z, n | { + | ld a, 1 + | save x { + | ld a, 2 + | } + | ld a, 3 + | } + = ok + +A defined value that has been saved can be trashed inside the block. +It will continue to be defined outside the block. + + | routine main + | inputs a + | outputs a, x + | trashes z, n + | { + | ld x, 0 | save x { | ld a, 0 + | trash x | } | } = ok +A trashed value that has been saved can be used inside the block. +It will continue to be trashed outside the block. + + | routine main + | inputs a + | outputs a, x + | trashes z, n + | { + | ld x, 0 + | trash x + | save x { + | ld a, 0 + | ld x, 1 + | } + | } + ? UnmeaningfulOutputError: x + ### copy ### Can't `copy` from a memory location that isn't initialized. From 8df16d7a2adaba29522650866f6e5d26662d3d3b Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Fri, 20 Apr 2018 14:35:41 +0100 Subject: [PATCH 03/10] Another passing test which is a little surprising. --- tests/SixtyPical Analysis.md | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index f8d2f4f..3b753b8 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1988,6 +1988,21 @@ It will continue to be trashed outside the block. | } ? UnmeaningfulOutputError: x +A value which is not output from the routine, is preserved by the +routine; and can appear in a `save` exactly because a `save` preserves it. + + | routine main + | inputs a + | outputs a + | trashes z, n + | { + | save x { + | ld a, 0 + | ld x, 1 + | } + | } + = ok + ### copy ### Can't `copy` from a memory location that isn't initialized. From 80c46485f191349f8efbc98b39a7d672ebdd501c Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 25 Apr 2018 13:06:11 +0100 Subject: [PATCH 04/10] Saving anything except `a` trashes `a`. --- src/sixtypical/analyzer.py | 6 ++++ tests/SixtyPical Analysis.md | 58 +++++++++++++++++++++++++++++++----- 2 files changed, 57 insertions(+), 7 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index dc2333d..15abcaf 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -777,3 +777,9 @@ class Analyzer(object): self.analyze_block(instr.block, context) # TODO assert no goto was encountered context.re_introduce(baton) + + if location == REG_A: + pass + else: + context.set_touched(REG_A) + context.set_unmeaningful(REG_A) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 3b753b8..20dd7fc 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -1955,17 +1955,45 @@ Basic neutral test, where the `save` makes no difference. | } = ok +Saving any location (other than `a`) will trash `a`. + + | routine main + | inputs a, x + | outputs a, x + | trashes z, n + | { + | ld a, 1 + | save x { + | ld a, 2 + | } + | } + ? UnmeaningfulOutputError + +Saving `a` does not trash anything. + + | routine main + | inputs a, x + | outputs a, x + | trashes z, n + | { + | ld x, 1 + | save a { + | ld x, 2 + | } + | ld x, 3 + | } + = ok + A defined value that has been saved can be trashed inside the block. It will continue to be defined outside the block. | routine main - | inputs a - | outputs a, x - | trashes z, n + | outputs x, y + | trashes a, z, n | { | ld x, 0 | save x { - | ld a, 0 + | ld y, 0 | trash x | } | } @@ -1991,14 +2019,30 @@ It will continue to be trashed outside the block. A value which is not output from the routine, is preserved by the routine; and can appear in a `save` exactly because a `save` preserves it. + | routine main + | outputs y + | trashes a, z, n + | { + | save x { + | ld y, 0 + | ld x, 1 + | } + | } + = ok + +Because saving anything except `a` trashes `a`, a common idiom is to save `a` +first in a nested series of `save`s. + | routine main | inputs a | outputs a | trashes z, n | { - | save x { - | ld a, 0 - | ld x, 1 + | save a { + | save x { + | ld a, 0 + | ld x, 1 + | } | } | } = ok From 07541d791334ec7d85ace98ba12fe70d1254c534 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 25 Apr 2018 13:15:53 +0100 Subject: [PATCH 05/10] Compile code for saving a, x, or y on the stack. --- src/sixtypical/compiler.py | 26 +++++++++++++++++++++++++- src/sixtypical/gen6502.py | 12 ++++++++++++ tests/SixtyPical Compilation.md | 24 ++++++++++++++++++++++++ 3 files changed, 61 insertions(+), 1 deletion(-) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index a7d0f64..aa0efc7 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -1,6 +1,6 @@ # encoding: UTF-8 -from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff +from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff, Save from sixtypical.model import ( ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef, TYPE_BIT, TYPE_BYTE, TYPE_WORD, @@ -12,6 +12,7 @@ from sixtypical.gen6502 import ( Immediate, Absolute, AbsoluteX, AbsoluteY, ZeroPage, Indirect, IndirectY, Relative, LDA, LDX, LDY, STA, STX, STY, TAX, TAY, TXA, TYA, + PHA, PLA, CLC, SEC, ADC, SBC, ROL, ROR, INC, INX, INY, DEC, DEX, DEY, CMP, CPX, CPY, AND, ORA, EOR, @@ -169,6 +170,8 @@ class Compiler(object): return self.compile_for(instr) elif isinstance(instr, WithInterruptsOff): return self.compile_with_interrupts_off(instr) + elif isinstance(instr, Save): + return self.compile_save(instr) else: raise NotImplementedError @@ -613,3 +616,24 @@ class Compiler(object): self.emitter.emit(SEI()) self.compile_block(instr.block) self.emitter.emit(CLI()) + + def compile_save(self, instr): + location = instr.locations[0] + if location == REG_A: + self.emitter.emit(PHA()) + self.compile_block(instr.block) + self.emitter.emit(PLA()) + elif location == REG_X: + self.emitter.emit(TXA()) + self.emitter.emit(PHA()) + self.compile_block(instr.block) + self.emitter.emit(PLA()) + self.emitter.emit(TAX()) + elif location == REG_Y: + self.emitter.emit(TYA()) + self.emitter.emit(PHA()) + self.compile_block(instr.block) + self.emitter.emit(PLA()) + self.emitter.emit(TAY()) + else: + raise NotImplementedError diff --git a/src/sixtypical/gen6502.py b/src/sixtypical/gen6502.py index c3c91d9..7b8d07c 100644 --- a/src/sixtypical/gen6502.py +++ b/src/sixtypical/gen6502.py @@ -306,6 +306,18 @@ class ORA(Instruction): } +class PHA(Instruction): + opcodes = { + Implied: 0x48, + } + + +class PLA(Instruction): + opcodes = { + Implied: 0x68, + } + + class ROL(Instruction): opcodes = { Implied: 0x2a, # Accumulator diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index fb45bc0..f08d835 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -579,6 +579,30 @@ Compiling `for ... down to`. = $0815 BNE $080F = $0817 RTS +Compiling `save`. + + | routine main + | inputs a + | outputs a + | trashes z, n + | { + | save a { + | save x { + | ld a, 0 + | ld x, 1 + | } + | } + | } + = $080D PHA + = $080E TXA + = $080F PHA + = $0810 LDA #$00 + = $0812 LDX #$01 + = $0814 PLA + = $0815 TAX + = $0816 PLA + = $0817 RTS + Indexed access. | byte one From 49864547331203156d130c69580375c2a3c9cb23 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 25 Apr 2018 13:27:47 +0100 Subject: [PATCH 06/10] Support save'ing user-defined locations. --- src/sixtypical/analyzer.py | 7 +++--- src/sixtypical/compiler.py | 7 +++++- tests/SixtyPical Analysis.md | 38 +++++++++++++++++++++++++++++++++ tests/SixtyPical Compilation.md | 19 +++++++++++++++++ 4 files changed, 67 insertions(+), 4 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 15abcaf..f1b99fe 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -335,9 +335,9 @@ class Analyzer(object): self.routines = {} self.debug = debug - def assert_type(self, type, *locations): + def assert_type(self, type_, *locations): for location in locations: - if location.type != type: + if location.type != type_: raise TypeMismatchError(self.current_routine, location.name) def assert_affected_within(self, name, affecting_type, limiting_type): @@ -464,7 +464,7 @@ class Analyzer(object): context.assert_meaningful(dest.ref, REG_Y) context.set_written(dest.ref) elif src.type != dest.type: - raise TypeMismatchError(instr, '{} and {}'.format(src, name)) + raise TypeMismatchError(instr, '{} and {}'.format(src, dest)) else: context.set_written(dest) # FIXME: context.copy_range(src, dest) ? @@ -772,6 +772,7 @@ class Analyzer(object): if len(instr.locations) != 1: raise NotImplementedError("Only 1 location in save is supported right now") location = instr.locations[0] + self.assert_type(TYPE_BYTE, location) baton = context.extract(location) self.analyze_block(instr.block, context) diff --git a/src/sixtypical/compiler.py b/src/sixtypical/compiler.py index aa0efc7..34ed70f 100644 --- a/src/sixtypical/compiler.py +++ b/src/sixtypical/compiler.py @@ -636,4 +636,9 @@ class Compiler(object): self.emitter.emit(PLA()) self.emitter.emit(TAY()) else: - raise NotImplementedError + src_label = self.get_label(location.name) + self.emitter.emit(LDA(Absolute(src_label))) + self.emitter.emit(PHA()) + self.compile_block(instr.block) + self.emitter.emit(PLA()) + self.emitter.emit(STA(Absolute(src_label))) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 20dd7fc..1f44673 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -2047,6 +2047,44 @@ first in a nested series of `save`s. | } = ok +Not just registers, but also user-defined locations can be saved. + + | byte foo + | + | routine main + | trashes a, z, n + | { + | save foo { + | st 5, foo + | } + | } + = ok + +But only if they are bytes. + + | word foo + | + | routine main + | trashes a, z, n + | { + | save foo { + | copy 555, foo + | } + | } + ? TypeMismatchError + + | byte table[16] tab + | + | routine main + | trashes a, y, z, n + | { + | save tab { + | ld y, 0 + | st 5, tab + y + | } + | } + ? TypeMismatchError + ### copy ### Can't `copy` from a memory location that isn't initialized. diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index f08d835..955e21e 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -603,6 +603,25 @@ Compiling `save`. = $0816 PLA = $0817 RTS +Compiling `save` on a user-defined location. + + | byte foo + | routine main + | trashes a, z, n + | { + | save foo { + | ld a, 0 + | st a, foo + | } + | } + = $080D LDA $081B + = $0810 PHA + = $0811 LDA #$00 + = $0813 STA $081B + = $0816 PLA + = $0817 STA $081B + = $081A RTS + Indexed access. | byte one From 76ec7224cd7869110a0ad40313b6a40bc8473c32 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 25 Apr 2018 13:38:54 +0100 Subject: [PATCH 07/10] Add failing tests for gotos inside runtime-context-making blocks. --- tests/SixtyPical Analysis.md | 78 ++++++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 1f44673..c41f998 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -2085,6 +2085,84 @@ But only if they are bytes. | } ? TypeMismatchError +A `goto` cannot appear within a `save` block, even if it is otherwise in tail position. + + | routine other + | trashes a, z, n + | { + | ld a, 0 + | } + | + | routine main + | trashes a, z, n + | { + | ld a, 1 + | save x { + | ld x, 2 + | goto other + | } + | } + ? IllegalJumpError + +### with interrupts ### + + | vector routine + | inputs x + | outputs x + | trashes z, n + | bar + | + | routine foo + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | routine main + | outputs bar + | trashes a, n, z + | { + | with interrupts off { + | copy foo, bar + | } + | } + = ok + +A `goto` cannot appear within a `with interrupts` block, even if it is +otherwise in tail position. + + | vector routine + | inputs x + | outputs x + | trashes z, n + | bar + | + | routine foo + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | routine other + | trashes bar, a, n, z + | { + | ld a, 0 + | } + | + | routine main + | trashes bar, a, n, z + | { + | with interrupts off { + | copy foo, bar + | goto other + | } + | } + ? IllegalJumpError + ### copy ### Can't `copy` from a memory location that isn't initialized. From 01e3ab00aa6cd288fbc4a5c442887ac1dadc2610 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 25 Apr 2018 13:40:39 +0100 Subject: [PATCH 08/10] Make tests pass. --- src/sixtypical/analyzer.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index f1b99fe..3a17c2a 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -420,6 +420,8 @@ class Analyzer(object): self.analyze_for(instr, context) elif isinstance(instr, WithInterruptsOff): self.analyze_block(instr.block, context) + if context.encountered_gotos(): + raise IllegalJumpError(instr, instr) elif isinstance(instr, Save): self.analyze_save(instr, context) else: @@ -776,7 +778,8 @@ class Analyzer(object): baton = context.extract(location) self.analyze_block(instr.block, context) - # TODO assert no goto was encountered + if context.encountered_gotos(): + raise IllegalJumpError(instr, instr) context.re_introduce(baton) if location == REG_A: From d037401b1842243df333f1cd47c9278a80d65175 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 25 Apr 2018 15:55:36 +0100 Subject: [PATCH 09/10] Add three tests. One fails when it shouldn't. --- tests/SixtyPical Analysis.md | 59 ++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index c41f998..3e75d97 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -2016,6 +2016,65 @@ It will continue to be trashed outside the block. | } ? UnmeaningfulOutputError: x +The known range of a value will be preserved outside the block as well. + + | word one: 77 + | word table[32] many + | + | routine main + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 31 + | ld x, a + | save x { + | ld x, 255 + | } + | copy one, many + x + | copy many + x, one + | } + = ok + + | word one: 77 + | word table[32] many + | + | routine main + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 63 + | ld x, a + | save x { + | ld x, 1 + | } + | copy one, many + x + | copy many + x, one + | } + ? RangeExceededError + +The known properties of a value are preserved inside the block, too. + + | word one: 77 + | word table[32] many + | + | routine main + | inputs a, many, one + | outputs many, one + | trashes a, x, n, z + | { + | and a, 31 + | ld x, a + | save x { + | copy one, many + x + | copy many + x, one + | } + | copy one, many + x + | copy many + x, one + | } + = ok + A value which is not output from the routine, is preserved by the routine; and can appear in a `save` exactly because a `save` preserves it. From eb4ed6a6bcfcc283cca3622cc08f944c9996e682 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Wed, 25 Apr 2018 16:58:21 +0100 Subject: [PATCH 10/10] Extract locations from context appropriately. All tests pass. --- src/sixtypical/analyzer.py | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 3a17c2a..ecb3b57 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -293,23 +293,23 @@ class Context(object): self.set_written(dest.ref) def extract(self, location): - """Used in `save`.""" + """Sets the given location as writeable in the context, and returns a 'baton' representing + the previous state of context for that location. This 'baton' can be used to later restore + this state of context.""" + # Used in `save`. baton = ( location, location in self._touched, self._range.get(location, None), location in self._writeable, ) - - if location in self._touched: - self._touched.remove(location) - self.set_unmeaningful(location) self.set_writeable(location) - return baton def re_introduce(self, baton): - """Used in `save`.""" + """Given a 'baton' produced by `extract()`, restores the context for that saved location + to what it was before `extract()` was called.""" + # Used in `save`. location, was_touched, was_range, was_writeable = baton if was_touched: