mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-06-07 22:29:27 +00:00
Compare commits
58 Commits
Author | SHA1 | Date | |
---|---|---|---|
|
df610e639c | ||
|
2bddea270f | ||
|
5f8802b305 | ||
|
5ecc4e6f56 | ||
|
d5e6eeacd3 | ||
|
880d1f53a1 | ||
|
1887ad9377 | ||
|
b670bd3316 | ||
|
9b74fd358f | ||
|
b38b94ed67 | ||
|
075ba9ff2a | ||
|
918b400fd4 | ||
|
4b539930bd | ||
|
632c2f4517 | ||
|
ff67e15208 | ||
|
023a415a14 | ||
|
a9917d3ea8 | ||
|
f4577804c7 | ||
|
fbfab44887 | ||
|
4b518508d5 | ||
|
1b5f4c0c4b | ||
|
dc4677ade8 | ||
|
d408e740ed | ||
|
c70e4a55e4 | ||
|
175c07781a | ||
|
987974cc21 | ||
|
565f959ee2 | ||
|
92b1cfeefb | ||
|
78a1f2910c | ||
|
44d97c33a2 | ||
|
b9df1482c6 | ||
|
ea788264f7 | ||
|
3d88226058 | ||
|
1df6941b01 | ||
|
1098347fa5 | ||
|
2192a48e0e | ||
|
a84cd4de8c | ||
|
40d9c57d64 | ||
|
684256f7e9 | ||
|
b09d0c0b76 | ||
|
182935a088 | ||
|
7187fa6285 | ||
|
bcc256aa5d | ||
|
2e002fc33e | ||
|
894fb1a0f2 | ||
|
58dc68f838 | ||
|
a966a496d0 | ||
|
87a2f70092 | ||
|
5912bf2684 | ||
|
0ef0dc1628 | ||
|
775af38960 | ||
|
952e3528a3 | ||
|
8467cd947e | ||
|
2e2e80664e | ||
|
3f666f4385 | ||
|
dd29b6fd4a | ||
|
81e28fa757 | ||
|
3d14b6ee6c |
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -1,2 +1,3 @@
|
|||
*.pyc
|
||||
__pycache__
|
||||
vicerc
|
||||
|
|
17
.reuse/dep5
Normal file
17
.reuse/dep5
Normal 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
|
64
HISTORY.md
64
HISTORY.md
|
@ -1,6 +1,70 @@
|
|||
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
|
||||
----
|
||||
|
||||
|
|
90
LICENSE
90
LICENSE
|
@ -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/>
|
27
LICENSES/LicenseRef-BSD-2-Clause-X-SixtyPical.txt
Normal file
27
LICENSES/LicenseRef-BSD-2-Clause-X-SixtyPical.txt
Normal 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
10
LICENSES/Unlicense.txt
Normal 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/>
|
39
README.md
39
README.md
|
@ -1,10 +1,28 @@
|
|||
SixtyPical
|
||||
==========
|
||||
|
||||
_Version 0.19. 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 [low-level](#low-level) programming language
|
||||
supporting a sophisticated [static analysis](#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/)
|
||||
|
||||
- - - -
|
||||
|
||||
_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._
|
||||
|
||||
- - - -
|
||||
|
||||
**SixtyPical** brings advanced static analysis to the [6502][].
|
||||
|
||||
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.
|
||||
|
@ -66,8 +84,9 @@ 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 to check that
|
||||
it conforms to it. It can detect common mistakes such as
|
||||
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
|
||||
|
@ -109,9 +128,12 @@ In order to run the tests for compilation, [dcc6502][] needs to be installed.
|
|||
|
||||
* [SixtyPical specification](doc/SixtyPical.md)
|
||||
* [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md)
|
||||
* [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md)
|
||||
* [Literate test suite for SixtyPical analysis (operations)](tests/SixtyPical%20Analysis.md)
|
||||
* [Literate test suite for SixtyPical analysis (storage)](tests/SixtyPical%20Storage.md)
|
||||
* [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
|
||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||
* [Literate test suite for SixtyPical callgraph construction](tests/SixtyPical%20Callgraph.md)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
@ -122,9 +144,12 @@ Documentation
|
|||
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
|
||||
* [TODO](TODO.md)
|
||||
|
||||
[MOS Technology 6520]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
||||
[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/
|
||||
|
|
165
TODO.md
165
TODO.md
|
@ -1,6 +1,15 @@
|
|||
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
|
||||
|
@ -9,35 +18,106 @@ Allow
|
|||
...
|
||||
}
|
||||
|
||||
Which uses some other storage location instead of the stack. A local static
|
||||
would be a good candidate for such.
|
||||
Which uses some other storage location instead of the stack. A local non-static
|
||||
would be a good candidate for such. At any rate, the location must not
|
||||
be writeable by anything that is called from within the block. So, probably
|
||||
just restrict this to local non-statics.
|
||||
|
||||
### Analyze `call` within blocks?
|
||||
### Copy byte to/from table
|
||||
|
||||
What happens if you call another routine from inside a `with interrupts off` block?
|
||||
Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
|
||||
You have to `ld a`, `st a`. I think maybe we should have one.
|
||||
|
||||
What happens if you call another routine from inside a `save` block?
|
||||
### Character literals
|
||||
|
||||
What happens if you call another routine from inside a `point into` block?
|
||||
For goodness sake, let the programmer say `'A'` instead of `65`.
|
||||
|
||||
What happens if you call another routine from inside a `for` block?
|
||||
### Character set mapping
|
||||
|
||||
Remember that any of these may have a `goto` ... and they may have a second
|
||||
instance of the same block (e.g. `with interrupts off` nested within
|
||||
`with interrupts off` shouldn't be allowed to turn them back on after the
|
||||
inner block has finished -- even if there is no `call`.)
|
||||
Not all computers think `'A'` should be `65`. Allow the character set to be
|
||||
mapped. Probably copy what Ophis does.
|
||||
|
||||
These holes need to be plugged.
|
||||
### Pointers into non-byte tables
|
||||
|
||||
### Reset pointer in `point into` blocks
|
||||
Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
|
||||
|
||||
We have `point into` blocks, but maybe the action when entering such a
|
||||
block shouldn't always be to set the given pointer to the start of the given 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.
|
||||
|
||||
That is, sometimes we would like to start at some fixed offset. And
|
||||
sometimes we want to (re)set the pointer, without closing and starting a new block.
|
||||
Laying them out for pointer access would make indexed access more difficult.
|
||||
|
||||
### Pointers associated globally with a table
|
||||
### 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.
|
||||
|
@ -53,50 +133,5 @@ at different times.
|
|||
|
||||
These can co-exist with general, non-specific-table-linked `pointer` variables.
|
||||
|
||||
### Local non-statics
|
||||
|
||||
Somewhat related to the above, it should be possible to declare a local storage
|
||||
location which is not static.
|
||||
|
||||
In this case, it would be considered uninitialized each time the routine was
|
||||
entered.
|
||||
|
||||
So, you do not have a guarantee that it has a valid value. But you are guaranteed
|
||||
that no other routine can read or modify it.
|
||||
|
||||
It also enables a trick: if there are two routines A and B, and A never calls B
|
||||
(even indirectly), and B never calls A (even indirectly), then their locals can
|
||||
be allocated at the same space.
|
||||
|
||||
A local could also be given an explicit address. In this case, two locals in
|
||||
different routines could be given the same address, and as long as the condition
|
||||
in the above paragraph holds, that's okay. (If it doesn't, the analyzer should
|
||||
detect it.)
|
||||
|
||||
This would permit local pointers, which would be one way of addressing the
|
||||
"same pointer to different tables" problem.
|
||||
|
||||
### 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.
|
||||
|
||||
### Tail-call optimization
|
||||
|
||||
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
|
||||
if the block is in tail position. The constraints should iron out the same both ways.
|
||||
|
||||
As long as the routine has consistent type context every place it exits, that should be fine.
|
||||
|
||||
### "Include" directives
|
||||
|
||||
Search a searchlist of include paths. And use them to make libraries of routines.
|
||||
|
||||
One such library routine might be an `interrupt routine` type for various architectures.
|
||||
Since "the supervisor" has stored values on the stack, we should be able to trash them
|
||||
with impunity, in such a routine.
|
||||
|
||||
### Line numbers in analysis error messages
|
||||
|
||||
For analysis errors, there is a line number, but it's the line of the routine
|
||||
after the routine in which the analysis error occurred. Fix this.
|
||||
If we have local pointers and space optimization for local non-statics, though,
|
||||
these don't add as much.
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from os.path import realpath, dirname, join
|
||||
import sys
|
||||
|
@ -16,23 +16,24 @@ import sys
|
|||
from tempfile import NamedTemporaryFile
|
||||
import traceback
|
||||
|
||||
from sixtypical.parser import Parser, SymbolTable, merge_programs
|
||||
from sixtypical.symtab import SymbolTable
|
||||
from sixtypical.parser import Parser, load_program, merge_programs
|
||||
from sixtypical.analyzer import Analyzer
|
||||
from sixtypical.callgraph import construct_callgraph, prune_unreachable_routines
|
||||
from sixtypical.outputter import outputter_class_for
|
||||
from sixtypical.compiler import Compiler
|
||||
|
||||
|
||||
def process_input_files(filenames, options):
|
||||
symtab = SymbolTable()
|
||||
include_path = options.include_path.split(':')
|
||||
|
||||
programs = []
|
||||
|
||||
for filename in options.filenames:
|
||||
text = open(filename).read()
|
||||
parser = Parser(symtab, text, filename)
|
||||
program = load_program(filename, symtab, include_path, include_file=False)
|
||||
if options.debug:
|
||||
print(symtab)
|
||||
program = parser.program()
|
||||
programs.append(program)
|
||||
|
||||
if options.parse_only:
|
||||
|
@ -46,25 +47,24 @@ def process_input_files(filenames, options):
|
|||
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(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):
|
||||
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, separators=(',', ':')))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
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 or (options.output is None and not options.run_on):
|
||||
return
|
||||
|
@ -138,6 +138,12 @@ if __name__ == '__main__':
|
|||
"Default: raw."
|
||||
)
|
||||
|
||||
argparser.add_argument(
|
||||
"--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(
|
||||
"--analyze-only",
|
||||
action="store_true",
|
||||
|
@ -160,6 +166,17 @@ if __name__ == '__main__':
|
|||
action="store_true",
|
||||
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",
|
||||
action="store_true",
|
||||
|
@ -184,7 +201,7 @@ if __name__ == '__main__':
|
|||
argparser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="%(prog)s 0.19"
|
||||
version="%(prog)s 0.21"
|
||||
)
|
||||
|
||||
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
66
doc/Future directions for SixtyPical.md
Normal file
66
doc/Future directions for SixtyPical.md
Normal 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.
|
|
@ -1,6 +1,12 @@
|
|||
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`
|
||||
|
|
|
@ -1,7 +1,13 @@
|
|||
SixtyPical
|
||||
==========
|
||||
|
||||
This document describes the SixtyPical programming language version 0.19,
|
||||
<!--
|
||||
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.)
|
||||
|
@ -196,7 +202,8 @@ table pointed to is implemented with "indirect indexed" addressing, as in
|
|||
There are extended instruction modes for using these types of memory location.
|
||||
See `copy` below, but here is some illustrative example code:
|
||||
|
||||
point ptr into buf { // this is the only way to initialize a pointer
|
||||
point ptr into buf { // this associates this pointer with this table
|
||||
reset ptr 0 // this is the only way to initialize a pointer
|
||||
add ptr, 4 // note, this is unchecked against table's size!
|
||||
ld y, 0 // you must set this to something yourself
|
||||
copy [ptr] + y, byt // read memory through pointer, into byte
|
||||
|
@ -658,4 +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
|
||||
.
|
||||
|
|
35
eg/README.md
35
eg/README.md
|
@ -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
|
||||
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
<!--
|
||||
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.
|
||||
|
||||
|
|
|
@ -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 ds_graphics @ $C050
|
||||
byte ds_text @ $C051
|
||||
byte ds_full @ $C052
|
||||
|
|
|
@ -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
|
||||
|
||||
// Write ">AB>" to "standard output"
|
||||
|
||||
define cout routine
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/
|
||||
|
|
|
@ -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
|
||||
// ----------------------------------------------------------------
|
||||
|
@ -56,7 +62,6 @@ byte vic_border @ 53280
|
|||
byte vic_bg @ 53281
|
||||
byte table[2048] screen @ 1024
|
||||
byte table[2048] colormap @ 55296
|
||||
byte joy2 @ $dc00
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Global Variables
|
||||
|
@ -69,7 +74,6 @@ word pos
|
|||
word new_pos
|
||||
|
||||
word table[256] actor_delta
|
||||
word delta
|
||||
|
||||
byte player_died
|
||||
|
||||
|
@ -103,71 +107,6 @@ vector game_state_routine
|
|||
// Utility Routines
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
define clear_screen routine
|
||||
outputs screen, colormap
|
||||
trashes a, y, c, n, z
|
||||
|
@ -265,37 +204,37 @@ define player_logic logic_routine
|
|||
|
||||
if c {
|
||||
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
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
point ptr into screen {
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 81
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
}
|
||||
|
||||
copy new_pos, pos
|
||||
copy new_pos, pos
|
||||
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 81, [ptr] + y
|
||||
}
|
||||
} else {
|
||||
ld a, 1
|
||||
st a, player_died
|
||||
}
|
||||
} else {
|
||||
ld a, 1
|
||||
st a, player_died
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -307,28 +246,29 @@ define enemy_logic logic_routine
|
|||
|
||||
if c {
|
||||
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
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
point ptr into screen {
|
||||
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 82
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
}
|
||||
|
||||
copy new_pos, pos
|
||||
copy new_pos, pos
|
||||
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 82, [ptr] + y
|
||||
|
@ -424,7 +364,7 @@ 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
|
||||
}
|
||||
|
|
23
eg/c64/demo-game/run.sh
Executable file
23
eg/c64/demo-game/run.sh
Executable 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
|
|
@ -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
|
||||
|
||||
// Displays 256 hearts at the top of the Commodore 64's screen.
|
||||
|
||||
// Define where the screen starts in memory:
|
||||
|
|
|
@ -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
|
||||
|
||||
//
|
||||
|
|
19
eg/c64/joystick-demo.60p
Normal file
19
eg/c64/joystick-demo.60p
Normal 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
|
||||
}
|
|
@ -1,49 +0,0 @@
|
|||
word screen @ 1024
|
||||
byte joy2 @ $dc00
|
||||
|
||||
word delta
|
||||
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
|
@ -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
|
|
@ -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 -----
|
||||
|
||||
|
|
|
@ -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 -----
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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 -----
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
||||
|
|
|
@ -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 -----
|
||||
|
||||
|
|
|
@ -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 screen @ 1024
|
||||
|
||||
define main routine
|
||||
|
|
|
@ -1,17 +1,23 @@
|
|||
<!--
|
||||
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.
|
||||
|
||||
Examples that are meant to fail and produce an error message
|
||||
are in the `errorful/` subdirectory.
|
||||
when being compiled are in the `errorful/` subdirectory.
|
||||
|
||||
These files are intended to be architecture-agnostic.
|
||||
For the ones that do produce output, an appropriate source
|
||||
under `support/` should be included first, so that system entry
|
||||
points such as `chrout` are defined. In addition, some of these
|
||||
programs use "standard" support modules, so those should be included
|
||||
first too. For example:
|
||||
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 support/c64.60p support/stdlib.60p vector-table.60p
|
||||
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,
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YY
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word score
|
||||
|
||||
define main routine
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print Y
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
byte table[2048] buf
|
||||
pointer ptr @ 254
|
||||
byte foo
|
||||
|
@ -11,10 +12,12 @@ define main routine
|
|||
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
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AA
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define print routine
|
||||
trashes a, z, n
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ENGGL
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
byte b
|
||||
|
||||
define main routine
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ENGGL
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word w1
|
||||
|
||||
define main routine
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ENGGL
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word w1
|
||||
word w2
|
||||
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
// Demonstrates vector tables.
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YN
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
trashes a, x, y, z, n, c, v
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YA
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
trashes a, x, y, z, n, c, v
|
||||
{
|
||||
|
|
|
@ -1,3 +1,9 @@
|
|||
<!--
|
||||
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
|
||||
|
|
|
@ -1,3 +1,8 @@
|
|||
// 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
|
||||
|
|
|
@ -1,3 +1,7 @@
|
|||
// 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
|
||||
|
|
|
@ -1,9 +1,14 @@
|
|||
vector vec
|
||||
// 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
|
||||
|
||||
routine foo
|
||||
define foo routine
|
||||
inputs x
|
||||
outputs x
|
||||
trashes z, n
|
||||
|
@ -11,7 +16,7 @@ routine foo
|
|||
inc x
|
||||
}
|
||||
|
||||
routine main
|
||||
define main routine
|
||||
inputs foo
|
||||
outputs vec
|
||||
trashes a, z, n
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Include `support/${PLATFORM}.60p` and `support/stdlib.60p` before this source
|
||||
// Should print 01
|
||||
|
||||
include "chrout.60p"
|
||||
include "prbyte.60p"
|
||||
|
||||
byte lives
|
||||
|
||||
define main routine
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AB
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define bar routine trashes a, z, n {
|
||||
ld a, 66
|
||||
call chrout
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print ABCDEFGHIJKLMNOPQRSTUVWXYZ
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
trashes a, y, z, n, c
|
||||
{
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AB
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
byte foo
|
||||
|
||||
define print routine
|
||||
|
|
34
eg/rudiments/nested-for.60p
Normal file
34
eg/rudiments/nested-for.60p
Normal 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
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print A
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
define main routine
|
||||
inputs a
|
||||
trashes a, z, n
|
||||
|
|
|
@ -1,6 +0,0 @@
|
|||
// Implementation of `chrout` for the Commodore 64 platform.
|
||||
|
||||
define chrout routine
|
||||
inputs a
|
||||
trashes a
|
||||
@ 65490
|
|
@ -1,6 +0,0 @@
|
|||
// Implementation of `chrout` for the Commodore VIC-20 platform.
|
||||
|
||||
define chrout routine
|
||||
inputs a
|
||||
trashes a
|
||||
@ 65490
|
|
@ -1,7 +1,9 @@
|
|||
// Demonstrates vector tables.
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AABAB
|
||||
|
||||
// Demonstrates vector tables.
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
vector routine
|
||||
trashes a, z, n
|
||||
print
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print AB
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
vector routine
|
||||
trashes a, z, n
|
||||
print
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print YY
|
||||
|
||||
include "chrout.60p"
|
||||
|
||||
word one
|
||||
word table[256] many
|
||||
|
||||
|
|
|
@ -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
|
||||
|
||||
// Displays 256 hearts at the top of the VIC-20's screen.
|
||||
|
||||
// Define where the screen starts in memory:
|
||||
|
|
10
include/c64/chrout.60p
Normal file
10
include/c64/chrout.60p
Normal 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
75
include/c64/joystick.60p
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,3 +1,7 @@
|
|||
// 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
|
10
include/vic20/chrout.60p
Normal file
10
include/vic20/chrout.60p
Normal 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
|
|
@ -1,8 +1,13 @@
|
|||
# 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, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.context import AnalysisContext
|
||||
from sixtypical.model import (
|
||||
TYPE_BYTE, TYPE_WORD,
|
||||
TableType, PointerType, VectorType, RoutineType,
|
||||
|
@ -80,316 +85,6 @@ class IncompatibleConstraintsError(ConstraintsError):
|
|||
pass
|
||||
|
||||
|
||||
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):
|
||||
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):
|
||||
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
|
||||
for ref in refs:
|
||||
# statics are always meaningful
|
||||
if self.symtab.has_static(self.routine.name, ref.name):
|
||||
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):
|
||||
exception_class = kwargs.get('exception_class', ForbiddenWriteError)
|
||||
for ref in refs:
|
||||
# statics are always writeable
|
||||
if self.symtab.has_static(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."""
|
||||
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_static(self.routine.name, ref.name):
|
||||
return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range
|
||||
else:
|
||||
return self.symtab.fetch_global_type(ref.name).max_range
|
||||
|
||||
|
||||
class Analyzer(object):
|
||||
|
||||
def __init__(self, symtab, debug=False):
|
||||
|
@ -401,15 +96,15 @@ class Analyzer(object):
|
|||
# - - - - helper methods - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
|
||||
return self.symtab.fetch_static_type(self.current_routine.name, 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
|
||||
raise NotImplementedError(str(ref))
|
||||
return self.get_type_for_name(ref.name)
|
||||
|
||||
def assert_type(self, type_, *locations):
|
||||
|
@ -450,18 +145,32 @@ class Analyzer(object):
|
|||
def analyze_program(self, program):
|
||||
assert isinstance(program, Program)
|
||||
for routine in program.routines:
|
||||
context = self.analyze_routine(routine)
|
||||
routine.called_routines = set()
|
||||
context, type_ = self.analyze_routine(routine)
|
||||
if type_:
|
||||
routine.routine_type = type_
|
||||
routine.encountered_gotos = list(context.encountered_gotos()) if context else []
|
||||
routine.called_routines = list(routine.called_routines)
|
||||
|
||||
def analyze_routine(self, routine):
|
||||
assert isinstance(routine, Routine)
|
||||
type_ = self.get_type_for_name(routine.name)
|
||||
|
||||
if routine.block is None:
|
||||
# it's an extern, that's fine
|
||||
return None
|
||||
return None, type_
|
||||
|
||||
self.current_routine = routine
|
||||
type_ = self.get_type_for_name(routine.name)
|
||||
|
||||
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||
|
||||
# register any local statics as already-initialized
|
||||
for local_name, local_symentry in self.symtab.locals.get(routine.name, {}).items():
|
||||
ref = self.symtab.fetch_local_ref(routine.name, local_name)
|
||||
if local_symentry.ast_node.initial is not None:
|
||||
context.set_meaningful(ref)
|
||||
context.set_range(ref, local_symentry.ast_node.initial, local_symentry.ast_node.initial)
|
||||
|
||||
self.exit_contexts = []
|
||||
|
||||
self.analyze_block(routine.block, context)
|
||||
|
@ -505,12 +214,12 @@ class Analyzer(object):
|
|||
|
||||
# if something was touched, then it should have been declared to be writable.
|
||||
for ref in context.each_touched():
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_static(routine.name, ref.name):
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_local(routine.name, ref.name):
|
||||
raise ForbiddenWriteError(routine, ref.name)
|
||||
|
||||
self.exit_contexts = None
|
||||
self.current_routine = None
|
||||
return context
|
||||
return context, type_
|
||||
|
||||
def analyze_block(self, block, context):
|
||||
assert isinstance(block, Block)
|
||||
|
@ -531,15 +240,15 @@ class Analyzer(object):
|
|||
elif isinstance(instr, For):
|
||||
self.analyze_for(instr, context)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
self.analyze_with_interrupts_off(instr, context)
|
||||
elif isinstance(instr, Save):
|
||||
self.analyze_save(instr, context)
|
||||
elif isinstance(instr, PointInto):
|
||||
self.analyze_point_into(instr, context)
|
||||
elif isinstance(instr, Reset):
|
||||
self.analyze_reset(instr, context)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError(str(instr))
|
||||
|
||||
def analyze_single_op(self, instr, context):
|
||||
|
||||
|
@ -589,7 +298,6 @@ class Analyzer(object):
|
|||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
elif self.get_type(src) != self.get_type(dest):
|
||||
|
@ -759,12 +467,11 @@ class Analyzer(object):
|
|||
# 2. check that the context is meaningful
|
||||
|
||||
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
|
||||
context.assert_meaningful(src, REG_Y)
|
||||
context.assert_meaningful(src, dest.ref, REG_Y)
|
||||
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
|
@ -775,10 +482,10 @@ class Analyzer(object):
|
|||
raise UnmeaningfulReadError(instr, src.ref)
|
||||
context.assert_meaningful(origin)
|
||||
|
||||
context.set_touched(dest)
|
||||
context.set_written(dest)
|
||||
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
context.assert_meaningful(src.ref, dest.ref, REG_Y)
|
||||
|
||||
origin = context.get_assoc(src.ref)
|
||||
if not origin:
|
||||
|
@ -788,7 +495,6 @@ class Analyzer(object):
|
|||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
||||
|
@ -799,7 +505,6 @@ class Analyzer(object):
|
|||
context.set_written(dest.ref)
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
|
||||
context.assert_meaningful(src.ref, src.index)
|
||||
context.set_touched(dest)
|
||||
context.set_written(dest)
|
||||
else:
|
||||
context.assert_meaningful(src)
|
||||
|
@ -817,16 +522,19 @@ class Analyzer(object):
|
|||
raise NotImplementedError(opcode)
|
||||
|
||||
def analyze_call(self, instr, context):
|
||||
type = self.get_type(instr.location)
|
||||
if not isinstance(type, (RoutineType, VectorType)):
|
||||
type_ = self.get_type(instr.location)
|
||||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, instr.location.name)
|
||||
if isinstance(type, VectorType):
|
||||
type = type.of_type
|
||||
for ref in type.inputs:
|
||||
|
||||
self.current_routine.called_routines.add((instr.location, type_))
|
||||
|
||||
if isinstance(type_, VectorType):
|
||||
type_ = type_.of_type
|
||||
for ref in type_.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
for ref in type.outputs:
|
||||
for ref in type_.outputs:
|
||||
context.set_written(ref)
|
||||
for ref in type.trashes:
|
||||
for ref in type_.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
|
@ -838,6 +546,8 @@ class Analyzer(object):
|
|||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, location.name)
|
||||
|
||||
self.current_routine.called_routines.add((instr.location, type_))
|
||||
|
||||
# assert that the dest routine's inputs are all initialized
|
||||
if isinstance(type_, VectorType):
|
||||
type_ = type_.of_type
|
||||
|
@ -865,7 +575,6 @@ class Analyzer(object):
|
|||
exit_context = context.clone()
|
||||
|
||||
for ref in type_.outputs:
|
||||
exit_context.set_touched(ref) # ?
|
||||
exit_context.set_written(ref)
|
||||
|
||||
for ref in type_.trashes:
|
||||
|
@ -973,6 +682,13 @@ class Analyzer(object):
|
|||
context.set_range(instr.dest, instr.final, instr.final)
|
||||
context.set_writeable(instr.dest)
|
||||
|
||||
def analyze_with_interrupts_off(self, instr, context):
|
||||
block = instr.block
|
||||
for instr in block.instrs:
|
||||
if isinstance(instr, (Call, GoTo, WithInterruptsOff)):
|
||||
raise IllegalJumpError(instr, instr)
|
||||
self.analyze_instr(instr, context)
|
||||
|
||||
def analyze_save(self, instr, context):
|
||||
batons = []
|
||||
for location in instr.locations:
|
||||
|
@ -1007,11 +723,11 @@ class Analyzer(object):
|
|||
if context.get_assoc(instr.pointer):
|
||||
raise ForbiddenWriteError(instr, instr.pointer)
|
||||
|
||||
# associate pointer with table, mark it as meaningful.
|
||||
# associate pointer with table
|
||||
# (do not mark it as meaningful yet - that's reset's job.)
|
||||
|
||||
context.set_assoc(instr.pointer, instr.table)
|
||||
context.set_meaningful(instr.pointer)
|
||||
context.set_touched(instr.pointer)
|
||||
context.set_unmeaningful(instr.pointer)
|
||||
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
|
@ -1021,3 +737,21 @@ class Analyzer(object):
|
|||
|
||||
context.set_assoc(instr.pointer, None)
|
||||
context.set_unmeaningful(instr.pointer)
|
||||
|
||||
def analyze_reset(self, instr, context):
|
||||
type = self.get_type(instr.pointer)
|
||||
if not isinstance(type, (PointerType)):
|
||||
raise TypeMismatchError(instr, instr.pointer.name)
|
||||
|
||||
table = context.get_assoc(instr.pointer)
|
||||
if not table:
|
||||
raise ForbiddenWriteError(instr, '{} is not associated with any table'.format(instr.pointer.name))
|
||||
context.assert_meaningful(table)
|
||||
low_limit, high_limit = context.get_range(table)
|
||||
|
||||
assert isinstance(instr.offset, ConstantRef)
|
||||
if instr.offset.value < low_limit or instr.offset.value > high_limit:
|
||||
raise RangeExceededError(instr, instr.pointer.name)
|
||||
|
||||
context.set_meaningful(instr.pointer)
|
||||
context.set_touched(instr.pointer)
|
||||
|
|
|
@ -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):
|
||||
|
@ -59,7 +63,7 @@ class Defn(AST):
|
|||
|
||||
class Routine(AST):
|
||||
value_attrs = ('name', 'addr', 'initial',)
|
||||
children_attrs = ('statics',)
|
||||
children_attrs = ('locals',)
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
|
@ -75,6 +79,10 @@ class SingleOp(Instr):
|
|||
value_attrs = ('opcode', 'dest', 'src',)
|
||||
|
||||
|
||||
class Reset(Instr):
|
||||
value_attrs = ('pointer', 'offset',)
|
||||
|
||||
|
||||
class Call(Instr):
|
||||
value_attrs = ('location',)
|
||||
|
||||
|
|
72
src/sixtypical/callgraph.py
Normal file
72
src/sixtypical/callgraph.py
Normal 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)
|
||||
])
|
|
@ -1,7 +1,11 @@
|
|||
# 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, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef,
|
||||
|
@ -34,16 +38,17 @@ class Compiler(object):
|
|||
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 - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
|
||||
return self.symtab.fetch_static_type(self.current_routine.name, 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):
|
||||
|
@ -76,9 +81,9 @@ class Compiler(object):
|
|||
|
||||
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):
|
||||
|
@ -107,25 +112,25 @@ class Compiler(object):
|
|||
label.set_addr(routine.addr)
|
||||
self.labels[routine.name] = label
|
||||
|
||||
if hasattr(routine, 'statics'):
|
||||
self.current_routine = routine
|
||||
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
|
||||
declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label))
|
||||
self.routine_statics[routine.name] = static_labels
|
||||
self.current_routine = None
|
||||
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.items():
|
||||
self.emitter.resolve_label(label)
|
||||
|
@ -155,23 +160,45 @@ class Compiler(object):
|
|||
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):
|
||||
|
@ -192,6 +219,8 @@ class Compiler(object):
|
|||
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
|
||||
|
||||
|
@ -435,19 +464,15 @@ class Compiler(object):
|
|||
raise NotImplementedError(location_type)
|
||||
|
||||
def compile_goto(self, instr):
|
||||
self.final_goto_seen = True
|
||||
if self.skip_final_goto:
|
||||
pass
|
||||
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:
|
||||
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)
|
||||
raise NotImplementedError(location_type)
|
||||
|
||||
def compile_cmp(self, instr, src, dest):
|
||||
"""`instr` is only for reporting purposes"""
|
||||
|
@ -660,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)
|
||||
|
||||
|
@ -742,12 +772,16 @@ class Compiler(object):
|
|||
self.emitter.emit(STA(Absolute(src_label)))
|
||||
|
||||
def compile_point_into(self, instr):
|
||||
src_label = self.get_label(instr.table.name)
|
||||
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))))
|
||||
|
||||
self.compile_block(instr.block)
|
||||
|
|
332
src/sixtypical/context.py
Normal file
332
src/sixtypical/context.py
Normal 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
|
|
@ -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."""
|
||||
|
@ -128,7 +132,7 @@ 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):
|
||||
|
@ -143,7 +147,7 @@ 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):
|
||||
|
@ -171,6 +175,16 @@ class Emitter(object):
|
|||
self.accum.append(thing)
|
||||
self.addr += thing.size()
|
||||
|
||||
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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
"""Data/storage model for SixtyPical."""
|
||||
|
||||
from collections import namedtuple
|
||||
|
|
|
@ -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
|
||||
|
||||
"""Executable file writer."""
|
||||
|
||||
from sixtypical.emitter import Emitter, Byte, Word
|
||||
|
|
|
@ -1,23 +1,19 @@
|
|||
# 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, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
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, PointerType,
|
||||
LocationRef, ConstantRef, IndirectRef, IndexedRef,
|
||||
ConstantRef, IndirectRef, IndexedRef,
|
||||
)
|
||||
from sixtypical.scanner import Scanner
|
||||
|
||||
|
||||
class SymEntry(object):
|
||||
def __init__(self, ast_node, type_):
|
||||
self.ast_node = ast_node
|
||||
self.type_ = type_
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_)
|
||||
from sixtypical.symtab import SymEntry
|
||||
|
||||
|
||||
class ForwardReference(object):
|
||||
|
@ -28,45 +24,10 @@ class ForwardReference(object):
|
|||
return "%s(%r)" % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
class SymbolTable(object):
|
||||
def __init__(self):
|
||||
self.symbols = {} # symbol name -> SymEntry
|
||||
self.statics = {} # 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: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
|
||||
|
||||
def has_static(self, routine_name, name):
|
||||
return name in self.statics.get(routine_name, {})
|
||||
|
||||
def fetch_global_type(self, name):
|
||||
return self.symbols[name].type_
|
||||
|
||||
def fetch_static_type(self, routine_name, name):
|
||||
return self.statics[routine_name][name].type_
|
||||
|
||||
def fetch_global_ref(self, name):
|
||||
if name in self.symbols:
|
||||
return LocationRef(name)
|
||||
return None
|
||||
|
||||
def fetch_static_ref(self, routine_name, name):
|
||||
routine_statics = self.statics.get(routine_name, {})
|
||||
if name in routine_statics:
|
||||
return LocationRef(name)
|
||||
return None
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, symtab, text, filename):
|
||||
def __init__(self, symtab, text, filename, include_path):
|
||||
self.symtab = symtab
|
||||
self.include_path = include_path
|
||||
self.scanner = Scanner(text, filename)
|
||||
self.current_routine_name = None
|
||||
|
||||
|
@ -76,7 +37,7 @@ class Parser(object):
|
|||
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_static_ref(routine_name, name)
|
||||
model = self.symtab.fetch_local_ref(routine_name, name)
|
||||
if model is None and allow_forward:
|
||||
return ForwardReference(name)
|
||||
if model is None:
|
||||
|
@ -88,10 +49,12 @@ class Parser(object):
|
|||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
self.symtab.symbols[name] = SymEntry(ast_node, type_)
|
||||
|
||||
def declare_static(self, routine_name, name, 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' % name)
|
||||
self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
|
||||
self.syntax_error('Symbol "%s" already declared globally' % name)
|
||||
self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
|
||||
|
||||
# ---- symbol resolution
|
||||
|
||||
|
@ -138,6 +101,12 @@ class Parser(object):
|
|||
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()
|
||||
|
@ -153,13 +122,20 @@ class Parser(object):
|
|||
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')
|
||||
|
||||
program = Program(self.scanner.line_number, defns=defns, routines=routines)
|
||||
programs = includes + [program]
|
||||
program = merge_programs(programs)
|
||||
|
||||
self.resolve_symbols(program)
|
||||
return program
|
||||
|
||||
|
@ -306,17 +282,17 @@ class Parser(object):
|
|||
type_ = self.defn_type()
|
||||
if not isinstance(type_, RoutineType):
|
||||
self.syntax_error("Can only define a routine, not {}".format(repr(type_)))
|
||||
statics = []
|
||||
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()
|
||||
locals_ = self.locals()
|
||||
block = self.block()
|
||||
addr = None
|
||||
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, statics=statics)
|
||||
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_)
|
||||
|
||||
def labels(self):
|
||||
accum = []
|
||||
|
@ -370,13 +346,19 @@ class Parser(object):
|
|||
loc = IndexedRef(loc, offset, index)
|
||||
return loc
|
||||
|
||||
def statics(self):
|
||||
def locals(self):
|
||||
defns = []
|
||||
while self.scanner.consume('static'):
|
||||
type_, defn = self.defn()
|
||||
if defn.initial is None:
|
||||
self.syntax_error("Static definition {} must have initial value".format(defn))
|
||||
self.declare_static(self.current_routine_name, defn.name, defn, type_)
|
||||
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
|
||||
|
||||
|
@ -424,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
|
||||
|
@ -498,6 +484,19 @@ class Parser(object):
|
|||
# - - - -
|
||||
|
||||
|
||||
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."""
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
54
src/sixtypical/symtab.py
Normal file
54
src/sixtypical/symtab.py
Normal 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
17
test.sh
|
@ -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
229
tests/SixtyPical Callgraph.md
Normal file
229
tests/SixtyPical Callgraph.md
Normal 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
|
|
@ -1,14 +1,17 @@
|
|||
SixtyPical Compilation
|
||||
======================
|
||||
|
||||
<!--
|
||||
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 compiling
|
||||
SixtyPical to 6502 machine code.
|
||||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Functionality "Compile SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||
|
||||
-> Tests for functionality "Compile SixtyPical program"
|
||||
|
||||
Null program.
|
||||
|
@ -54,10 +57,12 @@ Call extern.
|
|||
| {
|
||||
| ld a, 65
|
||||
| call chrout
|
||||
| ld a, 0
|
||||
| }
|
||||
= $080D LDA #$41
|
||||
= $080F JSR $FFD2
|
||||
= $0812 RTS
|
||||
= $0812 LDA #$00
|
||||
= $0814 RTS
|
||||
|
||||
Call defined routine.
|
||||
|
||||
|
@ -74,13 +79,39 @@ Call defined routine.
|
|||
| trashes a, x, y, z, n
|
||||
| {
|
||||
| call foo
|
||||
| ld a, 1
|
||||
| }
|
||||
= $080D JSR $0811
|
||||
= $0810 RTS
|
||||
= $0811 LDA #$00
|
||||
= $0813 LDX #$00
|
||||
= $0815 LDY #$00
|
||||
= $0817 RTS
|
||||
= $080D JSR $0813
|
||||
= $0810 LDA #$01
|
||||
= $0812 RTS
|
||||
= $0813 LDA #$00
|
||||
= $0815 LDX #$00
|
||||
= $0817 LDY #$00
|
||||
= $0819 RTS
|
||||
|
||||
Tail call is optimized into a jump.
|
||||
|
||||
| define foo routine
|
||||
| outputs a, x, y
|
||||
| trashes z, n
|
||||
| {
|
||||
| ld a, 0
|
||||
| ld x, 0
|
||||
| ld y, 0
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, y, z, n
|
||||
| {
|
||||
| ld a, 1
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDA #$01
|
||||
= $080F JMP $0812
|
||||
= $0812 LDA #$00
|
||||
= $0814 LDX #$00
|
||||
= $0816 LDY #$00
|
||||
= $0818 RTS
|
||||
|
||||
Access a defined memory location.
|
||||
|
||||
|
@ -613,7 +644,6 @@ Compiling `repeat forever`.
|
|||
= $080D LDY #$41
|
||||
= $080F INY
|
||||
= $0810 JMP $080F
|
||||
= $0813 RTS
|
||||
|
||||
The body of `repeat forever` can be empty.
|
||||
|
||||
|
@ -623,7 +653,6 @@ The body of `repeat forever` can be empty.
|
|||
| } forever
|
||||
| }
|
||||
= $080D JMP $080D
|
||||
= $0810 RTS
|
||||
|
||||
Compiling `for ... up to`.
|
||||
|
||||
|
@ -1058,7 +1087,7 @@ Copy word to word table and back, with constant offsets.
|
|||
= $0848 STA $084D
|
||||
= $084B RTS
|
||||
|
||||
Indirect call.
|
||||
Indirect call. TODO: we don't need the final RTS here, omit it.
|
||||
|
||||
| vector routine
|
||||
| outputs x
|
||||
|
@ -1079,16 +1108,15 @@ Indirect call.
|
|||
| copy bar, foo
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDA #$1B
|
||||
= $080F STA $0822
|
||||
= $080D LDA #$1A
|
||||
= $080F STA $0821
|
||||
= $0812 LDA #$08
|
||||
= $0814 STA $0823
|
||||
= $0817 JSR $081E
|
||||
= $081A RTS
|
||||
= $081B LDX #$C8
|
||||
= $081D RTS
|
||||
= $081E JMP ($0822)
|
||||
= $0821 RTS
|
||||
= $0814 STA $0822
|
||||
= $0817 JMP $081D
|
||||
= $081A LDX #$C8
|
||||
= $081C RTS
|
||||
= $081D JMP ($0821)
|
||||
= $0820 RTS
|
||||
|
||||
Compiling `goto`. Note that no `RTS` is emitted after the `JMP`.
|
||||
|
||||
|
@ -1142,28 +1170,27 @@ Copying to and from a vector table.
|
|||
| call one
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA #$3F
|
||||
= $0811 STA $0846
|
||||
= $080F LDA #$3E
|
||||
= $0811 STA $0845
|
||||
= $0814 LDA #$08
|
||||
= $0816 STA $0847
|
||||
= $0819 LDA #$3F
|
||||
= $081B STA $0848,X
|
||||
= $0816 STA $0846
|
||||
= $0819 LDA #$3E
|
||||
= $081B STA $0847,X
|
||||
= $081E LDA #$08
|
||||
= $0820 STA $0948,X
|
||||
= $0823 LDA $0846
|
||||
= $0826 STA $0848,X
|
||||
= $0829 LDA $0847
|
||||
= $082C STA $0948,X
|
||||
= $082F LDA $0848,X
|
||||
= $0832 STA $0846
|
||||
= $0835 LDA $0948,X
|
||||
= $0838 STA $0847
|
||||
= $083B JSR $0842
|
||||
= $083E RTS
|
||||
= $083F LDX #$C8
|
||||
= $0841 RTS
|
||||
= $0842 JMP ($0846)
|
||||
= $0845 RTS
|
||||
= $0820 STA $0947,X
|
||||
= $0823 LDA $0845
|
||||
= $0826 STA $0847,X
|
||||
= $0829 LDA $0846
|
||||
= $082C STA $0947,X
|
||||
= $082F LDA $0847,X
|
||||
= $0832 STA $0845
|
||||
= $0835 LDA $0947,X
|
||||
= $0838 STA $0846
|
||||
= $083B JMP $0841
|
||||
= $083E LDX #$C8
|
||||
= $0840 RTS
|
||||
= $0841 JMP ($0845)
|
||||
= $0844 RTS
|
||||
|
||||
Copying to and from a vector table, with constant offsets.
|
||||
|
||||
|
@ -1193,28 +1220,27 @@ Copying to and from a vector table, with constant offsets.
|
|||
| call one
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA #$3F
|
||||
= $0811 STA $0846
|
||||
= $080F LDA #$3E
|
||||
= $0811 STA $0845
|
||||
= $0814 LDA #$08
|
||||
= $0816 STA $0847
|
||||
= $0819 LDA #$3F
|
||||
= $081B STA $0849,X
|
||||
= $0816 STA $0846
|
||||
= $0819 LDA #$3E
|
||||
= $081B STA $0848,X
|
||||
= $081E LDA #$08
|
||||
= $0820 STA $0949,X
|
||||
= $0823 LDA $0846
|
||||
= $0826 STA $084A,X
|
||||
= $0829 LDA $0847
|
||||
= $082C STA $094A,X
|
||||
= $082F LDA $084B,X
|
||||
= $0832 STA $0846
|
||||
= $0835 LDA $094B,X
|
||||
= $0838 STA $0847
|
||||
= $083B JSR $0842
|
||||
= $083E RTS
|
||||
= $083F LDX #$C8
|
||||
= $0841 RTS
|
||||
= $0842 JMP ($0846)
|
||||
= $0845 RTS
|
||||
= $0820 STA $0948,X
|
||||
= $0823 LDA $0845
|
||||
= $0826 STA $0849,X
|
||||
= $0829 LDA $0846
|
||||
= $082C STA $0949,X
|
||||
= $082F LDA $084A,X
|
||||
= $0832 STA $0845
|
||||
= $0835 LDA $094A,X
|
||||
= $0838 STA $0846
|
||||
= $083B JMP $0841
|
||||
= $083E LDX #$C8
|
||||
= $0840 RTS
|
||||
= $0841 JMP ($0845)
|
||||
= $0844 RTS
|
||||
|
||||
### add, sub
|
||||
|
||||
|
@ -1400,7 +1426,7 @@ Subtracting a word memory location from another word memory location.
|
|||
|
||||
### Tables and Pointers
|
||||
|
||||
Load address of table into pointer.
|
||||
Associate pointer with table. Does nothing by itself.
|
||||
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
|
@ -1415,6 +1441,24 @@ Load address of table into pointer.
|
|||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F RTS
|
||||
|
||||
Reset pointer to table.
|
||||
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| outputs tab, y
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$18
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$08
|
||||
|
@ -1433,6 +1477,7 @@ Write literal through a pointer.
|
|||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
|
@ -1458,6 +1503,7 @@ Write stored value through a pointer.
|
|||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy foo, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
|
@ -1483,6 +1529,7 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
|||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy [ptr] + y, foo
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
|
@ -1497,6 +1544,39 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
|||
= $081C LDA ($FE),Y
|
||||
= $081E RTS
|
||||
|
||||
Multiple `reset`s may occur inside the same block.
|
||||
|
||||
| byte table[256] tab @ 1024
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| outputs y, foo
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 16
|
||||
| copy [ptr] + y, foo
|
||||
| reset ptr 18
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$10
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$04
|
||||
= $0815 STA $FF
|
||||
= $0817 LDA ($FE),Y
|
||||
= $0819 STA $0827
|
||||
= $081C LDA #$12
|
||||
= $081E STA $FE
|
||||
= $0820 LDA #$04
|
||||
= $0822 STA $FF
|
||||
= $0824 LDA ($FE),Y
|
||||
= $0826 RTS
|
||||
|
||||
Read and write through two pointers.
|
||||
|
||||
| byte table[256] tab
|
||||
|
@ -1510,7 +1590,9 @@ Read and write through two pointers.
|
|||
| {
|
||||
| ld y, 0
|
||||
| point ptra into tab {
|
||||
| reset ptra 0
|
||||
| point ptrb into tab {
|
||||
| reset ptrb 0
|
||||
| copy [ptra] + y, [ptrb] + y
|
||||
| }
|
||||
| }
|
||||
|
@ -1541,6 +1623,7 @@ Write the `a` register through a pointer.
|
|||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, 255
|
||||
| st a, [ptr] + y
|
||||
| }
|
||||
|
@ -1571,6 +1654,7 @@ Note that this is *not* range-checked. (Yet.)
|
|||
| ld y, 0
|
||||
| st off, c
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| add ptr, delta
|
||||
| add ptr, word 1
|
||||
| copy [ptr] + y, foo
|
||||
|
@ -1619,9 +1703,9 @@ Trash does nothing except indicate that we do not care about the value anymore.
|
|||
= $080E LDA #$00
|
||||
= $0810 RTS
|
||||
|
||||
### static ###
|
||||
### locals ###
|
||||
|
||||
Memory locations defined static to a routine are allocated
|
||||
Memory locations defined local static to a routine are allocated
|
||||
just the same as initialized global storage locations are.
|
||||
|
||||
| define foo routine
|
||||
|
@ -1642,12 +1726,72 @@ just the same as initialized global storage locations are.
|
|||
| ld x, t
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX $081F
|
||||
= $0810 JSR $0814
|
||||
= $0813 RTS
|
||||
= $0814 STX $081E
|
||||
= $0817 INC $081E
|
||||
= $081A LDX $081E
|
||||
= $081D RTS
|
||||
= $081E .byte $FF
|
||||
= $081F .byte $07
|
||||
= $080D LDX $081E
|
||||
= $0810 JMP $0813
|
||||
= $0813 STX $081D
|
||||
= $0816 INC $081D
|
||||
= $0819 LDX $081D
|
||||
= $081C RTS
|
||||
= $081D .byte $FF
|
||||
= $081E .byte $07
|
||||
|
||||
Memory locations defined local dynamic to a routine are allocated
|
||||
just the same as uninitialized global storage locations are.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| local byte t
|
||||
| {
|
||||
| ld x, 0
|
||||
| st x, t
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F STX $0820
|
||||
= $0812 JMP $0815
|
||||
= $0815 STX $081F
|
||||
= $0818 INC $081F
|
||||
= $081B LDX $081F
|
||||
= $081E RTS
|
||||
|
||||
Memory locations defined local dynamic to a routine are allocated
|
||||
just the same as uninitialized global storage locations are, even
|
||||
when declared with a fixed address.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t @ 1024
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| local byte t @ 1025
|
||||
| {
|
||||
| ld x, 0
|
||||
| st x, t
|
||||
| call foo
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F STX $0401
|
||||
= $0812 JMP $0815
|
||||
= $0815 STX $0400
|
||||
= $0818 INC $0400
|
||||
= $081B LDX $0400
|
||||
= $081E RTS
|
||||
|
|
1851
tests/SixtyPical Control Flow.md
Normal file
1851
tests/SixtyPical Control Flow.md
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -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.
|
||||
|
@ -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 --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /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
|
||||
|
@ -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
|
||||
|
@ -417,11 +412,10 @@ in the "true" branch is a `goto`.
|
|||
| }
|
||||
= $080D RTS
|
||||
= $080E LDA #$00
|
||||
= $0810 BNE $081A
|
||||
= $0810 BNE $0817
|
||||
= $0812 LDA #$01
|
||||
= $0814 JMP $081F
|
||||
= $0817 JMP $081F
|
||||
= $081A LDA #$02
|
||||
= $081C JMP $080D
|
||||
= $081F LDA #$FF
|
||||
= $0821 RTS
|
||||
= $0814 JMP $081C
|
||||
= $0817 LDA #$02
|
||||
= $0819 JMP $080D
|
||||
= $081C LDA #$FF
|
||||
= $081E RTS
|
||||
|
|
1990
tests/SixtyPical Storage.md
Normal file
1990
tests/SixtyPical Storage.md
Normal file
File diff suppressed because it is too large
Load Diff
|
@ -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,9 +15,6 @@ but not necessarily sensible programs.
|
|||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Functionality "Check syntax of SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok"
|
||||
|
||||
-> Tests for functionality "Check syntax of SixtyPical program"
|
||||
|
||||
Rudimentary program.
|
||||
|
@ -76,6 +79,18 @@ Extern routines
|
|||
| @ 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.
|
||||
|
||||
| define main routine {
|
||||
|
@ -176,6 +191,7 @@ Other blocks.
|
|||
| ld a, 0
|
||||
| }
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
|
@ -681,6 +697,7 @@ Tables and pointers.
|
|||
|
|
||||
| define main routine {
|
||||
| point ptr into buf {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, [ptrb] + y
|
||||
|
@ -746,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
|
||||
|
@ -762,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
|
||||
|
@ -782,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
|
||||
|
|
||||
|
@ -790,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
|
||||
|
@ -802,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
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
#!/usr/bin/env python3
|
||||
|
||||
# script that allows the binary output of sixtypical --output-format=c64-basic-prg --compile to be
|
||||
# disassembled by https://github.com/tcarmelveilleux/dcc6502
|
||||
|
@ -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))
|
||||
|
|
30
tests/appliances/sixtypical-py2.7.md
Normal file
30
tests/appliances/sixtypical-py2.7.md
Normal 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"
|
26
tests/appliances/sixtypical.md
Normal file
26
tests/appliances/sixtypical.md
Normal 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"
|
Loading…
Reference in New Issue
Block a user