1
0
mirror of https://github.com/catseye/SixtyPical.git synced 2024-06-14 23:29:29 +00:00

Compare commits

...

204 Commits
0.15 ... master

Author SHA1 Message Date
Chris Pressey
df610e639c Fix link in README. 2024-03-16 11:53:54 +00:00
Chris Pressey
2bddea270f Remove old, out-of-sync license information. 2024-02-02 12:18:46 +00:00
Chris Pressey
5f8802b305 Arrange license info in repo to follow REUSE 3.0 convention. 2024-02-02 12:10:23 +00:00
Chris Pressey
5ecc4e6f56 Import "Future directions for SixtyPical" into doc subdirectory. 2023-12-10 12:16:14 +00:00
Chris Pressey
d5e6eeacd3
Merge pull request #23 from catseye/develop-2023-1
Develop 2023 1
2023-03-09 09:58:21 +00:00
Chris Pressey
880d1f53a1 Update HISTORY for this minor revision. 2023-03-09 09:20:08 +00:00
Chris Pressey
1887ad9377 Update .gitignore. 2023-03-08 13:02:11 +00:00
Chris Pressey
b670bd3316 Remove experimental Circle CI configuration. 2023-03-08 12:53:21 +00:00
Chris Pressey
9b74fd358f Add sixtypical-py2.7.md appliance and have tests pass under 2.7. 2023-03-08 12:50:22 +00:00
Chris Pressey
b38b94ed67 sixtypical and dcc6502-adapter run under Python 3 by default. 2023-03-07 22:17:11 +00:00
Chris Pressey
075ba9ff2a Stylistic improvements to README. 2020-10-19 16:13:11 +01:00
Chris Pressey
918b400fd4 Update README to link to more related information. 2020-10-19 15:07:44 +01:00
Chris Pressey
4b539930bd
Merge pull request #22 from catseye/develop-0.21
Develop 0.21
2019-10-25 12:51:24 +01:00
Chris Pressey
632c2f4517 Correct comment. 2019-10-23 15:00:03 +01:00
Chris Pressey
ff67e15208 Link to new test suite from README 2019-10-23 10:34:37 +01:00
Chris Pressey
023a415a14 Don't search for filenames given on cmdline in the include path. 2019-10-23 10:32:26 +01:00
Chris Pressey
a9917d3ea8 Tweak. 2019-10-23 10:23:43 +01:00
Chris Pressey
f4577804c7 A little reshuffling and improving of READMEs and notes. 2019-10-23 10:21:02 +01:00
Chris Pressey
fbfab44887 Update the errorful rudiments. 2019-10-22 16:56:19 +01:00
Chris Pressey
4b518508d5 called_routines are not stored in context at all. 2019-10-22 16:26:50 +01:00
Chris Pressey
1b5f4c0c4b Add architecture-dependent "joy2delta.60p" to "standard library". 2019-10-22 16:09:58 +01:00
Chris Pressey
dc4677ade8 Update READMEs. 2019-10-22 15:30:24 +01:00
Chris Pressey
d408e740ed Establish an include directory and --include-path option. 2019-10-22 15:10:55 +01:00
Chris Pressey
c70e4a55e4 Merge included programs. 2019-10-22 13:26:21 +01:00
Chris Pressey
175c07781a First cut at include files. 2019-10-22 12:54:04 +01:00
Chris Pressey
987974cc21 Simplify, fixing an apparent bug in the process. 2019-10-22 12:42:17 +01:00
Chris Pressey
565f959ee2 Our CINV interrupt service routine should be preserved. 2019-10-22 12:35:29 +01:00
Chris Pressey
92b1cfeefb Implement tail-call optimization. 2019-10-22 12:23:15 +01:00
Chris Pressey
78a1f2910c Simplify and improve Emitter abstraction. 2019-10-22 11:55:21 +01:00
Chris Pressey
44d97c33a2 Better generation of code at tail of routines. 2019-10-22 11:37:00 +01:00
Chris Pressey
b9df1482c6 Pass next routine to each routine being compiled. 2019-10-22 10:32:14 +01:00
Chris Pressey
ea788264f7 Make test as intended. It fails atm. Simplify driver code. 2019-10-22 10:18:56 +01:00
Chris Pressey
3d88226058 Implement --prune-unreachable-routines. 2019-10-22 09:41:30 +01:00
Chris Pressey
1df6941b01 Callgraph uses reachability. 2019-10-22 09:07:16 +01:00
Chris Pressey
1098347fa5 Routines can be declared preserved. 2019-10-21 21:45:59 +01:00
Chris Pressey
2192a48e0e Update HISTORY. 2019-10-21 21:37:30 +01:00
Chris Pressey
a84cd4de8c Expand on the callgraph tests. 2019-10-21 21:35:28 +01:00
Chris Pressey
40d9c57d64 Fix test, dump format. 2019-10-21 21:23:14 +01:00
Chris Pressey
684256f7e9 Start of tests for this. 2019-10-21 17:29:38 +01:00
Chris Pressey
b09d0c0b76 Routines that are goto'd are also in the call graph. 2019-10-21 17:15:28 +01:00
Chris Pressey
182935a088 First cut at finding all routines that could be assigned to vector. 2019-10-21 16:12:06 +01:00
Chris Pressey
7187fa6285 Refactor: move to dedicated module. 2019-10-21 15:41:17 +01:00
Chris Pressey
bcc256aa5d Checkpoint. 2019-10-21 15:07:54 +01:00
Chris Pressey
2e002fc33e Merge branch 'circle-ci' of https://github.com/catseye/SixtyPical into construct-callgraph 2019-10-21 14:48:27 +01:00
Chris Pressey
894fb1a0f2 Refine the callgraph algorithm. Still incomplete though. 2019-10-21 14:43:21 +01:00
Chris Pressey
58dc68f838 Merge branch 'develop-0.21' into construct-callgraph 2019-10-21 14:08:06 +01:00
Chris Pressey
a966a496d0 Bump version number. 2019-10-21 14:07:41 +01:00
Chris Pressey
87a2f70092 First cut at constructing a call graph. 2019-10-21 14:03:35 +01:00
Chris Pressey
5912bf2684 Fix dcc6502-adapter for latest version of tcarmelveilleux/dcc6502. 2019-10-21 13:34:53 +01:00
Chris Pressey
0ef0dc1628 dcc6502-adapter fixes. 2019-07-16 16:26:16 +01:00
Chris Pressey
775af38960 Change config. 2019-07-16 16:23:30 +01:00
Chris Pressey
952e3528a3 Try this. 2019-07-16 16:22:29 +01:00
Chris Pressey
8467cd947e
Merge pull request #21 from catseye/develop-0.20
Develop 0.20
2019-05-14 16:25:27 +01:00
Chris Pressey
2e2e80664e Forbid nested with interrupts blocks, and more refactoring. 2019-05-14 15:01:10 +01:00
Chris Pressey
3f666f4385 Add reset instruction; much refactoring. 2019-05-14 08:56:35 +01:00
Chris Pressey
dd29b6fd4a Implement local locations that aren't statically initialized. 2019-05-13 12:32:18 +01:00
Chris Pressey
81e28fa757 Jot down some TODO items before I forget them. 2019-04-18 09:16:23 +01:00
Chris Pressey
3d14b6ee6c Fix typo. 2019-04-16 13:59:27 +01:00
Chris Pressey
1c7efb019d
Merge pull request #20 from catseye/develop-0.19
Develop 0.19
2019-04-16 12:35:59 +01:00
Chris Pressey
0c65954bc5 Updates to README. Fix awkward punctuation in usage message. 2019-04-16 11:37:46 +01:00
Chris Pressey
a10f1c6528 Replace --run option with terser --run-on option. 2019-04-16 10:35:59 +01:00
Chris Pressey
4d82e2352e Merge branch 'develop-0.19' into reform-loadngo 2019-04-15 17:39:09 +01:00
Chris Pressey
c246424930 Fix bug raising InconsistentExitError, and type-bug in ribos2.60p. 2019-04-15 17:35:17 +01:00
Chris Pressey
6d867867fe Remove the "local" loadngo script from here as well. 2019-04-15 13:16:07 +01:00
Chris Pressey
a44b007ff0 Declare that --run replaces loadngo.sh, and remove the latter. 2019-04-15 13:11:43 +01:00
Chris Pressey
ce8e83908b First cut at a --run option for sixtypical, replacing loadngo.sh. 2019-04-11 16:53:43 +01:00
Chris Pressey
04a9438898 The VICE emulators just keep going if they can't find the vicerc. 2019-04-10 16:53:01 +01:00
Chris Pressey
c906ab7817 A few further small edits to README. 2019-04-10 12:29:59 +01:00
Chris Pressey
1ca5cb0336 You could argue that it's not *that* low level, so, okay. 2019-04-10 12:23:50 +01:00
Chris Pressey
7854f71706 More edits to docs. 2019-04-10 12:02:35 +01:00
Chris Pressey
652ab1dc5c Clean up ArgumentParser usage message, add --version argument. 2019-04-10 10:42:50 +01:00
Chris Pressey
21a187a105 Update docs. 2019-04-10 09:29:45 +01:00
Chris Pressey
8d6e5e090d The Type and Ref class hierarchies are now namedtuples. 2019-04-10 08:50:13 +01:00
Chris Pressey
a0328b8840 Store type information in SymbolTable shared across phases. 2019-04-10 08:48:33 +01:00
Chris Pressey
394fbddad6 Refactor to avoid storing LocationRefs in SymEntry. 2019-04-09 09:42:22 +01:00
Chris Pressey
4615d8d054 Distinct AST nodes for call and goto instructions. 2019-04-08 16:26:51 +01:00
Chris Pressey
bd462d6d8b Include more info in --dump-exit-contexts. 2019-04-08 12:23:44 +01:00
Chris Pressey
b19267d3ba Checkpoint import of changes for version 0.19. 2019-04-08 11:50:54 +01:00
Chris Pressey
c7e1b69845
Merge pull request #19 from catseye/develop-0.18
Develop 0.18
2019-01-04 11:21:37 +00:00
Chris Pressey
49bb5b578a Extract utility routine to "support/stdlib.60p" source. 2018-12-25 18:10:58 +00:00
Chris Pressey
f2570729a4 Final (for now) cleanup of the example sources. 2018-12-12 15:27:57 +00:00
Chris Pressey
75449d7271
Merge pull request #18 from catseye/more-modes-on-add-and-sub
More modes on add and sub
2018-12-12 15:13:26 +00:00
Chris Pressey
cfca03ed7e
Merge pull request #17 from catseye/inconsistent-initialization
Drop check for inconsistent initialization
2018-12-12 15:11:57 +00:00
Chris Pressey
35a1053439 Support more modes on add and sub. 2018-12-12 10:34:25 +00:00
Chris Pressey
2803aa2d05 Add failing tests for add and sub on locations other than a. 2018-12-12 10:11:16 +00:00
Chris Pressey
69df175230 Merge branch 'develop-0.18' of https://github.com/catseye/SixtyPical into inconsistent-initialization 2018-12-12 09:58:26 +00:00
Chris Pressey
0ec8970c76 Expand example with code that will likely become library support. 2018-12-12 09:27:25 +00:00
Chris Pressey
03a682ff08 Make word-table print YY. 2018-12-12 09:13:13 +00:00
Chris Pressey
d1a29709f2 Add example test program for cmp-against-literal-word. Fix it. 2018-12-12 09:09:45 +00:00
Chris Pressey
97e6e619ff cmp can compare against a literal word. 2018-12-12 09:01:46 +00:00
Chris Pressey
538365f1e1 Add one more test case, to demonstrate that it's not just output. 2018-12-11 22:42:59 +00:00
Chris Pressey
547b7c960a Restore the debugging (which should be rethought, anyway). 2018-12-11 22:40:23 +00:00
Chris Pressey
d13c6a94a2 Drop the check for "consistent initialization" inside if blocks. 2018-12-11 19:27:40 +00:00
Chris Pressey
d86612acce Note what we've done with the example programs. 2018-12-11 19:11:03 +00:00
Chris Pressey
c85cb6722a Add local load'n'go script. Fill in what each is expected to write. 2018-12-11 18:14:36 +00:00
Chris Pressey
d3f730cc76 Move towards greater platform-agnosticism in these examples. 2018-12-11 17:55:08 +00:00
Chris Pressey
3a4c2e46c1 inc and dec on a known range usually keeps it known, now. 2018-12-11 17:35:01 +00:00
Chris Pressey
5d01820bb1 Small reorganization of example programs. 2018-12-11 17:17:15 +00:00
Chris Pressey
70ba40bf3e
Merge pull request #16 from catseye/goto-at-end-of-block
goto only at end of block, not only in tail position
2018-12-11 16:51:44 +00:00
Chris Pressey
bf3f1835ed Merge branch 'develop-0.18' of https://github.com/catseye/SixtyPical into goto-at-end-of-block 2018-11-29 17:47:48 +00:00
Chris Pressey
f99cf47661
Merge pull request #15 from catseye/16-bit-compare
16-bit compare instruction
2018-11-29 17:46:48 +00:00
Chris Pressey
d598b505e0 Add more tests. 2018-11-27 17:40:25 +00:00
Chris Pressey
3814d2624d Improve tests. 2018-11-27 17:26:58 +00:00
Chris Pressey
d1befe7123 All tests pass again -- but this needs lots more tests please. 2018-11-27 17:11:37 +00:00
Chris Pressey
07ec6752ee Introduce yet another new error. 2018-11-27 17:09:38 +00:00
Chris Pressey
a53a3529ea Better name for this error. 2018-11-27 17:06:07 +00:00
Chris Pressey
21e623f3ad Collect exit contexts. Disturbs fallthru analysis, but otherwise? 2018-11-27 16:59:16 +00:00
Chris Pressey
0ca545c89a Begin working out what happens when you encounter a goto. 2018-11-27 15:44:39 +00:00
Chris Pressey
27aca3dd86 Looks like, when we "goto", we should "pull in" the constraints. 2018-11-27 15:10:18 +00:00
Chris Pressey
0c481845e9 Add failing test. 2018-11-27 14:52:59 +00:00
Chris Pressey
6dbce205d1 goto is no longer restricted to appearing in tail position. 2018-11-27 14:25:43 +00:00
Chris Pressey
9364a5fbec Small step towards the goal of this branch. 2018-11-27 13:56:32 +00:00
Chris Pressey
d90ac92a33 Merge branch 'develop-0.18' into goto-at-end-of-block 2018-11-27 13:55:36 +00:00
Chris Pressey
9e9284bee5 goto may only occur at the end of a block. 2018-11-27 13:43:36 +00:00
Chris Pressey
94ee042a1e Fix order of operands in word-sized cmp. 2018-11-27 13:12:55 +00:00
Chris Pressey
046e43cd66 Add another example. Even more convincing that the order is wrong. 2018-11-27 13:05:19 +00:00
Chris Pressey
2d767e9fe7 Add example program that suggests we've got the arguments backwards. 2018-11-23 23:11:06 +00:00
Chris Pressey
dcc944d732 If add and sub can work on words, then cmp can work on words too. 2018-11-23 23:00:41 +00:00
Chris Pressey
a765a50c1e Notes on compare. 2018-11-23 22:23:41 +00:00
Chris Pressey
0bc79d20d6 Merge branch 'develop-0.18' into 16-bit-compare 2018-11-23 22:17:05 +00:00
Chris Pressey
f860db055c Note what's on this development branch so far. 2018-11-23 22:16:21 +00:00
Chris Pressey
e74c7f2b31 Initial attempt at 16-bit compare. Not super well tested yet. 2018-11-23 22:09:28 +00:00
Chris Pressey
0429e4bd90 Make lexer greatly less inefficient on large source files. 2018-11-20 13:14:45 +00:00
Chris Pressey
3cd28bdb3e
Merge pull request #14 from catseye/develop-0.17
Develop 0.17
2018-11-16 11:48:24 +00:00
Chris Pressey
23936333ec These notes have been incorporated in the loadngo.sh script. 2018-11-16 11:41:41 +00:00
Chris Pressey
92db4ac3f0 More tests for "for" loops. 2018-09-16 21:30:53 +01:00
Chris Pressey
dd86015cd5 Add another test for for. There's another one I want to add too. 2018-09-16 18:45:09 +01:00
Chris Pressey
6e9b15e017 Small documentation updates. 2018-09-14 13:14:32 +01:00
Chris Pressey
7680f09850 More conversion. I think that's the last of it. 2018-09-09 15:31:35 +01:00
Chris Pressey
7d32277e2c More conversion. 2018-09-09 15:03:43 +01:00
Chris Pressey
5549b8379f More conversion. 2018-09-09 14:01:38 +01:00
Chris Pressey
f81757fd76 Partial conversion of example programs. 2018-09-07 23:00:29 +01:00
Chris Pressey
38119dbe29 Convert all tests to new syntax. 2018-09-07 22:55:54 +01:00
Chris Pressey
5e692446b8 Convert remainder of routine declarations in this file. 2018-09-07 22:49:39 +01:00
Chris Pressey
da44e1b22e Many, many define main routine, define foo routine, etc. 2018-09-07 18:49:39 +01:00
Chris Pressey
0c63de9351 Merge branch 'develop-0.17' into remove-legacy-syntax 2018-09-07 17:49:03 +01:00
Chris Pressey
ff54a568a8 Update TODO 2018-09-07 17:47:59 +01:00
Chris Pressey
505fcb7b92 game_state_routines pass through player_died; saves a few bytes. 2018-09-07 17:41:40 +01:00
Chris Pressey
443c3186c2 Change global instead of returning with carry set. 2018-09-07 17:38:56 +01:00
Chris Pressey
fee36294db Update TODO and HISTORY. 2018-09-07 15:35:42 +01:00
Chris Pressey
684b26dd5c locexpr() always returns a ForwardReference if it can't lookup it. 2018-09-07 15:03:41 +01:00
Chris Pressey
07ef1e243a backpatch_constraint_labels can resolve ForwardReferences. 2018-09-07 14:51:16 +01:00
Chris Pressey
29a5bba85c Distinct symbol resolution phase (as a method on parser.) 2018-09-07 14:27:49 +01:00
Chris Pressey
cc98e023c4 Simply always produce ForwardReferences in locexpr(forward=True). 2018-09-07 14:02:40 +01:00
Chris Pressey
d70092c373 Even more general. 2018-09-07 13:42:18 +01:00
Chris Pressey
af0e1b41c7 Forward-reference resolution becomes increasingly general. 2018-09-07 13:40:41 +01:00
Chris Pressey
26a2fb448c Generalize forward reference resolution a smidge. 2018-09-07 13:27:18 +01:00
Chris Pressey
33d5093e5a Resolve forward references more explicitly. 2018-09-07 13:20:18 +01:00
Chris Pressey
7c9f76c007 Removing support for it is one thing, updating all tests another. 2018-09-07 13:03:57 +01:00
Chris Pressey
5bad7ff576 Another conversion away from self.context.fetch to self.declare. 2018-09-07 12:43:55 +01:00
Chris Pressey
c73590f88c Begin refactoring how the ParsingContext is used by the Parser. 2018-09-07 12:24:07 +01:00
Chris Pressey
e7674c44ce Confirm constraint on saveing a. 2018-09-07 11:33:24 +01:00
Chris Pressey
19461bc205 Support save X, Y, Z {} as a shortcut syntax for nested saves. 2018-09-07 11:27:42 +01:00
Chris Pressey
b63bcfcd2b Begin introducing shortcut syntax for nested saves. 2018-09-07 09:36:51 +01:00
Chris Pressey
0593ce6a66 Move merge_programs() out of main. Handle missing -o options. 2018-09-07 09:26:11 +01:00
Chris Pressey
8ce787a797 Inheritance hierarchy; binding is a little less awful. 2018-09-07 09:10:20 +01:00
Chris Pressey
0b6a66fdbb pyflakes. 2018-09-06 18:26:30 +01:00
Chris Pressey
99a0d5624a Awful binding, but at least this code isn't in main anymore. 2018-09-06 18:25:29 +01:00
Chris Pressey
24f093b1db Make all serialize() methods take addr() as an arg, not kwarg. 2018-09-06 18:11:47 +01:00
Chris Pressey
0a91d6bbc0 Rename method for more distinction/clarity. 2018-09-06 17:21:43 +01:00
Chris Pressey
8efa73f79d Explicitly sort the chains by their content, for stable sort. 2018-09-06 17:15:10 +01:00
Chris Pressey
10062fe2fb Fix whitespace (but not sorting) issue in --dump-fallthru-info. 2018-09-06 16:32:48 +01:00
Chris Pressey
4bba75857f --output cmdline argument, serialize() returns an array of bytes. 2018-09-06 16:18:41 +01:00
Chris Pressey
3fd7e52bc7 Reduce number of errors under Python 3 another smidge. 2018-09-06 14:21:29 +01:00
Chris Pressey
3c1564ea09 Abort loadngo apple2, if the SixtyPical program couldn't compile. 2018-09-06 12:50:47 +01:00
Chris Pressey
c11869f322 Not making any promises, but reduce the errors under Python 3. 2018-09-05 21:45:58 +01:00
Chris Pressey
fca00ff127 Rewrite the intro blurb in the README. 2018-09-05 21:36:18 +01:00
Chris Pressey
3e11d7122c Add apple2 target to loadngo.sh, apple2 example program. 2018-09-05 15:20:44 +01:00
Chris Pressey
3675a186a1 Add notes for Apple II target. 2018-09-05 12:59:38 +01:00
Chris Pressey
51b98df829 Split TODO off into own file. 2018-08-26 14:35:28 +01:00
Chris Pressey
8bba28cde2 Use relative, ?raw=true URL for embedded image. 2018-08-26 14:32:19 +01:00
Chris Pressey
642ca138a3
Merge pull request #13 from catseye/develop-0.16
Develop 0.16
2018-05-08 12:49:36 +01:00
Chris Pressey
a73f6e1d94 Update HISTORY and README. 2018-05-08 12:48:15 +01:00
Chris Pressey
c3a1bdb4cc Use save block (and a for loop) in the flagship demo game source. 2018-05-08 12:39:21 +01:00
Chris Pressey
8091c45a9a Fix test which is misleading due to the way the appliance works. 2018-05-08 12:14:30 +01:00
Chris Pressey
9b1d71a73e Correct some text in the test suite. 2018-05-08 12:00:55 +01:00
Chris Pressey
9536dd011f
Merge pull request #12 from catseye/save-block
Save block
2018-05-08 10:49:14 +01:00
Chris Pressey
eb4ed6a6bc Extract locations from context appropriately. All tests pass. 2018-04-25 16:58:21 +01:00
Chris Pressey
d037401b18 Add three tests. One fails when it shouldn't. 2018-04-25 15:55:36 +01:00
Chris Pressey
647fc33f63 Merge branch 'develop-0.16' of https://github.com/catseye/SixtyPical into save-block 2018-04-25 14:19:36 +01:00
Chris Pressey
2f200dc1ea Use --output-format, not --prelude, in loadngo script. 2018-04-25 13:48:38 +01:00
Chris Pressey
01e3ab00aa Make tests pass. 2018-04-25 13:40:39 +01:00
Chris Pressey
76ec7224cd Add failing tests for gotos inside runtime-context-making blocks. 2018-04-25 13:38:54 +01:00
Chris Pressey
4986454733 Support save'ing user-defined locations. 2018-04-25 13:27:47 +01:00
Chris Pressey
07541d7913 Compile code for saving a, x, or y on the stack. 2018-04-25 13:15:53 +01:00
Chris Pressey
80c46485f1 Saving anything except a trashes a. 2018-04-25 13:06:11 +01:00
Chris Pressey
1410989b2c Merge branch 'develop-0.16' of https://github.com/catseye/SixtyPical into save-block 2018-04-23 13:18:59 +01:00
Chris Pressey
89fa8361a8 Remove --prelude, reform and document --output-formats. 2018-04-23 13:18:01 +01:00
Chris Pressey
8df16d7a2a Another passing test which is a little surprising. 2018-04-20 14:35:41 +01:00
Chris Pressey
0f41857b39 Add two simple tests for save. Surprisingly, they both pass. 2018-04-20 13:36:08 +01:00
Chris Pressey
4d1f9d0f3c First cut at implementing save. Only the most basic tests though. 2018-04-19 13:18:52 +01:00
Chris Pressey
398b67cdcc Yet more TODO notes. 2018-04-19 10:35:21 +01:00
Chris Pressey
8a613febe2 More notes. 2018-04-19 09:54:24 +01:00
Chris Pressey
9adc543cc0 Add TODO note. 2018-04-18 17:59:42 +01:00
Chris Pressey
72fed579d1 Expand on the points in the TODOs. 2018-04-18 16:46:34 +01:00
Chris Pressey
bc91ef1602 Compile byte-table add, sub, cmp, and, or, xor, shl, shr, inc, dec. 2018-04-18 16:32:12 +01:00
Chris Pressey
5fcbfa1d21 Analyze reads and updates of tables. 2018-04-18 15:45:47 +01:00
Chris Pressey
0c22944afc Able to parse, but not yet able to analyze, other table accesses. 2018-04-18 14:21:03 +01:00
Chris Pressey
e565234d28 Support for shl foo and shr foo where foo is a byte storage. 2018-04-18 12:01:53 +01:00
Chris Pressey
b451176f37 (Not possible because there are not Indirect,Y modes for LDX, STX) 2018-04-18 11:27:23 +01:00
Chris Pressey
3c7d95a724 Support for copy [ptra]+y, [ptrb]+y to indirect LDA indirect STA 2018-04-18 10:57:57 +01:00
Chris Pressey
4f28f43bde The existing analysis is actually sufficient. Document why this is. 2018-04-18 10:25:23 +01:00
Chris Pressey
f9c3b56246 Work out routine-vector type compatibility, w/ one failing test. 2018-04-18 10:13:15 +01:00
Chris Pressey
70247e0e44 Generate zero-page code for and, or, and xor, when possible. 2018-04-17 17:58:26 +01:00
99 changed files with 9197 additions and 4352 deletions

1
.gitignore vendored
View File

@ -1,2 +1,3 @@
*.pyc
__pycache__
vicerc

17
.reuse/dep5 Normal file
View File

@ -0,0 +1,17 @@
Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/
Files:
.gitignore
bin/sixtypical
eg/rudiments/errorful/*.60p
eg/rudiments/*.60p
eg/*/.gitignore
eg/*/*.png
eg/*/*.prg
eg/*/*.sh
images/*
src/sixtypical/__init__.py
test.sh
tests/appliances/*
Copyright: Chris Pressey, the original author of this work, has dedicated it to the public domain.
License: Unlicense

View File

@ -1,6 +1,149 @@
History of SixtyPical
=====================
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
0.21-2023.0309
--------------
* Python 3 is the default interpreter for `sixtypical`.
* Test appliance added for testing under Python 2.7.
* `dcc6502` test adapter made runnable under both
Python 2 and Python 3.
0.21
----
* A source file can be included in another source file
by means of the `include` directive.
* A routine can be declared `preserved`, which prevents a
compiler from omitting it from the final executable, even
if it determines it is not called by any other routine.
* The reference implementation constructs a callgraph and
determines the set of routines which are not reachable
(directly or indirectly) from `main`, with an eye to
omitting them from the final executable.
* Added `--prune-unreachable-routines` option, which causes
the compiler to in fact omit routines determined to be
unreachable as described above.
* Added `--include-path` option, which configures the list
of directories that are searched when a source file is
included with the `include` directive.
* Code generation now performs modest peephole optimization
at the end of each routine. This results in better code
generation for constructs in tail position, notably
tail optimization of `calls`, but also for `goto`s and
`if` blocks at the end of a routine.
* Began collecting architecture-specific and portable
include-files for a nascent "standard library".
0.20
----
* A `point ... into` block no longer initializes the pointer
by default. A subequent `reset` instruction must be used
to initialize the pointer. The pointer may be reset to any
valid offset within the table (not only 0) and it may be
reset multiple times inside the block.
* 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 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
----
* A `table` may be defined with more than 256 entries, even
though the conventional index syntax can only refer to the
first 256 entries.
* A `pointer` may point inside values of type `byte table`,
allowing access to entries beyond the 256th.
* `buffer` types have been eliminated from the language,
as the above two improvements allow `byte table`s to
do everything `buffer`s previously did.
* When accessing a table with an index, a constant offset
can also be given.
* Accessing a `table` through a `pointer` must be done in
the context of a `point ... into` block. This allows the
analyzer to check *which* table is being accessed.
* Refactored compiler internals so that type information
is stored in a single symbol table shared by all phases.
* Refactored internal data structures that represent
references and types to be immutable `namedtuple`s.
* Added `--dump-exit-contexts` option to `sixtypical`.
* Added a new `--run-on=<emu>` option to `sixtypical`, which
replaces the old `loadngo.sh` script.
0.18
----
* The "consistent initialization" check inside `if` blocks has
been dropped. If a location is initialized inside one block
but not the other, it is treated as uninitialized afterwards.
* Syntactically, `goto` may only appear at the end of a block.
It need no longer be the final instruction in a routine,
as long as the type context is consistent at every exit.
* When the range of a location is known, `inc` and `dec`
on it will usually shift the known instead of invalidating it.
* `cmp` instruction can now perform a 16-bit unsigned comparison
of `word` memory locations and `word` literals (at the cost of
trashing the `a` register.)
* `add` (resp. `sub`) now support adding (resp. subtracting) a
byte location or a byte literal from a byte location.
* Fixed pathological memory use in the lexical scanner - should
be much less inefficient now when parsing large source files.
* Reorganized the examples in `eg/rudiments/` to make them
officially platform-agnostic and to state the expected output.
0.17
----
* `save X, Y, Z { }` now allowed as a shortcut for nested `save`s.
* If the name in a location expression isn't found in the symbol
table, a forward reference will _always_ be generated; and forward
references in _all_ operations will be resolved after parsing.
* As a consequence, trying to call or goto a non-routine-typed symbol
is now an analysis error, not a syntax error.
* Deprecated `routine foo ...` syntax has been removed.
* Split TODO off into own file.
* `sixtypical` no longer writes the compiled binary to standard
output. The `--output` command-line argument should be given
to get a compiled binary; otherwise only analysis is run.
* Internal cleanups, including a hierarchy of `Outputters`.
* All tests pass when `sixtypical` is run under Python 3.5.2.
0.16
----
* Added `save` block, which allows the named locations to be modified
arbitrarily inside the block, and automatically restored at the end.
* More thorough tests and justifications written for the case of
assigning a routine to a vector with a "wider" type.
* Support for `copy [ptra]+y, [ptrb]+y` to indirect LDA indirect STA.
* Support for `shl foo` and `shr foo` where `foo` is a byte storage.
* Support for `I a, btable + x` where `I` is `add`, `sub`, `cmp`,
`and`, `or`, or `xor`
* Support for `I btable + x` where `I` is `shl`, `shr`, `inc`, `dec`
* `or a, z`, `and a, z`, and `eor a, z` compile to zero-page operations
if the address of z < 256.
* Removed `--prelude` in favour of specifying both format and prelude
with a single option, `--output-format`. Documentation for same.
0.15
----

90
LICENSE
View File

@ -1,90 +0,0 @@
The contents of the SixtyPical distribution are distributed under the
following three licenses.
The documentation and tests (in the `doc` and `tests` directories) is
covered by the following BSD-compatible license, modelled after the
"Report on the Programming Language Haskell 98" license:
-----------------------------------------------------------------------------
Copyright (c)2014-2018 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
any purpose, provided that it is reproduced in its entirety,
including this Notice. Modified versions of this Report may also be
copied and distributed for any purpose, provided that the modified
version is clearly presented as such, and that it does not claim to
be a definition of the SixtyPical Programming Language.
-----------------------------------------------------------------------------
The source code for the reference implementation and supporting tools (in the
`src` subdirectory) is covered under the following BSD-style license:
-----------------------------------------------------------------------------
Copyright (c)2014-2018, Chris Pressey, Cat's Eye Technologies.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright
notices, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notices, this list of conditions, and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
Neither the names of the copyright holders nor the names of their
contributors may be used to endorse or promote products derived
from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.
-----------------------------------------------------------------------------
All example sources in the `eg` directory were written by Chris Pressey,
and are hereby placed in the public domain, as described in the following
UNLICENSE:
-----------------------------------------------------------------------------
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or
distribute this software, either in source code form or as a compiled
binary, for any purpose, commercial or non-commercial, and by any
means.
In jurisdictions that recognize copyright laws, the author or authors
of this software dedicate any and all copyright interest in the
software to the public domain. We make this dedication for the benefit
of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of
relinquishment in perpetuity of all present and future rights to this
software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

View File

@ -0,0 +1,27 @@
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
Redistributions of source code must retain the above copyright
notices, this list of conditions and the following disclaimer.
Redistributions in binary form must reproduce the above copyright
notices, this list of conditions, and the following disclaimer in
the documentation and/or other materials provided with the
distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
POSSIBILITY OF SUCH DAMAGE.

10
LICENSES/Unlicense.txt Normal file
View File

@ -0,0 +1,10 @@
This is free and unencumbered software released into the public domain.
Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means.
In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and
successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
For more information, please refer to <http://unlicense.org/>

196
README.md
View File

@ -1,94 +1,162 @@
SixtyPical
==========
_Version 0.15. Work-in-progress, everything is subject to change._
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
**SixtyPical** is a 6502-like programming language with advanced
static analysis.
_Version 0.21_
| _See also:_ [Bubble Escape 2K](https://codeberg.org/catseye/Bubble-Escape#bubble-escape)
∘ [SITU-SOL](https://git.catseye.tc/SITU-SOL/)
"6502-like" means that it has similar restrictions as programming
in 6502 assembly (e.g. the programmer must choose the registers that
values will be stored in) and is concomitantly easy for a compiler to
translate it to 6502 machine language code.
- - - -
"Advanced static analysis" includes _abstract interpretation_, where we
go through the program step by step, tracking not just the changes that
happen during a _specific_ execution of the program, but _sets_ of changes
that could _possibly_ happen in any run of the program. This lets us
determine that certain things can never happen, which we can then formulate
as safety checks.
_NOTE: Having met the majority of its goals, the SixtyPical project_
_might not undergo much more development going forward. See_
[Future directions for SixtyPical][] _for more information._
In practice, this means it catches things like
- - - -
* you forgot to clear carry before adding something to the accumulator
* a subroutine that you call trashes a register you thought was preserved
* you tried to read or write a byte beyond the end of a byte array
* you tried to write the address of something that was not a routine, to
a jump vector
**SixtyPical** brings advanced static analysis to the [6502][].
and suchlike. It also provides some convenient operations based on
machine-language programming idioms, such as
* copying values from one register to another (via a third register when
there are no underlying instructions that directly support it); this
includes 16-bit values, which are copied in two steps
* explicit tail calls
* indirect subroutine calls
The reference implementation can analyze and compile SixtyPical programs to
6502 machine code.
SixtyPical is a [low-level](#low-level) programming language
supporting some advanced [static analysis](#static-analysis) methods.
Its reference compiler can generate [efficient code](#efficient-code) for
several 6502-based [target platforms](#target-platforms) while catching many
common mistakes at compile-time, reducing the time spent in debugging.
Quick Start
-----------
If you have the [VICE][] emulator installed, from this directory, you can run
Make sure you have Python (2.7 or 3.5+) installed. Then
clone this repository and put its `bin` directory on your
executable search path. Then you can run:
./loadngo.sh c64 eg/c64/hearts.60p
sixtypical
If you have the [VICE][] emulator suite installed, you can run
sixtypical --run-on=x64 eg/c64/hearts.60p
and it will compile the [hearts.60p source code](eg/c64/hearts.60p) and
automatically start it in the `x64` emulator, and you should see:
![Screenshot of result of running hearts.60p](https://raw.github.com/catseye/SixtyPical/master/images/hearts.png)
![Screenshot of result of running hearts.60p](images/hearts.png?raw=true)
You can try the `loadngo.sh` script on other sources in the `eg` directory
You can try `sixtypical --run-on` on other sources in the `eg` directory
tree, which contains more extensive examples, including an entire
game(-like program); see [eg/README.md](eg/README.md) for a listing.
Features
--------
SixtyPical aims to fill this niche:
* You'd use assembly, but you don't want to spend hours
debugging (say) a memory overrun that happened because of a
ridiculous silly error.
* You'd use C or some other "high-level" language, but you don't
want the extra overhead added by the compiler to manage the
stack and registers.
SixtyPical gives the programmer a coding regimen on par with assembly
language in terms of size and hands-on-ness, but also able to catch
many ridiculous silly errors at compile time.
### Low level
Many of SixtyPical's primitive instructions resemble those of the
[MOS Technology 6502][] — it is in fact intended to be compiled to 6502
machine code. However, it also provides some "higher-level" operations
based on common 8-bit machine-language programming idioms, including
* copying values from one register to another (via a third register when
there are no underlying instructions that directly support it)
* copying, adding, and comparing 16-bit values (done in two steps)
* explicit tail calls
* indirect subroutine calls
While a programmer will find these constructs convenient, their
inclusion in the language is primarily to make programs easier to analyze.
### Static analysis
The SixtyPical language defines an [effect system][], and the reference
compiler [abstractly interprets][] the input program in the manner of
[flow typing][] to confirm that it does not violate it. This can detect
common mistakes such as
* you forgot to clear carry before adding something to the accumulator
* a subroutine that you called trashes a register you thought it preserved
* you tried to read or write a byte beyond the end of a byte array
* you tried to write the address of something that was not a routine, to
a jump vector
### Efficient code
Unlike most conventional languages, in SixtyPical the programmer must manage
memory very explicitly, selecting the registers and memory locations to store
each piece of data in. So, unlike a C compiler such as [cc65][], a SixtyPical
compiler doesn't need to generate code to handle [calling conventions][] or
[register allocation][]. This results in smaller (and thus faster) programs.
The flagship demo, a minigame for the Commodore 64, compiles to
a **930**-byte `.PRG` file.
### Target platforms
The reference implementation can analyze and compile SixtyPical programs to
6502 machine code formats which can run on several 6502-based 8-bit architectures:
* [Commodore 64][]
* [Commodore VIC-20][]
* [Atari 2600][]
* [Apple II series][]
For example programs for each of these, see [eg/README.md](eg/README.md).
Specification
-------------
SixtyPical is defined by a specification document, a set of test cases,
and a reference implementation written in Python.
There are over 400 test cases, written in [Falderal][] format for readability.
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 (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)
* [Literate test suite for SixtyPical callgraph construction](tests/SixtyPical%20Callgraph.md)
Documentation
-------------
* [Design Goals](doc/Design%20Goals.md)
* [SixtyPical specification](doc/SixtyPical.md)
* [SixtyPical revision history](HISTORY.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 compilation](tests/SixtyPical%20Compilation.md)
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
* [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
* [TODO](TODO.md)
TODO
----
### Save registers on stack
This preserves them, so that, semantically, they can be used later even though they
are trashed inside the block.
### And at some point...
* `low` and `high` address operators - to turn `word` type into `byte`.
* Tests, and implementation, ensuring a routine can be assigned to a vector of "wider" type
* Related: can we simply view a (small) part of a buffer as a byte table? If not, why not?
* Related: add constant to buffer to get new buffer. (Or to table, but... well, maybe.)
* Check that the buffer being read or written to through pointer, appears in appropriate inputs or outputs set.
(Associate each pointer with the buffer it points into.)
* `static` pointers -- currently not possible because pointers must be zero-page, thus `@`, thus uninitialized.
* Question the value of the "consistent initialization" principle for `if` statement analysis.
* `interrupt` routines -- to indicate that "the supervisor" has stored values on the stack, so we can trash them.
* Add absolute addressing in shl/shr, absolute-indexed for add, sub, etc.
* Automatic tail-call optimization (could be tricky, w/constraints?)
* Possibly `ld x, [ptr] + y`, possibly `st x, [ptr] + y`.
* Maybe even `copy [ptra] + y, [ptrb] + y`, which can be compiled to indirect LDA then indirect STA!
* Optimize `or|and|eor a, z` to zero-page operations if address of z < 256.
[Future directions for SixtyPical]: doc/Future%20directions%20for%20SixtyPical.md
[6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
[MOS Technology 6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
[effect system]: https://en.wikipedia.org/wiki/Effect_system
[abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation
[flow typing]: https://en.wikipedia.org/wiki/Flow-sensitive_typing
[calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
[register allocation]: https://en.wikipedia.org/wiki/Register_allocation
[VICE]: http://vice-emu.sourceforge.net/
[cc65]: https://cc65.github.io/
[Commodore 64]: https://en.wikipedia.org/wiki/Commodore_64
[Commodore VIC-20]: https://en.wikipedia.org/wiki/Commodore_VIC-20
[Atari 2600]: https://en.wikipedia.org/wiki/Atari_2600
[Apple II series]: https://en.wikipedia.org/wiki/Apple_II_series
[Falderal]: https://catseye.tc/node/Falderal
[dcc6502]: https://github.com/tcarmelveilleux/dcc6502

137
TODO.md Normal file
View File

@ -0,0 +1,137 @@
TODO for SixtyPical
===================
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
Language
--------
### Save values to other-than-the-stack
Allow
save a to temp_a {
...
}
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.
### 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.
### 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.
### 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.)
However note, it's okay for a routine to goto itself. It's a common
pattern for implementing a state machine, for a routine to tail-goto a
vector, which might contain the address of the same routine.
The problems only come up, I think, when a routine calls itself re-entrantly.
So the callgraph would need to distinguish between these two 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.
Implementation
--------------
### Filename and line number 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.
### Better selection of options
`-O` should turn on the standard optimizations.
There should maybe be a flag to turn off tail-call optimization.
Some options should automatically add the appropriate architecture include
directory to the path.
Distribution
------------
### Demo game
Seems you're not be able to get killed unless you go off the top or bottom of
the screen? In particular, you cannot collide with a bar?
Blue-skying
-----------
### Pointers associated globally with a table(?)
We have `point into` blocks, but we would also like to sometimes pass a pointer
around to different routines, and have them all "know" what table it operates on.
We could associate every pointer variable with a specific table variable, in its
declaration. This makes some things simple, and would allow us to know what table a
pointer is supposed to point into, even if that pointer was passed into our routine.
One drawback is that it would limit each pointer to be used only on one table. Since a
pointer basically represents a zero-page location, and since those are a relatively scarce
resource, we would prefer if a single pointer could be used to point into different tables
at different times.
These can co-exist with general, non-specific-table-linked `pointer` variables.
If we have local pointers and space optimization for local non-statics, though,
these don't add as much.

View File

@ -1,9 +1,4 @@
#!/usr/bin/env python
"""Usage: sixtypical [OPTIONS] FILES
Analyzes and compiles a Sixtypical program.
"""
#!/usr/bin/env python3
from os.path import realpath, dirname, join
import sys
@ -12,42 +7,33 @@ sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
# ----------------------------------------------------------------- #
import codecs
from argparse import ArgumentParser
import codecs
import json
from pprint import pprint
from subprocess import check_call
import sys
from tempfile import NamedTemporaryFile
import traceback
from sixtypical.parser import Parser, ParsingContext
from sixtypical.symtab import SymbolTable
from sixtypical.parser import Parser, load_program, merge_programs
from sixtypical.analyzer import Analyzer
from sixtypical.emitter import Emitter, Byte, Word
from sixtypical.callgraph import construct_callgraph, prune_unreachable_routines
from sixtypical.outputter import outputter_class_for
from sixtypical.compiler import Compiler
def merge_programs(programs):
"""Assumes that the programs do not have any conflicts."""
from sixtypical.ast import Program
full = Program(1, defns=[], routines=[])
for p in programs:
full.defns.extend(p.defns)
full.routines.extend(p.routines)
return full
def process_input_files(filenames, options):
context = ParsingContext()
symtab = SymbolTable()
include_path = options.include_path.split(':')
programs = []
for filename in options.filenames:
text = open(filename).read()
parser = Parser(context, text, filename)
program = load_program(filename, symtab, include_path, include_file=False)
if options.debug:
print(context)
program = parser.program()
print(symtab)
programs.append(program)
if options.parse_only:
@ -55,85 +41,81 @@ def process_input_files(filenames, options):
program = merge_programs(programs)
analyzer = Analyzer(debug=options.debug)
analyzer.analyze_program(program)
analyzer = Analyzer(symtab, debug=options.debug)
try:
analyzer.analyze_program(program)
finally:
if options.dump_exit_contexts:
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ': ')))
sys.stdout.write("\n")
callgraph = construct_callgraph(program)
if options.dump_callgraph:
sys.stdout.write(json.dumps(callgraph, indent=4, sort_keys=True, separators=(',', ': ')))
if options.prune_unreachable_routines:
program = prune_unreachable_routines(program, callgraph)
compilation_roster = None
if options.optimize_fallthru:
from sixtypical.fallthru import FallthruAnalyzer
def dump(data, label=None):
import json
if not options.dump_fallthru_info:
return
if label:
sys.stdout.write("*** {}:\n".format(label))
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True))
sys.stdout.write("\n")
fa = FallthruAnalyzer(debug=options.debug)
fa = FallthruAnalyzer(symtab, debug=options.debug)
fa.analyze_program(program)
compilation_roster = fa.serialize()
dump(compilation_roster)
if options.dump_fallthru_info:
sys.stdout.write(json.dumps(compilation_roster, indent=4, sort_keys=True, separators=(',', ': ')))
if options.analyze_only:
if options.analyze_only or (options.output is None and not options.run_on):
return
fh = sys.stdout
start_addr = None
if options.origin is not None:
if options.origin.startswith('0x'):
start_addr = int(options.origin, 16)
else:
start_addr = int(options.origin, 10)
if options.origin.startswith('0x'):
start_addr = int(options.origin, 16)
if options.run_on:
fh = NamedTemporaryFile(delete=False)
output_filename = fh.name
Outputter = outputter_class_for({
'x64': 'c64-basic-prg',
'xvic': 'vic20-basic-prg',
'stella': 'atari2600-cart',
}.get(options.run_on))
else:
start_addr = int(options.origin, 10)
fh = open(options.output, 'wb')
output_filename = options.output
Outputter = outputter_class_for(options.output_format)
output_format = options.output_format
prelude = []
if options.prelude == 'c64':
output_format = 'prg'
start_addr = 0x0801
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
elif options.prelude == 'vic20':
output_format = 'prg'
start_addr = 0x1001
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
elif options.prelude == 'atari2600':
output_format = 'crtbb'
start_addr = 0xf000
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
0x00,0x95, 0x00, 0xca, 0xd0, 0xfb]
elif options.prelude:
raise NotImplementedError("Unknown prelude: {}".format(options.prelude))
# If we are outputting a .PRG, we output the load address first.
# We don't use the Emitter for this b/c not part of addr space.
if output_format == 'prg':
fh.write(Word(start_addr).serialize(0))
emitter = Emitter(start_addr)
for byte in prelude:
emitter.emit(Byte(byte))
compiler = Compiler(emitter)
outputter = Outputter(fh, start_addr=start_addr)
outputter.write_prelude()
compiler = Compiler(symtab, outputter.emitter)
compiler.compile_program(program, compilation_roster=compilation_roster)
# If we are outputting a cartridge with boot and BRK address
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
if output_format == 'crtbb':
emitter.pad_to_size(4096 - 4)
emitter.emit(Word(start_addr))
emitter.emit(Word(start_addr))
outputter.write_postlude()
if options.debug:
pprint(emitter.accum)
pprint(outputter.emitter)
else:
emitter.serialize(fh)
outputter.emitter.serialize_to(fh)
fh.close()
if options.run_on:
emu = {
'x64': "x64 -config vicerc",
'xvic': "xvic -config vicerc",
'stella': "stella"
}.get(options.run_on)
if not emu:
raise ValueError("No emulator configured for selected --run-on '{}'".format(options.output_format))
command = "{} {}".format(emu, output_filename)
check_call(command, shell=True)
if __name__ == '__main__':
argparser = ArgumentParser(__doc__.strip())
argparser = ArgumentParser()
argparser.add_argument(
'filenames', metavar='FILENAME', type=str, nargs='+',
@ -141,21 +123,25 @@ if __name__ == '__main__':
)
argparser.add_argument(
"--origin", type=str, default='0xc000',
"--output", "-o", type=str, metavar='FILENAME',
help="File to which generated 6502 code will be written."
)
argparser.add_argument(
"--origin", type=str, default=None,
help="Location in memory where the `main` routine will be "
"located. Default: 0xc000."
"located. Default: depends on output format."
)
argparser.add_argument(
"--output-format", type=str, default='prg',
help="Executable format to produce. Options are: prg, crtbb. "
"Default: prg."
"--output-format", type=str, default='raw',
help="Executable format to produce; also sets a default origin. "
"Options are: raw, prg, c64-basic-prg, vic20-basic-prg, atari2600-cart."
"Default: raw."
)
argparser.add_argument(
"--prelude", type=str,
help="Insert a snippet of code before the compiled program so that "
"it can be booted automatically on a particular platform. "
"Also sets the origin and format. "
"Options are: c64, vic20, atari2600."
"--include-path", "-I", type=str, metavar='PATH', default='.',
help="A colon-separated list of directories in which to look for "
"files which are included during `include` directives."
)
argparser.add_argument(
@ -163,6 +149,12 @@ if __name__ == '__main__':
action="store_true",
help="Only parse and analyze the program; do not compile it."
)
argparser.add_argument(
"--dump-exit-contexts",
action="store_true",
help="Dump a map, in JSON, of the analysis context at each exit of each routine "
"after analyzing the program."
)
argparser.add_argument(
"--optimize-fallthru",
action="store_true",
@ -172,7 +164,18 @@ if __name__ == '__main__':
argparser.add_argument(
"--dump-fallthru-info",
action="store_true",
help="Dump the fallthru map and ordering to stdout after analyzing the program."
help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
)
argparser.add_argument(
"--prune-unreachable-routines",
action="store_true",
help="Omit code for unreachable routines (as determined by the callgraph) "
"from the final output."
)
argparser.add_argument(
"--dump-callgraph",
action="store_true",
help="Dump the call graph, in JSON, to stdout after analyzing the program."
)
argparser.add_argument(
"--parse-only",
@ -184,11 +187,22 @@ if __name__ == '__main__':
action="store_true",
help="Display debugging information when analyzing and compiling."
)
argparser.add_argument(
"--run-on", type=str, default=None,
help="If given, engage 'load-and-go' operation with the given emulator: write "
"the output to a temporary filename using an appropriate --output-format, "
"and boot the emulator with it. Options are: x64, xvic, stella."
)
argparser.add_argument(
"--traceback",
action="store_true",
help="When an error occurs, display a full Python traceback."
)
argparser.add_argument(
"--version",
action="version",
version="%(prog)s 0.21"
)
options, unknown = argparser.parse_known_args(sys.argv[1:])
remainder = ' '.join(unknown)

View File

@ -1,6 +1,12 @@
6502 Opcodes
============
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
As used or unused in SixtyPical.
### ld ###
@ -75,41 +81,41 @@ As used or unused in SixtyPical.
### if ###
if z → BEQ LABEL
if z → BEQ LABEL
if not z → BNE LABEL
if n → BMI LABEL
if not n → BPL LABEL
if not n → BPL LABEL
if c → BCS LABEL
if not c → BCC LABEL
if not c → BCC LABEL
if v → BVS LABEL
if not v → BVC LABEL
if not v → BVC LABEL
### 6502 instructions unsupported ###
ASL Shift Left One Bit (Memory or Accumulator)
LSR Shift Right One Bit (Memory or Accumulator)
ASL Shift Left One Bit (Memory or Accumulator)
LSR Shift Right One Bit (Memory or Accumulator)
BIT Test Bits in Memory with Accumulator
BRK Force Break
BIT Test Bits in Memory with Accumulator
BRK Force Break
CLD Clear Decimal Mode
CLI Clear interrupt Disable Bit
CLV Clear Overflow Flag
CLD Clear Decimal Mode
CLI Clear interrupt Disable Bit
CLV Clear Overflow Flag
NOP No Operation
NOP No Operation
JMP Jump to New Location // but may be generated as part of `if`
JMP Jump to New Location // but may be generated as part of `if`
PHA Push Accumulator on Stack
PHP Push Processor Status on Stack
PLA Pull Accumulator from Stack
PLP Pull Processor Status from Stack
PHA Push Accumulator on Stack
PHP Push Processor Status on Stack
PLA Pull Accumulator from Stack
PLP Pull Processor Status from Stack
RTI Return from Interrupt
RTS Return from Subroutine
RTI Return from Interrupt
RTS Return from Subroutine
SED Set Decimal Mode
SEI Set Interrupt Disable Status
TSX Transfer Stack Pointer to Index X
TXS Transfer Index X to Stack Pointer
SED Set Decimal Mode
SEI Set Interrupt Disable Status
TSX Transfer Stack Pointer to Index X
TXS Transfer Index X to Stack Pointer

View File

@ -1,6 +1,12 @@
Design Goals for SixtyPical
===========================
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
(draft)
The intent of SixtyPical is to have a very low-level language that

View File

@ -0,0 +1,66 @@
Future directions for SixtyPical
================================
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
[SixtyPical](https://codeberg.org/catseye/SixtyPical) has reached a mature
stage of development. There are small features that could be added, but
they are minor compared to the main features
(abstract-interpretation/symbolic-execution/flow-typing-based
static analysis and optimization of low-level 6502 programs).
So the question arises -- what would be next for SixtyPical?
It would be nice to port SixtyPical to work on a ISA other than the 6502 --
the Z80, for example. Or, a more practical choice would be the X86
architecture.
It would also be nice if it could somehow be made to work on regular
assembly language programs. It would still be acceptable if, in this
case, it only worked on programs written in a particular style, to be
compatible with SixtyPical (structured "for" loops, no arbitrary jumps,
and so forth).
It would also be nice to simply generalize the idea: a generic
low-level language with a generic set of registers
(global variables), the rules for which we can specify as part of
the program. The static analyzer then checks the program according to the rules.
It would also be nice to design a more formal theory behind all
this. While SixtyPical works well for what it does, much of it was
"unit-tested into existence" and the theories behind these parts are not made explicit.
In fact, I think we can combine all these ideas. (And, if these are
really all nice to have in a next version of SixtyPical -- we should.)
We can do this by splitting it up into a few different phases:
The first phase is a "frontend" that takes a 6502 assembly language program in a common
6502 assembly language format (perhaps with annotations, or perhaps accompanied by a
configuration file) and translated it into a program description in the generic language,
including the specification of the rules particular to the 6502.
There is then a generic analyzer which checks the program in the
generic language.
If all the checks pass, then our guarantees have been met and the
original assembly language program can simply be assembled, using any
assembler that can understand its format. (This approach is similar
to using a model checker.)
Unlike SixtyPical currently,
which is an optimizing translator, in this method there would be no
optimizations applied. But I think this is an acceptable trade-off.
It is especially acceptable if the assembly language input can be hand-optimized
and still be checked by the analyzer, but this would be trickier to accomplish.
Then, to support other architectures, one could define a similar "frontend"
which works on Z80 assembly code, or X86 assembly, or what have you.
In practice, it would probably be easiest to start with a
"frontend" which converts 6502 assembly to the existing SixtyPical
language. Then design and implement the generic language.
Then re-target the frontend to the generic language.

71
doc/Output Formats.md Normal file
View File

@ -0,0 +1,71 @@
Output Formats
==============
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
`sixtypical` can generate an output file in a number of formats.
### `raw`
The file contains only the emitted bytes of the compiled SixtyPical
program.
The default origin is $0000; you will likely want to override this.
Note that the origin is not stored in the output file in this format;
that information must be recorded separately.
### `prg`
The first two bytes of the file contain the origin address in
little-endian format. The remainder of the file is the emitted bytes
of the compiled SixtyPical program, starting at that origin.
The default origin is $C000; you will likely want override this.
This format coincides with Commodore's PRG format for disk files,
thus its name.
### `c64-basic-prg`
The first few bytes of the file contain a short Commodore 2.0 BASIC
program. Directly after this is the emitted bytes of the compiled
SixtyPical program. The BASIC program contains a `SYS` to that code.
The default origin is $0801; it is unlikely that you will want to
override this.
This format allows the PRG file to be loaded and run on a Commodore 64
with
LOAD"FOO.PRG",8:RUN
### `vic20-basic-prg`
Exactly like `--c64-basic-prg` except intended for the Commodore VIC-20.
The default origin is $1001; it is unlikely that you will want to
override this.
This format allows the PRG file to be loaded and run on a VIC-20 with
LOAD"FOO.PRG",8:RUN
### `atari2600-cart`
The file starts with a short machine-language prelude which is intended
to initialize an Atari 2600 system, followed by the emitted bytes of the
compiled SixtyPical program.
The file is padded to 4096 bytes in length. The padding is mostly
zeroes, except for the final 4 bytes of the file, which consist of
two addresses in little-endian format; both are the origin address.
The default origin is $F000; it is unlikely you will want to
override this.
This is the format used by Atari 2600 cartridges.

View File

@ -1,7 +1,13 @@
SixtyPical
==========
This document describes the SixtyPical programming language version 0.15,
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
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.)
@ -12,34 +18,55 @@ are even more normative.
Refer to the bottom of this document for an EBNF grammar of the syntax of
the language.
Types
-----
Data Model
----------
There are five *primitive types* in SixtyPical:
SixtyPical defines a data model where every value has some type
information associated with it. The values include those that are
directly manipulable by a SixtyPical program, but are not limited to them.
Type information includes not only what kind of structure the data has,
but other properties as well (sometimes called "type annotations".)
### Basic types ###
SixtyPical defines a handful of basic types. There are three types that
are "primitive" in that they are not parameterized in any way:
* bit (2 possible values)
* byte (256 possible values)
* word (65536 possible values)
* routine (code stored somewhere in memory, read-only)
* pointer (address of a byte in a buffer)
There are also three *type constructors*:
Types can also be parameterized and constructed from other types
(which is a kind of parameterization). One such type constructor is
* T table[N] (N entries, 1 ≤ N ≤ 256; each entry holds a value
of type T, where T is `byte`, `word`, or `vector`)
* buffer[N] (N entries; each entry is a byte; 1 ≤ N ≤ 65536)
* pointer (16-bit address of a byte inside a byte table)
* vector T (address of a value of type T; T must be a routine type)
### User-defined ###
Values of the above-listed types are directly manipulable by a SixtyPical
program. Other types describe values which can only be indirectly
manipulated by a program:
* routine (code stored somewhere in memory, read-only)
* T table[N] (series of 1 ≤ N ≤ 65536 values of type T)
There are some restrictions here; for example, a table may only
consist of `byte`, `word`, or `vector` types. A pointer may only
point to a byte inside a `table` of `byte` type.
Each routine is associated with a rich set of type information,
which is basically the types and statuses of memory locations that
have been declared as being relevant to that routine.
#### User-defined ####
A program may define its own types using the `typedef` feature. Typedefs
must occur before everything else in the program. A typedef takes a
type expression and an identifier which has not previously been used in
the program. It associates that identifer with that type. This is merely
a type alias; two types with different names will compare as equal.
a type alias; if two types have identical structure but different names,
they will compare as equal.
Memory locations
----------------
### Memory locations ###
A primary concept in SixtyPical is the *memory location*. At any given point
in time during execution, each memory location is either *uninitialized* or
@ -51,7 +78,7 @@ the program text; thus, it is a static property.
There are four general kinds of memory location. The first three are
pre-defined and built-in.
### Registers ###
#### Registers ####
Each of these hold a byte. They are initially uninitialized.
@ -59,7 +86,7 @@ Each of these hold a byte. They are initially uninitialized.
x
y
### Flags ###
#### Flags ####
Each of these hold a bit. They are initially uninitialized.
@ -68,7 +95,7 @@ Each of these hold a bit. They are initially uninitialized.
v (overflow)
n (negative)
### Constants ###
#### Constants ####
It may be strange to think of constants as memory locations, but keep in mind
that a memory location in SixtyPical need not map to a memory location in the
@ -97,7 +124,7 @@ and sixty-five thousand five hundred and thirty-six word constants,
Note that if a word constant is between 256 and 65535, the leading `word`
token can be omitted.
### User-defined ###
#### User-defined ####
There may be any number of user-defined memory locations. They are defined
by giving the type (which may be any type except `bit` and `routine`) and the
@ -137,13 +164,37 @@ This is actually useful, at least at this point, as you can rely on the fact
that literal integers in the code are always immediate values. (But this
may change at some point.)
### Buffers and Pointers ###
### Tables and Pointers ###
Roughly speaking, a `buffer` is a table that can be longer than 256 bytes,
and a `pointer` is an address within a buffer.
A table is a collection of memory locations that can be indexed in a number
of ways.
The simplest way is to use another memory location as an index. There
are restrictions on which memory locations can be used as indexes;
only the `x` and `y` locations can be used this way. Since those can
only hold a byte, this method, by itself, only allows access to the first
256 entries of the table.
byte table[1024] tab
...
ld a, tab + x
st a, tab + y
However, by combining indexing with a constant _offset_, entries beyond the
256th entry can be accessed.
byte table[1024] tab
...
ld a, tab + 512 + x
st a, tab + 512 + y
Even with an offset, the range of indexing still cannot exceed 256 entries.
Accessing entries at an arbitrary address inside a table can be done with
a `pointer`. Pointers can only be point inside `byte` tables. When a
pointer is used, indexing with `x` or `y` will also take place.
A `pointer` is implemented as a zero-page memory location, and accessing the
buffer pointed to is implemented with "indirect indexed" addressing, as in
table pointed to is implemented with "indirect indexed" addressing, as in
LDA ($02), Y
STA ($02), Y
@ -151,14 +202,16 @@ buffer 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:
copy ^buf, ptr // this is the only way to initialize a pointer
add ptr, 4 // ok, but only if it does not exceed buffer's size
ld y, 0 // you must set this to something yourself
copy [ptr] + y, byt // read memory through pointer, into byte
copy 100, [ptr] + y // write memory through pointer (still trashes a)
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
copy 100, [ptr] + y // write memory through pointer (still trashes a)
} // after this block, ptr can no longer be used
where `ptr` is a user-defined storage location of `pointer` type, and the
`+ y` part is mandatory.
where `ptr` is a user-defined storage location of `pointer` type, `buf`
is a `table` of `byte` type, and the `+ y` part is mandatory.
Routines
--------
@ -288,9 +341,9 @@ this mode is used.
copy <src-memory-location>, <dest-memory-location>
Reads from src and writes to dest. Differs from `st` in that is able to
copy more general types of data (for example, vectors,) and it trashes the
`z` and `n` flags and the `a` register.
Reads from src and writes to dest. Differs from `ld` and `st` in that
it is able to copy more general types of data (for example, vectors,)
and it trashes the `z` and `n` flags and the `a` register.
* It is illegal if dest is read-only.
* It is illegal if dest does not occur in the WRITES of the current routine.
@ -300,17 +353,7 @@ copy more general types of data (for example, vectors,) and it trashes the
After execution, dest is considered initialized, and `z` and `n`, and
`a` are considered uninitialized.
There are two extra modes that this instruction can be used in. The first is
to load an address into a pointer:
copy ^<src-memory-location>, <dest-memory-location>
This copies the address of src into dest. In this case, src must be
of type buffer, and dest must be of type pointer. src will not be
considered a memory location that is read, since it is only its address
that is being retrieved.
The second is to read or write indirectly through a pointer.
There is an extra mode that this instruction can be used in:
copy [<src-memory-location>] + y, <dest-memory-location>
copy <src-memory-location>, [<dest-memory-location>] + y
@ -333,8 +376,9 @@ this mode is used.
Adds the contents of src to dest and stores the result in dest.
* It is illegal if src OR dest OR c is uninitialized.
* It is illegal if src OR dest OR `c` is uninitialized.
* It is illegal if dest is read-only.
* It is illegal if dest is `x` or `y`.
* It is illegal if dest does not occur in the WRITES of the current routine.
Affects n, z, c, and v flags, requiring that they be in the WRITES,
@ -345,8 +389,11 @@ dest and src continue to be initialized afterwards.
In addition, if dest is of `word` type, then src must also be of `word`
type, and in this case this instruction trashes the `a` register.
In fact, this instruction trashes the `a` register in all cases except
when the dest is `a`.
NOTE: If dest is a pointer, the addition does not check if the result of
the pointer arithmetic continues to be valid (within a buffer) or not.
the pointer arithmetic continues to be valid (within a table) or not.
### inc ###
@ -367,8 +414,9 @@ and initializing them afterwards.
Subtracts the contents of src from dest and stores the result in dest.
* It is illegal if src OR dest OR c is uninitialized.
* It is illegal if src OR dest OR `c` is uninitialized.
* It is illegal if dest is read-only.
* It is illegal if dest is `x` or `y`.
* It is illegal if dest does not occur in the WRITES of the current routine.
Affects n, z, c, and v flags, requiring that they be in the WRITES,
@ -376,6 +424,12 @@ and initializing them afterwards.
dest and src continue to be initialized afterwards.
In addition, if dest is of `word` type, then src must also be of `word`
type, and in this case this instruction trashes the `a` register.
In fact, this instruction trashes the `a` register in all cases except
when the dest is `a`.
### dec ###
dec <dest-memory-location>
@ -395,12 +449,24 @@ and initializing them afterwards.
Subtracts the contents of src from dest (without considering carry) but
does not store the result anywhere, only sets the resulting flags.
This means that `z` is set if src and dest are equal,
and `c` is set if dest is greater than or equal to src
(`c` is unset if dest is less than src.)
* It is illegal if src OR dest is uninitialized.
Affects n, z, and c flags, requiring that they be in the WRITES,
and initializing them afterwards.
In addition, if dest is of `word` type, then src must also be of `word`
type, and in this case this instruction trashes the `a` register.
Note that `cmp` is not suitable for making a
signed comparison; this article, which mentions
techniques that a SixtyPical compiler could use to
implement `cmp`, also explains why that is:
[Beyond 8-bit Unsigned Comparisons, by Bruce Clark](http://www.6502.org/tutorials/compare_beyond.html).
### and, or, xor ###
and <dest-memory-location>, <src-memory-location>
@ -558,11 +624,10 @@ Grammar
Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}.
ConstDefn::= "const" Ident<new> Const.
TypeDefn::= "typedef" Type Ident<new>.
Defn ::= Type Ident<new> [Constraints] (":" Const | "@" LitWord).
Defn ::= Type Ident<new> (":" Const | "@" LitWord).
Type ::= TypeTerm ["table" TypeSize].
TypeExpr::= "byte"
| "word"
| "buffer" TypeSize
| "pointer"
| "vector" TypeTerm
| "routine" Constraints
@ -571,10 +636,8 @@ Grammar
TypeSize::= "[" LitWord "]".
Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs].
Routine ::= "define" Ident<new> Type (Block | "@" LitWord).
| "routine" Ident<new> Constraints (Block | "@" LitWord)
.
LocExprs::= LocExpr {"," LocExpr}.
LocExpr ::= Register | Flag | Const | Ident.
LocExpr ::= Register | Flag | Const | Ident [["+" Const] "+" Register].
Register::= "a" | "x" | "y".
Flag ::= "c" | "z" | "n" | "v".
Const ::= Literal | Ident<const>.
@ -602,4 +665,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
.

View File

@ -1,37 +1,26 @@
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This directory contains SixtyPical example programs, categorized
in subdirectories by machine architecture.
### rudiments
In the [rudiments](rudiments/) directory are programs which are not for
any particular machine, but meant to demonstrate the features of SixtyPical.
Some are meant to fail and produce an error message. Others can run on
any architecture where there is a routine at 65490 which outputs the value
of the accumulator as an ASCII character.
In the [rudiments](rudiments/) directory are programs which are
meant to demonstrate the elementary features of SixtyPical, and
to serve as manual integration test cases. See
[the README in that directory](rudiments/README.md) for details.
### c64
In the [c64](c64/) directory are programs that run on the Commodore 64.
The directory itself contains some simple demos, for example
[hearts.60p](c64/hearts.60p), while there are subdirectories for more
elaborate demos:
* [demo-game](c64/demo-game/): a little game-like program written as a
"can we write something you'd see in practice?" test case for SixtyPical.
* [ribos](c64/ribos/): a well-commented example of a C64 raster interrupt
routine. Originally written with the P65 assembler (which has since
been reborn as [Ophis][]).
The second version of Ribos has been translated to SixtyPical.
* [petulant](c64/petulant/): "The PETulant Cursor", a tiny (44 bytes)
"display hack". Originally written in the late 80's. Rewritten with
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
hint as to its nature).
Translated to SixtyPical (in 2018), after adding some optimizations
to the SixtyPical compiler, the resulting executable is still 44 bytes!
elaborate demos, like the flagship demo game. See
[the README in that directory](c64/README.md) for details.
### vic20
@ -46,4 +35,10 @@ Atari 2600 (4K cartridge). The directory itself contains a simple
demo, [smiley.60p](atari2600/smiley.60p) which was converted from an
older Atari 2600 skeleton program written in [Ophis][].
### apple2
In the [apple2](apple2/) directory are programs that run on
Apple II series computers (Apple II+, Apple //e). `sixtypical`'s
support for this architecture could be called embryonic.
[Ophis]: http://michaelcmartin.github.io/Ophis/

35
eg/apple2/README.md Normal file
View File

@ -0,0 +1,35 @@
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This directory contains SixtyPical example programs
specifically for the Apple II series of computers.
See the [README in the parent directory](../README.md) for
more information on these example programs.
Note that `sixtypical` does not currently support "load
and go" execution of these programs, because constructing
an Apple II disk image file on the fly is not something
it can currently do. If you have the linapple sources
checked out, and the a2tools available, you could do
something like this:
bin/sixtypical --traceback --origin=0x2000 --output-format=raw eg/apple2/prog.60p --output prog.bin
cp /path/to/linapple/res/Master.dsk sixtypical.dsk
a2rm sixtypical.dsk PROG
a2in B sixtypical.dsk PROG prog.bin
linapple -d1 sixtypical.dsk -autoboot
and then enter
BLOAD PROG
CALL 8192
Ideally you could
BRUN PROG
But that does not always return to BASIC and I'm not sure why.

24
eg/apple2/lores.60p Normal file
View File

@ -0,0 +1,24 @@
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
byte ds_graphics @ $C050
byte ds_text @ $C051
byte ds_full @ $C052
byte ds_split @ $C053
byte ds_page1 @ $C054
byte ds_page2 @ $C055
byte ds_lores @ $C056
byte ds_hires @ $C057
define main routine
inputs a
outputs ds_lores, ds_page1, ds_split, ds_graphics
trashes a, z, n
{
ld a, 0
st a, ds_lores
st a, ds_page1
st a, ds_split
st a, ds_graphics
}

23
eg/apple2/print.60p Normal file
View File

@ -0,0 +1,23 @@
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
// Write ">AB>" to "standard output"
define cout routine
inputs a
trashes a
@ $FDED
define main routine
trashes a, z, n
{
ld a, 62
call cout
ld a, 65
call cout
ld a, 66
call cout
ld a, 62
call cout
}

View File

@ -4,7 +4,9 @@
; plus an example of reading the joystick.
; By Chris Pressey, November 2, 2012.
;
; This work is in the public domain. See the file UNLICENSE for more info.
; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
; For more information, please refer to <https://unlicense.org/>
; SPDX-License-Identifier: Unlicense
;
; Based on Chris Cracknell's Atari 2600 clock (also in the public domain):
; http://everything2.com/title/An+example+of+Atari+2600+source+code

View File

@ -1,6 +1,10 @@
// smiley.60p - SixtyPical translation of smiley.oph (2018),
// which is itself a stripped-down version of atari-2600-example.oph
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
byte VSYNC @ $00
byte VBLANK @ $01
byte WSYNC @ $02

View File

@ -2,7 +2,9 @@
; smiley.oph (2018)
; stripped-down version of atari-2600-example.oph (2012)
;
; This work is in the public domain. See the file UNLICENSE for more info.
; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
; For more information, please refer to <https://unlicense.org/>
; SPDX-License-Identifier: Unlicense
;
; to build and run in Stella:
; ophis smiley.oph -o smiley.bin

View File

@ -1,5 +1,29 @@
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This directory contains SixtyPical example programs
specifically for the Commodore 64.
See the [README in the parent directory](../README.md) for
more information on these example programs.
There are subdirectories for more elaborate demos:
* [demo-game](demo-game/): a little game-like program written as a
"can we write something you'd see in practice?" test case for SixtyPical.
* [ribos](ribos/): a well-commented example of a C64 raster interrupt
routine. Originally written with the P65 assembler (which has since
been reborn as [Ophis][]).
The second version of Ribos has been translated to SixtyPical.
* [petulant](petulant/): "The PETulant Cursor", a tiny (44 bytes)
"display hack". Originally written in the late 80's. Rewritten with
the P65 assembler (now Ophis) and re-released on April 1st, 2008 (a
hint as to its nature).
Translated to SixtyPical (in 2018), after adding some optimizations
to the SixtyPical compiler, the resulting executable is still 44 bytes!
[Ophis]: http://michaelcmartin.github.io/Ophis/

View File

@ -2,6 +2,12 @@
// * Demo Game for SixtyPical *
// ****************************
// Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
// This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
// SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
include "joystick.60p"
// ----------------------------------------------------------------
// Type Definitions
// ----------------------------------------------------------------
@ -21,22 +27,16 @@
// and the end of their own routines, so the type needs to be compatible.
// (In a good sense, it is a continuation.)
//
// Further,
//
// It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs.
// They're only there to support the fact that game states sometimes clear the
// screen, and sometimes don't. When they don't, they preserve the screen, and
// currently the way to say "we preserve the screen" is to have it as both input
// and output. There is probably a better way to do this, but it needs thought.
//
typedef routine
inputs joy2, press_fire_msg, dispatch_game_state,
actor_pos, actor_delta, actor_logic,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
player_died,
screen, colormap
outputs dispatch_game_state,
actor_pos, actor_delta, actor_logic,
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
player_died,
screen, colormap
trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
game_state_routine
@ -45,13 +45,13 @@ typedef routine
//
// Routines that conform to this type also follow this convention:
//
// Set carry if the player perished. Carry clear otherwise.
// Set player_died to 1 if the player perished. Unchanged otherwise.
//
typedef routine
inputs pos, delta, joy2, screen
outputs pos, delta, new_pos, screen, c
trashes a, x, y, z, n, v, ptr
inputs pos, delta, joy2, screen, player_died
outputs pos, delta, new_pos, screen, player_died
trashes a, x, y, z, n, v, c, ptr
logic_routine
// ----------------------------------------------------------------
@ -60,19 +60,8 @@ typedef routine
byte vic_border @ 53280
byte vic_bg @ 53281
byte table[256] screen1 @ 1024
byte table[256] screen2 @ 1274
byte table[256] screen3 @ 1524
byte table[256] screen4 @ 1774
byte table[256] colormap1 @ 55296
byte table[256] colormap2 @ 55546
byte table[256] colormap3 @ 55796
byte table[256] colormap4 @ 56046
buffer[2048] screen @ 1024
byte joy2 @ $dc00
byte table[2048] screen @ 1024
byte table[2048] colormap @ 55296
// ----------------------------------------------------------------
// Global Variables
@ -85,7 +74,8 @@ word pos
word new_pos
word table[256] actor_delta
word delta
byte player_died
vector logic_routine table[256] actor_logic
vector logic_routine dispatch_logic
@ -117,95 +107,30 @@ vector game_state_routine
// Utility Routines
// ----------------------------------------------------------------
routine read_stick
inputs joy2
outputs delta
trashes a, x, z, n
{
ld x, joy2
ld a, x
and a, 1 // up
if z {
copy $ffd8, delta // -40
} else {
ld a, x
and a, 2 // down
if z {
copy word 40, delta
} else {
ld a, x
and a, 4 // left
if z {
copy $ffff, delta // -1
} else {
ld a, x
and a, 8 // right
if z {
copy word 1, delta
} else {
copy word 0, delta
}
}
}
}
}
// You can repeatedly (i.e. as part of actor logic or an IRQ handler)
// call this routine.
// Upon return, if carry is set, the button was pressed then released.
define check_button routine
inputs joy2
outputs c
trashes a, z, n
static byte button_down : 0
{
ld a, button_down
if z {
ld a, joy2
and a, $10
if z {
ld a, 1
st a, button_down
}
st off, c
} else {
ld a, joy2
and a, $10
if not z {
ld a, 0
st a, button_down
st on, c
} else {
st off, c
}
}
}
routine clear_screen
outputs screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
define clear_screen routine
outputs screen, colormap
trashes a, y, c, n, z
{
ld y, 0
repeat {
ld a, 1
st a, colormap1 + y
st a, colormap2 + y
st a, colormap3 + y
st a, colormap4 + y
st a, colormap + y
st a, colormap + 250 + y
st a, colormap + 500 + y
st a, colormap + 750 + y
ld a, 32
st a, screen1 + y
st a, screen2 + y
st a, screen3 + y
st a, screen4 + y
st a, screen + y
st a, screen + 250 + y
st a, screen + 500 + y
st a, screen + 750 + y
inc y
cmp y, 250
} until z
}
routine calculate_new_position
define calculate_new_position routine
inputs pos, delta
outputs new_pos
trashes a, c, n, z, v
@ -239,9 +164,9 @@ define check_new_position_in_bounds routine
}
}
routine init_game
define init_game routine
inputs actor_pos, actor_delta, actor_logic
outputs actor_pos, actor_delta, actor_logic
outputs actor_pos, actor_delta, actor_logic, player_died
trashes pos, a, y, z, n, c, v
{
ld y, 0
@ -259,9 +184,11 @@ routine init_game
} until z
ld y, 0
copy word 0, actor_pos + y
copy word 40, actor_pos + y
copy word 0, actor_delta + y
copy player_logic, actor_logic + y
st y, player_died
}
// ----------------------------------------------------------------
@ -276,46 +203,38 @@ define player_logic logic_routine
call check_new_position_in_bounds
if c {
copy ^screen, ptr
st off, c
add ptr, new_pos
ld y, 0
point ptr into screen {
reset ptr 0
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
// 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 {
reset ptr 0
st off, c
add ptr, pos
copy 32, [ptr] + y
copy new_pos, pos
reset ptr 0
st off, c
add ptr, pos
copy 81, [ptr] + y
}
}
cmp a, 32
if z {
copy ^screen, ptr
st off, c
add ptr, pos
copy 32, [ptr] + y
copy new_pos, pos
copy ^screen, ptr
st off, c
add ptr, pos
copy 81, [ptr] + y
st off, c
} else {
st on, c
}
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
// but currently the compiler cares a little too much about values that are
// initialized in one branch of an `if`, but not the other, but are trashed
// at the end of the routine anyway.
trash ptr
trash y
trash v
} else {
st off, c
ld a, 1
st a, player_died
}
}
@ -326,43 +245,35 @@ define enemy_logic logic_routine
call check_new_position_in_bounds
if c {
copy ^screen, ptr
st off, c
add ptr, new_pos
ld y, 0
point ptr into screen {
reset ptr 0
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
// 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 {
reset ptr 0
st off, c
add ptr, pos
copy 32, [ptr] + y
copy new_pos, pos
reset ptr 0
st off, c
add ptr, pos
copy 82, [ptr] + y
}
}
cmp a, 32
if z {
copy ^screen, ptr
st off, c
add ptr, pos
copy 32, [ptr] + y
copy new_pos, pos
copy ^screen, ptr
st off, c
add ptr, pos
copy 82, [ptr] + y
st off, c
} else {
st on, c
}
// FIXME these trashes, strictly speaking, probably shouldn't be needed,
// but currently the compiler cares too much about values that are
// initialized in one branch of an `if`, but not the other, but trashed
// at the end of the routine anyway.
trash ptr
trash y
} else {
copy delta, compare_target
st on, c
@ -373,8 +284,6 @@ define enemy_logic logic_routine
copy $ffd8, delta
}
}
st off, c
}
// ----------------------------------------------------------------
@ -390,7 +299,7 @@ define game_state_title_screen game_state_routine
st on, c
sub a, 64 // yuck. oh well
st a, screen1 + y
st a, screen + y
}
st off, c
@ -406,33 +315,33 @@ define game_state_title_screen game_state_routine
}
define game_state_play game_state_routine
static byte save_x : 0
{
ld x, 0
repeat {
st x, player_died
for x up to 15 {
copy actor_pos + x, pos
copy actor_delta + x, delta
st x, save_x
copy actor_logic + x, dispatch_logic
call dispatch_logic
if c {
// Player died! Want no dead! Break out of the loop (this is a bit awkward.)
call clear_screen
copy game_state_game_over, dispatch_game_state
ld x, 15
} else {
ld x, save_x
//
// Save our loop counter on the stack temporarily. This means that routines
// like `dispatch_logic` and `clear_screen` are allowed to do whatever they
// want with the `x` register; we will restore it at the end of this block.
//
save x {
copy actor_logic + x, dispatch_logic
call dispatch_logic
}
copy pos, actor_pos + x
copy delta, actor_delta + x
}
inc x
cmp x, 16
} until z
ld a, player_died
if not z {
// Player died! Want no dead!
call clear_screen
copy game_state_game_over, dispatch_game_state
}
goto save_cinv
}
@ -455,15 +364,14 @@ define game_state_game_over game_state_routine
// * Main Game Loop Driver *
// *************************
define our_cinv game_state_routine
define our_cinv preserved game_state_routine
{
goto dispatch_game_state
}
routine main
define main routine
inputs cinv
outputs cinv, save_cinv, pos, dispatch_game_state,
screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
outputs cinv, save_cinv, pos, dispatch_game_state, screen, colormap
trashes a, y, n, c, z, vic_border, vic_bg
{
ld a, 5

23
eg/c64/demo-game/run.sh Executable file
View File

@ -0,0 +1,23 @@
#!/bin/sh
# This script builds and runs the demo game. You need
# the VICE emulatore installed, in particular VICE's x64.
# You might want a `vicerc` file like the following:
# [C64]
# VICIIDoubleScan=0
# VICIIDoubleSize=0
# KeySet1NorthWest=0
# KeySet1North=273
# KeySet1NorthEast=0
# KeySet1East=275
# KeySet1SouthEast=0
# KeySet1South=274
# KeySet1SouthWest=0
# KeySet1West=276
# KeySet1Fire=306
# KeySetEnable=1
# JoyDevice1=0
# JoyDevice2=2
../../../bin/sixtypical --run-on x64 -I ../../../include/c64/ demo-game.60p

View File

@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
// Displays 256 hearts at the top of the Commodore 64's screen.
// Define where the screen starts in memory:
byte table[256] screen @ 1024
routine main
define main routine
// These are the values that will be written to by this routine:
trashes a, x, z, n, screen
{

View File

@ -1,3 +1,7 @@
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
byte vic_border @ 53280
//
@ -24,7 +28,7 @@ vector routine
trashes z, n
save_cinv
routine our_cinv
define our_cinv routine
inputs vic_border
outputs vic_border
trashes z, n
@ -33,7 +37,7 @@ routine our_cinv
goto save_cinv
}
routine main
define main routine
inputs cinv
outputs cinv, save_cinv
trashes a, n, z

19
eg/c64/joystick-demo.60p Normal file
View File

@ -0,0 +1,19 @@
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
include "joystick.60p"
word screen @ 1024
define main routine
inputs joy2
outputs delta
trashes a, x, z, n, screen
{
repeat {
call read_stick
copy delta, screen
ld a, 1
} until z
}

View File

@ -1,49 +0,0 @@
word screen @ 1024
byte joy2 @ $dc00
word delta
routine read_stick
inputs joy2
outputs delta
trashes a, x, z, n
{
ld x, joy2
ld a, x
and a, 1 // up
if z {
copy $ffd8, delta // -40
} else {
ld a, x
and a, 2 // down
if z {
copy word 40, delta
} else {
ld a, x
and a, 4 // left
if z {
copy $ffff, delta // -1
} else {
ld a, x
and a, 8 // right
if z {
copy word 1, delta
} else {
copy word 0, delta
}
}
}
}
}
routine main
inputs joy2
outputs delta
trashes a, x, z, n, screen
{
repeat {
call read_stick
copy delta, screen
ld a, 1
} until z
}

View File

@ -1,6 +1,12 @@
The PETulant Cursor
===================
<!--
SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
For more information, please refer to <https://unlicense.org/>
SPDX-License-Identifier: Unlicense
-->
This is a tiny (44 bytes long) machine-language demo for the Commodore 64,
somewhat in the style of later "display hacks" for the Amiga -- surprising
and silly ways to play with the user interface.
@ -10,8 +16,8 @@ So as not to not spoil the fun, try running it before reading the source.
To run it, make the file PETULANT.PRG accessible to your favourite Commodore
64 (or Commodore 64 emulator) in your favourite way, then
LOAD "PETULANT.PRG",8,1
SYS 679
LOAD "PETULANT.PRG",8,1
SYS 679
For further fun, try changing the text colour (hold the C= or CTRL key while
pressing a number) or the background colour (POKE 53281 with a number from
@ -27,6 +33,6 @@ makes it much more obvious when control has returned to BASIC immediate mode
Enjoy!
-Chris Pressey
April 1, 2008
Chicago, IL
-Chris Pressey
April 1, 2008
Chicago, IL

View File

@ -2,7 +2,10 @@
// Originally written by Chris Pressey sometime in the late 1980's
// Rewritten in P65 assembly and released March 2008
// Rewritten in SixtyPical in March 2018
// This work is part of the public domain.
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
// ----- Types -----

View File

@ -1,7 +1,10 @@
; petulant.p65 - The PETulant Cursor, a "display hack" for the Commodore 64
; Originally written by Chris Pressey sometime in the late 1980's
; Rewritten in P65 assembly and released March 2008
; This work is part of the public domain.
; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
; For more information, please refer to <https://unlicense.org/>
; SPDX-License-Identifier: Unlicense
; ----- BEGIN petulant.p65 -----

View File

@ -1,6 +1,12 @@
Ribos
=====
<!--
SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
For more information, please refer to <https://unlicense.org/>
SPDX-License-Identifier: Unlicense
-->
This little demo is intended to be a well-commented example of how to
program a raster interrupt in 6502 assembly language on a Commodore 64.
@ -18,9 +24,9 @@ How to Run the Demo (using the VICE C64 emulator, x64)
1. Mount this project's directory as drive 8:
Make sure
Peripheral settings > Device #8 > Enable IEC Device
`Peripheral settings > Device #8 > Enable IEC Device`
is checked, then select
Peripheral settings > Device #8 > File system directory...
`Peripheral settings > Device #8 > File system directory...`
and enter the path to the project directory.
2. LOAD "RIBOS.PRG",8,1
@ -74,6 +80,6 @@ points. What I learned in the process is written into the comments.
Happy raster-interrupting!
-Chris Pressey
April 10, 2007
Vancouver, BC
-Chris Pressey
April 10, 2007
Vancouver, BC

View File

@ -2,8 +2,10 @@
; Demonstration of the VIC-II raster interrupt on the Commodore 64:
; Alter the border colour in the middle part of the screen only.
; Original (hardware IRQ vector) version.
; By Chris Pressey, Cat's Eye Technologies.
; This work has been placed in the public domain.
; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
; For more information, please refer to <https://unlicense.org/>
; SPDX-License-Identifier: Unlicense
; ----- BEGIN ribos.p65 -----

View File

@ -2,8 +2,10 @@
// Demonstration of the VIC-II raster interrupt on the Commodore 64:
// Alter the border colour in the middle part of the screen only,
// Simplified (KERNAL IRQ vector) version.
// By Chris Pressey, Cat's Eye Technologies.
// This work has been placed in the public domain.
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
// For comments, see ribos2.p65.
@ -49,9 +51,10 @@ byte scanline : 85 // %01010101
// generating them as part of a SixtyPical program would not
// be practical. So we just jump to this location instead.
routine pla_tay_pla_tax_pla_rti
inputs a
trashes a
define pla_tay_pla_tax_pla_rti routine
inputs border_color, vic_intr
outputs border_color, vic_intr
trashes a, z, n, c
@ $EA81
// ----- Interrupt Handler -----

View File

@ -2,8 +2,10 @@
; Demonstration of the VIC-II raster interrupt on the Commodore 64:
; Alter the border colour in the middle part of the screen only,
; Simplified (KERNAL IRQ vector) version.
; By Chris Pressey, Cat's Eye Technologies.
; This work has been placed in the public domain.
; SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
; For more information, please refer to <https://unlicense.org/>
; SPDX-License-Identifier: Unlicense
; ----- BEGIN ribos2.p65 -----

View File

@ -1,6 +1,10 @@
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
byte screen @ 1024
routine main
define main routine
trashes a, z, n, screen
{
ld a, 83

View File

@ -1,12 +1,28 @@
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This directory contains example sources which demonstrate
the rudiments of SixtyPical.
Some are meant to fail and produce an error message.
Examples that are meant to fail and produce an error message
when being compiled are in the `errorful/` subdirectory.
They are not meant to be specific to any architecture, but
many do assume the existence of a routine at 65490 which
outputs the value of the accumulator as an ASCII character,
The other sources are portable across architectures. They use
`include` directives to bring in architecture-dependent libraries
to produce output. Libraries for such are provided in the
architecture-specific subdirectories of the `include` directory
in the root directory of this repository; make sure it is on the
compiler's include search path. For example:
sixtypical --run-on=x64 -I../../include/c64/:../../include/stdlib/ vector-table.60p
`chrout` is a routine with outputs the value of the accumulator
as an ASCII character, disturbing none of the other registers,
simply for the purposes of producing some observable output.
(This is an address of a KERNAL routine which does this
on both the Commodore 64 and the Commodore VIC-20, so these
sources should be usable on these architectures.)
(There is a KERNAL routine which does this on both the
Commodore 64 and the Commodore VIC-20. It should not be hard
to find or write such a routine for most other architectures.)

View File

@ -1,6 +0,0 @@
routine add_four
inputs a
outputs a
{
add a, 4
}

View File

@ -1,8 +0,0 @@
routine main
inputs a
outputs a
trashes c, z, n, v
{
st off, c
add a, 4
}

View File

@ -1,9 +0,0 @@
word score
routine main
inputs score
outputs score
trashes a, c, z, v, n
{
st off, c
add score, 1999
}

37
eg/rudiments/add.60p Normal file
View File

@ -0,0 +1,37 @@
// Should print YY
include "chrout.60p"
word score
define main routine
inputs a, score
outputs score
trashes a, c, z, n, v
{
ld a, 3
st off, c
add a, 4
cmp a, 7
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
copy 999, score
st off, c
add score, 1999
cmp score, 2998
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
}

View File

@ -1,20 +0,0 @@
vector vec
inputs y
outputs y
trashes z, n
routine foo
inputs x
outputs x
trashes z, n
{
inc x
}
routine main
inputs foo
outputs vec
trashes a, z, n
{
copy foo, vec
}

View File

@ -1,15 +1,33 @@
buffer[2048] buf
// Should print Y
include "chrout.60p"
byte table[2048] buf
pointer ptr @ 254
byte foo
routine main
define main routine
inputs buf
outputs buf, y, foo
trashes a, z, n, ptr
trashes a, z, n, c, ptr
{
ld y, 0
copy ^buf, ptr
copy 123, [ptr] + y
copy [ptr] + y, foo
copy foo, [ptr] + y
point ptr into buf {
reset ptr 0
copy 123, [ptr] + y
copy [ptr] + y, foo
copy foo, [ptr] + y
}
// TODO: support saying `cmp foo, 123`, maybe
ld a, foo
cmp a, 123
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
}

View File

@ -1,16 +1,15 @@
routine chrout
inputs a
trashes a
@ 65490
// Should print AA
routine print
include "chrout.60p"
define print routine
trashes a, z, n
{
ld a, 65
call chrout
}
routine main
define main routine
trashes a, z, n
{
call print

71
eg/rudiments/cmp-byte.60p Normal file
View File

@ -0,0 +1,71 @@
// Should print ENGGL
include "chrout.60p"
byte b
define main routine
outputs b
trashes a, x, y, z, n, c, v
{
ld a, 40
st a, b
cmp a, b
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
ld a, 41
st a, b
ld a, 40
cmp a, b
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
ld a, 20
st a, b
ld a, 21
cmp a, b // 21 >= 20
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
ld a, 20
cmp a, b // 20 >= 20
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
ld a, 19
cmp a, b // 19 < 20
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
}

View File

@ -0,0 +1,65 @@
// Should print ENGGL
include "chrout.60p"
word w1
define main routine
outputs w1
trashes a, x, y, z, n, c, v
{
copy 4000, w1
cmp w1, 4000
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 4000, w1
cmp w1, 4001
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 20002, w1
cmp w1, 20001 // 20002 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20001, w1
cmp w1, 20001 // 20001 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20000, w1
cmp w1, 20001 // 20000 < 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
}

69
eg/rudiments/cmp-word.60p Normal file
View File

@ -0,0 +1,69 @@
// Should print ENGGL
include "chrout.60p"
word w1
word w2
define main routine
outputs w1, w2
trashes a, x, y, z, n, c, v
{
copy 4000, w1
copy 4000, w2
cmp w1, w2
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 4000, w1
copy 4001, w2
cmp w1, w2
if z {
ld a, 69 // E
call chrout
} else {
ld a, 78 // N
call chrout
}
copy 20002, w1
copy 20001, w2
cmp w1, w2 // 20002 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20001, w1
cmp w1, w2 // 20001 >= 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
copy 20000, w1
cmp w1, w2 // 20000 < 20001
if c {
ld a, 71 // G
call chrout
} else {
ld a, 76 // L
call chrout
}
}

View File

@ -1,9 +1,8 @@
routine chrout
inputs a
trashes a
@ 65490
// Should print YN
routine main
include "chrout.60p"
define main routine
trashes a, x, y, z, n, c, v
{
ld a, 0

View File

@ -1,9 +1,8 @@
routine chrout
inputs a
trashes a
@ 65490
// Should print YA
routine main
include "chrout.60p"
define main routine
trashes a, x, y, z, n, c, v
{
ld a, 0

View File

@ -1,10 +0,0 @@
byte bar
byte baz
routine main
inputs baz
outputs bar
trashes a, n, z
{
copy baz, bar
}

View File

@ -0,0 +1,10 @@
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This directory contains example SixtyPical sources which
are intentionally invalid (for demonstration purposes)
and are expected to elicit an error message from a
SixtyPical implementation.

View File

@ -0,0 +1,11 @@
// should fail analysis with an UnmeaningfulReadError
// because adding 4 to the accumulator reads the carry
// but the carry has neither been provided as input
// nor set to anything in particular by this routine.
define add_four routine
inputs a
outputs a
{
add a, 4
}

View File

@ -0,0 +1,13 @@
// should fail analysis with a RangeExceededError
// because the index is detected to fall outside the
// allowable range of the table it is indexing.
byte table[8] message : "WHAT?"
define main routine
inputs message
outputs x, a, z, n
{
ld x, 9
ld a, message + x
}

View File

@ -0,0 +1,25 @@
// should fail analysis with a ConstantConstraintError
// because it cannot copy the address of `foo` into `vec`
// because it has incompatible constraints.
vector routine
inputs y
outputs y
trashes z, n
vec
define foo routine
inputs x
outputs x
trashes z, n
{
inc x
}
define main routine
inputs foo
outputs vec
trashes a, z, n
{
copy foo, vec
}

View File

@ -1,14 +1,21 @@
// Should print 01
include "chrout.60p"
include "prbyte.60p"
byte lives
routine main
inputs lives
define main routine
inputs lives, hexchars
outputs lives
trashes a, x
{
ld a, 0
st a, lives
ld x, lives
st off, c
add x, 1
st x, lives
}
trashes a, x, z, n, c, v
{
ld a, 0
st a, lives
ld x, lives
st off, c
inc x
st x, lives
ld a, lives
call prbyte
}

View File

@ -1,4 +1,7 @@
routine main
// This program is expected to loop forever.
// Be prepared to forcibly terminate your emulator.
define main routine
trashes a, y, z, n, c
{
ld y, 65

View File

@ -1,14 +1,13 @@
routine chrout
inputs a
trashes a
@ 65490
// Should print AB
routine bar trashes a, z, n {
include "chrout.60p"
define bar routine trashes a, z, n {
ld a, 66
call chrout
}
routine main trashes a, z, n {
define main routine trashes a, z, n {
ld a, 65
call chrout
goto bar

View File

@ -1,11 +0,0 @@
routine foo
inputs a
outputs a
{
cmp a, 42
if z {
ld a, 7
} else {
ld a, 23
}
}

View File

@ -1,9 +1,8 @@
routine chrout
inputs a
trashes a
@ 65490
// Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
routine main
include "chrout.60p"
define main routine
trashes a, y, z, n, c
{
ld y, 65

View File

@ -1,11 +1,10 @@
// Should print AB
include "chrout.60p"
byte foo
routine chrout
inputs a
trashes a
@ 65490
routine print
define print routine
inputs foo
trashes a, z, n
{
@ -13,7 +12,7 @@ routine print
call chrout
}
routine main
define main routine
trashes a, y, z, n, foo
{
ld y, 65

View File

@ -0,0 +1,34 @@
// Should print H (being ASCII 72 = 8 * 9)
include "chrout.60p"
// 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
}

View File

@ -1,22 +0,0 @@
// SixtyPical 0.11 introduced a new syntax for defining routines
// (routine was made into a type, and you can now say: define name type.)
typedef routine
inputs x
outputs x
trashes z, n
routine_type
vector routine_type vec
define foo routine_type
{
inc x
}
define main routine
outputs vec
trashes a, z, n
{
copy foo, vec
}

View File

@ -1,9 +1,8 @@
routine chrout
inputs a
trashes a
@ 65490
// Should print A
routine main
include "chrout.60p"
define main routine
inputs a
trashes a, z, n
{

View File

@ -1,9 +0,0 @@
byte table[8] message : "WHAT?"
routine main
inputs message
outputs x, a, z, n
{
ld x, 9
ld a, message + x
}

View File

@ -1,21 +0,0 @@
// This will not compile on its own, because there is no `main`.
// But this and `vector-main.60p` together will compile.
routine chrout
inputs a
trashes a
@ 65490
routine printa
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
trashes a, z, n
{
ld a, 66
call chrout
}

View File

@ -1,22 +0,0 @@
// This will not compile on its own, because `printa` and `printb` are not defined.
// But `vector-inc.60p` and this together will compile.
vector routine
trashes a, z, n
print
// routine printb
// trashes a, z, n
// {
// ld a, 66
// call chrout
// }
routine main
trashes print, a, z, n
{
copy printa, print
call print
copy printb, print
call print
}

View File

@ -1,7 +1,8 @@
//
// Should print AABAB
// Demonstrates vector tables.
// Prints "AABAB".
//
include "chrout.60p"
vector routine
trashes a, z, n
@ -11,26 +12,21 @@ vector (routine
trashes a, z, n)
table[32] vectors
routine chrout
inputs a
trashes a
@ 65490
routine printa
define printa routine
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
define printb routine
trashes a, z, n
{
ld a, 66
call chrout
}
routine main
define main routine
inputs vectors
outputs vectors
trashes print, a, x, z, n, c
@ -49,10 +45,8 @@ routine main
copy printb, vectors + x
ld x, 0
repeat {
for x up to 4 {
copy vectors + x, print
call print
inc x
cmp x, 5
} until z
}
}

View File

@ -1,27 +1,26 @@
// Should print AB
include "chrout.60p"
vector routine
trashes a, z, n
print
routine chrout
inputs a
trashes a
@ 65490
routine printa
define printa routine
trashes a, z, n
{
ld a, 65
call chrout
}
routine printb
define printb routine
trashes a, z, n
{
ld a, 66
call chrout
}
routine main
define main routine
trashes print, a, z, n
{
copy printa, print

View File

@ -1,16 +1,42 @@
// Should print YY
include "chrout.60p"
word one
word table[256] many
routine main
define main routine
inputs one, many
outputs one, many
trashes a, x, y, n, z
trashes a, x, y, c, n, z
{
ld x, 0
ld y, 0
ld y, 1
copy 777, one
copy one, many + x
copy 888, one
copy one, many + y
ld x, 1
ld y, 0
copy many + x, one
cmp one, 888
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
copy many + y, one
cmp one, 777
if z {
ld a, 89
call chrout
} else {
ld a, 78
call chrout
}
}

View File

@ -1,9 +1,13 @@
// SPDX-FileCopyrightText: Chris Pressey, the author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
// Displays 256 hearts at the top of the VIC-20's screen.
// Define where the screen starts in memory:
byte table[256] screen @ 7680
routine main
define main routine
// These are the values that will be written to by this routine:
trashes a, x, z, n, screen
{

10
include/c64/chrout.60p Normal file
View File

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
// Implementation of `chrout` for the Commodore 64 platform.
define chrout routine
inputs a
trashes a
@ 65490

75
include/c64/joystick.60p Normal file
View File

@ -0,0 +1,75 @@
// SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
byte joy2 @ $dc00
word delta
// Read the joystick and compute the delta it represents
// in a row-based 40-column grid like the C64's screen.
define read_stick routine
inputs joy2
outputs delta
trashes a, x, z, n
{
ld x, joy2
ld a, x
and a, 1 // up
if z {
copy $ffd8, delta // -40
} else {
ld a, x
and a, 2 // down
if z {
copy word 40, delta
} else {
ld a, x
and a, 4 // left
if z {
copy $ffff, delta // -1
} else {
ld a, x
and a, 8 // right
if z {
copy word 1, delta
} else {
copy word 0, delta
}
}
}
}
}
// You can repeatedly (i.e. as part of actor logic or an IRQ handler)
// call this routine.
// Upon return, if carry is set, the button was pressed then released.
define check_button routine
inputs joy2
outputs c
trashes a, z, n
static byte button_down : 0
{
ld a, button_down
if z {
ld a, joy2
and a, $10
if z {
ld a, 1
st a, button_down
}
st off, c
} else {
ld a, joy2
and a, $10
if not z {
ld a, 0
st a, button_down
st on, c
} else {
st off, c
}
}
}

30
include/stdlib/prbyte.60p Normal file
View File

@ -0,0 +1,30 @@
// SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
byte table[16] hexchars : "0123456789ABCDEF"
define prbyte routine
inputs a, hexchars
trashes a, z, n, c, v
{
save x {
save a {
st off, c
shr a
shr a
shr a
shr a
and a, 15
ld x, a
ld a, hexchars + x
call chrout
}
save a {
and a, 15
ld x, a
ld a, hexchars + x
call chrout
}
}
}

10
include/vic20/chrout.60p Normal file
View File

@ -0,0 +1,10 @@
// SPDX-FileCopyrightText: Chris Pressey, the original author of this work, has dedicated it to the public domain.
// For more information, please refer to <https://unlicense.org/>
// SPDX-License-Identifier: Unlicense
// Implementation of `chrout` for the Commodore VIC-20 platform.
define chrout routine
inputs a
trashes a
@ 65490

View File

@ -1,44 +0,0 @@
#!/bin/sh
usage="Usage: loadngo.sh (c64|vic20|atari2600) [--dry-run] <source.60p>"
arch="$1"
shift 1
if [ "X$arch" = "Xc64" ]; then
prelude='c64'
if [ -e vicerc ]; then
emu="x64 -config vicerc"
else
emu="x64"
fi
elif [ "X$arch" = "Xvic20" ]; then
prelude='vic20'
if [ -e vicerc ]; then
emu="xvic -config vicerc"
else
emu="xvic"
fi
elif [ "X$arch" = "Xatari2600" ]; then
prelude='atari2600'
emu='stella'
else
echo $usage && exit 1
fi
if [ "X$1" = "X--dry-run" ]; then
shift 1
emu='echo'
fi
src="$1"
if [ "X$src" = "X" ]; then
echo $usage && exit 1
fi
### do it ###
out=/tmp/a-out.prg
bin/sixtypical --traceback --prelude=$prelude $src > $out || exit 1
ls -la $out
$emu $out
rm -f $out

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,7 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# encoding: UTF-8
class AST(object):
@ -37,14 +41,16 @@ class AST(object):
def all_children(self):
for attr in self.children_attrs:
for child in self.attrs[attr]:
if child is not None:
yield child
for subchild in child.all_children():
yield subchild
for attr in self.child_attrs:
child = self.attrs[attr]
if child is not None:
yield child
for subchild in child.all_children():
yield subchild
for attr in self.child_attrs:
child = self.attrs[attr]
yield child
for subchild in child.all_children():
yield subchild
class Program(AST):
@ -52,12 +58,12 @@ class Program(AST):
class Defn(AST):
value_attrs = ('name', 'addr', 'initial', 'location',)
value_attrs = ('name', 'addr', 'initial',)
class Routine(AST):
value_attrs = ('name', 'addr', 'initial', 'location',)
children_attrs = ('statics',)
value_attrs = ('name', 'addr', 'initial',)
children_attrs = ('locals',)
child_attrs = ('block',)
@ -70,7 +76,19 @@ class Instr(AST):
class SingleOp(Instr):
value_attrs = ('opcode', 'dest', 'src', 'location',)
value_attrs = ('opcode', 'dest', 'src',)
class Reset(Instr):
value_attrs = ('pointer', 'offset',)
class Call(Instr):
value_attrs = ('location',)
class GoTo(Instr):
value_attrs = ('location',)
class If(Instr):
@ -90,3 +108,13 @@ class For(Instr):
class WithInterruptsOff(Instr):
child_attrs = ('block',)
class Save(Instr):
value_attrs = ('locations',)
child_attrs = ('block',)
class PointInto(Instr):
value_attrs = ('pointer', 'table',)
child_attrs = ('block',)

View File

@ -0,0 +1,72 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
from sixtypical.ast import Program
from sixtypical.model import RoutineType, VectorType
def find_routines_matching_type(program, type_):
"""Return the subset of routines of the program whose value
may be assigned to a location of the given type_ (typically
a vector)."""
return [r for r in program.routines if RoutineType.executable_types_compatible(r.routine_type, type_)]
def mark_as_reachable(graph, routine_name):
node = graph[routine_name]
if node.get('reachable', False):
return
node['reachable'] = True
for next_routine_name in node['potentially-calls']:
mark_as_reachable(graph, next_routine_name)
def construct_callgraph(program):
graph = {}
for routine in program.routines:
potentially_calls = []
for (called_routine, called_routine_type) in routine.called_routines:
if isinstance(called_routine_type, RoutineType):
potentially_calls.append(called_routine.name)
elif isinstance(called_routine_type, VectorType):
for potentially_called in find_routines_matching_type(program, called_routine_type):
potentially_calls.append(potentially_called.name)
else:
raise NotImplementedError
graph[routine.name] = {
'potentially-calls': potentially_calls,
}
# Symmetric closure
# (Note, this information isn't used anywhere yet)
for routine in program.routines:
potentially_called_by = []
for (name, node) in graph.items():
potential_calls = node['potentially-calls']
if routine.name in potential_calls:
potentially_called_by.append(name)
graph[routine.name]['potentially-called-by'] = potentially_called_by
# Root set
root_set = set()
for routine in program.routines:
if getattr(routine, 'preserved', False) or routine.name == 'main':
root_set.add(routine)
# Reachability
for routine in root_set:
mark_as_reachable(graph, routine.name)
return graph
def prune_unreachable_routines(program, callgraph):
return Program(1, defns=program.defns, routines=[
r for r in program.routines if callgraph[r.name].get('reachable', False)
])

View File

@ -1,10 +1,16 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# encoding: UTF-8
from sixtypical.ast import Program, Routine, Block, Instr, SingleOp, If, Repeat, For, WithInterruptsOff
from sixtypical.ast import (
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
)
from sixtypical.model import (
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
ConstantRef, LocationRef, IndexedRef, IndirectRef,
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
TableType, BufferType, PointerType, RoutineType, VectorType,
TableType, PointerType, RoutineType, VectorType,
REG_A, REG_X, REG_Y, FLAG_C
)
from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte
@ -12,6 +18,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,
@ -27,15 +34,29 @@ class UnsupportedOpcodeError(KeyError):
class Compiler(object):
def __init__(self, emitter):
def __init__(self, symtab, emitter):
self.symtab = symtab
self.emitter = emitter
self.routines = {} # routine.name -> Routine
self.routine_statics = {} # routine.name -> { static.name -> Label }
self.routine_locals = {} # routine.name -> { local.name -> Label }
self.labels = {} # global.name -> Label ("global" includes routines)
self.trampolines = {} # Location -> Label
self.pointer_assoc = {} # pointer name -> table name (I'm not entirely happy about this)
self.current_routine = None
# helper methods
# - - - - helper methods - - - -
def get_type_for_name(self, name):
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
return self.symtab.fetch_local_type(self.current_routine.name, name)
return self.symtab.fetch_global_type(name)
def get_type(self, ref):
if isinstance(ref, ConstantRef):
return ref.type
if not isinstance(ref, LocationRef):
raise NotImplementedError
return self.get_type_for_name(ref.name)
def addressing_mode_for_index(self, index):
if index == REG_X:
@ -47,24 +68,22 @@ class Compiler(object):
def compute_length_of_defn(self, defn):
length = None
type_ = defn.location.type
type_ = self.get_type_for_name(defn.name)
if type_ == TYPE_BYTE:
length = 1
elif type_ == TYPE_WORD or isinstance(type_, (PointerType, VectorType)):
length = 2
elif isinstance(type_, TableType):
length = type_.size * (1 if type_.of_type == TYPE_BYTE else 2)
elif isinstance(type_, BufferType):
length = type_.size
if length is None:
raise NotImplementedError("Need size for type {}".format(type_))
return length
def get_label(self, name):
if self.current_routine:
static_label = self.routine_statics.get(self.current_routine.name, {}).get(name)
if static_label:
return static_label
local_label = self.routine_locals.get(self.current_routine.name, {}).get(name)
if local_label:
return local_label
return self.labels[name]
def absolute_or_zero_page(self, label):
@ -73,18 +92,18 @@ class Compiler(object):
else:
return Absolute(label)
# visitor methods
# - - - - visitor methods - - - -
def compile_program(self, program, compilation_roster=None):
assert isinstance(program, Program)
defn_labels = []
declarations = []
for defn in program.defns:
length = self.compute_length_of_defn(defn)
label = Label(defn.name, addr=defn.addr, length=length)
self.labels[defn.name] = label
defn_labels.append((defn, label))
declarations.append((defn, self.symtab.fetch_global_type(defn.name), label))
for routine in program.routines:
self.routines[routine.name] = routine
@ -93,34 +112,35 @@ class Compiler(object):
label.set_addr(routine.addr)
self.labels[routine.name] = label
if hasattr(routine, 'statics'):
static_labels = {}
for defn in routine.statics:
length = self.compute_length_of_defn(defn)
label = Label(defn.name, addr=defn.addr, length=length)
static_labels[defn.name] = label
defn_labels.append((defn, label))
self.routine_statics[routine.name] = static_labels
self.current_routine = routine
local_labels = {}
for defn in routine.locals:
length = self.compute_length_of_defn(defn)
label = Label(defn.name, addr=defn.addr, length=length)
local_labels[defn.name] = label
declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label))
self.routine_locals[routine.name] = local_labels
self.current_routine = None
if compilation_roster is None:
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
for roster_row in compilation_roster:
for routine_name in roster_row[0:-1]:
self.compile_routine(self.routines[routine_name], skip_final_goto=True)
routine_name = roster_row[-1]
self.compile_routine(self.routines[routine_name])
for i, routine_name in enumerate(roster_row):
if i < len(roster_row) - 1:
self.compile_routine(self.routines[routine_name], next_routine=self.routines[roster_row[i + 1]])
else:
self.compile_routine(self.routines[routine_name])
for location, label in self.trampolines.iteritems():
for location, label in self.trampolines.items():
self.emitter.resolve_label(label)
self.emitter.emit(JMP(Indirect(self.get_label(location.name))))
self.emitter.emit(RTS())
# initialized data
for defn, label in defn_labels:
for defn, type_, label in declarations:
if defn.initial is not None:
initial_data = None
type_ = defn.location.type
if type_ == TYPE_BYTE:
initial_data = Byte(defn.initial)
elif type_ == TYPE_WORD:
@ -136,31 +156,57 @@ class Compiler(object):
self.emitter.emit(initial_data)
# uninitialized, "BSS" data
for defn, label in defn_labels:
for defn, type_, label in declarations:
if defn.initial is None and defn.addr is None:
self.emitter.resolve_bss_label(label)
def compile_routine(self, routine, skip_final_goto=False):
self.current_routine = routine
self.skip_final_goto = skip_final_goto
self.final_goto_seen = False
def compile_routine(self, routine, next_routine=None):
assert isinstance(routine, Routine)
self.current_routine = routine
if routine.block:
self.emitter.resolve_label(self.get_label(routine.name))
self.compile_block(routine.block)
if not self.final_goto_seen:
needs_rts = True
last_op = self.emitter.get_tail()
if isinstance(last_op, JSR):
if isinstance(last_op.operand, Absolute):
if isinstance(last_op.operand.value, Label):
label = last_op.operand.value
self.emitter.retract()
self.emitter.emit(JMP(Absolute(label)))
last_op = self.emitter.get_tail()
if isinstance(last_op, JMP):
needs_rts = False
if isinstance(last_op.operand, Absolute):
if isinstance(last_op.operand.value, Label):
if next_routine and last_op.operand.value.name == next_routine.name:
self.emitter.retract()
if needs_rts:
self.emitter.emit(RTS())
self.current_routine = None
self.skip_final_goto = False
def compile_block(self, block):
assert isinstance(block, Block)
block.shallow_contains_goto = False
for instr in block.instrs:
self.compile_instr(instr)
if isinstance(instr, GoTo):
block.shallow_contains_goto = True
def compile_instr(self, instr):
if isinstance(instr, SingleOp):
return self.compile_single_op(instr)
elif isinstance(instr, Call):
return self.compile_call(instr)
elif isinstance(instr, GoTo):
return self.compile_goto(instr)
elif isinstance(instr, If):
return self.compile_if(instr)
elif isinstance(instr, Repeat):
@ -169,6 +215,12 @@ 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)
elif isinstance(instr, PointInto):
return self.compile_point_into(instr)
elif isinstance(instr, Reset):
return self.compile_reset(instr)
else:
raise NotImplementedError
@ -187,10 +239,10 @@ class Compiler(object):
elif isinstance(src, ConstantRef):
self.emitter.emit(LDA(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef) and src.index == REG_X:
self.emitter.emit(LDA(AbsoluteX(self.get_label(src.ref.name))))
self.emitter.emit(LDA(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value))))
elif isinstance(src, IndexedRef) and src.index == REG_Y:
self.emitter.emit(LDA(AbsoluteY(self.get_label(src.ref.name))))
elif isinstance(src, IndirectRef) and isinstance(src.ref.type, PointerType):
self.emitter.emit(LDA(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value))))
elif isinstance(src, IndirectRef) and isinstance(self.get_type(src.ref), PointerType):
self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name))))
else:
self.emitter.emit(LDA(self.absolute_or_zero_page(self.get_label(src.name))))
@ -200,7 +252,7 @@ class Compiler(object):
elif isinstance(src, ConstantRef):
self.emitter.emit(LDX(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef) and src.index == REG_Y:
self.emitter.emit(LDX(AbsoluteY(self.get_label(src.ref.name))))
self.emitter.emit(LDX(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(LDX(self.absolute_or_zero_page(self.get_label(src.name))))
elif dest == REG_Y:
@ -209,7 +261,7 @@ class Compiler(object):
elif isinstance(src, ConstantRef):
self.emitter.emit(LDY(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef) and src.index == REG_X:
self.emitter.emit(LDY(AbsoluteX(self.get_label(src.ref.name))))
self.emitter.emit(LDY(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(LDY(self.absolute_or_zero_page(self.get_label(src.name))))
else:
@ -231,8 +283,8 @@ class Compiler(object):
REG_X: AbsoluteX,
REG_Y: AbsoluteY,
}[dest.index]
operand = mode_cls(self.get_label(dest.ref.name))
elif isinstance(dest, IndirectRef) and isinstance(dest.ref.type, PointerType):
operand = mode_cls(Offset(self.get_label(dest.ref.name), dest.offset.value))
elif isinstance(dest, IndirectRef) and isinstance(self.get_type(dest.ref), PointerType):
operand = IndirectY(self.get_label(dest.ref.name))
else:
operand = self.absolute_or_zero_page(self.get_label(dest.name))
@ -241,12 +293,31 @@ class Compiler(object):
raise UnsupportedOpcodeError(instr)
self.emitter.emit(op_cls(operand))
elif opcode == 'add':
if dest == REG_X or dest == REG_Y:
raise UnsupportedOpcodeError(instr)
if dest == REG_A:
if isinstance(src, ConstantRef):
self.emitter.emit(ADC(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(ADC(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_BYTE and self.get_type(dest) == TYPE_BYTE:
if isinstance(src, ConstantRef):
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(ADC(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(Absolute(dest_label)))
elif isinstance(src, LocationRef):
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(ADC(Absolute(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
else:
raise UnsupportedOpcodeError(instr)
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and self.get_type(dest) == TYPE_WORD:
if isinstance(src, ConstantRef):
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
@ -266,7 +337,7 @@ class Compiler(object):
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
raise UnsupportedOpcodeError(instr)
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and isinstance(dest.type, PointerType):
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and isinstance(self.get_type(dest), PointerType):
if isinstance(src, ConstantRef):
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(ZeroPage(dest_label)))
@ -289,12 +360,31 @@ class Compiler(object):
else:
raise UnsupportedOpcodeError(instr)
elif opcode == 'sub':
if dest == REG_X or dest == REG_Y:
raise UnsupportedOpcodeError(instr)
if dest == REG_A:
if isinstance(src, ConstantRef):
self.emitter.emit(SBC(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(SBC(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_BYTE and self.get_type(dest) == TYPE_BYTE:
if isinstance(src, ConstantRef):
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(SBC(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(Absolute(dest_label)))
elif isinstance(src, LocationRef):
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(SBC(Absolute(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
else:
raise UnsupportedOpcodeError(instr)
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and self.get_type(dest) == TYPE_WORD:
if isinstance(src, ConstantRef):
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
@ -316,10 +406,6 @@ class Compiler(object):
raise UnsupportedOpcodeError(instr)
else:
raise UnsupportedOpcodeError(instr)
elif opcode == 'inc':
self.compile_inc(instr, instr.dest)
elif opcode == 'dec':
self.compile_dec(instr, instr.dest)
elif opcode == 'cmp':
self.compile_cmp(instr, instr.src, instr.dest)
elif opcode in ('and', 'or', 'xor',):
@ -331,10 +417,17 @@ class Compiler(object):
if dest == REG_A:
if isinstance(src, ConstantRef):
self.emitter.emit(cls(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(src.name))))
else:
raise UnsupportedOpcodeError(instr)
elif opcode == 'inc':
self.compile_inc(instr, instr.dest)
elif opcode == 'dec':
self.compile_dec(instr, instr.dest)
elif opcode in ('shl', 'shr'):
cls = {
'shl': ROL,
@ -342,33 +435,11 @@ class Compiler(object):
}[opcode]
if dest == REG_A:
self.emitter.emit(cls())
elif isinstance(dest, IndexedRef):
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(cls(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
else:
raise UnsupportedOpcodeError(instr)
elif opcode == 'call':
location = instr.location
label = self.get_label(instr.location.name)
if isinstance(location.type, RoutineType):
self.emitter.emit(JSR(Absolute(label)))
elif isinstance(location.type, VectorType):
trampoline = self.trampolines.setdefault(
location, Label(location.name + '_trampoline')
)
self.emitter.emit(JSR(Absolute(trampoline)))
else:
raise NotImplementedError
elif opcode == 'goto':
self.final_goto_seen = True
if self.skip_final_goto:
pass
else:
location = instr.location
label = self.get_label(instr.location.name)
if isinstance(location.type, RoutineType):
self.emitter.emit(JMP(Absolute(label)))
elif isinstance(location.type, VectorType):
self.emitter.emit(JMP(Indirect(label)))
else:
raise NotImplementedError
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name))))
elif opcode == 'copy':
self.compile_copy(instr, instr.src, instr.dest)
elif opcode == 'trash':
@ -378,8 +449,54 @@ class Compiler(object):
else:
raise NotImplementedError(opcode)
def compile_call(self, instr):
location = instr.location
label = self.get_label(instr.location.name)
location_type = self.get_type(location)
if isinstance(location_type, RoutineType):
self.emitter.emit(JSR(Absolute(label)))
elif isinstance(location_type, VectorType):
trampoline = self.trampolines.setdefault(
location, Label(location.name + '_trampoline')
)
self.emitter.emit(JSR(Absolute(trampoline)))
else:
raise NotImplementedError(location_type)
def compile_goto(self, instr):
location = instr.location
label = self.get_label(instr.location.name)
location_type = self.get_type(location)
if isinstance(location_type, RoutineType):
self.emitter.emit(JMP(Absolute(label)))
elif isinstance(location_type, VectorType):
self.emitter.emit(JMP(Indirect(label)))
else:
raise NotImplementedError(location_type)
def compile_cmp(self, instr, src, dest):
"""`instr` is only for reporting purposes"""
if isinstance(src, LocationRef) and self.get_type(src) == TYPE_WORD:
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(CMP(Absolute(src_label)))
end_label = Label('end_label')
self.emitter.emit(BNE(Relative(end_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(CMP(Absolute(Offset(src_label, 1))))
self.emitter.resolve_label(end_label)
return
if isinstance(src, ConstantRef) and self.get_type(src) == TYPE_WORD:
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(dest_label)))
self.emitter.emit(CMP(Immediate(Byte(src.low_byte()))))
end_label = Label('end_label')
self.emitter.emit(BNE(Relative(end_label)))
self.emitter.emit(LDA(Absolute(Offset(dest_label, 1))))
self.emitter.emit(CMP(Immediate(Byte(src.high_byte()))))
self.emitter.resolve_label(end_label)
return
cls = {
'a': CMP,
'x': CPX,
@ -389,6 +506,10 @@ class Compiler(object):
raise UnsupportedOpcodeError(instr)
if isinstance(src, ConstantRef):
self.emitter.emit(cls(Immediate(Byte(src.value))))
elif isinstance(src, IndexedRef):
# FIXME might not work for some dest's (that is, cls's)
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
else:
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
@ -398,6 +519,9 @@ class Compiler(object):
self.emitter.emit(INX())
elif dest == REG_Y:
self.emitter.emit(INY())
elif isinstance(dest, IndexedRef):
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(INC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
else:
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
@ -407,98 +531,116 @@ class Compiler(object):
self.emitter.emit(DEX())
elif dest == REG_Y:
self.emitter.emit(DEY())
elif isinstance(dest, IndexedRef):
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(DEC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
else:
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
def compile_copy(self, instr, src, dest):
if isinstance(src, ConstantRef) and isinstance(dest, IndirectRef) and src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
if isinstance(src, (IndirectRef, IndexedRef)):
src_ref_type = self.get_type(src.ref)
else:
src_type = self.get_type(src)
if isinstance(dest, (IndirectRef, IndexedRef)):
dest_ref_type = self.get_type(dest.ref)
else:
dest_type = self.get_type(dest)
if isinstance(src, ConstantRef) and isinstance(dest, IndirectRef) and src_type == TYPE_BYTE and isinstance(dest_ref_type, PointerType):
### copy 123, [ptr] + y
dest_label = self.get_label(dest.ref.name)
self.emitter.emit(LDA(Immediate(Byte(src.value))))
self.emitter.emit(STA(IndirectY(dest_label)))
elif isinstance(src, LocationRef) and isinstance(dest, IndirectRef) and src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
elif isinstance(src, LocationRef) and isinstance(dest, IndirectRef) and src_type == TYPE_BYTE and isinstance(dest_ref_type, PointerType):
### copy b, [ptr] + y
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.ref.name)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(STA(IndirectY(dest_label)))
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef) and dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType):
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef) and dest_type == TYPE_BYTE and isinstance(src_ref_type, PointerType):
### copy [ptr] + y, b
src_label = self.get_label(src.ref.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(IndirectY(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
### copy ^buf, ptr
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef) and isinstance(src_ref_type, PointerType) and isinstance(dest_ref_type, PointerType):
### copy [ptra] + y, [ptrb] + y
src_label = self.get_label(src.ref.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
self.emitter.emit(STA(ZeroPage(dest_label)))
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
dest_label = self.get_label(dest.ref.name)
self.emitter.emit(LDA(IndirectY(src_label)))
self.emitter.emit(STA(IndirectY(dest_label)))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and src_type == TYPE_WORD and TableType.is_a_table_type(dest_ref_type, TYPE_WORD):
### copy w, wtab + y
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src_type, VectorType) and isinstance(dest_ref_type, TableType) and isinstance(dest_ref_type.of_type, VectorType):
### copy vec, vtab + y
# FIXME this is the exact same as above - can this be simplified?
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src_type, RoutineType) and isinstance(dest_ref_type, TableType) and isinstance(dest_ref_type.of_type, VectorType):
### copy routine, vtab + y
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef) and src_type == TYPE_WORD and TableType.is_a_table_type(dest_ref_type, TYPE_WORD):
### copy 9999, wtab + y
dest_label = self.get_label(dest.ref.name)
mode = self.addressing_mode_for_index(dest.index)
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and TableType.is_a_table_type(src_ref_type, TYPE_WORD) and dest_type == TYPE_WORD:
### copy wtab + y, w
src_label = self.get_label(src.ref.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and isinstance(dest.type, VectorType) and isinstance(src.ref.type, TableType) and isinstance(src.ref.type.of_type, VectorType):
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and isinstance(dest_type, VectorType) and isinstance(src_ref_type, TableType) and isinstance(src_ref_type.of_type, VectorType):
### copy vtab + y, vec
# FIXME this is the exact same as above - can this be simplified?
src_label = self.get_label(src.ref.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
mode = self.addressing_mode_for_index(src.index)
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif src.type == TYPE_BYTE and dest.type == TYPE_BYTE and not isinstance(src, ConstantRef):
elif src_type == TYPE_BYTE and dest_type == TYPE_BYTE and not isinstance(src, ConstantRef):
### copy b1, b2
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(STA(Absolute(dest_label)))
elif src.type == TYPE_WORD and dest.type == TYPE_WORD and isinstance(src, ConstantRef):
elif src_type == TYPE_WORD and dest_type == TYPE_WORD and isinstance(src, ConstantRef):
### copy 9999, w
dest_label = self.get_label(dest.name)
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif src.type == TYPE_WORD and dest.type == TYPE_WORD and not isinstance(src, ConstantRef):
elif src_type == TYPE_WORD and dest_type == TYPE_WORD and not isinstance(src, ConstantRef):
### copy w1, w2
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
@ -506,7 +648,7 @@ class Compiler(object):
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif isinstance(src.type, VectorType) and isinstance(dest.type, VectorType):
elif isinstance(src_type, VectorType) and isinstance(dest_type, VectorType):
### copy v1, v2
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
@ -514,7 +656,7 @@ class Compiler(object):
self.emitter.emit(STA(Absolute(dest_label)))
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType):
elif isinstance(src_type, RoutineType) and isinstance(dest_type, VectorType):
### copy routine, vec
src_label = self.get_label(src.name)
dest_label = self.get_label(dest.name)
@ -523,7 +665,7 @@ class Compiler(object):
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
else:
raise NotImplementedError(src.type)
raise NotImplementedError(src_type)
def compile_if(self, instr):
cls = {
@ -543,12 +685,17 @@ class Compiler(object):
else_label = Label('else_label')
self.emitter.emit(cls(Relative(else_label)))
self.compile_block(instr.block1)
if instr.block2 is not None:
end_label = Label('end_label')
self.emitter.emit(JMP(Absolute(end_label)))
self.emitter.resolve_label(else_label)
self.compile_block(instr.block2)
self.emitter.resolve_label(end_label)
if instr.block1.shallow_contains_goto:
self.emitter.resolve_label(else_label)
self.compile_block(instr.block2)
else:
end_label = Label('end_label')
self.emitter.emit(JMP(Absolute(end_label)))
self.emitter.resolve_label(else_label)
self.compile_block(instr.block2)
self.emitter.resolve_label(end_label)
else:
self.emitter.resolve_label(else_label)
@ -592,3 +739,49 @@ class Compiler(object):
self.emitter.emit(SEI())
self.compile_block(instr.block)
self.emitter.emit(CLI())
def compile_save(self, instr):
for location in instr.locations:
if location == REG_A:
self.emitter.emit(PHA())
elif location == REG_X:
self.emitter.emit(TXA())
self.emitter.emit(PHA())
elif location == REG_Y:
self.emitter.emit(TYA())
self.emitter.emit(PHA())
else:
src_label = self.get_label(location.name)
self.emitter.emit(LDA(Absolute(src_label)))
self.emitter.emit(PHA())
self.compile_block(instr.block)
for location in reversed(instr.locations):
if location == REG_A:
self.emitter.emit(PLA())
elif location == REG_X:
self.emitter.emit(PLA())
self.emitter.emit(TAX())
elif location == REG_Y:
self.emitter.emit(PLA())
self.emitter.emit(TAY())
else:
src_label = self.get_label(location.name)
self.emitter.emit(PLA())
self.emitter.emit(STA(Absolute(src_label)))
def compile_point_into(self, instr):
self.pointer_assoc[instr.pointer.name] = instr.table.name
self.compile_block(instr.block)
del self.pointer_assoc[instr.pointer.name]
def compile_reset(self, instr):
table_name = self.pointer_assoc[instr.pointer.name]
src_label = Offset(self.get_label(table_name), instr.offset.value)
dest_label = self.get_label(instr.pointer.name)
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
self.emitter.emit(STA(ZeroPage(dest_label)))
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))

332
src/sixtypical/context.py Normal file
View File

@ -0,0 +1,332 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# encoding: UTF-8
from sixtypical.model import (
RoutineType, ConstantRef, LocationRef, IndirectRef, IndexedRef,
)
class AnalysisContext(object):
"""
A location is touched if it was changed (or even potentially
changed) during this routine, or some routine called by this routine.
A location is meaningful if it was an input to this routine,
or if it was set to a meaningful value by some operation in this
routine (or some routine called by this routine).
If a location is meaningful, it has a range. This range represents
the lowest and highest values that it might possibly be (i.e. we know
it cannot possibly be below the lowest or above the highest.) In the
absence of any usage information, the range of a byte, is 0..255 and
the range of a word is 0..65535.
A location is writeable if it was listed in the outputs and trashes
lists of this routine. A location can also be temporarily marked
unwriteable in certain contexts, such as `for` loops.
"""
def __init__(self, symtab, routine, inputs, outputs, trashes):
from sixtypical.analyzer import ConstantConstraintError, InconsistentConstraintsError
self.symtab = symtab
self.routine = routine # Routine (AST node)
self._touched = set() # {LocationRef}
self._range = dict() # LocationRef -> (Int, Int)
self._writeable = set() # {LocationRef}
self._terminated = False
self._gotos_encountered = set()
self._pointer_assoc = dict()
for ref in inputs:
if self.is_constant(ref):
raise ConstantConstraintError(self.routine, ref.name)
self._range[ref] = self.max_range(ref)
output_names = set()
for ref in outputs:
if self.is_constant(ref):
raise ConstantConstraintError(self.routine, ref.name)
output_names.add(ref.name)
self._writeable.add(ref)
for ref in trashes:
if self.is_constant(ref):
raise ConstantConstraintError(self.routine, ref.name)
if ref.name in output_names:
raise InconsistentConstraintsError(self.routine, ref.name)
self._writeable.add(ref)
def __str__(self):
return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format(
self.__class__.__name__,
LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
)
def to_json_data(self):
type_ = self.symtab.fetch_global_type(self.routine.name)
return {
'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)),
'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)),
'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)),
'touched': ','.join(sorted(loc.name for loc in self._touched)),
'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()),
'writeable': ','.join(sorted(loc.name for loc in self._writeable)),
'terminated': self._terminated,
'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)),
}
def clone(self):
c = AnalysisContext(self.symtab, self.routine, [], [], [])
c._touched = set(self._touched)
c._range = dict(self._range)
c._writeable = set(self._writeable)
c._pointer_assoc = dict(self._pointer_assoc)
c._gotos_encountered = set(self._gotos_encountered)
return c
def update_from(self, other):
"""Replaces the information in this context, with the information from the other context.
This is an overwriting action - it does not attempt to merge the contexts.
We do not replace the gotos_encountered for technical reasons. (In `analyze_if`,
we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the
set of contexts we are updating from, and we want to retain our own.)"""
self.routine = other.routine
self._touched = set(other._touched)
self._range = dict(other._range)
self._writeable = set(other._writeable)
self._terminated = other._terminated
self._pointer_assoc = dict(other._pointer_assoc)
def each_meaningful(self):
for ref in self._range.keys():
yield ref
def each_touched(self):
for ref in self._touched:
yield ref
def each_writeable(self):
for ref in self._writeable:
yield ref
def assert_meaningful(self, *refs, **kwargs):
from sixtypical.analyzer import UnmeaningfulReadError
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
for ref in refs:
if self.symtab.has_local(self.routine.name, ref.name):
if ref not in self._range:
message = ref.name
if kwargs.get('message'):
message += ' (%s)' % kwargs['message']
raise exception_class(self.routine, message)
else:
continue
if self.is_constant(ref):
pass
elif isinstance(ref, LocationRef):
if ref not in self._range:
message = ref.name
if kwargs.get('message'):
message += ' (%s)' % kwargs['message']
raise exception_class(self.routine, message)
elif isinstance(ref, IndexedRef):
self.assert_meaningful(ref.ref, **kwargs)
self.assert_meaningful(ref.index, **kwargs)
else:
raise NotImplementedError(ref)
def assert_writeable(self, *refs, **kwargs):
from sixtypical.analyzer import ForbiddenWriteError
exception_class = kwargs.get('exception_class', ForbiddenWriteError)
for ref in refs:
# locals are always writeable
if self.symtab.has_local(self.routine.name, ref.name):
continue
if ref not in self._writeable:
message = ref.name
if kwargs.get('message'):
message += ' (%s)' % kwargs['message']
raise exception_class(self.routine, message)
def assert_in_range(self, inside, outside, offset):
"""Given two locations, assert that the first location, offset by the given offset,
is contained 'inside' the second location."""
from sixtypical.analyzer import RangeExceededError
assert isinstance(inside, LocationRef)
assert isinstance(outside, LocationRef)
# inside should always be meaningful
inside_range = self._range[inside]
# outside might not be meaningful, so default to max range if necessary
if outside in self._range:
outside_range = self._range[outside]
else:
outside_range = self.max_range(outside)
if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]:
raise RangeExceededError(self.routine,
"Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
inside, inside_range, offset, outside, outside_range
)
)
def set_touched(self, *refs):
for ref in refs:
self._touched.add(ref)
# TODO: it might be possible to invalidate the range here
def set_meaningful(self, *refs):
for ref in refs:
if ref not in self._range:
self._range[ref] = self.max_range(ref)
def set_top_of_range(self, ref, top):
self.assert_meaningful(ref)
(bottom, _) = self._range[ref]
self._range[ref] = (bottom, top)
def set_bottom_of_range(self, ref, bottom):
self.assert_meaningful(ref)
(top, _) = self._range[ref]
self._range[ref] = (bottom, top)
def set_range(self, ref, bottom, top):
self.assert_meaningful(ref)
self._range[ref] = (bottom, top)
def get_top_of_range(self, ref):
if isinstance(ref, ConstantRef):
return ref.value
self.assert_meaningful(ref)
(_, top) = self._range[ref]
return top
def get_bottom_of_range(self, ref):
if isinstance(ref, ConstantRef):
return ref.value
self.assert_meaningful(ref)
(bottom, _) = self._range[ref]
return bottom
def get_range(self, ref):
if isinstance(ref, ConstantRef):
return (ref.value, ref.value)
self.assert_meaningful(ref)
(bottom, top) = self._range[ref]
return bottom, top
def copy_range(self, src, dest):
self.assert_meaningful(src)
if src in self._range:
src_range = self._range[src]
else:
src_range = self.max_range(src)
self._range[dest] = src_range
def invalidate_range(self, ref):
self.assert_meaningful(ref)
self._range[ref] = self.max_range(ref)
def set_unmeaningful(self, *refs):
for ref in refs:
if ref in self._range:
del self._range[ref]
def set_written(self, *refs):
"""A "helper" method which does the following common sequence for
the given refs: asserts they're all writable, and sets them all
as touched and meaningful."""
self.assert_writeable(*refs)
self.set_touched(*refs)
self.set_meaningful(*refs)
def set_unwriteable(self, *refs):
"""Intended to be used for implementing analyzing `for`."""
for ref in refs:
self._writeable.remove(ref)
def set_writeable(self, *refs):
"""Intended to be used for implementing analyzing `for`, but also used in `save`."""
for ref in refs:
self._writeable.add(ref)
def encounter_gotos(self, gotos):
self._gotos_encountered |= gotos
def encountered_gotos(self):
return self._gotos_encountered
def set_terminated(self):
# Having a terminated context and having encountered gotos is not the same thing.
self._terminated = True
def has_terminated(self):
return self._terminated
def extract(self, location):
"""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,
)
self.set_writeable(location)
return baton
def re_introduce(self, baton):
"""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:
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)
def get_assoc(self, pointer):
return self._pointer_assoc.get(pointer)
def set_assoc(self, pointer, table):
self._pointer_assoc[pointer] = table
def is_constant(self, ref):
"""read-only means that the program cannot change the value
of a location. constant means that the value of the location
will not change during the lifetime of the program."""
if isinstance(ref, ConstantRef):
return True
if isinstance(ref, (IndirectRef, IndexedRef)):
return False
if isinstance(ref, LocationRef):
type_ = self.symtab.fetch_global_type(ref.name)
return isinstance(type_, RoutineType)
raise NotImplementedError
def max_range(self, ref):
if isinstance(ref, ConstantRef):
return (ref.value, ref.value)
elif self.symtab.has_local(self.routine.name, ref.name):
return self.symtab.fetch_local_type(self.routine.name, ref.name).max_range
else:
return self.symtab.fetch_global_type(ref.name).max_range

View File

@ -1,3 +1,7 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
"""Binary machine code emitter. Used in SixtyPical to emit 6502 machine code,
but not specific to SixtyPical, or 6502. Not even necessarily machine code -
though some parts are written around the assumptions of 8-bit architectures."""
@ -8,12 +12,15 @@ class Emittable(object):
raise NotImplementedError
def serialize(self, addr):
"""Should return an array of unsigned bytes (integers from 0 to 255.)
`addr` is the address the value is being serialized at; for most objects
it makes no difference, but some objects (like relative branches) do care."""
raise NotImplementedError
class Byte(Emittable):
def __init__(self, value):
if isinstance(value, basestring):
if isinstance(value, str):
value = ord(value)
if value < -127 or value > 255:
raise IndexError(value)
@ -24,8 +31,8 @@ class Byte(Emittable):
def size(self):
return 1
def serialize(self, addr=None):
return chr(self.value)
def serialize(self, addr):
return [self.value]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.value)
@ -39,11 +46,11 @@ class Word(Emittable):
def size(self):
return 2
def serialize(self, addr=None):
def serialize(self, addr):
word = self.value
low = word & 255
high = (word >> 8) & 255
return chr(low) + chr(high)
return [low, high]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.value)
@ -59,10 +66,12 @@ class Table(Emittable):
def size(self):
return self._size
def serialize(self, addr=None):
buf = ''.join([emittable.serialize() for emittable in self.value])
def serialize(self, addr):
buf = []
for emittable in self.value:
buf.extend(emittable.serialize(addr)) # FIXME: addr + offset
while len(buf) < self.size():
buf += chr(0)
buf.append(0)
return buf
def __repr__(self):
@ -84,17 +93,17 @@ class Label(Emittable):
def size(self):
return 2
def serialize(self, addr=None, offset=0):
def serialize(self, addr, offset=0):
assert self.addr is not None, "unresolved label: %s" % self.name
return Word(self.addr + offset).serialize()
return Word(self.addr + offset).serialize(addr)
def serialize_relative_to(self, addr):
assert self.addr is not None, "unresolved label: %s" % self.name
return Byte(self.addr - (addr + 2)).serialize()
return Byte(self.addr - (addr + 2)).serialize(addr)
def serialize_as_zero_page(self, offset=0):
def serialize_as_zero_page(self, addr, offset=0):
assert self.addr is not None, "unresolved label: %s" % self.name
return Byte(self.addr + offset).serialize()
return Byte(self.addr + offset).serialize(addr)
def __repr__(self):
addr_s = ', addr=%r' % self.addr if self.addr is not None else ''
@ -111,11 +120,11 @@ class Offset(Emittable):
def size(self):
self.label.size()
def serialize(self, addr=None):
return self.label.serialize(offset=self.offset)
def serialize(self, addr):
return self.label.serialize(addr, offset=self.offset)
def serialize_as_zero_page(self, offset=0):
return self.label.serialize_as_zero_page(offset=self.offset)
def serialize_as_zero_page(self, addr, offset=0):
return self.label.serialize_as_zero_page(addr, offset=self.offset)
def __repr__(self):
return "%s(%r, %r)" % (self.__class__.__name__, self.label, self.offset)
@ -123,14 +132,14 @@ class Offset(Emittable):
class HighAddressByte(Emittable):
def __init__(self, label):
assert isinstance(label, Label)
assert isinstance(label, (Label, Offset))
self.label = label
def size(self):
return 1
def serialize(self, addr=None):
return self.label.serialize()[0]
def serialize(self, addr):
return [self.label.serialize(addr)[0]]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.label)
@ -138,14 +147,14 @@ class HighAddressByte(Emittable):
class LowAddressByte(Emittable):
def __init__(self, label):
assert isinstance(label, Label)
assert isinstance(label, (Label, Offset))
self.label = label
def size(self):
return 1
def serialize(self, addr=None):
return self.label.serialize()[1]
def serialize(self, addr):
return [self.label.serialize(addr)[1]]
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.label)
@ -166,11 +175,22 @@ class Emitter(object):
self.accum.append(thing)
self.addr += thing.size()
def serialize(self, stream):
def get_tail(self):
if self.accum:
return self.accum[-1]
else:
return None
def retract(self):
thing = self.accum.pop()
self.addr -= thing.size()
def serialize_to(self, stream):
"""`stream` should be a file opened in binary mode."""
addr = self.start_addr
for emittable in self.accum:
chunk = emittable.serialize(addr)
stream.write(chunk)
stream.write(bytearray(chunk))
addr += len(chunk)
def make_label(self, name=None):

View File

@ -1,3 +1,7 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# encoding: UTF-8
from copy import copy
@ -7,7 +11,8 @@ from sixtypical.model import RoutineType
class FallthruAnalyzer(object):
def __init__(self, debug=False):
def __init__(self, symtab, debug=False):
self.symtab = symtab
self.debug = debug
def analyze_program(self, program):
@ -16,7 +21,7 @@ class FallthruAnalyzer(object):
self.fallthru_map = {}
for routine in program.routines:
encountered_gotos = list(routine.encountered_gotos)
if len(encountered_gotos) == 1 and isinstance(encountered_gotos[0].type, RoutineType):
if len(encountered_gotos) == 1 and isinstance(self.symtab.fetch_global_type(encountered_gotos[0].name), RoutineType):
self.fallthru_map[routine.name] = encountered_gotos[0].name
else:
self.fallthru_map[routine.name] = None
@ -43,7 +48,7 @@ class FallthruAnalyzer(object):
while pending_routines:
chains = [self.find_chain(k, pending_routines) for k in pending_routines.keys()]
chains.sort(key=len, reverse=True)
chains.sort(key=lambda x: (len(x), str(x)), reverse=True)
c = chains[0]
roster.append(c)
for k in c:

View File

@ -1,3 +1,7 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
"""Emittables for 6502 machine code."""
from sixtypical.emitter import Emittable, Byte, Label, Offset, LowAddressByte, HighAddressByte
@ -8,7 +12,7 @@ class AddressingMode(Emittable):
"""Size of the operand for the mode (not including the opcode)"""
raise NotImplementedError
def serialize(self, addr=None):
def serialize(self, addr):
raise NotImplementedError
def __repr__(self):
@ -19,8 +23,8 @@ class Implied(AddressingMode):
def size(self):
return 0
def serialize(self, addr=None):
return ''
def serialize(self, addr):
return []
def __repr__(self):
return "%s()" % (self.__class__.__name__)
@ -34,8 +38,8 @@ class Immediate(AddressingMode):
def size(self):
return 1
def serialize(self, addr=None):
return self.value.serialize()
def serialize(self, addr):
return self.value.serialize(addr)
class Absolute(AddressingMode):
@ -46,8 +50,8 @@ class Absolute(AddressingMode):
def size(self):
return 2
def serialize(self, addr=None):
return self.value.serialize()
def serialize(self, addr):
return self.value.serialize(addr)
class AbsoluteX(Absolute):
@ -66,8 +70,8 @@ class ZeroPage(AddressingMode):
def size(self):
return 1
def serialize(self, addr=None):
return self.value.serialize_as_zero_page()
def serialize(self, addr):
return self.value.serialize_as_zero_page(addr)
class Indirect(AddressingMode):
@ -78,8 +82,8 @@ class Indirect(AddressingMode):
def size(self):
return 2
def serialize(self, addr=None):
return self.value.serialize()
def serialize(self, addr):
return self.value.serialize(addr)
class IndirectY(ZeroPage):
@ -94,7 +98,7 @@ class Relative(AddressingMode):
def size(self):
return 1
def serialize(self, addr=None):
def serialize(self, addr):
return self.value.serialize_relative_to(addr)
@ -108,11 +112,8 @@ class Instruction(Emittable):
def size(self):
return 1 + self.operand.size() if self.operand else 0
def serialize(self, addr=None):
return (
chr(self.opcodes[self.operand.__class__]) +
self.operand.serialize(addr)
)
def serialize(self, addr):
return [self.opcodes[self.operand.__class__]] + self.operand.serialize(addr)
def __repr__(self):
return "%s(%r)" % (self.__class__.__name__, self.operand)
@ -133,6 +134,7 @@ class AND(Instruction):
Absolute: 0x2d,
AbsoluteX: 0x3d,
AbsoluteY: 0x39,
ZeroPage: 0x25,
}
@ -210,6 +212,7 @@ class CPY(Instruction):
class DEC(Instruction):
opcodes = {
Absolute: 0xce,
AbsoluteX: 0xde,
}
@ -231,6 +234,7 @@ class EOR(Instruction):
Absolute: 0x4d,
AbsoluteX: 0x5d,
AbsoluteY: 0x59,
ZeroPage: 0x45,
}
@ -299,6 +303,19 @@ class ORA(Instruction):
Absolute: 0x0d,
AbsoluteX: 0x1d,
AbsoluteY: 0x19,
ZeroPage: 0x05,
}
class PHA(Instruction):
opcodes = {
Implied: 0x48,
}
class PLA(Instruction):
opcodes = {
Implied: 0x68,
}

View File

@ -1,67 +1,39 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
"""Data/storage model for SixtyPical."""
class Type(object):
def __init__(self, name, max_range=None):
self.name = name
self.max_range = max_range
def __repr__(self):
return 'Type(%r)' % self.name
def __str__(self):
return self.name
def __eq__(self, other):
return isinstance(other, Type) and other.name == self.name
def __hash__(self):
return hash(self.name)
def backpatch_constraint_labels(self, resolver):
def resolve(w):
if not isinstance(w, basestring):
return w
return resolver(w)
if isinstance(self, TableType):
self.of_type.backpatch_constraint_labels(resolver)
elif isinstance(self, VectorType):
self.of_type.backpatch_constraint_labels(resolver)
elif isinstance(self, RoutineType):
self.inputs = set([resolve(w) for w in self.inputs])
self.outputs = set([resolve(w) for w in self.outputs])
self.trashes = set([resolve(w) for w in self.trashes])
from collections import namedtuple
TYPE_BIT = Type('bit', max_range=(0, 1))
TYPE_BYTE = Type('byte', max_range=(0, 255))
TYPE_WORD = Type('word', max_range=(0, 65535))
class BitType(namedtuple('BitType', ['typename'])):
max_range = (0, 1)
def __new__(cls):
return super(BitType, cls).__new__(cls, 'bit')
TYPE_BIT = BitType()
class ByteType(namedtuple('ByteType', ['typename'])):
max_range = (0, 255)
def __new__(cls):
return super(ByteType, cls).__new__(cls, 'byte')
TYPE_BYTE = ByteType()
class RoutineType(Type):
class WordType(namedtuple('WordType', ['typename'])):
max_range = (0, 65535)
def __new__(cls):
return super(WordType, cls).__new__(cls, 'word')
TYPE_WORD = WordType()
class RoutineType(namedtuple('RoutineType', ['typename', 'inputs', 'outputs', 'trashes'])):
"""This memory location contains the code for a routine."""
def __init__(self, inputs=None, outputs=None, trashes=None):
self.name = 'routine'
self.inputs = inputs or set()
self.outputs = outputs or set()
self.trashes = trashes or set()
max_range = (0, 0)
def __repr__(self):
return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (
self.__class__.__name__, self.name, self.inputs, self.outputs, self.trashes
)
def __eq__(self, other):
return isinstance(other, RoutineType) and (
other.name == self.name and
other.inputs == self.inputs and
other.outputs == self.outputs and
other.trashes == self.trashes
)
def __hash__(self):
return hash(self.name) ^ hash(self.inputs) ^ hash(self.outputs) ^ hash(self.trashes)
def __new__(cls, *args):
return super(RoutineType, cls).__new__(cls, 'routine', *args)
@classmethod
def executable_types_compatible(cls_, src, dest):
@ -81,214 +53,68 @@ class RoutineType(Type):
return False
class VectorType(Type):
class VectorType(namedtuple('VectorType', ['typename', 'of_type'])):
"""This memory location contains the address of some other type (currently, only RoutineType)."""
def __init__(self, of_type):
self.name = 'vector'
self.of_type = of_type
max_range = (0, 65535)
def __repr__(self):
return '%s(%r)' % (
self.__class__.__name__, self.of_type
)
def __eq__(self, other):
return self.name == other.name and self.of_type == other.of_type
def __hash__(self):
return hash(self.name) ^ hash(self.of_type)
def __new__(cls, *args):
return super(VectorType, cls).__new__(cls, 'vector', *args)
class TableType(Type):
def __init__(self, of_type, size):
self.of_type = of_type
self.size = size
self.name = '{} table[{}]'.format(self.of_type.name, self.size)
class TableType(namedtuple('TableType', ['typename', 'of_type', 'size'])):
def __repr__(self):
return '%s(%r, %r)' % (
self.__class__.__name__, self.of_type, self.size
)
def __new__(cls, *args):
return super(TableType, cls).__new__(cls, 'table', *args)
@property
def max_range(self):
return (0, self.size - 1)
@classmethod
def is_a_table_type(cls_, x, of_type):
return isinstance(x, TableType) and x.of_type == of_type
class BufferType(Type):
def __init__(self, size):
self.size = size
self.name = 'buffer[%s]' % self.size
class PointerType(namedtuple('PointerType', ['typename'])):
max_range = (0, 65535)
def __new__(cls):
return super(PointerType, cls).__new__(cls, 'pointer')
class PointerType(Type):
def __init__(self):
self.name = 'pointer'
# --------------------------------------------------------
class Ref(object):
def is_constant(self):
"""read-only means that the program cannot change the value
of a location. constant means that the value of the location
will not change during the lifetime of the program."""
raise NotImplementedError("class {} must implement is_constant()".format(self.__class__.__name__))
def max_range(self):
raise NotImplementedError("class {} must implement max_range()".format(self.__class__.__name__))
class LocationRef(Ref):
def __init__(self, type, name):
self.type = type
self.name = name
def __eq__(self, other):
# Ordinarily there will only be one ref with a given name,
# but because we store the type in here and we want to treat
# these objects as immutable, we compare the types, too,
# just to be sure.
equal = isinstance(other, self.__class__) and other.name == self.name
if equal:
assert other.type == self.type, repr((self, other))
return equal
def __hash__(self):
return hash(self.name + str(self.type))
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name)
def __str__(self):
return "{}:{}".format(self.name, self.type)
def is_constant(self):
return isinstance(self.type, RoutineType)
def max_range(self):
try:
return self.type.max_range
except:
return (0, 0)
class LocationRef(namedtuple('LocationRef', ['reftype', 'name'])):
def __new__(cls, *args):
return super(LocationRef, cls).__new__(cls, 'location', *args)
@classmethod
def format_set(cls, location_refs):
return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs)])
return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs, key=lambda x: x.name)])
class IndirectRef(Ref):
def __init__(self, ref):
self.ref = ref
def __eq__(self, other):
return isinstance(other, self.__class__) and self.ref == other.ref
def __hash__(self):
return hash(self.__class__.name) ^ hash(self.ref)
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.ref)
class IndirectRef(namedtuple('IndirectRef', ['reftype', 'ref'])):
def __new__(cls, *args):
return super(IndirectRef, cls).__new__(cls, 'indirect', *args)
@property
def name(self):
return '[{}]+y'.format(self.ref.name)
def is_constant(self):
return False
class IndexedRef(Ref):
def __init__(self, ref, index):
self.ref = ref
self.index = index
def __eq__(self, other):
return isinstance(other, self.__class__) and self.ref == other.ref and self.index == other.index
def __hash__(self):
return hash(self.__class__.name) ^ hash(self.ref) ^ hash(self.index)
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.index)
class IndexedRef(namedtuple('IndexedRef', ['reftype', 'ref', 'offset', 'index'])):
def __new__(cls, *args):
return super(IndexedRef, cls).__new__(cls, 'indexed', *args)
@property
def name(self):
return '{}+{}'.format(self.ref.name, self.index.name)
def is_constant(self):
return False
return '{}+{}+{}'.format(self.ref.name, self.offset, self.index.name)
class AddressRef(Ref):
def __init__(self, ref):
self.ref = ref
def __eq__(self, other):
return self.ref == other.ref
def __hash__(self):
return hash(self.__class__.name) ^ hash(self.ref)
def __repr__(self):
return '%s(%r)' % (self.__class__.__name__, self.ref)
@property
def name(self):
return '^{}'.format(self.ref.name)
def is_constant(self):
return True
class PartRef(Ref):
"""For 'low byte of' location and 'high byte of' location modifiers.
height=0 = low byte, height=1 = high byte.
NOTE: Not actually used yet. Might require more thought before it's usable.
"""
def __init__(self, ref, height):
assert isinstance(ref, Ref)
assert ref.type == TYPE_WORD
self.ref = ref
self.height = height
self.type = TYPE_BYTE
def __eq__(self, other):
return isinstance(other, PartRef) and (
other.height == self.height and other.ref == self.ref
)
def __hash__(self):
return hash(self.ref) ^ hash(self.height) ^ hash(self.type)
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.height)
def is_constant(self):
return self.ref.is_constant()
class ConstantRef(Ref):
def __init__(self, type, value):
self.type = type
self.value = value
def __eq__(self, other):
return isinstance(other, ConstantRef) and (
other.type == self.type and other.value == self.value
)
def __hash__(self):
return hash(str(self.value) + str(self.type))
def __repr__(self):
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.value)
def is_constant(self):
return True
def max_range(self):
return (self.value, self.value)
class ConstantRef(namedtuple('ConstantRef', ['reftype', 'type', 'value'])):
def __new__(cls, *args):
return super(ConstantRef, cls).__new__(cls, 'constant', *args)
def high_byte(self):
return (self.value >> 8) & 255
@ -310,12 +136,16 @@ class ConstantRef(Ref):
value -= 256
return ConstantRef(self.type, value)
@property
def name(self):
return 'constant({})'.format(self.value)
REG_A = LocationRef(TYPE_BYTE, 'a')
REG_X = LocationRef(TYPE_BYTE, 'x')
REG_Y = LocationRef(TYPE_BYTE, 'y')
FLAG_Z = LocationRef(TYPE_BIT, 'z')
FLAG_C = LocationRef(TYPE_BIT, 'c')
FLAG_N = LocationRef(TYPE_BIT, 'n')
FLAG_V = LocationRef(TYPE_BIT, 'v')
REG_A = LocationRef('a')
REG_X = LocationRef('x')
REG_Y = LocationRef('y')
FLAG_Z = LocationRef('z')
FLAG_C = LocationRef('c')
FLAG_N = LocationRef('n')
FLAG_V = LocationRef('v')

View File

@ -0,0 +1,78 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
"""Executable file writer."""
from sixtypical.emitter import Emitter, Byte, Word
class Outputter(object):
def __init__(self, fh, start_addr=None):
self.start_addr = self.__class__.start_addr
if start_addr is not None:
self.start_addr = start_addr
self.prelude = self.__class__.prelude
self.fh = fh
self.emitter = Emitter(self.start_addr)
def write_header(self):
pass
def write_prelude(self):
self.write_header()
for byte in self.prelude:
self.emitter.emit(Byte(byte))
def write_postlude(self):
pass
class RawOutputter(Outputter):
start_addr = 0x0000
prelude = []
class PrgOutputter(Outputter):
start_addr = 0xc000
prelude = []
def write_header(self):
# If we are outputting a .PRG, we output the load address first.
# We don't use the Emitter for this b/c not part of addr space.
self.fh.write(bytearray(Word(self.start_addr).serialize(0)))
class C64BasicPrgOutputter(PrgOutputter):
start_addr = 0x0801
prelude = [0x10, 0x08, 0xc9, 0x07, 0x9e, 0x32,
0x30, 0x36, 0x31, 0x00, 0x00, 0x00]
class Vic20BasicPrgOutputter(PrgOutputter):
start_addr = 0x1001
prelude = [0x0b, 0x10, 0xc9, 0x07, 0x9e, 0x34,
0x31, 0x30, 0x39, 0x00, 0x00, 0x00]
class Atari2600CartOutputter(Outputter):
start_addr = 0xf000
prelude = [0x78, 0xd8, 0xa2, 0xff, 0x9a, 0xa9,
0x00, 0x95, 0x00, 0xca, 0xd0, 0xfb]
def write_postlude(self):
# If we are outputting a cartridge with boot and BRK address
# at the end, pad to ROM size minus 4 bytes, and emit addresses.
self.emitter.pad_to_size(4096 - 4)
self.emitter.emit(Word(self.start_addr))
self.emitter.emit(Word(self.start_addr))
def outputter_class_for(output_format):
return {
'raw': RawOutputter,
'prg': PrgOutputter,
'c64-basic-prg': C64BasicPrgOutputter,
'vic20-basic-prg': Vic20BasicPrgOutputter,
'atari2600-cart': Atari2600CartOutputter,
}[output_format]

View File

@ -1,133 +1,160 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# 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, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
)
from sixtypical.model import (
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
RoutineType, VectorType, TableType, BufferType, PointerType,
LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef,
RoutineType, VectorType, TableType, PointerType,
ConstantRef, IndirectRef, IndexedRef,
)
from sixtypical.scanner import Scanner
from sixtypical.symtab import SymEntry
class SymEntry(object):
def __init__(self, ast_node, model):
self.ast_node = ast_node
self.model = model
class ForwardReference(object):
def __init__(self, name):
self.name = name
def __repr__(self):
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
class ParsingContext(object):
def __init__(self):
self.symbols = {} # token -> SymEntry
self.statics = {} # token -> SymEntry
self.typedefs = {} # token -> Type AST
self.consts = {} # token -> Loc
for token in ('a', 'x', 'y'):
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token))
for token in ('c', 'z', 'n', 'v'):
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token))
def __str__(self):
return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
def lookup(self, name):
if name in self.statics:
return self.statics[name].model
if name in self.symbols:
return self.symbols[name].model
return None
return "%s(%r)" % (self.__class__.__name__, self.name)
class Parser(object):
def __init__(self, context, text, filename):
self.context = context
def __init__(self, symtab, text, filename, include_path):
self.symtab = symtab
self.include_path = include_path
self.scanner = Scanner(text, filename)
self.backpatch_instrs = []
self.current_routine_name = None
def syntax_error(self, msg):
self.scanner.syntax_error(msg)
def lookup(self, name):
model = self.context.lookup(name)
def lookup(self, name, allow_forward=False, routine_name=None):
model = self.symtab.fetch_global_ref(name)
if model is None and routine_name:
model = self.symtab.fetch_local_ref(routine_name, name)
if model is None and allow_forward:
return ForwardReference(name)
if model is None:
self.syntax_error('Undefined symbol "{}"'.format(name))
return model
def declare(self, name, ast_node, type_):
if self.symtab.fetch_global_ref(name):
self.syntax_error('Symbol "%s" already declared' % name)
self.symtab.symbols[name] = SymEntry(ast_node, type_)
def declare_local(self, routine_name, name, ast_node, type_):
if self.symtab.fetch_local_ref(routine_name, name):
self.syntax_error('Symbol "%s" already declared locally' % name)
if self.symtab.fetch_global_ref(name):
self.syntax_error('Symbol "%s" already declared globally' % name)
self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
# ---- symbol resolution
def resolve_symbols(self, program):
# This could stand to be better unified.
def resolve(w):
return self.lookup(w.name) if isinstance(w, ForwardReference) else w
def backpatched_type(type_):
if isinstance(type_, TableType):
return TableType(backpatched_type(type_.of_type), type_.size)
elif isinstance(type_, VectorType):
return VectorType(backpatched_type(type_.of_type))
elif isinstance(type_, RoutineType):
return RoutineType(
frozenset([resolve(w) for w in type_.inputs]),
frozenset([resolve(w) for w in type_.outputs]),
frozenset([resolve(w) for w in type_.trashes]),
)
else:
return type_
for name, symentry in self.symtab.symbols.items():
symentry.type_ = backpatched_type(symentry.type_)
def resolve_fwd_reference(obj, field):
field_value = getattr(obj, field, None)
if isinstance(field_value, ForwardReference):
setattr(obj, field, self.lookup(field_value.name))
elif isinstance(field_value, IndexedRef):
if isinstance(field_value.ref, ForwardReference):
field_value.ref = self.lookup(field_value.ref.name)
for node in program.all_children():
if isinstance(node, SingleOp):
resolve_fwd_reference(node, 'src')
resolve_fwd_reference(node, 'dest')
if isinstance(node, (Call, GoTo)):
resolve_fwd_reference(node, 'location')
# --- grammar productions
def program(self):
defns = []
routines = []
includes = []
while self.scanner.consume('include'):
filename = self.scanner.token
self.scanner.scan()
program = load_program(filename, self.symtab, self.include_path, include_file=True)
includes.append(program)
while self.scanner.on('typedef', 'const'):
if self.scanner.on('typedef'):
self.typedef()
if self.scanner.on('const'):
self.defn_const()
typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine',
typenames.extend(self.context.typedefs.keys())
typenames = ['byte', 'word', 'table', 'vector', 'pointer'] # 'routine',
typenames.extend(self.symtab.typedefs.keys())
while self.scanner.on(*typenames):
defn = self.defn()
name = defn.name
if self.context.lookup(name):
self.syntax_error('Symbol "%s" already declared' % name)
self.context.symbols[name] = SymEntry(defn, defn.location)
type_, defn = self.defn()
self.declare(defn.name, defn, type_)
defns.append(defn)
while self.scanner.on('define', 'routine'):
if self.scanner.consume('define'):
name = self.scanner.token
self.scanner.scan()
routine = self.routine(name)
else:
routine = self.legacy_routine()
name = routine.name
if self.context.lookup(name):
self.syntax_error('Symbol "%s" already declared' % name)
self.context.symbols[name] = SymEntry(routine, routine.location)
while self.scanner.consume('define'):
name = self.scanner.token
self.scanner.scan()
self.current_routine_name = name
preserved = False
if self.scanner.consume('preserved'):
preserved = True
type_, routine = self.routine(name)
self.declare(name, routine, type_)
routine.preserved = preserved
routines.append(routine)
self.current_routine_name = None
self.scanner.check_type('EOF')
# now backpatch the executable types.
#for type_name, type_ in self.context.typedefs.iteritems():
# type_.backpatch_constraint_labels(lambda w: self.lookup(w))
for defn in defns:
defn.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
for routine in routines:
routine.location.type.backpatch_constraint_labels(lambda w: self.lookup(w))
for instr in self.backpatch_instrs:
if instr.opcode in ('call', 'goto'):
name = instr.location
model = self.lookup(name)
if not isinstance(model.type, (RoutineType, VectorType)):
self.syntax_error('Illegal call of non-executable "%s"' % name)
instr.location = model
if instr.opcode in ('copy',) and isinstance(instr.src, basestring):
name = instr.src
model = self.lookup(name)
if not isinstance(model.type, (RoutineType, VectorType)):
self.syntax_error('Illegal copy of non-executable "%s"' % name)
instr.src = model
program = Program(self.scanner.line_number, defns=defns, routines=routines)
programs = includes + [program]
program = merge_programs(programs)
return Program(self.scanner.line_number, defns=defns, routines=routines)
self.resolve_symbols(program)
return program
def typedef(self):
self.scanner.expect('typedef')
type_ = self.defn_type()
name = self.defn_name()
if name in self.context.typedefs:
if name in self.symtab.typedefs:
self.syntax_error('Type "%s" already declared' % name)
self.context.typedefs[name] = type_
self.symtab.typedefs[name] = type_
return type_
def defn_const(self):
self.scanner.expect('const')
name = self.defn_name()
if name in self.context.consts:
if name in self.symtab.consts:
self.syntax_error('Const "%s" already declared' % name)
loc = self.const()
self.context.consts[name] = loc
self.symtab.consts[name] = loc
return loc
def defn(self):
@ -157,9 +184,7 @@ class Parser(object):
if initial is not None and addr is not None:
self.syntax_error("Definition cannot have both initial value and explicit address")
location = LocationRef(type_, name)
return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location)
return type_, Defn(self.scanner.line_number, name=name, addr=addr, initial=initial)
def const(self):
if self.scanner.token in ('on', 'off'):
@ -176,8 +201,8 @@ class Parser(object):
loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
self.scanner.scan()
return loc
elif self.scanner.token in self.context.consts:
loc = self.context.consts[self.scanner.token]
elif self.scanner.token in self.symtab.consts:
loc = self.symtab.consts[self.scanner.token]
self.scanner.scan()
return loc
else:
@ -194,8 +219,8 @@ class Parser(object):
if self.scanner.consume('table'):
size = self.defn_size()
if size <= 0 or size > 256:
self.syntax_error("Table size must be > 0 and <= 256")
if size <= 0 or size > 65536:
self.syntax_error("Table size must be > 0 and <= 65536")
type_ = TableType(type_, size)
return type_
@ -219,18 +244,15 @@ class Parser(object):
type_ = VectorType(type_)
elif self.scanner.consume('routine'):
(inputs, outputs, trashes) = self.constraints()
type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes)
elif self.scanner.consume('buffer'):
size = self.defn_size()
type_ = BufferType(size)
type_ = RoutineType(frozenset(inputs), frozenset(outputs), frozenset(trashes))
elif self.scanner.consume('pointer'):
type_ = PointerType()
else:
type_name = self.scanner.token
self.scanner.scan()
if type_name not in self.context.typedefs:
if type_name not in self.symtab.typedefs:
self.syntax_error("Undefined type '%s'" % type_name)
type_ = self.context.typedefs[type_name]
type_ = self.symtab.typedefs[type_name]
return type_
@ -250,62 +272,27 @@ class Parser(object):
outputs = set(self.labels())
if self.scanner.consume('trashes'):
trashes = set(self.labels())
return (inputs, outputs, trashes)
def legacy_routine(self):
self.scanner.expect('routine')
name = self.scanner.token
self.scanner.scan()
(inputs, outputs, trashes) = self.constraints()
type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes)
if self.scanner.consume('@'):
self.scanner.check_type('integer literal')
block = None
addr = int(self.scanner.token)
self.scanner.scan()
else:
block = self.block()
addr = None
location = LocationRef(type_, name)
return Routine(
self.scanner.line_number,
name=name, block=block, addr=addr,
location=location
return (
set([ForwardReference(n) for n in inputs]),
set([ForwardReference(n) for n in outputs]),
set([ForwardReference(n) for n in trashes])
)
def routine(self, name):
type_ = self.defn_type()
if not isinstance(type_, RoutineType):
self.syntax_error("Can only define a routine, not %r" % type_)
statics = []
self.syntax_error("Can only define a routine, not {}".format(repr(type_)))
locals_ = []
if self.scanner.consume('@'):
self.scanner.check_type('integer literal')
block = None
addr = int(self.scanner.token)
self.scanner.scan()
else:
statics = self.statics()
self.context.statics = self.compose_statics_dict(statics)
locals_ = self.locals()
block = self.block()
self.context.statics = {}
addr = None
location = LocationRef(type_, name)
return Routine(
self.scanner.line_number,
name=name, block=block, addr=addr,
location=location, statics=statics
)
def compose_statics_dict(self, statics):
c = {}
for defn in statics:
name = defn.name
if self.context.lookup(name):
self.syntax_error('Symbol "%s" already declared' % name)
c[name] = SymEntry(defn, defn.location)
return c
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_)
def labels(self):
accum = []
@ -328,50 +315,50 @@ class Parser(object):
accum.append(self.locexpr())
return accum
def locexpr(self, forward=False):
if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
def locexpr(self):
if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.symtab.consts or self.scanner.on_type('integer literal'):
return self.const()
elif forward:
else:
name = self.scanner.token
self.scanner.scan()
loc = self.context.lookup(name)
if loc is not None:
return loc
else:
return name
else:
loc = self.lookup(self.scanner.token)
self.scanner.scan()
return loc
return self.lookup(name, allow_forward=True, routine_name=self.current_routine_name)
def indlocexpr(self, forward=False):
def indlocexpr(self):
if self.scanner.consume('['):
loc = self.locexpr()
self.scanner.expect(']')
self.scanner.expect('+')
self.scanner.expect('y')
return IndirectRef(loc)
elif self.scanner.consume('^'):
loc = self.locexpr()
return AddressRef(loc)
else:
return self.indexed_locexpr(forward=forward)
return self.indexed_locexpr()
def indexed_locexpr(self, forward=False):
loc = self.locexpr(forward=forward)
if not isinstance(loc, basestring):
def indexed_locexpr(self):
loc = self.locexpr()
if not isinstance(loc, str):
index = None
offset = ConstantRef(TYPE_BYTE, 0)
if self.scanner.consume('+'):
if self.scanner.token in self.symtab.consts or self.scanner.on_type('integer literal'):
offset = self.const()
self.scanner.expect('+')
index = self.locexpr()
loc = IndexedRef(loc, index)
loc = IndexedRef(loc, offset, index)
return loc
def statics(self):
def locals(self):
defns = []
while self.scanner.consume('static'):
defn = self.defn()
type_, defn = self.defn()
if defn.initial is None:
self.syntax_error("Static definition {} must have initial value".format(defn))
self.declare_local(self.current_routine_name, defn.name, defn, type_)
defns.append(defn)
while self.scanner.consume('local'):
type_, defn = self.defn()
if defn.initial is not None:
self.syntax_error("Local definition {} may not have initial value".format(defn))
self.declare_local(self.current_routine_name, defn.name, defn, type_)
defns.append(defn)
return defns
@ -380,6 +367,8 @@ class Parser(object):
self.scanner.expect('{')
while not self.scanner.on('}'):
instrs.append(self.instr())
if isinstance(instrs[-1], GoTo):
break
self.scanner.expect('}')
return Block(self.scanner.line_number, instrs=instrs)
@ -417,6 +406,10 @@ class Parser(object):
final = self.const()
block = self.block()
return For(self.scanner.line_number, dest=dest, direction=direction, final=final, block=block)
elif self.scanner.consume('reset'):
pointer = self.locexpr()
offset = self.const()
return Reset(self.scanner.line_number, pointer=pointer, offset=offset)
elif self.scanner.token in ("ld",):
# the same as add, sub, cmp etc below, except supports an indlocexpr for the src
opcode = self.scanner.token
@ -442,36 +435,74 @@ class Parser(object):
elif self.scanner.token in ("shl", "shr", "inc", "dec"):
opcode = self.scanner.token
self.scanner.scan()
dest = self.locexpr()
dest = self.indexed_locexpr()
return SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=None)
elif self.scanner.token in ("nop",):
opcode = self.scanner.token
self.scanner.scan()
return SingleOp(self.scanner.line_number, opcode=opcode, dest=None, src=None)
elif self.scanner.token in ("call", "goto"):
opcode = self.scanner.token
self.scanner.scan()
elif self.scanner.consume("call"):
name = self.scanner.token
self.scanner.scan()
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=name, dest=None, src=None)
self.backpatch_instrs.append(instr)
instr = Call(self.scanner.line_number, location=ForwardReference(name))
return instr
elif self.scanner.consume("goto"):
name = self.scanner.token
self.scanner.scan()
instr = GoTo(self.scanner.line_number, location=ForwardReference(name))
return instr
elif self.scanner.token in ("copy",):
opcode = self.scanner.token
self.scanner.scan()
src = self.indlocexpr(forward=True)
src = self.indlocexpr()
self.scanner.expect(',')
dest = self.indlocexpr()
instr = SingleOp(self.scanner.line_number, opcode=opcode, dest=dest, src=src)
self.backpatch_instrs.append(instr)
return instr
elif self.scanner.consume("with"):
self.scanner.expect("interrupts")
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("point"):
pointer = self.locexpr()
self.scanner.expect("into")
table = self.locexpr()
block = self.block()
return PointInto(self.scanner.line_number, pointer=pointer, table=table, block=block)
elif self.scanner.consume("trash"):
dest = self.locexpr()
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
else:
self.syntax_error('bad opcode "%s"' % self.scanner.token)
# - - - -
def load_program(filename, symtab, include_path, include_file=False):
import os
if include_file:
for include_dir in include_path:
if os.path.exists(os.path.join(include_dir, filename)):
filename = os.path.join(include_dir, filename)
break
text = open(filename).read()
parser = Parser(symtab, text, filename, include_path)
program = parser.program()
return program
def merge_programs(programs):
"""Assumes that the programs do not have any conflicts."""
full = Program(1, defns=[], routines=[])
for p in programs:
full.defns.extend(p.defns)
full.routines.extend(p.routines)
return full

View File

@ -1,3 +1,7 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# encoding: UTF-8
import re
@ -17,18 +21,20 @@ class Scanner(object):
self.filename = filename
self.token = None
self.type = None
self.pos = 0
self.line_number = 1
self.scan()
def scan_pattern(self, pattern, type, token_group=1, rest_group=2):
pattern = r'^(' + pattern + r')(.*?)$'
match = re.match(pattern, self.text, re.DOTALL)
def scan_pattern(self, pattern, type, token_group=1):
pattern = r'(' + pattern + r')'
regexp = re.compile(pattern, flags=re.DOTALL)
match = regexp.match(self.text, pos=self.pos)
if not match:
return False
else:
self.type = type
self.token = match.group(token_group)
self.text = match.group(rest_group)
self.pos += len(match.group(0))
self.line_number += self.token.count('\n')
return True
@ -36,7 +42,7 @@ class Scanner(object):
self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
while self.scan_pattern(r'\/\/.*?[\n\r]', 'comment'):
self.scan_pattern(r'[ \t\n\r]*', 'whitespace')
if not self.text:
if self.pos >= len(self.text):
self.token = None
self.type = 'EOF'
return
@ -44,20 +50,18 @@ class Scanner(object):
return
if self.scan_pattern(r'\d+', 'integer literal'):
return
if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal',
token_group=2, rest_group=3):
if self.scan_pattern(r'\$([0-9a-fA-F]+)', 'integer literal', token_group=2):
# ecch
self.token = str(eval('0x' + self.token))
return
if self.scan_pattern(r'\"(.*?)\"', 'string literal',
token_group=2, rest_group=3):
if self.scan_pattern(r'\"(.*?)\"', 'string literal', token_group=2):
return
if self.scan_pattern(r'\w+', 'identifier'):
return
if self.scan_pattern(r'.', 'unknown character'):
return
else:
raise AssertionError("this should never happen, self.text=({})".format(self.text))
raise AssertionError("this should never happen, self.text=({}), self.pos=({})".format(self.text, self.pos))
def expect(self, token):
if self.token == token:

54
src/sixtypical/symtab.py Normal file
View File

@ -0,0 +1,54 @@
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
# 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

17
test.sh
View File

@ -1,7 +1,16 @@
#!/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.
# Note also that the `sixtypical-py2.7.md` appliance, in the same directory as
# `sixtypical.md`, can be used to run the tests under Python 2.7.
falderal --substring-error \
tests/SixtyPical\ Syntax.md \
tests/SixtyPical\ Analysis.md \
tests/SixtyPical\ Fallthru.md \
tests/SixtyPical\ Compilation.md
"tests/appliances/sixtypical.md" \
"tests/SixtyPical Syntax.md" \
"tests/SixtyPical Analysis.md" \
"tests/SixtyPical Storage.md" \
"tests/SixtyPical Control Flow.md" \
"tests/SixtyPical Fallthru.md" \
"tests/SixtyPical Callgraph.md" \
"tests/SixtyPical Compilation.md"

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,229 @@
SixtyPical Callgraph
====================
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This is a test suite, written in [Falderal][] format, for the ability of
a SixtyPical analyzer to construct a callgraph of which routines call which
other routines, and its ability to discover which routines will never be
called.
[Falderal]: http://catseye.tc/node/Falderal
-> Tests for functionality "Dump callgraph info for SixtyPical program"
The `main` routine is always called. The thing that it will
be called by is the system, but the callgraph analyzer simply
considers it to be "reachable".
| define main routine
| {
| }
= {
= "main": {
= "potentially-called-by": [],
= "potentially-calls": [],
= "reachable": true
= }
= }
If a routine is called by another routine, this fact will be noted.
If it is reachable (directly or indirectly) from `main`, this will
be noted as well.
| define main routine
| {
| call other
| }
|
| define other routine
| {
| }
= {
= "main": {
= "potentially-called-by": [],
= "potentially-calls": [
= "other"
= ],
= "reachable": true
= },
= "other": {
= "potentially-called-by": [
= "main"
= ],
= "potentially-calls": [],
= "reachable": true
= }
= }
If a routine is not potentially called by any other routine that is
ultimately potentially called by `main`, this absence will be noted
— the routine will not be considered reachable — and a compiler or
linker will be permitted to omit it from the final executable.
| define main routine
| {
| }
|
| define other routine
| {
| }
= {
= "main": {
= "potentially-called-by": [],
= "potentially-calls": [],
= "reachable": true
= },
= "other": {
= "potentially-called-by": [],
= "potentially-calls": []
= }
= }
If a routine is not called by another routine, but it is declared
explicitly as `preserved`, then it will still be considered
reachable, and a compiler or linker will not be permitted to omit it
from the final executable. This is useful for interrupt routines
and such that really are used by some part of the system, even if
not directly by another SixtyPical routine.
| define main routine
| {
| }
|
| define other preserved routine
| {
| }
= {
= "main": {
= "potentially-called-by": [],
= "potentially-calls": [],
= "reachable": true
= },
= "other": {
= "potentially-called-by": [],
= "potentially-calls": [],
= "reachable": true
= }
= }
If a routine is called from a preserved routine, that routine is
reachable.
| define main routine
| {
| }
|
| define other1 preserved routine
| {
| call other2
| }
|
| define other2 preserved routine
| {
| }
= {
= "main": {
= "potentially-called-by": [],
= "potentially-calls": [],
= "reachable": true
= },
= "other1": {
= "potentially-called-by": [],
= "potentially-calls": [
= "other2"
= ],
= "reachable": true
= },
= "other2": {
= "potentially-called-by": [
= "other1"
= ],
= "potentially-calls": [],
= "reachable": true
= }
= }
If a group of routines potentially call each other, but neither is
found to be reachable (directly or indirectly) from `main` or a
`preserved` routine, the routines in the group will not be considered
reachable.
| define main routine
| {
| }
|
| define other1 routine
| {
| call other2
| }
|
| define other2 routine
| {
| call other1
| }
= {
= "main": {
= "potentially-called-by": [],
= "potentially-calls": [],
= "reachable": true
= },
= "other1": {
= "potentially-called-by": [
= "other2"
= ],
= "potentially-calls": [
= "other2"
= ]
= },
= "other2": {
= "potentially-called-by": [
= "other1"
= ],
= "potentially-calls": [
= "other1"
= ]
= }
= }
-> Tests for functionality "Compile SixtyPical program with unreachable routine removal"
Basic test for actually removing unreachable routines from the resulting
executable when compiling SixtyPical programs.
| define main routine outputs a trashes z, n
| {
| ld a, 100
| }
|
| define other1 routine
| {
| call other2
| }
|
| define other2 routine
| {
| call other1
| }
= $080D LDA #$64
= $080F RTS
Test that marking routine as `preserved` preserves it in the output.
| define main routine outputs a trashes z, n
| {
| ld a, 100
| }
|
| define other preserved routine outputs a trashes z, n
| {
| ld a, 5
| }
= $080D LDA #$64
= $080F RTS
= $0810 LDA #$05
= $0812 RTS

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,12 @@
SixtyPical Fallthru
===================
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This is a test suite, written in [Falderal][] format, for SixtyPical's
ability to detect which routines make tail calls to other routines,
and thus can be re-arranged to simply "fall through" to them.
@ -48,7 +54,7 @@ Treat R as a mutable set and start with an empty list of lists L. Then,
- Remove all elements occurring in C, from R.
- Repeat until R is empty.
When times comes to generate code, generate it in the order given by L.
When time comes to generate code, generate it in the order given by L.
In addition, each sublist in L represents a number of routines to
generate; all except the final routine in such a sublist need not have
any jump instruction generated for its final `goto`.
@ -61,12 +67,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 --prelude=c64 --optimize-fallthru --traceback %(test-body-file) >/tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> Tests for functionality "Dump fallthru info for SixtyPical program"
A single routine, obviously, falls through to nothing and has nothing fall
@ -94,7 +94,7 @@ If `main` does a `goto foo`, then it can fall through to `foo`.
| }
= [
= [
= "main",
= "main",
= "foo"
= ]
= ]
@ -119,9 +119,9 @@ of them to fall through, when selecting the order of routines.
| }
= [
= [
= "main",
= "main",
= "foo"
= ],
= ],
= [
= "bar"
= ]
@ -144,7 +144,7 @@ nothing ever falls through to `main`.
= [
= [
= "main"
= ],
= ],
= [
= "foo"
= ]
@ -172,10 +172,10 @@ fall through to the other.
= [
= [
= "main"
= ],
= ],
= [
= "bar",
= "foo"
= "foo",
= "bar"
= ]
= ]
@ -204,12 +204,12 @@ routine.
= [
= [
= "main"
= ],
= [
= "bar"
= ],
= ],
= [
= "foo"
= ],
= [
= "bar"
= ]
= ]
@ -238,9 +238,9 @@ If, however, they are the same goto, one can be optimized away.
= [
= [
= "main"
= ],
= ],
= [
= "foo",
= "foo",
= "bar"
= ]
= ]
@ -269,12 +269,12 @@ because we don't necessarily know what actual routine the vector contains.
= [
= [
= "main"
= ],
= [
= "bar"
= ],
= ],
= [
= "foo"
= ],
= [
= "bar"
= ]
= ]
@ -321,14 +321,14 @@ Our algorithm might not be strictly optimal, but it does a good job.
| }
= [
= [
= "main",
= "r1",
= "r2",
= "r3",
= "main",
= "r1",
= "r2",
= "r3",
= "r4"
= ],
= ],
= [
= "r5",
= "r5",
= "r6"
= ]
= ]
@ -390,11 +390,6 @@ It can optimize out one of the `goto`s if they are the same.
It cannot optimize out the `goto`s if they are different.
Note, this currently produces unfortunately unoptimized code,
because generating code for the "true" branch of an `if` always
generates a jump out of the `if`, even if the last instruction
in the "true" branch is a `goto`.
| define foo routine trashes a, z, n
| {
| ld a, 0
@ -416,12 +411,11 @@ in the "true" branch is a `goto`.
| {
| }
= $080D RTS
= $080E LDA #$FF
= $0810 RTS
= $0811 LDA #$00
= $0813 BNE $081D
= $0815 LDA #$01
= $0817 JMP $080E
= $081A JMP $0822
= $081D LDA #$02
= $081F JMP $080D
= $080E LDA #$00
= $0810 BNE $0817
= $0812 LDA #$01
= $0814 JMP $081C
= $0817 LDA #$02
= $0819 JMP $080D
= $081C LDA #$FF
= $081E RTS

1990
tests/SixtyPical Storage.md Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,6 +1,12 @@
SixtyPical Syntax
=================
<!--
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
-->
This is a test suite, written in [Falderal][] format, for the syntax of
the Sixtypical language, disgregarding execution, static analysis, etc.
@ -9,14 +15,11 @@ 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.
| routine main {
| define main routine {
| ld a, 0
| add a, 1
| }
@ -26,15 +29,21 @@ Program with comments.
| // Welcome to my program.
|
| routine main {
| define main routine {
| ld a, 0
| add a, 1 // We are adding the thing.
| sub a, 1
| shl a
| shr a
| and a, 1
| or a, 1
| xor a, 1
| }
= ok
Hex literals.
| routine main {
| define main routine {
| ld a, $ff
| add a, $01
| }
@ -42,7 +51,7 @@ Hex literals.
Syntax error.
| routine foo (
| define foo routine (
| ld a, 0
| add a, 1
| )
@ -59,20 +68,32 @@ Another syntax error.
Extern routines
| routine chrout
| define chrout routine
| inputs a
| trashes a
| @ 65490
|
| routine chrin
| define chrin routine
| outputs a
| trashes x
| @ 65487
= ok
Preserved routine.
| define main routine {
| ld a, $ff
| add a, $01
| }
| define foo preserved routine {
| ld a, 0
| add a, 1
| }
= ok
Trash.
| routine main {
| define main routine {
| trash a
| trash n
| }
@ -80,7 +101,7 @@ Trash.
`nop`.
| routine main
| define main routine
| {
| nop
| }
@ -88,7 +109,7 @@ Trash.
If with not
| routine foo {
| define foo routine {
| ld y, 0
| cmp y, 10
| if not z {
@ -100,7 +121,7 @@ If with not
Repeat loop
| routine foo {
| define foo routine {
| ld y, 0
| repeat {
| inc y
@ -111,7 +132,7 @@ Repeat loop
"While" loop
| routine foo inputs y {
| define foo routine inputs y {
| repeat {
| cmp y, 10
| if not z {
@ -123,7 +144,7 @@ Repeat loop
Repeat forever
| routine foo inputs y {
| define foo routine inputs y {
| repeat {
| inc y
| } forever
@ -132,7 +153,7 @@ Repeat forever
Repeat with not
| routine foo inputs y {
| define foo routine inputs y {
| repeat {
| inc y
| } until not z
@ -143,7 +164,7 @@ Basic "open-faced for" loops, up and down.
| byte table[256] tab
|
| routine foo trashes a, x, c, z, v {
| define foo routine trashes a, x, c, z, v {
| ld x, 0
| for x up to 15 {
| ld a, tab + x
@ -155,34 +176,107 @@ Basic "open-faced for" loops, up and down.
| }
= ok
Other blocks.
| byte table[256] tab
| pointer ptr
|
| define main routine trashes a, x, c, z, v {
| with interrupts off {
| save a, x, c {
| ld a, 0
| }
| }
| save a, x, c {
| ld a, 0
| }
| point ptr into tab {
| reset ptr 0
| ld a, [ptr] + y
| }
| }
= ok
User-defined memory addresses of different types.
| byte byt
| word wor
| vector routine trashes a vec
| buffer[2048] buf
| byte table[2048] buf
| pointer ptr
|
| routine main {
| define main routine {
| }
= ok
Tables of different types.
Tables of different types and some operations on them.
| byte table[256] tab
| word table[256] wtab
| vector (routine trashes a) table[256] vtab
| byte table[256] many
| word table[256] wmany
| vector (routine trashes a) table[256] vmany
| byte bval
| word wval
|
| routine main {
| define main routine {
| ld x, 0
| ld a, 0
| st off, c
| add a, many + x
| sub a, many + x
| cmp a, many + x
| and a, many + x
| or a, many + x
| xor a, many + x
| shl many + x
| shr many + x
| inc many + x
| dec many + x
| ld a, many + x
| st a, many + x
| copy wval, wmany + x
| copy wmany + x, wval
| }
= ok
Indexing with an offset in some tables.
| byte table[256] many
| word table[256] wmany
| byte bval
| word wval
|
| define main routine {
| ld x, 0
| ld a, 0
| st off, c
| add a, many + 100 + x
| sub a, many + 100 + x
| cmp a, many + 100 + x
| and a, many + 100 + x
| or a, many + 100 + x
| xor a, many + 100 + x
| shl many + 100 + x
| shr many + 100 + x
| inc many + 100 + x
| dec many + 100 + x
| ld a, many + 100 + x
| st a, many + 100 + x
| copy wval, wmany + 100 + x
| copy wmany + 100 + x, wval
| }
= ok
The number of entries in a table must be
greater than 0 and less than or equal to 256.
greater than 0 and less than or equal to 65536.
(In previous versions, a table could have at
most 256 entries. They can now have more, however
the offset-access syntax can only access the
first 256. To access more, a pointer is required.)
| word table[512] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
@ -190,11 +284,35 @@ greater than 0 and less than or equal to 256.
| ld x, 0
| copy 9999, many + x
| }
= ok
| byte table[65536] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy 99, many + x
| }
= ok
| byte table[65537] many
|
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
| {
| ld x, 0
| copy 99, many + x
| }
? SyntaxError
| word table[0] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
@ -206,7 +324,7 @@ greater than 0 and less than or equal to 256.
| word table[48] many
|
| routine main
| define main routine
| inputs many
| outputs many
| trashes a, x, n, z
@ -223,7 +341,7 @@ Typedefs of different types.
| typedef routine trashes a game_routine
| vector game_routine start_game
|
| routine main {
| define main routine {
| }
= ok
@ -232,7 +350,7 @@ Can't have two typedefs with the same name.
| typedef byte frank
| typedef word frank
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -247,17 +365,30 @@ Constants.
|
| byte lark: lives
|
| routine main {
| define main routine {
| ld a, lives
| }
= ok
Named constants can be used as offsets.
| const lives 3
| const w1 1000
|
| byte table[w1] those
|
| define main routine {
| ld y, 0
| ld a, those + lives + y
| }
= ok
Can't have two constants with the same name.
| const w1 1000
| const w1 word 0
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -265,9 +396,11 @@ Explicit memory address.
| byte screen @ 1024
|
| routine main {
| define main routine {
| ld a, 100
| st a, screen
| shl screen
| shr screen
| }
= ok
@ -275,7 +408,7 @@ Initialized memory locations.
| byte lives : 3
|
| routine main {
| define main routine {
| ld a, lives
| st a, lives
| }
@ -285,7 +418,7 @@ Cannot have both initial value and explicit address.
| byte screen : 3 @ 1024
|
| routine main {
| define main routine {
| ld a, lives
| st a, lives
| }
@ -298,7 +431,7 @@ User-defined locations of other types.
| word r2 @ 60000
| word r3 : 2000
|
| routine main {
| define main routine {
| }
= ok
@ -306,15 +439,15 @@ Initialized byte table, initialized with ASCII string.
| byte table[32] message : "WHAT DO YOU WANT TO DO NEXT?"
|
| routine main {
| define main routine {
| }
= ok
Can't initialize anything but a byte table with a string.
| word message : "WHAT DO YOU WANT TO DO NEXT?"
| word message : "OUCH! WHAT DO YOU DO?"
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -322,13 +455,13 @@ Initialized byte table, initialized with list of bytes.
| byte table[8] charmap : 0, 255, 129, 192, 0, 1, 2, 4
|
| routine main {
| define main routine {
| }
= ok
Can't access an undeclared memory location.
| routine main {
| define main routine {
| ld a, 0
| st a, lives
| }
@ -339,7 +472,7 @@ Can't define two memory locations with the same name.
| byte lives
| byte lives
|
| routine main {
| define main routine {
| ld a, 0
| st a, lives
| }
@ -349,19 +482,19 @@ Can't shadow the name of a register or a flag.
| byte a
|
| routine main {
| define main routine {
| }
? SyntaxError
| byte z
|
| routine main {
| define main routine {
| }
? SyntaxError
Can't call routine that hasn't been defined.
| routine main {
| define main routine {
| ld x, 0
| ld y, 1
| call up
@ -369,44 +502,26 @@ Can't call routine that hasn't been defined.
| }
? SyntaxError
And you can't call a non-routine.
| byte up
|
| routine main {
| ld x, 0
| ld y, 1
| call up
| }
? SyntaxError
| routine main {
| ld x, 0
| ld y, 1
| call x
| }
? SyntaxError
But you can call a routine that is yet to be defined, further on.
| routine main {
| define main routine {
| ld x, 0
| ld y, 1
| call up
| call up
| }
| routine up {
| define up routine {
| ld a, 0
| }
= ok
Can't define two routines with the same name.
| routine main {
| define main routine {
| inc x
| inc y
| }
| routine main {
| define main routine {
| ld x, 0
| ld y, 1
| }
@ -416,7 +531,7 @@ Declaring byte and word table memory location.
| byte table[256] tab
|
| routine main {
| define main routine {
| ld x, 0
| ld y, 0
| ld a, tab + x
@ -427,7 +542,7 @@ Declaring byte and word table memory location.
| word one
| word table[256] many
|
| routine main {
| define main routine {
| ld x, 0
| copy one, many + x
| copy word 0, many + x
@ -443,10 +558,10 @@ Declaring and calling a vector.
| trashes a, x, z, n
| cinv @ 788
|
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
@ -462,7 +577,7 @@ Only vectors can be decorated with constraints like that.
| trashes a, x, z, n
| @ 788
|
| routine main {
| define main routine {
| }
? SyntaxError
@ -474,10 +589,10 @@ Constraints set may only contain labels.
| trashes a, x, z, n
| cinv @ 788
|
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
@ -493,10 +608,10 @@ A vector can name itself in its inputs, outputs, and trashes.
| trashes a, x, z, n
| cinv @ 788
|
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
@ -513,64 +628,80 @@ references in the source of a `copy` instruction.
| outputs cinv, x
| trashes a, x, z, n
| cinv @ 788
| routine main {
| define main routine {
| with interrupts off {
| copy foo, cinv
| }
| call cinv
| }
| routine foo {
| define foo routine {
| ld a, 0
| }
= ok
goto.
| routine foo {
| define foo routine {
| ld a, 0
| }
| routine main {
| define main routine {
| goto foo
| }
= ok
| routine main {
The label doesn't have to be defined yet at the point
in the program text where it is `goto`d.
| define main routine {
| goto foo
| }
| routine foo {
| define foo routine {
| ld a, 0
| }
= ok
Syntactically, you can `goto` a vector.
| vector routine foo
|
| routine main {
| define main routine {
| goto foo
| }
= ok
| routine main {
But you can't `goto` a label that never gets defined.
| define main routine {
| goto foo
| }
? SyntaxError
| byte foo
`goto` may only be the final instruction in a block.
| define bar routine trashes x, z, n {
| ld x, 200
| }
|
| routine main {
| goto foo
| define main routine trashes x, z, n {
| goto bar
| ld x, 0
| }
? SyntaxError
? Expected '}', but found 'ld'
Buffers and pointers.
Tables and pointers.
| buffer[2048] buf
| byte table[2048] buf
| pointer ptr
| pointer ptrb
| byte foo
|
| routine main {
| copy ^buf, ptr
| copy 123, [ptr] + y
| copy [ptr] + y, foo
| define main routine {
| point ptr into buf {
| reset ptr 0
| copy 123, [ptr] + y
| copy [ptr] + y, foo
| copy [ptr] + y, [ptrb] + y
| }
| }
= ok
@ -592,7 +723,28 @@ Routines can be defined in a new style.
| inc x
| }
|
| routine main
| define main routine
| outputs vec
| trashes a, z, n
| {
| copy foo, vec
| }
= ok
| typedef routine
| inputs x
| outputs x
| trashes z, n
| routine_type
|
| vector routine_type vec
|
| define foo routine_type
| {
| inc x
| }
|
| define main routine
| outputs vec
| trashes a, z, n
| {
@ -611,13 +763,14 @@ Only routines can be defined in the new style.
| }
? SyntaxError
Memory locations can be defined static to a routine.
Memory locations can be defined local to a routine.
| define foo routine
| inputs x
| outputs x
| trashes z, n
| static byte t : 0
| local word w
| {
| st x, t
| inc t
@ -627,13 +780,14 @@ Memory locations can be defined static to a routine.
| define main routine
| trashes a, x, z, n
| static byte t : 0
| local word w
| {
| ld x, t
| call foo
| }
= ok
Static memory locations must always be given an initial value.
Local static memory locations must always be given an initial value.
| define main routine
| inputs x
@ -647,7 +801,49 @@ Static memory locations must always be given an initial value.
| }
? SyntaxError
Name of a static cannot shadow an existing global or static.
Local static memory locations may not be given an address.
| define main routine
| inputs x
| outputs x
| trashes z, n
| static byte t @ 1024
| {
| st x, t
| inc t
| ld x, t
| }
? SyntaxError
Local dynamic memory locations may not be given an initial value.
| define main routine
| inputs x
| outputs x
| trashes z, n
| local byte t : 10
| {
| st x, t
| inc t
| ld x, t
| }
? SyntaxError
Local dynamic memory locations may be given an address.
| define main routine
| inputs x
| outputs x
| trashes z, n
| local byte t @ 1024
| {
| st x, t
| inc t
| ld x, t
| }
= ok
Name of a local cannot shadow an existing global or local.
| byte t
|
@ -655,7 +851,7 @@ Name of a static cannot shadow an existing global or static.
| inputs x
| outputs x
| trashes z, n
| static byte t
| static byte t : 10
| {
| st x, t
| inc t
@ -667,11 +863,89 @@ Name of a static cannot shadow an existing global or static.
| inputs x
| outputs x
| trashes z, n
| static byte t
| static byte t
| static byte t : 10
| static byte t : 20
| {
| st x, t
| inc t
| ld x, t
| }
? SyntaxError
| byte t
|
| define main routine
| inputs x
| outputs x
| trashes z, n
| local byte t
| {
| st x, t
| inc t
| ld x, t
| }
? SyntaxError
| define main routine
| inputs x
| outputs x
| trashes z, n
| local word w
| local word w
| {
| st x, t
| inc t
| ld x, t
| }
? SyntaxError
Since the names of locals are lexically local to a routine, they cannot
appear in the inputs, outputs, trashes list of the routine.
| define main routine
| inputs t
| static byte t : 0
| {
| inc t
| }
? SyntaxError
| define main routine
| outputs t
| static byte t : 0
| {
| inc t
| }
? SyntaxError
| define main routine
| trashes t
| static byte t : 0
| {
| inc t
| }
? SyntaxError
| define main routine
| inputs t
| local byte t
| {
| inc t
| }
? SyntaxError
| define main routine
| outputs t
| local byte t
| {
| inc t
| }
? SyntaxError
| define main routine
| trashes t
| local byte t
| {
| inc t
| }
? SyntaxError

View File

@ -1,6 +1,6 @@
#!/usr/bin/env python
#!/usr/bin/env python3
# script that allows the binary output of sixtypical --prelude=c64 --compile to be
# script that allows the binary output of sixtypical --output-format=c64-basic-prg --compile to be
# disassembled by https://github.com/tcarmelveilleux/dcc6502
import sys
@ -8,7 +8,10 @@ import re
from subprocess import check_output
from tempfile import NamedTemporaryFile
bytes = sys.stdin.read()
try:
bytes = sys.stdin.buffer.read()
except AttributeError:
bytes = sys.stdin.read()
bytes = bytes[14:]
@ -17,7 +20,8 @@ filename = f.name
f.write(bytes)
f.close()
lines = [line for line in check_output("dcc6502 -o 2061 {}".format(filename), shell=True).split('\n') if line and not line.startswith(';')]
output = check_output("dcc6502 -o 2061 {}".format(filename), shell=True)
output_lines = output.decode('utf-8').split('\n')
lines = [line for line in output_lines if line and not line.startswith(';')]
lines = [re.sub(r'\s*\;.*$', '', line) for line in lines]
lines.pop()
sys.stdout.write('\n'.join(lines))

View File

@ -0,0 +1,30 @@
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,
and additionally that `sixtypical` is running under Python 2.7.
NOTE that this is not well-supported anymore, given that Python 2.7 is past
end-of-life.
[Falderal]: http://catseye.tc/node/Falderal
-> Functionality "Check syntax of SixtyPical program" is implemented by
-> shell command "python2.7 bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok"
-> Functionality "Analyze SixtyPical program" is implemented by
-> shell command "python2.7 bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok"
-> Functionality "Compile SixtyPical program" is implemented by
-> shell command "python2.7 bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && python2.7 tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> Functionality "Dump callgraph info for SixtyPical program" is implemented by
-> shell command "python2.7 bin/sixtypical --dump-callgraph --analyze-only --traceback %(test-body-file)"
-> Functionality "Compile SixtyPical program with unreachable routine removal" is implemented by
-> shell command "python2.7 bin/sixtypical --output-format=c64-basic-prg --prune-unreachable-routines --traceback %(test-body-file) --output /tmp/foo && python2.7 tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> Functionality "Dump fallthru info for SixtyPical program" is implemented by
-> shell command "python2.7 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 "python2.7 bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && python2.7 tests/appliances/bin/dcc6502-adapter </tmp/foo"

View File

@ -0,0 +1,26 @@
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 </tmp/foo"
-> Functionality "Dump callgraph info for SixtyPical program" is implemented by
-> shell command "bin/sixtypical --dump-callgraph --analyze-only --traceback %(test-body-file)"
-> Functionality "Compile SixtyPical program with unreachable routine removal" is implemented by
-> shell command "bin/sixtypical --output-format=c64-basic-prg --prune-unreachable-routines --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
-> 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 </tmp/foo"