From 2e2e80664e5ebab58db0d63121aebfd972dab966 Mon Sep 17 00:00:00 2001 From: Chris Pressey Date: Tue, 14 May 2019 15:01:10 +0100 Subject: [PATCH] Forbid nested `with interrupts` blocks, and more refactoring. --- HISTORY.md | 12 ++- LICENSE | 4 +- README.md | 6 +- TODO.md | 162 +++++++++++++++++++------------ bin/sixtypical | 5 +- doc/SixtyPical.md | 7 +- eg/c64/demo-game/demo-game.60p | 49 +++++----- eg/rudiments/nested-for.60p | 33 +++++++ src/sixtypical/analyzer.py | 21 ++-- src/sixtypical/parser.py | 48 +-------- src/sixtypical/symtab.py | 50 ++++++++++ test.sh | 4 + tests/SixtyPical Analysis.md | 63 +++++++++++- tests/SixtyPical Compilation.md | 3 - tests/SixtyPical Control Flow.md | 76 +++++++++++++++ tests/SixtyPical Fallthru.md | 6 -- tests/SixtyPical Storage.md | 62 ++++++++++++ tests/SixtyPical Syntax.md | 3 - tests/appliances/sixtypical.md | 20 ++++ 19 files changed, 463 insertions(+), 171 deletions(-) create mode 100644 eg/rudiments/nested-for.60p create mode 100644 src/sixtypical/symtab.py create mode 100644 tests/appliances/sixtypical.md diff --git a/HISTORY.md b/HISTORY.md index 3ba4ac0..36bf490 100644 --- a/HISTORY.md +++ b/HISTORY.md @@ -12,10 +12,18 @@ History of SixtyPical * Local locations need no longer be static. If they are not static, they are considered uninitialized until assigned, and they can be declared with an explicit fixed address. +* Along with `goto`, `call` and `with interrupts off` are + now forbidden inside a `with interrupts off` block. +* More tests to assure that using `call` inside a `point into` + block or inside a `for` block does not cause trouble, + particularly when the routine being called also uses the + variable named in that block. * Fixed a bug where two local statics could be declared with the same name. -* Split context off from analyzer and put it in its own module. -* Split the SixtyPical Analysis tests across three files. +* Split analysis context support off from analyzer, and + symbol table support from parse, and it their own modules. +* Split the SixtyPical Analysis tests across three files, + and placed test appliances for `sixtypical` in own file. 0.19 ---- diff --git a/LICENSE b/LICENSE index 9d5dd32..23aa2a4 100644 --- a/LICENSE +++ b/LICENSE @@ -7,7 +7,7 @@ covered by the following BSD-compatible license, modelled after the ----------------------------------------------------------------------------- - Copyright (c)2014-2018 Chris Pressey, Cat's Eye Technologies. + Copyright (c)2014-2019 Chris Pressey, Cat's Eye Technologies. The authors intend this Report to belong to the entire SixtyPical community, and so we grant permission to copy and distribute it for @@ -24,7 +24,7 @@ The source code for the reference implementation and supporting tools (in the ----------------------------------------------------------------------------- - Copyright (c)2014-2018, Chris Pressey, Cat's Eye Technologies. + Copyright (c)2014-2019, Chris Pressey, Cat's Eye Technologies. All rights reserved. Redistribution and use in source and binary forms, with or without diff --git a/README.md b/README.md index 7be91d3..ab177fc 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ SixtyPical ========== -_Version 0.19. Work-in-progress, everything is subject to change._ +_Version 0.20. Work-in-progress, everything is subject to change._ **SixtyPical** is a [low-level](#low-level) programming language supporting a sophisticated [static analysis](#static-analysis). @@ -109,7 +109,9 @@ In order to run the tests for compilation, [dcc6502][] needs to be installed. * [SixtyPical specification](doc/SixtyPical.md) * [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md) -* [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md) +* [Literate test suite for SixtyPical analysis (operations)](tests/SixtyPical%20Analysis.md) +* [Literate test suite for SixtyPical analysis (storage)](tests/SixtyPical%20Storage.md) +* [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md) * [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md) * [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md) diff --git a/TODO.md b/TODO.md index 97d77a1..1761bde 100644 --- a/TODO.md +++ b/TODO.md @@ -1,6 +1,9 @@ TODO for SixtyPical =================== +Language +-------- + ### Save values to other-than-the-stack Allow @@ -9,25 +12,109 @@ Allow ... } -Which uses some other storage location instead of the stack. A local static -would be a good candidate for such. +Which uses some other storage location instead of the stack. A local non-static +would be a good candidate for such. At any rate, the location must not +be writeable by anything that is called from within the block. So, probably +just restrict this to local non-statics. -### Analyze `call` within blocks? +### Copy byte to/from table -What happens if you call another routine from inside a `with interrupts off` block? +Do we want a `copy bytevar, table + x` instruction? We don't currently have one. +You have to `ld a`, `st a`. I think maybe we should have one. -What happens if you call another routine from inside a `save` block? +### Character literals -What happens if you call another routine from inside a `point into` block? +For goodness sake, let the programmer say `'A'` instead of `65`. -What happens if you call another routine from inside a `for` block? +### Character set mapping -Remember that any of these may have a `goto` ... and they may have a second -instance of the same block (e.g. `with interrupts off` nested within -`with interrupts off` shouldn't be allowed to turn them back on after the -inner block has finished -- even if there is no `call`.) +Not all computers think `'A'` should be `65`. Allow the character set to be +mapped. Probably copy what Ophis does. -These holes need to be plugged. +### "Include" directives + +Search a searchlist of include paths. And use them to make libraries of routines. + +One such library routine might be an `interrupt routine` type for various architectures. +Since "the supervisor" has stored values on the stack, we should be able to trash them +with impunity, in such a routine. + +### Pointers into non-byte tables + +Right now you cannot get a pointer into a non-byte (for instance, word or vector) table. + +Word and vector tables are stored as two byte tables in memory. This is useful for +indexed access, but makes pointer access more difficult. + +Laying them out for pointer access would make indexed access more difficult. + +### Saving non-byte values + +Right now you cannot save a word value. + +There doesn't seem to be a hugely pressing reason why not. + +Analysis +-------- + +### Forbid recursion + +What happens if a routine calls itself, directly or indirectly? Many +constraints might be violated in this case. We should probably disallow +recursion by default. (Which means assembling the callgraph in all cases.) + +### Analyze memory usage + +If you define two variables that occupy the same address, an analysis error ought +to be raised. (But there should also be a way to annotate this as intentional. +Intentionally making two tables overlap could be valuable. However, the analysis +will probably completely miss this fact.) + +Optimization +------------ + +### Space optimization of local non-statics + +If there are two routines A and B, and A never calls B (even indirectly), and +B never calls A (even indirectly), then their non-static locals can +be allocated at the same space. + +This is not just an impressive trick -- in the presence of local pointers, which +use up a word in zero-page, which we consider a precious resource, it allow those +zero-page locations to be re-used. + +### Tail-call optimization + +If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, +if the block is in tail position. The constraints should iron out the same both ways. + +As long as the routine has consistent type context every place it exits, that should be fine. + +### Branch optimization in `if` + +Currently the `if` generator is not smart enough to avoid generating silly +jump instructions. (See the Fallthru tests.) Improve it. + +### Dead code removal + +Once we have a call graph we can omit routines that we're sure aren't called. + +This would let us use include-files and standard-libraries nicely: any +routines they define, but that you don't use, don't get included. + +Analyzing the set of possible routines that a vector can take on would help +this immensely. + +Implementation +-------------- + +### Line numbers in analysis error messages + +For analysis errors, there is a line number, but it's the line of the routine +after the routine in which the analysis error occurred. Fix this. + +Blue-skying +----------- ### Pointers associated globally with a table(?) @@ -45,52 +132,5 @@ at different times. These can co-exist with general, non-specific-table-linked `pointer` variables. -### Space optimization of local non-statics - -If there are two routines A and B, and A never calls B (even indirectly), and -B never calls A (even indirectly), then their non-static locals can -be allocated at the same space. - -This is more an impressive trick than a really useful feature, but still. -Impressive tricks are impressive. - -### Copy byte to/from table - -Do we want a `copy bytevar, table + x` instruction? We don't currently have one. -You have to `ld a`, `st a`. I think maybe we should have one. - -### Analyze memory usage - -If you define two variables that occupy the same address, an analysis error ought -to be raised. (But there should also be a way to annotate this as intentional. -Intentionally making two tables overlap could be valuable. However, the analysis -will probably completely miss this fact.) - -### Character literals - -For goodness sake, let the programmer say `'A'` instead of `65`. - -### Character set mapping - -Not all computers think `'A'` should be `65`. Allow the character set to be -mapped. Probably copy what Ophis does. - -### Tail-call optimization - -If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can, -if the block is in tail position. The constraints should iron out the same both ways. - -As long as the routine has consistent type context every place it exits, that should be fine. - -### "Include" directives - -Search a searchlist of include paths. And use them to make libraries of routines. - -One such library routine might be an `interrupt routine` type for various architectures. -Since "the supervisor" has stored values on the stack, we should be able to trash them -with impunity, in such a routine. - -### Line numbers in analysis error messages - -For analysis errors, there is a line number, but it's the line of the routine -after the routine in which the analysis error occurred. Fix this. +If we have local pointers and space optimization for local non-statics, though, +these don't add as much. diff --git a/bin/sixtypical b/bin/sixtypical index 2777b8f..c847389 100755 --- a/bin/sixtypical +++ b/bin/sixtypical @@ -16,7 +16,8 @@ import sys from tempfile import NamedTemporaryFile import traceback -from sixtypical.parser import Parser, SymbolTable, merge_programs +from sixtypical.symtab import SymbolTable +from sixtypical.parser import Parser, merge_programs from sixtypical.analyzer import Analyzer from sixtypical.outputter import outputter_class_for from sixtypical.compiler import Compiler @@ -184,7 +185,7 @@ if __name__ == '__main__': argparser.add_argument( "--version", action="version", - version="%(prog)s 0.19" + version="%(prog)s 0.20" ) options, unknown = argparser.parse_known_args(sys.argv[1:]) diff --git a/doc/SixtyPical.md b/doc/SixtyPical.md index 1107ef4..5608fcd 100644 --- a/doc/SixtyPical.md +++ b/doc/SixtyPical.md @@ -1,7 +1,7 @@ SixtyPical ========== -This document describes the SixtyPical programming language version 0.19, +This document describes the SixtyPical programming language version 0.20, both its static semantics (the capabilities and limits of the static analyses it defines) and its runtime semantics (with reference to the semantics of 6502 machine code.) @@ -196,7 +196,8 @@ table pointed to is implemented with "indirect indexed" addressing, as in There are extended instruction modes for using these types of memory location. See `copy` below, but here is some illustrative example code: - point ptr into buf { // this is the only way to initialize a pointer + point ptr into buf { // this associates this pointer with this table + reset ptr 0 // this is the only way to initialize a pointer add ptr, 4 // note, this is unchecked against table's size! ld y, 0 // you must set this to something yourself copy [ptr] + y, byt // read memory through pointer, into byte @@ -658,4 +659,6 @@ Grammar | "repeat" Block ("until" ["not"] LocExpr | "forever") | "for" LocExpr ("up"|"down") "to" Const Block | "with" "interrupts" LitBit Block + | "point" LocExpr "into" LocExpr Block + | "reset" LocExpr Const . diff --git a/eg/c64/demo-game/demo-game.60p b/eg/c64/demo-game/demo-game.60p index 85563a6..31d9f97 100644 --- a/eg/c64/demo-game/demo-game.60p +++ b/eg/c64/demo-game/demo-game.60p @@ -269,36 +269,33 @@ define player_logic logic_routine st off, c add ptr, new_pos ld y, 0 + // check collision. ld a, [ptr] + y - } - // if "collision" is with your own self, treat it as if it's blank space! - cmp a, 81 - if z { - ld a, 32 - } - cmp a, 32 - if z { - point ptr into screen { + // if "collision" is with your own self, treat it as if it's blank space! + cmp a, 81 + if z { + ld a, 32 + } + cmp a, 32 + if z { reset ptr 0 st off, c add ptr, pos copy 32, [ptr] + y - } - copy new_pos, pos + copy new_pos, pos - point ptr into screen { reset ptr 0 st off, c add ptr, pos copy 81, [ptr] + y } - } else { - ld a, 1 - st a, player_died } + } else { + ld a, 1 + st a, player_died } } @@ -314,26 +311,24 @@ define enemy_logic logic_routine st off, c add ptr, new_pos ld y, 0 + // check collision. ld a, [ptr] + y - } - // if "collision" is with your own self, treat it as if it's blank space! - cmp a, 82 - if z { - ld a, 32 - } - cmp a, 32 - if z { - point ptr into screen { + + // if "collision" is with your own self, treat it as if it's blank space! + cmp a, 82 + if z { + ld a, 32 + } + cmp a, 32 + if z { reset ptr 0 st off, c add ptr, pos copy 32, [ptr] + y - } - copy new_pos, pos + copy new_pos, pos - point ptr into screen { reset ptr 0 st off, c add ptr, pos diff --git a/eg/rudiments/nested-for.60p b/eg/rudiments/nested-for.60p new file mode 100644 index 0000000..833f09d --- /dev/null +++ b/eg/rudiments/nested-for.60p @@ -0,0 +1,33 @@ +// Include `support/${PLATFORM}.60p` before this source +// Should print H (being ASCII 72 = 8 * 9) + +// Increase y by 7, circuitously +// +define foo routine + inputs y + outputs y, n, z + trashes a, c +{ + save x { + ld x, 0 + for x up to 6 { + inc y + } + } +} + +// Each iteration increases y by 8; there are 9 iterations +// +define main routine + outputs x, y, n, z + trashes a, c +{ + ld x, 0 + ld y, 0 + for x up to 8 { + inc y + call foo + } + ld a, y + call chrout +} diff --git a/src/sixtypical/analyzer.py b/src/sixtypical/analyzer.py index 4404135..1e3991b 100644 --- a/src/sixtypical/analyzer.py +++ b/src/sixtypical/analyzer.py @@ -230,9 +230,7 @@ class Analyzer(object): elif isinstance(instr, For): self.analyze_for(instr, context) elif isinstance(instr, WithInterruptsOff): - self.analyze_block(instr.block, context) - if context.encountered_gotos(): - raise IllegalJumpError(instr, instr) + self.analyze_with_interrupts_off(instr, context) elif isinstance(instr, Save): self.analyze_save(instr, context) elif isinstance(instr, PointInto): @@ -290,7 +288,6 @@ class Analyzer(object): target = context.get_assoc(dest.ref) if not target: raise ForbiddenWriteError(instr, dest.ref) - context.set_touched(target) context.set_written(target) elif self.get_type(src) != self.get_type(dest): @@ -465,8 +462,6 @@ class Analyzer(object): target = context.get_assoc(dest.ref) if not target: raise ForbiddenWriteError(instr, dest.ref) - context.assert_writeable(target) - context.set_touched(target) context.set_written(target) elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef): @@ -477,9 +472,8 @@ class Analyzer(object): raise UnmeaningfulReadError(instr, src.ref) context.assert_meaningful(origin) - context.assert_writeable(dest) - context.set_touched(dest) context.set_written(dest) + elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef): context.assert_meaningful(src.ref, dest.ref, REG_Y) @@ -491,8 +485,6 @@ class Analyzer(object): target = context.get_assoc(dest.ref) if not target: raise ForbiddenWriteError(instr, dest.ref) - context.assert_writeable(target) - context.set_touched(target) context.set_written(target) elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef): @@ -503,7 +495,6 @@ class Analyzer(object): context.set_written(dest.ref) elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef): context.assert_meaningful(src.ref, src.index) - context.set_touched(dest) context.set_written(dest) else: context.assert_meaningful(src) @@ -569,7 +560,6 @@ class Analyzer(object): exit_context = context.clone() for ref in type_.outputs: - exit_context.set_touched(ref) # ? exit_context.set_written(ref) for ref in type_.trashes: @@ -677,6 +667,13 @@ class Analyzer(object): context.set_range(instr.dest, instr.final, instr.final) context.set_writeable(instr.dest) + def analyze_with_interrupts_off(self, instr, context): + block = instr.block + for instr in block.instrs: + if isinstance(instr, (Call, GoTo, WithInterruptsOff)): + raise IllegalJumpError(instr, instr) + self.analyze_instr(instr, context) + def analyze_save(self, instr, context): batons = [] for location in instr.locations: diff --git a/src/sixtypical/parser.py b/src/sixtypical/parser.py index 0143f5f..0cf7107 100644 --- a/src/sixtypical/parser.py +++ b/src/sixtypical/parser.py @@ -6,18 +6,10 @@ from sixtypical.ast import ( from sixtypical.model import ( TYPE_BIT, TYPE_BYTE, TYPE_WORD, RoutineType, VectorType, TableType, PointerType, - LocationRef, ConstantRef, IndirectRef, IndexedRef, + ConstantRef, IndirectRef, IndexedRef, ) from sixtypical.scanner import Scanner - - -class SymEntry(object): - def __init__(self, ast_node, type_): - self.ast_node = ast_node - self.type_ = type_ - - def __repr__(self): - return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_) +from sixtypical.symtab import SymEntry class ForwardReference(object): @@ -28,42 +20,6 @@ class ForwardReference(object): return "%s(%r)" % (self.__class__.__name__, self.name) -class SymbolTable(object): - def __init__(self): - self.symbols = {} # symbol name -> SymEntry - self.locals = {} # routine name -> (symbol name -> SymEntry) - self.typedefs = {} # type name -> Type AST - self.consts = {} # const name -> ConstantRef - - for name in ('a', 'x', 'y'): - self.symbols[name] = SymEntry(None, TYPE_BYTE) - for name in ('c', 'z', 'n', 'v'): - self.symbols[name] = SymEntry(None, TYPE_BIT) - - def __str__(self): - return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts) - - def has_local(self, routine_name, name): - return name in self.locals.get(routine_name, {}) - - def fetch_global_type(self, name): - return self.symbols[name].type_ - - def fetch_local_type(self, routine_name, name): - return self.locals[routine_name][name].type_ - - def fetch_global_ref(self, name): - if name in self.symbols: - return LocationRef(name) - return None - - def fetch_local_ref(self, routine_name, name): - routine_locals = self.locals.get(routine_name, {}) - if name in routine_locals: - return LocationRef(name) - return None - - class Parser(object): def __init__(self, symtab, text, filename): self.symtab = symtab diff --git a/src/sixtypical/symtab.py b/src/sixtypical/symtab.py new file mode 100644 index 0000000..99dacec --- /dev/null +++ b/src/sixtypical/symtab.py @@ -0,0 +1,50 @@ +# encoding: UTF-8 + +from sixtypical.model import ( + TYPE_BIT, TYPE_BYTE, LocationRef, +) + + +class SymEntry(object): + def __init__(self, ast_node, type_): + self.ast_node = ast_node + self.type_ = type_ + + def __repr__(self): + return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_) + + +class SymbolTable(object): + def __init__(self): + self.symbols = {} # symbol name -> SymEntry + self.locals = {} # routine name -> (symbol name -> SymEntry) + self.typedefs = {} # type name -> Type AST + self.consts = {} # const name -> ConstantRef + + for name in ('a', 'x', 'y'): + self.symbols[name] = SymEntry(None, TYPE_BYTE) + for name in ('c', 'z', 'n', 'v'): + self.symbols[name] = SymEntry(None, TYPE_BIT) + + def __str__(self): + return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts) + + def has_local(self, routine_name, name): + return name in self.locals.get(routine_name, {}) + + def fetch_global_type(self, name): + return self.symbols[name].type_ + + def fetch_local_type(self, routine_name, name): + return self.locals[routine_name][name].type_ + + def fetch_global_ref(self, name): + if name in self.symbols: + return LocationRef(name) + return None + + def fetch_local_ref(self, routine_name, name): + routine_locals = self.locals.get(routine_name, {}) + if name in routine_locals: + return LocationRef(name) + return None diff --git a/test.sh b/test.sh index c177d50..a19ec97 100755 --- a/test.sh +++ b/test.sh @@ -1,6 +1,10 @@ #!/bin/sh +# This currently represents a lot of tests! If you only want to run a subset, +# it's probably best to run `falderal` manually on the file(s) you want to test. + falderal --substring-error \ + "tests/appliances/sixtypical.md" \ "tests/SixtyPical Syntax.md" \ "tests/SixtyPical Analysis.md" \ "tests/SixtyPical Storage.md" \ diff --git a/tests/SixtyPical Analysis.md b/tests/SixtyPical Analysis.md index 9af05e6..ffa89a8 100644 --- a/tests/SixtyPical Analysis.md +++ b/tests/SixtyPical Analysis.md @@ -10,9 +10,6 @@ For control flow, see [SixtyPical Control Flow](SixtyPical%20Control%20Flow.md). [Falderal]: http://catseye.tc/node/Falderal - -> Functionality "Analyze SixtyPical program" is implemented by - -> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok" - -> Tests for functionality "Analyze SixtyPical program" ### add ### @@ -754,6 +751,66 @@ A `goto` cannot appear within a `with interrupts` block. | } ? IllegalJumpError +A `call` cannot appear within a `with interrupts` block. + + | vector routine + | inputs x + | outputs x + | trashes z, n + | bar + | + | define foo routine + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | define other routine + | trashes bar, a, n, z + | { + | ld a, 0 + | } + | + | define main routine + | trashes bar, a, n, z + | { + | with interrupts off { + | copy foo, bar + | call other + | } + | } + ? IllegalJumpError + +A `with interrupts` block cannot appear within a `with interrupts` block. + + | vector routine + | inputs x + | outputs x + | trashes z, n + | bar + | + | define foo routine + | inputs x + | outputs x + | trashes z, n + | { + | inc x + | } + | + | define main routine + | trashes bar, a, n, z + | { + | with interrupts off { + | copy foo, bar + | with interrupts off { + | copy foo, bar + | } + | } + | } + ? IllegalJumpError + ### typedef ### A typedef is a more-readable alias for a type. "Alias" means diff --git a/tests/SixtyPical Compilation.md b/tests/SixtyPical Compilation.md index ade5ec5..687d57e 100644 --- a/tests/SixtyPical Compilation.md +++ b/tests/SixtyPical Compilation.md @@ -6,9 +6,6 @@ SixtyPical to 6502 machine code. [Falderal]: http://catseye.tc/node/Falderal - -> Functionality "Compile SixtyPical program" is implemented by - -> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter Tests for functionality "Compile SixtyPical program" Null program. diff --git a/tests/SixtyPical Control Flow.md b/tests/SixtyPical Control Flow.md index 3be659f..5572529 100644 --- a/tests/SixtyPical Control Flow.md +++ b/tests/SixtyPical Control Flow.md @@ -704,6 +704,82 @@ initialized at the start of that loop. | } ? UnmeaningfulReadError: y +And in particular, you can't uninitialize the loop variable, in the loop. + + | define foo routine + | trashes x + | { + | } + | + | define main routine + | outputs x, y, n, z + | trashes c + | { + | ld x, 0 + | ld y, 15 + | for x up to 15 { + | inc y + | call foo + | } + | } + ? ForbiddenWriteError: x + +So, if you call a routine from inside the loop, it better not also +loop on the same variable. + + | define foo routine + | inputs y + | outputs x, y, n, z + | trashes c + | { + | ld x, 0 + | for x up to 15 { + | inc y + | } + | } + | + | define main routine + | outputs x, y, n, z + | trashes c + | { + | ld x, 0 + | ld y, 15 + | for x up to 15 { + | inc y + | call foo + | } + | } + ? ForbiddenWriteError: x + +But if you take care to save and restore the loop variable in the +called routine, it will be okay. + + | define foo routine + | inputs y + | outputs y, n, z + | trashes a, c + | { + | save x { + | ld x, 0 + | for x up to 15 { + | inc y + | } + | } + | } + | + | define main routine + | outputs x, y, n, z + | trashes a, c + | { + | ld x, 0 + | ld y, 15 + | for x up to 15 { + | inc y + | call foo + | } + | } + = ok + The "for" loop does not preserve the `z` or `n` registers. | define foo routine trashes x { diff --git a/tests/SixtyPical Fallthru.md b/tests/SixtyPical Fallthru.md index 75e3779..7f95573 100644 --- a/tests/SixtyPical Fallthru.md +++ b/tests/SixtyPical Fallthru.md @@ -61,12 +61,6 @@ to pass these tests to be considered an implementation of SixtyPical. [Falderal]: http://catseye.tc/node/Falderal - -> Functionality "Dump fallthru info for SixtyPical program" is implemented by - -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)" - - -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by - -> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter Tests for functionality "Dump fallthru info for SixtyPical program" A single routine, obviously, falls through to nothing and has nothing fall diff --git a/tests/SixtyPical Storage.md b/tests/SixtyPical Storage.md index 573551a..5222fc8 100644 --- a/tests/SixtyPical Storage.md +++ b/tests/SixtyPical Storage.md @@ -1589,6 +1589,68 @@ The offset in `reset` may not exceed the table's size. | } ? RangeExceededError +### dynamic recurrence of `point ... into` blocks + +You cannot call a routine which trashes the pointer from inside a +`point ... into` block. Remember that `point ... into` by +itself doesn't change anything, so doesn't trash anything. + + | byte table[256] tab + | byte table[256] other + | pointer ptr + | + | define sub routine + | inputs other + | outputs other + | { + | point ptr into other { + | } + | } + | + | define main routine + | inputs tab, other + | outputs tab, other + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | call sub + | copy 123, [ptr] + y + | } + | } + = ok + + | byte table[256] tab + | byte table[256] other + | pointer ptr + | + | define sub routine + | inputs other + | outputs other + | trashes ptr + | { + | point ptr into other { + | reset ptr 0 + | } + | } + | + | define main routine + | inputs tab, other + | outputs tab, other + | trashes a, y, c, z, n, v, ptr + | { + | ld y, 0 + | point ptr into tab { + | reset ptr 0 + | copy 123, [ptr] + y + | call sub + | copy 123, [ptr] + y + | } + | } + ? UnmeaningfulReadError + ### locals ### When memory locations are defined static to a routine, they cannot be diff --git a/tests/SixtyPical Syntax.md b/tests/SixtyPical Syntax.md index c4458ea..f830394 100644 --- a/tests/SixtyPical Syntax.md +++ b/tests/SixtyPical Syntax.md @@ -9,9 +9,6 @@ but not necessarily sensible programs. [Falderal]: http://catseye.tc/node/Falderal - -> Functionality "Check syntax of SixtyPical program" is implemented by - -> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok" - -> Tests for functionality "Check syntax of SixtyPical program" Rudimentary program. diff --git a/tests/appliances/sixtypical.md b/tests/appliances/sixtypical.md new file mode 100644 index 0000000..1ee2bc4 --- /dev/null +++ b/tests/appliances/sixtypical.md @@ -0,0 +1,20 @@ +This file contains only the [Falderal][] directives that define the different +functionalities tested by the test suite, assuming that it's the reference +implementation, `sixtypical`, that is going to implement these functionalities. + +[Falderal]: http://catseye.tc/node/Falderal + + -> Functionality "Check syntax of SixtyPical program" is implemented by + -> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok" + + -> Functionality "Analyze SixtyPical program" is implemented by + -> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok" + + -> Functionality "Compile SixtyPical program" is implemented by + -> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter Functionality "Dump fallthru info for SixtyPical program" is implemented by + -> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)" + + -> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by + -> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter