mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-06-07 06:29:32 +00:00
Compare commits
78 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 | ||
|
1c7efb019d | ||
|
0c65954bc5 | ||
|
a10f1c6528 | ||
|
4d82e2352e | ||
|
c246424930 | ||
|
6d867867fe | ||
|
a44b007ff0 | ||
|
ce8e83908b | ||
|
04a9438898 | ||
|
c906ab7817 | ||
|
1ca5cb0336 | ||
|
7854f71706 | ||
|
652ab1dc5c | ||
|
21a187a105 | ||
|
8d6e5e090d | ||
|
a0328b8840 | ||
|
394fbddad6 | ||
|
4615d8d054 | ||
|
bd462d6d8b | ||
|
b19267d3ba |
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
|
88
HISTORY.md
88
HISTORY.md
|
@ -1,6 +1,94 @@
|
|||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
<!--
|
||||
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
|
||||
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
|
||||
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
|
||||
-->
|
||||
|
||||
0.21-2023.0309
|
||||
--------------
|
||||
|
||||
* Python 3 is the default interpreter for `sixtypical`.
|
||||
* Test appliance added for testing under Python 2.7.
|
||||
* `dcc6502` test adapter made runnable under both
|
||||
Python 2 and Python 3.
|
||||
|
||||
0.21
|
||||
----
|
||||
|
||||
* A source file can be included in another source file
|
||||
by means of the `include` directive.
|
||||
* A routine can be declared `preserved`, which prevents a
|
||||
compiler from omitting it from the final executable, even
|
||||
if it determines it is not called by any other routine.
|
||||
* The reference implementation constructs a callgraph and
|
||||
determines the set of routines which are not reachable
|
||||
(directly or indirectly) from `main`, with an eye to
|
||||
omitting them from the final executable.
|
||||
* Added `--prune-unreachable-routines` option, which causes
|
||||
the compiler to in fact omit routines determined to be
|
||||
unreachable as described above.
|
||||
* Added `--include-path` option, which configures the list
|
||||
of directories that are searched when a source file is
|
||||
included with the `include` directive.
|
||||
* Code generation now performs modest peephole optimization
|
||||
at the end of each routine. This results in better code
|
||||
generation for constructs in tail position, notably
|
||||
tail optimization of `calls`, but also for `goto`s and
|
||||
`if` blocks at the end of a routine.
|
||||
* Began collecting architecture-specific and portable
|
||||
include-files for a nascent "standard library".
|
||||
|
||||
0.20
|
||||
----
|
||||
|
||||
* A `point ... into` block no longer initializes the pointer
|
||||
by default. A subequent `reset` instruction must be used
|
||||
to initialize the pointer. The pointer may be reset to any
|
||||
valid offset within the table (not only 0) and it may be
|
||||
reset multiple times inside the block.
|
||||
* Local locations need no longer be static. If they are not
|
||||
static, they are considered uninitialized until assigned,
|
||||
and they can be declared with an explicit fixed address.
|
||||
* Along with `goto`, `call` and `with interrupts off` are
|
||||
now forbidden inside a `with interrupts off` block.
|
||||
* More tests to assure that using `call` inside a `point into`
|
||||
block or inside a `for` block does not cause trouble,
|
||||
particularly when the routine being called also uses the
|
||||
variable named in that block.
|
||||
* Fixed a bug where two local statics could be declared with
|
||||
the same name.
|
||||
* Split analysis context support off from analyzer, and
|
||||
symbol table support from parse, and it their own modules.
|
||||
* Split the SixtyPical Analysis tests across three files,
|
||||
and placed test appliances for `sixtypical` in own file.
|
||||
|
||||
0.19
|
||||
----
|
||||
|
||||
* A `table` may be defined with more than 256 entries, even
|
||||
though the conventional index syntax can only refer to the
|
||||
first 256 entries.
|
||||
* A `pointer` may point inside values of type `byte table`,
|
||||
allowing access to entries beyond the 256th.
|
||||
* `buffer` types have been eliminated from the language,
|
||||
as the above two improvements allow `byte table`s to
|
||||
do everything `buffer`s previously did.
|
||||
* When accessing a table with an index, a constant offset
|
||||
can also be given.
|
||||
* Accessing a `table` through a `pointer` must be done in
|
||||
the context of a `point ... into` block. This allows the
|
||||
analyzer to check *which* table is being accessed.
|
||||
* Refactored compiler internals so that type information
|
||||
is stored in a single symbol table shared by all phases.
|
||||
* Refactored internal data structures that represent
|
||||
references and types to be immutable `namedtuple`s.
|
||||
* Added `--dump-exit-contexts` option to `sixtypical`.
|
||||
* Added a new `--run-on=<emu>` option to `sixtypical`, which
|
||||
replaces the old `loadngo.sh` script.
|
||||
|
||||
0.18
|
||||
----
|
||||
|
||||
|
|
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/>
|
174
README.md
174
README.md
|
@ -1,14 +1,58 @@
|
|||
SixtyPical
|
||||
==========
|
||||
|
||||
_Version 0.18. 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 programming language with advanced
|
||||
static analysis. Many of its primitive instructions resemble
|
||||
those of the 6502 CPU — in fact it is intended to be compiled to
|
||||
6502 machine code — but along with these instructions are
|
||||
constructs which ease structuring and analyzing the code. The
|
||||
language aims to fill this niche:
|
||||
_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.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
Make sure you have Python (2.7 or 3.5+) installed. Then
|
||||
clone this repository and put its `bin` directory on your
|
||||
executable search path. Then you can run:
|
||||
|
||||
sixtypical
|
||||
|
||||
If you have the [VICE][] emulator suite installed, you can run
|
||||
|
||||
sixtypical --run-on=x64 eg/c64/hearts.60p
|
||||
|
||||
and it will compile the [hearts.60p source code](eg/c64/hearts.60p) and
|
||||
automatically start it in the `x64` emulator, and you should see:
|
||||
|
||||
![Screenshot of result of running hearts.60p](images/hearts.png?raw=true)
|
||||
|
||||
You can try `sixtypical --run-on` on other sources in the `eg` directory
|
||||
tree, which contains more extensive examples, including an entire
|
||||
game(-like program); see [eg/README.md](eg/README.md) for a listing.
|
||||
|
||||
Features
|
||||
--------
|
||||
|
||||
SixtyPical aims to fill this niche:
|
||||
|
||||
* You'd use assembly, but you don't want to spend hours
|
||||
debugging (say) a memory overrun that happened because of a
|
||||
|
@ -19,7 +63,30 @@ language aims to fill this niche:
|
|||
|
||||
SixtyPical gives the programmer a coding regimen on par with assembly
|
||||
language in terms of size and hands-on-ness, but also able to catch
|
||||
many ridiculous silly errors at compile time, such as
|
||||
many ridiculous silly errors at compile time.
|
||||
|
||||
### Low level
|
||||
|
||||
Many of SixtyPical's primitive instructions resemble those of the
|
||||
[MOS Technology 6502][] — it is in fact intended to be compiled to 6502
|
||||
machine code. However, it also provides some "higher-level" operations
|
||||
based on common 8-bit machine-language programming idioms, including
|
||||
|
||||
* copying values from one register to another (via a third register when
|
||||
there are no underlying instructions that directly support it)
|
||||
* copying, adding, and comparing 16-bit values (done in two steps)
|
||||
* explicit tail calls
|
||||
* indirect subroutine calls
|
||||
|
||||
While a programmer will find these constructs convenient, their
|
||||
inclusion in the language is primarily to make programs easier to analyze.
|
||||
|
||||
### Static analysis
|
||||
|
||||
The SixtyPical language defines an [effect system][], and the reference
|
||||
compiler [abstractly interprets][] the input program in the manner of
|
||||
[flow typing][] to confirm that it does not violate it. This can detect
|
||||
common mistakes such as
|
||||
|
||||
* you forgot to clear carry before adding something to the accumulator
|
||||
* a subroutine that you called trashes a register you thought it preserved
|
||||
|
@ -27,58 +94,69 @@ many ridiculous silly errors at compile time, such as
|
|||
* you tried to write the address of something that was not a routine, to
|
||||
a jump vector
|
||||
|
||||
Many of these checks are done with _abstract interpretation_, where we
|
||||
go through the program step by step, tracking not just the changes that
|
||||
happen during a _specific_ execution of the program, but _sets_ of changes
|
||||
that could _possibly_ happen in any run of the program.
|
||||
### Efficient code
|
||||
|
||||
SixtyPical also provides some convenient operations based on
|
||||
machine-language programming idioms, such as
|
||||
Unlike most conventional languages, in SixtyPical the programmer must manage
|
||||
memory very explicitly, selecting the registers and memory locations to store
|
||||
each piece of data in. So, unlike a C compiler such as [cc65][], a SixtyPical
|
||||
compiler doesn't need to generate code to handle [calling conventions][] or
|
||||
[register allocation][]. This results in smaller (and thus faster) programs.
|
||||
|
||||
* copying values from one register to another (via a third register when
|
||||
there are no underlying instructions that directly support it); this
|
||||
includes 16-bit values, which are copied in two steps
|
||||
* explicit tail calls
|
||||
* indirect subroutine calls
|
||||
The flagship demo, a minigame for the Commodore 64, compiles to
|
||||
a **930**-byte `.PRG` file.
|
||||
|
||||
### Target platforms
|
||||
|
||||
The reference implementation can analyze and compile SixtyPical programs to
|
||||
6502 machine code formats which can run on several 6502-based 8-bit architectures:
|
||||
|
||||
* [Commodore 64][]
|
||||
* [Commodore VIC-20][]
|
||||
* [Atari 2600][]
|
||||
* [Apple II series][]
|
||||
|
||||
For example programs for each of these, see [eg/README.md](eg/README.md).
|
||||
|
||||
Specification
|
||||
-------------
|
||||
|
||||
SixtyPical is defined by a specification document, a set of test cases,
|
||||
and a reference implementation written in Python 2. The reference
|
||||
implementation can analyze and compile SixtyPical programs to 6502 machine
|
||||
code, which can be run on several 6502-based 8-bit architectures:
|
||||
and a reference implementation written in Python.
|
||||
|
||||
* Commodore 64
|
||||
* Commodore VIC-20
|
||||
* Atari 2600 VCS
|
||||
* Apple II
|
||||
There are over 400 test cases, written in [Falderal][] format for readability.
|
||||
In order to run the tests for compilation, [dcc6502][] needs to be installed.
|
||||
|
||||
Quick Start
|
||||
-----------
|
||||
|
||||
If you have the [VICE][] emulator installed, from this directory, you can run
|
||||
|
||||
./loadngo.sh c64 eg/c64/hearts.60p
|
||||
|
||||
and it will compile the [hearts.60p source code](eg/c64/hearts.60p) and
|
||||
automatically start it in the `x64` emulator, and you should see:
|
||||
|
||||
![Screenshot of result of running hearts.60p](images/hearts.png?raw=true)
|
||||
|
||||
You can try the `loadngo.sh` script on other sources in the `eg` directory
|
||||
tree, which contains more extensive examples, including an entire
|
||||
game(-like program); see [eg/README.md](eg/README.md) for a listing.
|
||||
|
||||
[VICE]: http://vice-emu.sourceforge.net/
|
||||
* [SixtyPical specification](doc/SixtyPical.md)
|
||||
* [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md)
|
||||
* [Literate test suite for SixtyPical analysis (operations)](tests/SixtyPical%20Analysis.md)
|
||||
* [Literate test suite for SixtyPical analysis (storage)](tests/SixtyPical%20Storage.md)
|
||||
* [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
|
||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||
* [Literate test suite for SixtyPical callgraph construction](tests/SixtyPical%20Callgraph.md)
|
||||
|
||||
Documentation
|
||||
-------------
|
||||
|
||||
* [Design Goals](doc/Design%20Goals.md)
|
||||
* [SixtyPical specification](doc/SixtyPical.md)
|
||||
* [SixtyPical revision history](HISTORY.md)
|
||||
* [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md)
|
||||
* [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md)
|
||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||
* [6502 Opcodes used/not used in SixtyPical](doc/6502%20Opcodes.md)
|
||||
* [Output formats supported by `sixtypical`](doc/Output%20Formats.md)
|
||||
* [TODO](TODO.md)
|
||||
|
||||
[Future directions for SixtyPical]: doc/Future%20directions%20for%20SixtyPical.md
|
||||
[6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
||||
[MOS Technology 6502]: https://en.wikipedia.org/wiki/MOS_Technology_6502
|
||||
[effect system]: https://en.wikipedia.org/wiki/Effect_system
|
||||
[abstractly interprets]: https://en.wikipedia.org/wiki/Abstract_interpretation
|
||||
[flow typing]: https://en.wikipedia.org/wiki/Flow-sensitive_typing
|
||||
[calling conventions]: https://en.wikipedia.org/wiki/Calling_convention
|
||||
[register allocation]: https://en.wikipedia.org/wiki/Register_allocation
|
||||
[VICE]: http://vice-emu.sourceforge.net/
|
||||
[cc65]: https://cc65.github.io/
|
||||
[Commodore 64]: https://en.wikipedia.org/wiki/Commodore_64
|
||||
[Commodore VIC-20]: https://en.wikipedia.org/wiki/Commodore_VIC-20
|
||||
[Atari 2600]: https://en.wikipedia.org/wiki/Atari_2600
|
||||
[Apple II series]: https://en.wikipedia.org/wiki/Apple_II_series
|
||||
[Falderal]: https://catseye.tc/node/Falderal
|
||||
[dcc6502]: https://github.com/tcarmelveilleux/dcc6502
|
||||
|
|
136
TODO.md
136
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,57 +18,120 @@ 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.
|
||||
|
||||
### Associate each pointer with the buffer it points into
|
||||
### Copy byte to/from table
|
||||
|
||||
Check that the buffer being read or written to through pointer, appears in appropriate
|
||||
inputs or outputs set.
|
||||
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.
|
||||
|
||||
In the analysis, when we obtain a pointer, we need to record, in context, what buffer
|
||||
that pointer came from.
|
||||
### Character literals
|
||||
|
||||
When we write through that pointer, we need to set that buffer as written.
|
||||
For goodness sake, let the programmer say `'A'` instead of `65`.
|
||||
|
||||
When we read through the pointer, we need to check that the buffer is readable.
|
||||
### Character set mapping
|
||||
|
||||
### Table overlays
|
||||
Not all computers think `'A'` should be `65`. Allow the character set to be
|
||||
mapped. Probably copy what Ophis does.
|
||||
|
||||
They are uninitialized, but the twist is, the address is a buffer that is
|
||||
an input to and/or output of the routine. So, they are defined (insofar
|
||||
as the buffer is defined.)
|
||||
### Pointers into non-byte tables
|
||||
|
||||
They are therefore a "view" of a section of a buffer.
|
||||
Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
|
||||
|
||||
This is slightly dangerous since it does permit aliases: the buffer and the
|
||||
table refer to the same memory.
|
||||
Word and vector tables are stored as two byte tables in memory. This is useful for
|
||||
indexed access, but makes pointer access more difficult.
|
||||
|
||||
Although, if they are `static`, you could say, in the routine in which they
|
||||
are `static`, as soon as you've established one, you can no longer use the
|
||||
buffer; and the ones you establish must be disjoint.
|
||||
Laying them out for pointer access would make indexed access more difficult.
|
||||
|
||||
(That seems to be the most compelling case for restricting them to `static`.)
|
||||
### Saving non-byte values
|
||||
|
||||
An alternative would be `static` pointers, which are currently not possible because
|
||||
pointers must be zero-page, thus `@`, thus uninitialized.
|
||||
Right now you cannot save a word value.
|
||||
|
||||
### Tail-call optimization
|
||||
There doesn't seem to be a hugely pressing reason why not.
|
||||
|
||||
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.
|
||||
Analysis
|
||||
--------
|
||||
|
||||
As long as the routine has consistent type context every place it exits, that should be fine.
|
||||
### Forbid recursion
|
||||
|
||||
### "Include" directives
|
||||
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.)
|
||||
|
||||
Search a searchlist of include paths. And use them to make libraries of routines.
|
||||
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.
|
||||
|
||||
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.
|
||||
The problems only come up, I think, when a routine calls itself re-entrantly.
|
||||
|
||||
### Line numbers in analysis error messages
|
||||
So the callgraph would need to distinguish between these two cases.
|
||||
|
||||
### Analyze memory usage
|
||||
|
||||
If you define two variables that occupy the same address, an analysis error ought
|
||||
to be raised. (But there should also be a way to annotate this as intentional.
|
||||
Intentionally making two tables overlap could be valuable. However, the analysis
|
||||
will probably completely miss this fact.)
|
||||
|
||||
Optimization
|
||||
------------
|
||||
|
||||
### Space optimization of local non-statics
|
||||
|
||||
If there are two routines A and B, and A never calls B (even indirectly), and
|
||||
B never calls A (even indirectly), then their non-static locals can
|
||||
be allocated at the same space.
|
||||
|
||||
This is not just an impressive trick -- in the presence of local pointers, which
|
||||
use up a word in zero-page, which we consider a precious resource, it allow those
|
||||
zero-page locations to be re-used.
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
### Filename and line number in analysis error messages
|
||||
|
||||
For analysis errors, there is a line number, but it's the line of the routine
|
||||
after the routine in which the analysis error occurred. Fix this.
|
||||
|
||||
### Better selection of options
|
||||
|
||||
`-O` should turn on the standard optimizations.
|
||||
|
||||
There should maybe be a flag to turn off tail-call optimization.
|
||||
|
||||
Some options should automatically add the appropriate architecture include
|
||||
directory to the path.
|
||||
|
||||
Distribution
|
||||
------------
|
||||
|
||||
### Demo game
|
||||
|
||||
Seems you're not be able to get killed unless you go off the top or bottom of
|
||||
the screen? In particular, you cannot collide with a bar?
|
||||
|
||||
Blue-skying
|
||||
-----------
|
||||
|
||||
### Pointers associated globally with a table(?)
|
||||
|
||||
We have `point into` blocks, but we would also like to sometimes pass a pointer
|
||||
around to different routines, and have them all "know" what table it operates on.
|
||||
|
||||
We could associate every pointer variable with a specific table variable, in its
|
||||
declaration. This makes some things simple, and would allow us to know what table a
|
||||
pointer is supposed to point into, even if that pointer was passed into our routine.
|
||||
|
||||
One drawback is that it would limit each pointer to be used only on one table. Since a
|
||||
pointer basically represents a zero-page location, and since those are a relatively scarce
|
||||
resource, we would prefer if a single pointer could be used to point into different tables
|
||||
at different times.
|
||||
|
||||
These can co-exist with general, non-specific-table-linked `pointer` variables.
|
||||
|
||||
If we have local pointers and space optimization for local non-statics, though,
|
||||
these don't add as much.
|
||||
|
|
141
bin/sixtypical
141
bin/sixtypical
|
@ -1,9 +1,4 @@
|
|||
#!/usr/bin/env python
|
||||
|
||||
"""Usage: sixtypical [OPTIONS] FILES
|
||||
|
||||
Analyzes and compiles a Sixtypical program.
|
||||
"""
|
||||
#!/usr/bin/env python3
|
||||
|
||||
from os.path import realpath, dirname, join
|
||||
import sys
|
||||
|
@ -12,29 +7,33 @@ sys.path.insert(0, join(dirname(realpath(sys.argv[0])), '..', 'src'))
|
|||
|
||||
# ----------------------------------------------------------------- #
|
||||
|
||||
import codecs
|
||||
from argparse import ArgumentParser
|
||||
import codecs
|
||||
import json
|
||||
from pprint import pprint
|
||||
from subprocess import check_call
|
||||
import sys
|
||||
from tempfile import NamedTemporaryFile
|
||||
import traceback
|
||||
|
||||
from sixtypical.parser import Parser, ParsingContext, 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):
|
||||
context = ParsingContext()
|
||||
symtab = SymbolTable()
|
||||
include_path = options.include_path.split(':')
|
||||
|
||||
programs = []
|
||||
|
||||
for filename in options.filenames:
|
||||
text = open(filename).read()
|
||||
parser = Parser(context, text, filename)
|
||||
program = load_program(filename, symtab, include_path, include_file=False)
|
||||
if options.debug:
|
||||
print(context)
|
||||
program = parser.program()
|
||||
print(symtab)
|
||||
programs.append(program)
|
||||
|
||||
if options.parse_only:
|
||||
|
@ -42,28 +41,32 @@ def process_input_files(filenames, options):
|
|||
|
||||
program = merge_programs(programs)
|
||||
|
||||
analyzer = Analyzer(debug=options.debug)
|
||||
analyzer.analyze_program(program)
|
||||
analyzer = Analyzer(symtab, debug=options.debug)
|
||||
|
||||
try:
|
||||
analyzer.analyze_program(program)
|
||||
finally:
|
||||
if options.dump_exit_contexts:
|
||||
sys.stdout.write(json.dumps(analyzer.exit_contexts_map, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
callgraph = construct_callgraph(program)
|
||||
if options.dump_callgraph:
|
||||
sys.stdout.write(json.dumps(callgraph, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||
if options.prune_unreachable_routines:
|
||||
program = prune_unreachable_routines(program, callgraph)
|
||||
|
||||
compilation_roster = None
|
||||
if options.optimize_fallthru:
|
||||
from sixtypical.fallthru import FallthruAnalyzer
|
||||
|
||||
def dump(data, label=None):
|
||||
import json
|
||||
if not options.dump_fallthru_info:
|
||||
return
|
||||
if label:
|
||||
sys.stdout.write("*** {}:\n".format(label))
|
||||
sys.stdout.write(json.dumps(data, indent=4, sort_keys=True, separators=(',', ':')))
|
||||
sys.stdout.write("\n")
|
||||
|
||||
fa = FallthruAnalyzer(debug=options.debug)
|
||||
fa = FallthruAnalyzer(symtab, debug=options.debug)
|
||||
fa.analyze_program(program)
|
||||
compilation_roster = fa.serialize()
|
||||
dump(compilation_roster)
|
||||
if options.dump_fallthru_info:
|
||||
sys.stdout.write(json.dumps(compilation_roster, indent=4, sort_keys=True, separators=(',', ': ')))
|
||||
|
||||
if options.analyze_only or options.output is None:
|
||||
if options.analyze_only or (options.output is None and not options.run_on):
|
||||
return
|
||||
|
||||
start_addr = None
|
||||
|
@ -73,20 +76,46 @@ def process_input_files(filenames, options):
|
|||
else:
|
||||
start_addr = int(options.origin, 10)
|
||||
|
||||
with open(options.output, 'wb') as fh:
|
||||
outputter = outputter_class_for(options.output_format)(fh, start_addr=start_addr)
|
||||
outputter.write_prelude()
|
||||
compiler = Compiler(outputter.emitter)
|
||||
compiler.compile_program(program, compilation_roster=compilation_roster)
|
||||
outputter.write_postlude()
|
||||
if options.debug:
|
||||
pprint(outputter.emitter)
|
||||
else:
|
||||
outputter.emitter.serialize_to(fh)
|
||||
if options.run_on:
|
||||
fh = NamedTemporaryFile(delete=False)
|
||||
output_filename = fh.name
|
||||
Outputter = outputter_class_for({
|
||||
'x64': 'c64-basic-prg',
|
||||
'xvic': 'vic20-basic-prg',
|
||||
'stella': 'atari2600-cart',
|
||||
}.get(options.run_on))
|
||||
else:
|
||||
fh = open(options.output, 'wb')
|
||||
output_filename = options.output
|
||||
Outputter = outputter_class_for(options.output_format)
|
||||
|
||||
outputter = Outputter(fh, start_addr=start_addr)
|
||||
outputter.write_prelude()
|
||||
compiler = Compiler(symtab, outputter.emitter)
|
||||
compiler.compile_program(program, compilation_roster=compilation_roster)
|
||||
outputter.write_postlude()
|
||||
if options.debug:
|
||||
pprint(outputter.emitter)
|
||||
else:
|
||||
outputter.emitter.serialize_to(fh)
|
||||
|
||||
fh.close()
|
||||
|
||||
if options.run_on:
|
||||
emu = {
|
||||
'x64': "x64 -config vicerc",
|
||||
'xvic': "xvic -config vicerc",
|
||||
'stella': "stella"
|
||||
}.get(options.run_on)
|
||||
if not emu:
|
||||
raise ValueError("No emulator configured for selected --run-on '{}'".format(options.output_format))
|
||||
|
||||
command = "{} {}".format(emu, output_filename)
|
||||
check_call(command, shell=True)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
argparser = ArgumentParser(__doc__.strip())
|
||||
argparser = ArgumentParser()
|
||||
|
||||
argparser.add_argument(
|
||||
'filenames', metavar='FILENAME', type=str, nargs='+',
|
||||
|
@ -109,11 +138,23 @@ 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",
|
||||
help="Only parse and analyze the program; do not compile it."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--dump-exit-contexts",
|
||||
action="store_true",
|
||||
help="Dump a map, in JSON, of the analysis context at each exit of each routine "
|
||||
"after analyzing the program."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--optimize-fallthru",
|
||||
action="store_true",
|
||||
|
@ -123,7 +164,18 @@ if __name__ == '__main__':
|
|||
argparser.add_argument(
|
||||
"--dump-fallthru-info",
|
||||
action="store_true",
|
||||
help="Dump the fallthru map and ordering to stdout after analyzing the program."
|
||||
help="Dump the ordered fallthru map, in JSON, to stdout after analyzing the program."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--prune-unreachable-routines",
|
||||
action="store_true",
|
||||
help="Omit code for unreachable routines (as determined by the callgraph) "
|
||||
"from the final output."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--dump-callgraph",
|
||||
action="store_true",
|
||||
help="Dump the call graph, in JSON, to stdout after analyzing the program."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--parse-only",
|
||||
|
@ -135,11 +187,22 @@ if __name__ == '__main__':
|
|||
action="store_true",
|
||||
help="Display debugging information when analyzing and compiling."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--run-on", type=str, default=None,
|
||||
help="If given, engage 'load-and-go' operation with the given emulator: write "
|
||||
"the output to a temporary filename using an appropriate --output-format, "
|
||||
"and boot the emulator with it. Options are: x64, xvic, stella."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--traceback",
|
||||
action="store_true",
|
||||
help="When an error occurs, display a full Python traceback."
|
||||
)
|
||||
argparser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="%(prog)s 0.21"
|
||||
)
|
||||
|
||||
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
||||
remainder = ' '.join(unknown)
|
||||
|
|
|
@ -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.15,
|
||||
<!--
|
||||
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
|
||||
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
|
||||
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
|
||||
-->
|
||||
|
||||
This document describes the SixtyPical programming language version 0.20,
|
||||
both its static semantics (the capabilities and limits of the static
|
||||
analyses it defines) and its runtime semantics (with reference to the
|
||||
semantics of 6502 machine code.)
|
||||
|
@ -12,34 +18,55 @@ are even more normative.
|
|||
Refer to the bottom of this document for an EBNF grammar of the syntax of
|
||||
the language.
|
||||
|
||||
Types
|
||||
-----
|
||||
Data Model
|
||||
----------
|
||||
|
||||
There are five *primitive types* in SixtyPical:
|
||||
SixtyPical defines a data model where every value has some type
|
||||
information associated with it. The values include those that are
|
||||
directly manipulable by a SixtyPical program, but are not limited to them.
|
||||
Type information includes not only what kind of structure the data has,
|
||||
but other properties as well (sometimes called "type annotations".)
|
||||
|
||||
### Basic types ###
|
||||
|
||||
SixtyPical defines a handful of basic types. There are three types that
|
||||
are "primitive" in that they are not parameterized in any way:
|
||||
|
||||
* bit (2 possible values)
|
||||
* byte (256 possible values)
|
||||
* word (65536 possible values)
|
||||
* routine (code stored somewhere in memory, read-only)
|
||||
* pointer (address of a byte in a buffer)
|
||||
|
||||
There are also three *type constructors*:
|
||||
Types can also be parameterized and constructed from other types
|
||||
(which is a kind of parameterization). One such type constructor is
|
||||
|
||||
* T table[N] (N entries, 1 ≤ N ≤ 256; each entry holds a value
|
||||
of type T, where T is `byte`, `word`, or `vector`)
|
||||
* buffer[N] (N entries; each entry is a byte; 1 ≤ N ≤ 65536)
|
||||
* pointer (16-bit address of a byte inside a byte table)
|
||||
* vector T (address of a value of type T; T must be a routine type)
|
||||
|
||||
### User-defined ###
|
||||
Values of the above-listed types are directly manipulable by a SixtyPical
|
||||
program. Other types describe values which can only be indirectly
|
||||
manipulated by a program:
|
||||
|
||||
* routine (code stored somewhere in memory, read-only)
|
||||
* T table[N] (series of 1 ≤ N ≤ 65536 values of type T)
|
||||
|
||||
There are some restrictions here; for example, a table may only
|
||||
consist of `byte`, `word`, or `vector` types. A pointer may only
|
||||
point to a byte inside a `table` of `byte` type.
|
||||
|
||||
Each routine is associated with a rich set of type information,
|
||||
which is basically the types and statuses of memory locations that
|
||||
have been declared as being relevant to that routine.
|
||||
|
||||
#### User-defined ####
|
||||
|
||||
A program may define its own types using the `typedef` feature. Typedefs
|
||||
must occur before everything else in the program. A typedef takes a
|
||||
type expression and an identifier which has not previously been used in
|
||||
the program. It associates that identifer with that type. This is merely
|
||||
a type alias; two types with different names will compare as equal.
|
||||
a type alias; if two types have identical structure but different names,
|
||||
they will compare as equal.
|
||||
|
||||
Memory locations
|
||||
----------------
|
||||
### Memory locations ###
|
||||
|
||||
A primary concept in SixtyPical is the *memory location*. At any given point
|
||||
in time during execution, each memory location is either *uninitialized* or
|
||||
|
@ -51,7 +78,7 @@ the program text; thus, it is a static property.
|
|||
There are four general kinds of memory location. The first three are
|
||||
pre-defined and built-in.
|
||||
|
||||
### Registers ###
|
||||
#### Registers ####
|
||||
|
||||
Each of these hold a byte. They are initially uninitialized.
|
||||
|
||||
|
@ -59,7 +86,7 @@ Each of these hold a byte. They are initially uninitialized.
|
|||
x
|
||||
y
|
||||
|
||||
### Flags ###
|
||||
#### Flags ####
|
||||
|
||||
Each of these hold a bit. They are initially uninitialized.
|
||||
|
||||
|
@ -68,7 +95,7 @@ Each of these hold a bit. They are initially uninitialized.
|
|||
v (overflow)
|
||||
n (negative)
|
||||
|
||||
### Constants ###
|
||||
#### Constants ####
|
||||
|
||||
It may be strange to think of constants as memory locations, but keep in mind
|
||||
that a memory location in SixtyPical need not map to a memory location in the
|
||||
|
@ -97,7 +124,7 @@ and sixty-five thousand five hundred and thirty-six word constants,
|
|||
Note that if a word constant is between 256 and 65535, the leading `word`
|
||||
token can be omitted.
|
||||
|
||||
### User-defined ###
|
||||
#### User-defined ####
|
||||
|
||||
There may be any number of user-defined memory locations. They are defined
|
||||
by giving the type (which may be any type except `bit` and `routine`) and the
|
||||
|
@ -137,13 +164,37 @@ This is actually useful, at least at this point, as you can rely on the fact
|
|||
that literal integers in the code are always immediate values. (But this
|
||||
may change at some point.)
|
||||
|
||||
### Buffers and Pointers ###
|
||||
### Tables and Pointers ###
|
||||
|
||||
Roughly speaking, a `buffer` is a table that can be longer than 256 bytes,
|
||||
and a `pointer` is an address within a buffer.
|
||||
A table is a collection of memory locations that can be indexed in a number
|
||||
of ways.
|
||||
|
||||
The simplest way is to use another memory location as an index. There
|
||||
are restrictions on which memory locations can be used as indexes;
|
||||
only the `x` and `y` locations can be used this way. Since those can
|
||||
only hold a byte, this method, by itself, only allows access to the first
|
||||
256 entries of the table.
|
||||
|
||||
byte table[1024] tab
|
||||
...
|
||||
ld a, tab + x
|
||||
st a, tab + y
|
||||
|
||||
However, by combining indexing with a constant _offset_, entries beyond the
|
||||
256th entry can be accessed.
|
||||
|
||||
byte table[1024] tab
|
||||
...
|
||||
ld a, tab + 512 + x
|
||||
st a, tab + 512 + y
|
||||
|
||||
Even with an offset, the range of indexing still cannot exceed 256 entries.
|
||||
Accessing entries at an arbitrary address inside a table can be done with
|
||||
a `pointer`. Pointers can only be point inside `byte` tables. When a
|
||||
pointer is used, indexing with `x` or `y` will also take place.
|
||||
|
||||
A `pointer` is implemented as a zero-page memory location, and accessing the
|
||||
buffer pointed to is implemented with "indirect indexed" addressing, as in
|
||||
table pointed to is implemented with "indirect indexed" addressing, as in
|
||||
|
||||
LDA ($02), Y
|
||||
STA ($02), Y
|
||||
|
@ -151,14 +202,16 @@ buffer pointed to is implemented with "indirect indexed" addressing, as in
|
|||
There are extended instruction modes for using these types of memory location.
|
||||
See `copy` below, but here is some illustrative example code:
|
||||
|
||||
copy ^buf, ptr // this is the only way to initialize a pointer
|
||||
add ptr, 4 // ok, but only if it does not exceed buffer's size
|
||||
ld y, 0 // you must set this to something yourself
|
||||
copy [ptr] + y, byt // read memory through pointer, into byte
|
||||
copy 100, [ptr] + y // write memory through pointer (still trashes a)
|
||||
point ptr into buf { // this associates this pointer with this table
|
||||
reset ptr 0 // this is the only way to initialize a pointer
|
||||
add ptr, 4 // note, this is unchecked against table's size!
|
||||
ld y, 0 // you must set this to something yourself
|
||||
copy [ptr] + y, byt // read memory through pointer, into byte
|
||||
copy 100, [ptr] + y // write memory through pointer (still trashes a)
|
||||
} // after this block, ptr can no longer be used
|
||||
|
||||
where `ptr` is a user-defined storage location of `pointer` type, and the
|
||||
`+ y` part is mandatory.
|
||||
where `ptr` is a user-defined storage location of `pointer` type, `buf`
|
||||
is a `table` of `byte` type, and the `+ y` part is mandatory.
|
||||
|
||||
Routines
|
||||
--------
|
||||
|
@ -300,17 +353,7 @@ and it trashes the `z` and `n` flags and the `a` register.
|
|||
After execution, dest is considered initialized, and `z` and `n`, and
|
||||
`a` are considered uninitialized.
|
||||
|
||||
There are two extra modes that this instruction can be used in. The first is
|
||||
to load an address into a pointer:
|
||||
|
||||
copy ^<src-memory-location>, <dest-memory-location>
|
||||
|
||||
This copies the address of src into dest. In this case, src must be
|
||||
of type buffer, and dest must be of type pointer. src will not be
|
||||
considered a memory location that is read, since it is only its address
|
||||
that is being retrieved.
|
||||
|
||||
The second is to read or write indirectly through a pointer.
|
||||
There is an extra mode that this instruction can be used in:
|
||||
|
||||
copy [<src-memory-location>] + y, <dest-memory-location>
|
||||
copy <src-memory-location>, [<dest-memory-location>] + y
|
||||
|
@ -350,7 +393,7 @@ In fact, this instruction trashes the `a` register in all cases except
|
|||
when the dest is `a`.
|
||||
|
||||
NOTE: If dest is a pointer, the addition does not check if the result of
|
||||
the pointer arithmetic continues to be valid (within a buffer) or not.
|
||||
the pointer arithmetic continues to be valid (within a table) or not.
|
||||
|
||||
### inc ###
|
||||
|
||||
|
@ -581,11 +624,10 @@ Grammar
|
|||
Program ::= {ConstDefn | TypeDefn} {Defn} {Routine}.
|
||||
ConstDefn::= "const" Ident<new> Const.
|
||||
TypeDefn::= "typedef" Type Ident<new>.
|
||||
Defn ::= Type Ident<new> [Constraints] (":" Const | "@" LitWord).
|
||||
Defn ::= Type Ident<new> (":" Const | "@" LitWord).
|
||||
Type ::= TypeTerm ["table" TypeSize].
|
||||
TypeExpr::= "byte"
|
||||
| "word"
|
||||
| "buffer" TypeSize
|
||||
| "pointer"
|
||||
| "vector" TypeTerm
|
||||
| "routine" Constraints
|
||||
|
@ -594,10 +636,8 @@ Grammar
|
|||
TypeSize::= "[" LitWord "]".
|
||||
Constrnt::= ["inputs" LocExprs] ["outputs" LocExprs] ["trashes" LocExprs].
|
||||
Routine ::= "define" Ident<new> Type (Block | "@" LitWord).
|
||||
| "routine" Ident<new> Constraints (Block | "@" LitWord)
|
||||
.
|
||||
LocExprs::= LocExpr {"," LocExpr}.
|
||||
LocExpr ::= Register | Flag | Const | Ident.
|
||||
LocExpr ::= Register | Flag | Const | Ident [["+" Const] "+" Register].
|
||||
Register::= "a" | "x" | "y".
|
||||
Flag ::= "c" | "z" | "n" | "v".
|
||||
Const ::= Literal | Ident<const>.
|
||||
|
@ -625,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
|
||||
.
|
||||
|
|
41
eg/README.md
41
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
|
||||
|
||||
|
@ -46,4 +35,10 @@ Atari 2600 (4K cartridge). The directory itself contains a simple
|
|||
demo, [smiley.60p](atari2600/smiley.60p) which was converted from an
|
||||
older Atari 2600 skeleton program written in [Ophis][].
|
||||
|
||||
### apple2
|
||||
|
||||
In the [apple2](apple2/) directory are programs that run on
|
||||
Apple II series computers (Apple II+, Apple //e). `sixtypical`'s
|
||||
support for this architecture could be called embryonic.
|
||||
|
||||
[Ophis]: http://michaelcmartin.github.io/Ophis/
|
||||
|
|
35
eg/apple2/README.md
Normal file
35
eg/apple2/README.md
Normal file
|
@ -0,0 +1,35 @@
|
|||
<!--
|
||||
Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
|
||||
This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
|
||||
SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
|
||||
-->
|
||||
|
||||
This directory contains SixtyPical example programs
|
||||
specifically for the Apple II series of computers.
|
||||
|
||||
See the [README in the parent directory](../README.md) for
|
||||
more information on these example programs.
|
||||
|
||||
Note that `sixtypical` does not currently support "load
|
||||
and go" execution of these programs, because constructing
|
||||
an Apple II disk image file on the fly is not something
|
||||
it can currently do. If you have the linapple sources
|
||||
checked out, and the a2tools available, you could do
|
||||
something like this:
|
||||
|
||||
bin/sixtypical --traceback --origin=0x2000 --output-format=raw eg/apple2/prog.60p --output prog.bin
|
||||
cp /path/to/linapple/res/Master.dsk sixtypical.dsk
|
||||
a2rm sixtypical.dsk PROG
|
||||
a2in B sixtypical.dsk PROG prog.bin
|
||||
linapple -d1 sixtypical.dsk -autoboot
|
||||
|
||||
and then enter
|
||||
|
||||
BLOAD PROG
|
||||
CALL 8192
|
||||
|
||||
Ideally you could
|
||||
|
||||
BRUN PROG
|
||||
|
||||
But that does not always return to BASIC and I'm not sure why.
|
|
@ -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
|
||||
// ----------------------------------------------------------------
|
||||
|
@ -21,24 +27,16 @@
|
|||
// and the end of their own routines, so the type needs to be compatible.
|
||||
// (In a good sense, it is a continuation.)
|
||||
//
|
||||
// Further,
|
||||
//
|
||||
// It's very arguable that screen1/2/3/4 and colormap1/2/3/4 are not REALLY inputs.
|
||||
// They're only there to support the fact that game states sometimes clear the
|
||||
// screen, and sometimes don't. When they don't, they preserve the screen, and
|
||||
// currently the way to say "we preserve the screen" is to have it as both input
|
||||
// and output. There is probably a better way to do this, but it needs thought.
|
||||
//
|
||||
|
||||
typedef routine
|
||||
inputs joy2, press_fire_msg, dispatch_game_state,
|
||||
actor_pos, actor_delta, actor_logic,
|
||||
player_died,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
screen, colormap
|
||||
outputs dispatch_game_state,
|
||||
actor_pos, actor_delta, actor_logic,
|
||||
player_died,
|
||||
screen, screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
screen, colormap
|
||||
trashes a, x, y, c, z, n, v, pos, new_pos, delta, ptr, dispatch_logic
|
||||
game_state_routine
|
||||
|
||||
|
@ -62,19 +60,8 @@ typedef routine
|
|||
|
||||
byte vic_border @ 53280
|
||||
byte vic_bg @ 53281
|
||||
|
||||
byte table[256] screen1 @ 1024
|
||||
byte table[256] screen2 @ 1274
|
||||
byte table[256] screen3 @ 1524
|
||||
byte table[256] screen4 @ 1774
|
||||
|
||||
byte table[256] colormap1 @ 55296
|
||||
byte table[256] colormap2 @ 55546
|
||||
byte table[256] colormap3 @ 55796
|
||||
byte table[256] colormap4 @ 56046
|
||||
|
||||
buffer[2048] screen @ 1024
|
||||
byte joy2 @ $dc00
|
||||
byte table[2048] screen @ 1024
|
||||
byte table[2048] colormap @ 55296
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Global Variables
|
||||
|
@ -87,7 +74,6 @@ word pos
|
|||
word new_pos
|
||||
|
||||
word table[256] actor_delta
|
||||
word delta
|
||||
|
||||
byte player_died
|
||||
|
||||
|
@ -121,88 +107,23 @@ 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 screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs screen, colormap
|
||||
trashes a, y, c, n, z
|
||||
{
|
||||
ld y, 0
|
||||
repeat {
|
||||
ld a, 1
|
||||
st a, colormap1 + y
|
||||
st a, colormap2 + y
|
||||
st a, colormap3 + y
|
||||
st a, colormap4 + y
|
||||
st a, colormap + y
|
||||
st a, colormap + 250 + y
|
||||
st a, colormap + 500 + y
|
||||
st a, colormap + 750 + y
|
||||
|
||||
ld a, 32
|
||||
st a, screen1 + y
|
||||
st a, screen2 + y
|
||||
st a, screen3 + y
|
||||
st a, screen4 + y
|
||||
st a, screen + y
|
||||
st a, screen + 250 + y
|
||||
st a, screen + 500 + y
|
||||
st a, screen + 750 + y
|
||||
|
||||
inc y
|
||||
cmp y, 250
|
||||
|
@ -282,35 +203,38 @@ define player_logic logic_routine
|
|||
call check_new_position_in_bounds
|
||||
|
||||
if c {
|
||||
copy ^screen, ptr
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
// 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 {
|
||||
copy ^screen, ptr
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
copy new_pos, pos
|
||||
// check collision.
|
||||
ld a, [ptr] + y
|
||||
|
||||
copy ^screen, ptr
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 81, [ptr] + y
|
||||
} else {
|
||||
ld a, 1
|
||||
st a, player_died
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 81
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
|
||||
copy new_pos, pos
|
||||
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 81, [ptr] + y
|
||||
}
|
||||
}
|
||||
} else {
|
||||
ld a, 1
|
||||
st a, player_died
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -321,31 +245,34 @@ define enemy_logic logic_routine
|
|||
call check_new_position_in_bounds
|
||||
|
||||
if c {
|
||||
copy ^screen, ptr
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
// 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 {
|
||||
copy ^screen, ptr
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
copy new_pos, pos
|
||||
// check collision.
|
||||
ld a, [ptr] + y
|
||||
|
||||
copy ^screen, ptr
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 82, [ptr] + y
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 82
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
|
||||
copy new_pos, pos
|
||||
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 82, [ptr] + y
|
||||
}
|
||||
}
|
||||
} else {
|
||||
copy delta, compare_target
|
||||
|
@ -372,7 +299,7 @@ define game_state_title_screen game_state_routine
|
|||
st on, c
|
||||
sub a, 64 // yuck. oh well
|
||||
|
||||
st a, screen1 + y
|
||||
st a, screen + y
|
||||
}
|
||||
|
||||
st off, c
|
||||
|
@ -437,15 +364,14 @@ define game_state_game_over game_state_routine
|
|||
// * Main Game Loop Driver *
|
||||
// *************************
|
||||
|
||||
define our_cinv game_state_routine
|
||||
define our_cinv preserved game_state_routine
|
||||
{
|
||||
goto dispatch_game_state
|
||||
}
|
||||
|
||||
define main routine
|
||||
inputs cinv
|
||||
outputs cinv, save_cinv, pos, dispatch_game_state,
|
||||
screen1, screen2, screen3, screen4, colormap1, colormap2, colormap3, colormap4
|
||||
outputs cinv, save_cinv, pos, dispatch_game_state, screen, colormap
|
||||
trashes a, y, n, c, z, vic_border, vic_bg
|
||||
{
|
||||
ld a, 5
|
||||
|
|
23
eg/c64/demo-game/run.sh
Executable file
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.
|
||||
|
||||
|
@ -50,8 +52,9 @@ byte scanline : 85 // %01010101
|
|||
// be practical. So we just jump to this location instead.
|
||||
|
||||
define pla_tay_pla_tax_pla_rti routine
|
||||
inputs a
|
||||
trashes a
|
||||
inputs border_color, vic_intr
|
||||
outputs border_color, vic_intr
|
||||
trashes a, z, n, c
|
||||
@ $EA81
|
||||
|
||||
// ----- Interrupt Handler -----
|
||||
|
|
|
@ -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,20 +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 `platform/`, should be included first, like
|
||||
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 platform/c64.60p vector-table.60p
|
||||
|
||||
so that system entry points such as `chrout` are defined.
|
||||
|
||||
There's a `loadngo.sh` script in this directory that does this.
|
||||
|
||||
./loadngo.sh c64 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,7 +1,8 @@
|
|||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print Y
|
||||
|
||||
buffer[2048] buf
|
||||
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,36 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
usage="Usage: loadngo.sh (c64|vic20) <source.60p>"
|
||||
|
||||
arch="$1"
|
||||
shift 1
|
||||
if [ "X$arch" = "Xc64" ]; then
|
||||
output_format='c64-basic-prg'
|
||||
if [ -e vicerc ]; then
|
||||
emu="x64 -config vicerc"
|
||||
else
|
||||
emu="x64"
|
||||
fi
|
||||
elif [ "X$arch" = "Xvic20" ]; then
|
||||
output_format='vic20-basic-prg'
|
||||
if [ -e vicerc ]; then
|
||||
emu="xvic -config vicerc"
|
||||
else
|
||||
emu="xvic"
|
||||
fi
|
||||
else
|
||||
echo $usage && exit 1
|
||||
fi
|
||||
|
||||
src="$1"
|
||||
if [ "X$src" = "X" ]; then
|
||||
echo $usage && exit 1
|
||||
fi
|
||||
|
||||
### do it ###
|
||||
|
||||
out=/tmp/a-out.prg
|
||||
../../bin/sixtypical --traceback --output-format=$output_format support/$arch.60p support/stdlib.60p $src --output $out || exit 1
|
||||
ls -la $out
|
||||
$emu $out
|
||||
rm -f $out
|
|
@ -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
|
59
loadngo.sh
59
loadngo.sh
|
@ -1,59 +0,0 @@
|
|||
#!/bin/sh
|
||||
|
||||
usage="Usage: loadngo.sh (c64|vic20|atari2600|apple2) [--dry-run] <source.60p>"
|
||||
|
||||
arch="$1"
|
||||
shift 1
|
||||
if [ "X$arch" = "Xc64" ]; then
|
||||
output_format='c64-basic-prg'
|
||||
if [ -e vicerc ]; then
|
||||
emu="x64 -config vicerc"
|
||||
else
|
||||
emu="x64"
|
||||
fi
|
||||
elif [ "X$arch" = "Xvic20" ]; then
|
||||
output_format='vic20-basic-prg'
|
||||
if [ -e vicerc ]; then
|
||||
emu="xvic -config vicerc"
|
||||
else
|
||||
emu="xvic"
|
||||
fi
|
||||
elif [ "X$arch" = "Xatari2600" ]; then
|
||||
output_format='atari2600-cart'
|
||||
emu='stella'
|
||||
elif [ "X$arch" = "Xapple2" ]; then
|
||||
src="$1"
|
||||
out=/tmp/a-out.bin
|
||||
bin/sixtypical --traceback --origin=0x2000 --output-format=raw $src --output $out || exit 1
|
||||
ls -la $out
|
||||
cp ~/scratchpad/linapple/res/Master.dsk sixtypical.dsk
|
||||
# TODO: replace HELLO with something that does like
|
||||
# BLOAD "PROG"
|
||||
# CALL 8192
|
||||
# (not BRUN because it does not always return to BASIC afterwards not sure why)
|
||||
a2rm sixtypical.dsk PROG
|
||||
a2in B sixtypical.dsk PROG $out
|
||||
linapple -d1 sixtypical.dsk -autoboot
|
||||
rm -f $out sixtypical.dsk
|
||||
exit 0
|
||||
else
|
||||
echo $usage && exit 1
|
||||
fi
|
||||
|
||||
if [ "X$1" = "X--dry-run" ]; then
|
||||
shift 1
|
||||
emu='echo'
|
||||
fi
|
||||
|
||||
src="$1"
|
||||
if [ "X$src" = "X" ]; then
|
||||
echo $usage && exit 1
|
||||
fi
|
||||
|
||||
### do it ###
|
||||
|
||||
out=/tmp/a-out.prg
|
||||
bin/sixtypical --traceback --output-format=$output_format $src --output $out || exit 1
|
||||
ls -la $out
|
||||
$emu $out
|
||||
rm -f $out
|
|
@ -1,10 +1,17 @@
|
|||
# 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, If, Repeat, For, WithInterruptsOff, Save
|
||||
from sixtypical.ast import (
|
||||
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, BufferType, PointerType, VectorType, RoutineType,
|
||||
ConstantRef, LocationRef, IndirectRef, IndexedRef, AddressRef,
|
||||
TableType, PointerType, VectorType, RoutineType,
|
||||
ConstantRef, LocationRef, IndirectRef, IndexedRef,
|
||||
REG_A, REG_Y, FLAG_Z, FLAG_N, FLAG_V, FLAG_C
|
||||
)
|
||||
|
||||
|
@ -78,298 +85,31 @@ class IncompatibleConstraintsError(ConstraintsError):
|
|||
pass
|
||||
|
||||
|
||||
def routine_has_static(routine, ref):
|
||||
if not hasattr(routine, 'statics'):
|
||||
return False
|
||||
for static in routine.statics:
|
||||
if static.location == ref:
|
||||
return True
|
||||
return False
|
||||
|
||||
|
||||
class Context(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, routines, routine, inputs, outputs, trashes):
|
||||
self.routines = routines # Location -> AST node
|
||||
self.routine = routine
|
||||
self._touched = set()
|
||||
self._range = dict()
|
||||
self._writeable = set()
|
||||
self._terminated = False
|
||||
self._gotos_encountered = set()
|
||||
|
||||
for ref in inputs:
|
||||
if ref.is_constant():
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
self._range[ref] = ref.max_range()
|
||||
output_names = set()
|
||||
for ref in outputs:
|
||||
if ref.is_constant():
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
output_names.add(ref.name)
|
||||
self._writeable.add(ref)
|
||||
for ref in trashes:
|
||||
if ref.is_constant():
|
||||
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 "Context(\n _touched={},\n _range={},\n _writeable={}\n)".format(
|
||||
LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
|
||||
)
|
||||
|
||||
def clone(self):
|
||||
c = Context(self.routines, self.routine, [], [], [])
|
||||
c._touched = set(self._touched)
|
||||
c._range = dict(self._range)
|
||||
c._writeable = set(self._writeable)
|
||||
return c
|
||||
|
||||
def update_from(self, other):
|
||||
self.routines = other.routines
|
||||
self.routine = other.routine
|
||||
self._touched = set(other._touched)
|
||||
self._range = dict(other._range)
|
||||
self._writeable = set(other._writeable)
|
||||
self._terminated = other._terminated
|
||||
self._gotos_encounters = set(other._gotos_encountered)
|
||||
|
||||
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 routine_has_static(self.routine, ref):
|
||||
continue
|
||||
if ref.is_constant() or ref in self.routines:
|
||||
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 routine_has_static(self.routine, ref):
|
||||
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):
|
||||
# FIXME there's a bit of I'm-not-sure-the-best-way-to-do-this-ness, here...
|
||||
|
||||
# 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 = outside.max_range()
|
||||
if isinstance(outside.type, TableType):
|
||||
outside_range = (0, outside.type.size-1)
|
||||
|
||||
if inside_range[0] < outside_range[0] or inside_range[1] > outside_range[1]:
|
||||
raise RangeExceededError(self.routine,
|
||||
"Possible range of {} {} exceeds acceptable range of {} {}".format(
|
||||
inside, inside_range, 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] = ref.max_range()
|
||||
|
||||
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 = src.max_range()
|
||||
self._range[dest] = src_range
|
||||
|
||||
def invalidate_range(self, ref):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = ref.max_range()
|
||||
|
||||
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 assert_types_for_read_table(self, instr, src, dest, type_):
|
||||
if (not TableType.is_a_table_type(src.ref.type, type_)) or (not dest.type == type_):
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
|
||||
self.assert_meaningful(src, src.index)
|
||||
self.assert_in_range(src.index, src.ref)
|
||||
|
||||
def assert_types_for_update_table(self, instr, dest, type_):
|
||||
if not TableType.is_a_table_type(dest.ref.type, type_):
|
||||
raise TypeMismatchError(instr, '{}'.format(dest.ref.name))
|
||||
self.assert_meaningful(dest.index)
|
||||
self.assert_in_range(dest.index, dest.ref)
|
||||
self.set_written(dest.ref)
|
||||
|
||||
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)
|
||||
|
||||
|
||||
class Analyzer(object):
|
||||
|
||||
def __init__(self, debug=False):
|
||||
def __init__(self, symtab, debug=False):
|
||||
self.symtab = symtab
|
||||
self.current_routine = None
|
||||
self.routines = {}
|
||||
self.debug = debug
|
||||
self.exit_contexts_map = {}
|
||||
|
||||
# - - - - helper methods - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
|
||||
return self.symtab.fetch_local_type(self.current_routine.name, name)
|
||||
return self.symtab.fetch_global_type(name)
|
||||
|
||||
def get_type(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.type
|
||||
if not isinstance(ref, LocationRef):
|
||||
raise NotImplementedError(str(ref))
|
||||
return self.get_type_for_name(ref.name)
|
||||
|
||||
def assert_type(self, type_, *locations):
|
||||
for location in locations:
|
||||
if location.type != type_:
|
||||
if self.get_type(location) != type_:
|
||||
raise TypeMismatchError(self.current_routine, location.name)
|
||||
|
||||
def assert_affected_within(self, name, affecting_type, limiting_type):
|
||||
|
@ -387,43 +127,60 @@ class Analyzer(object):
|
|||
)
|
||||
raise IncompatibleConstraintsError(self.current_routine, message)
|
||||
|
||||
def assert_types_for_read_table(self, context, instr, src, dest, type_, offset):
|
||||
if (not TableType.is_a_table_type(self.get_type(src.ref), type_)) or (not self.get_type(dest) == type_):
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src.ref.name, dest.name))
|
||||
context.assert_meaningful(src, src.index)
|
||||
context.assert_in_range(src.index, src.ref, offset)
|
||||
|
||||
def assert_types_for_update_table(self, context, instr, dest, type_, offset):
|
||||
if not TableType.is_a_table_type(self.get_type(dest.ref), type_):
|
||||
raise TypeMismatchError(instr, '{}'.format(dest.ref.name))
|
||||
context.assert_meaningful(dest.index)
|
||||
context.assert_in_range(dest.index, dest.ref, offset)
|
||||
context.set_written(dest.ref)
|
||||
|
||||
# - - - - visitor methods - - - -
|
||||
|
||||
def analyze_program(self, program):
|
||||
assert isinstance(program, Program)
|
||||
self.routines = {r.location: r for r in program.routines}
|
||||
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
|
||||
return None, type_
|
||||
|
||||
self.current_routine = routine
|
||||
type_ = routine.location.type
|
||||
context = Context(self.routines, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||
self.exit_contexts = []
|
||||
|
||||
if self.debug:
|
||||
print("at start of routine `{}`:".format(routine.name))
|
||||
print(context)
|
||||
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)
|
||||
|
||||
trashed = set(context.each_touched()) - set(context.each_meaningful())
|
||||
|
||||
if self.debug:
|
||||
print("at end of routine `{}`:".format(routine.name))
|
||||
print(context)
|
||||
print("trashed: ", LocationRef.format_set(trashed))
|
||||
print("outputs: ", LocationRef.format_set(type_.outputs))
|
||||
trashed_outputs = type_.outputs & trashed
|
||||
if trashed_outputs:
|
||||
print("TRASHED OUTPUTS: ", LocationRef.format_set(trashed_outputs))
|
||||
print('')
|
||||
print('-' * 79)
|
||||
print('')
|
||||
self.exit_contexts_map[routine.name] = {
|
||||
'end_context': context.to_json_data(),
|
||||
'exit_contexts': [e.to_json_data() for e in self.exit_contexts]
|
||||
}
|
||||
|
||||
if self.exit_contexts:
|
||||
# check that they are all consistent
|
||||
|
@ -433,11 +190,15 @@ class Analyzer(object):
|
|||
exit_writeable = set(exit_context.each_writeable())
|
||||
for ex in self.exit_contexts[1:]:
|
||||
if set(ex.each_meaningful()) != exit_meaningful:
|
||||
raise InconsistentExitError("Exit contexts are not consistent")
|
||||
raise InconsistentExitError(routine, "Exit contexts are not consistent")
|
||||
if set(ex.each_touched()) != exit_touched:
|
||||
raise InconsistentExitError("Exit contexts are not consistent")
|
||||
raise InconsistentExitError(routine, "Exit contexts are not consistent")
|
||||
if set(ex.each_writeable()) != exit_writeable:
|
||||
raise InconsistentExitError("Exit contexts are not consistent")
|
||||
raise InconsistentExitError(routine, "Exit contexts are not consistent")
|
||||
|
||||
# We now set the main context to the (consistent) exit context
|
||||
# so that this routine is perceived as having the same effect
|
||||
# that any of the goto'ed routines have.
|
||||
context.update_from(exit_context)
|
||||
|
||||
# these all apply whether we encountered goto(s) in this routine, or not...:
|
||||
|
@ -453,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 routine_has_static(routine, ref):
|
||||
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)
|
||||
|
@ -468,6 +229,10 @@ class Analyzer(object):
|
|||
def analyze_instr(self, instr, context):
|
||||
if isinstance(instr, SingleOp):
|
||||
self.analyze_single_op(instr, context)
|
||||
elif isinstance(instr, Call):
|
||||
self.analyze_call(instr, context)
|
||||
elif isinstance(instr, GoTo):
|
||||
self.analyze_goto(instr, context)
|
||||
elif isinstance(instr, If):
|
||||
self.analyze_if(instr, context)
|
||||
elif isinstance(instr, Repeat):
|
||||
|
@ -475,13 +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):
|
||||
|
||||
|
@ -494,15 +261,21 @@ class Analyzer(object):
|
|||
|
||||
if opcode == 'ld':
|
||||
if isinstance(src, IndexedRef):
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
|
||||
elif isinstance(src, IndirectRef):
|
||||
# copying this analysis from the matching branch in `copy`, below
|
||||
if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE:
|
||||
if isinstance(self.get_type(src.ref), PointerType) and self.get_type(dest) == TYPE_BYTE:
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
|
||||
origin = context.get_assoc(src.ref)
|
||||
if not origin:
|
||||
raise UnmeaningfulReadError(instr, src.ref)
|
||||
context.assert_meaningful(origin)
|
||||
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
elif src.type != dest.type:
|
||||
elif self.get_type(src) != self.get_type(dest):
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src.name, dest.name))
|
||||
else:
|
||||
context.assert_meaningful(src)
|
||||
|
@ -510,18 +283,24 @@ class Analyzer(object):
|
|||
context.set_written(dest, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'st':
|
||||
if isinstance(dest, IndexedRef):
|
||||
if src.type != TYPE_BYTE:
|
||||
if self.get_type(src) != TYPE_BYTE:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
|
||||
self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset)
|
||||
elif isinstance(dest, IndirectRef):
|
||||
# copying this analysis from the matching branch in `copy`, below
|
||||
if isinstance(dest.ref.type, PointerType) and src.type == TYPE_BYTE:
|
||||
if isinstance(self.get_type(dest.ref), PointerType) and self.get_type(src) == TYPE_BYTE:
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
|
||||
context.assert_meaningful(dest.ref, REG_Y)
|
||||
context.set_written(dest.ref)
|
||||
elif src.type != dest.type:
|
||||
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_written(target)
|
||||
|
||||
elif self.get_type(src) != self.get_type(dest):
|
||||
raise TypeMismatchError(instr, '{} and {}'.format(src, dest))
|
||||
else:
|
||||
context.set_written(dest)
|
||||
|
@ -530,18 +309,19 @@ class Analyzer(object):
|
|||
elif opcode == 'add':
|
||||
context.assert_meaningful(src, dest, FLAG_C)
|
||||
if isinstance(src, IndexedRef):
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
elif src.type == TYPE_BYTE:
|
||||
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
|
||||
elif self.get_type(src) == TYPE_BYTE:
|
||||
self.assert_type(TYPE_BYTE, src, dest)
|
||||
if dest != REG_A:
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
else:
|
||||
self.assert_type(TYPE_WORD, src)
|
||||
if dest.type == TYPE_WORD:
|
||||
dest_type = self.get_type(dest)
|
||||
if dest_type == TYPE_WORD:
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
elif isinstance(dest.type, PointerType):
|
||||
elif isinstance(dest_type, PointerType):
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
else:
|
||||
|
@ -551,8 +331,8 @@ class Analyzer(object):
|
|||
elif opcode == 'sub':
|
||||
context.assert_meaningful(src, dest, FLAG_C)
|
||||
if isinstance(src, IndexedRef):
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
elif src.type == TYPE_BYTE:
|
||||
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
|
||||
elif self.get_type(src) == TYPE_BYTE:
|
||||
self.assert_type(TYPE_BYTE, src, dest)
|
||||
if dest != REG_A:
|
||||
context.set_touched(REG_A)
|
||||
|
@ -566,8 +346,8 @@ class Analyzer(object):
|
|||
elif opcode == 'cmp':
|
||||
context.assert_meaningful(src, dest)
|
||||
if isinstance(src, IndexedRef):
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
elif src.type == TYPE_BYTE:
|
||||
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
|
||||
elif self.get_type(src) == TYPE_BYTE:
|
||||
self.assert_type(TYPE_BYTE, src, dest)
|
||||
else:
|
||||
self.assert_type(TYPE_WORD, src, dest)
|
||||
|
@ -576,7 +356,7 @@ class Analyzer(object):
|
|||
context.set_written(FLAG_Z, FLAG_N, FLAG_C)
|
||||
elif opcode == 'and':
|
||||
if isinstance(src, IndexedRef):
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
|
||||
else:
|
||||
self.assert_type(TYPE_BYTE, src, dest)
|
||||
context.assert_meaningful(src, dest)
|
||||
|
@ -588,7 +368,7 @@ class Analyzer(object):
|
|||
context.set_top_of_range(dest, context.get_top_of_range(src))
|
||||
elif opcode in ('or', 'xor'):
|
||||
if isinstance(src, IndexedRef):
|
||||
context.assert_types_for_read_table(instr, src, dest, TYPE_BYTE)
|
||||
self.assert_types_for_read_table(context, instr, src, dest, TYPE_BYTE, src.offset)
|
||||
else:
|
||||
self.assert_type(TYPE_BYTE, src, dest)
|
||||
context.assert_meaningful(src, dest)
|
||||
|
@ -597,7 +377,7 @@ class Analyzer(object):
|
|||
elif opcode in ('inc', 'dec'):
|
||||
context.assert_meaningful(dest)
|
||||
if isinstance(dest, IndexedRef):
|
||||
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
|
||||
self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset)
|
||||
context.set_written(dest.ref, FLAG_Z, FLAG_N)
|
||||
#context.invalidate_range(dest)
|
||||
else:
|
||||
|
@ -620,84 +400,65 @@ class Analyzer(object):
|
|||
elif opcode in ('shl', 'shr'):
|
||||
context.assert_meaningful(dest, FLAG_C)
|
||||
if isinstance(dest, IndexedRef):
|
||||
context.assert_types_for_update_table(instr, dest, TYPE_BYTE)
|
||||
self.assert_types_for_update_table(context, instr, dest, TYPE_BYTE, dest.offset)
|
||||
context.set_written(dest.ref, FLAG_Z, FLAG_N, FLAG_C)
|
||||
#context.invalidate_range(dest)
|
||||
else:
|
||||
self.assert_type(TYPE_BYTE, dest)
|
||||
context.set_written(dest, FLAG_Z, FLAG_N, FLAG_C)
|
||||
context.invalidate_range(dest)
|
||||
elif opcode == 'call':
|
||||
type = instr.location.type
|
||||
if not isinstance(type, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, instr.location)
|
||||
if isinstance(type, VectorType):
|
||||
type = type.of_type
|
||||
for ref in type.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
for ref in type.outputs:
|
||||
context.set_written(ref)
|
||||
for ref in type.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
elif opcode == 'copy':
|
||||
if dest == REG_A:
|
||||
raise ForbiddenWriteError(instr, "{} cannot be used as destination for copy".format(dest))
|
||||
|
||||
# 1. check that their types are compatible
|
||||
|
||||
if isinstance(src, AddressRef) and isinstance(dest, LocationRef):
|
||||
if isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
|
||||
if src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
|
||||
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
|
||||
if self.get_type(src) == TYPE_BYTE and isinstance(self.get_type(dest.ref), PointerType):
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
if isinstance(src.ref.type, PointerType) and dest.type == TYPE_BYTE:
|
||||
if isinstance(self.get_type(src.ref), PointerType) and self.get_type(dest) == TYPE_BYTE:
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
|
||||
if isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType):
|
||||
if isinstance(self.get_type(src.ref), PointerType) and isinstance(self.get_type(dest.ref), PointerType):
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
|
||||
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndexedRef):
|
||||
if src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
|
||||
if self.get_type(src) == TYPE_WORD and TableType.is_a_table_type(self.get_type(dest.ref), TYPE_WORD):
|
||||
pass
|
||||
elif (isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and
|
||||
RoutineType.executable_types_compatible(src.type.of_type, dest.ref.type.of_type)):
|
||||
elif (isinstance(self.get_type(src), VectorType) and isinstance(self.get_type(dest.ref), TableType) and
|
||||
RoutineType.executable_types_compatible(self.get_type(src).of_type, self.get_type(dest.ref).of_type)):
|
||||
pass
|
||||
elif (isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and
|
||||
RoutineType.executable_types_compatible(src.type, dest.ref.type.of_type)):
|
||||
elif (isinstance(self.get_type(src), RoutineType) and isinstance(self.get_type(dest.ref), TableType) and
|
||||
RoutineType.executable_types_compatible(self.get_type(src), self.get_type(dest.ref).of_type)):
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
context.assert_in_range(dest.index, dest.ref)
|
||||
context.assert_in_range(dest.index, dest.ref, dest.offset)
|
||||
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
|
||||
if TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
|
||||
if TableType.is_a_table_type(self.get_type(src.ref), TYPE_WORD) and self.get_type(dest) == TYPE_WORD:
|
||||
pass
|
||||
elif (isinstance(src.ref.type, TableType) and isinstance(dest.type, VectorType) and
|
||||
RoutineType.executable_types_compatible(src.ref.type.of_type, dest.type.of_type)):
|
||||
elif (isinstance(self.get_type(src.ref), TableType) and isinstance(self.get_type(dest), VectorType) and
|
||||
RoutineType.executable_types_compatible(self.get_type(src.ref).of_type, self.get_type(dest).of_type)):
|
||||
pass
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
context.assert_in_range(src.index, src.ref)
|
||||
context.assert_in_range(src.index, src.ref, src.offset)
|
||||
|
||||
elif isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, LocationRef):
|
||||
if src.type == dest.type:
|
||||
if self.get_type(src) == self.get_type(dest):
|
||||
pass
|
||||
elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType):
|
||||
self.assert_affected_within('inputs', src.type, dest.type.of_type)
|
||||
self.assert_affected_within('outputs', src.type, dest.type.of_type)
|
||||
self.assert_affected_within('trashes', src.type, dest.type.of_type)
|
||||
elif isinstance(self.get_type(src), RoutineType) and isinstance(self.get_type(dest), VectorType):
|
||||
self.assert_affected_within('inputs', self.get_type(src), self.get_type(dest).of_type)
|
||||
self.assert_affected_within('outputs', self.get_type(src), self.get_type(dest).of_type)
|
||||
self.assert_affected_within('trashes', self.get_type(src), self.get_type(dest).of_type)
|
||||
else:
|
||||
raise TypeMismatchError(instr, (src, dest))
|
||||
else:
|
||||
|
@ -706,17 +467,36 @@ 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)
|
||||
# TODO this will need to be more sophisticated. it's the thing ref points to that is written, not ref itself.
|
||||
context.set_written(dest.ref)
|
||||
context.assert_meaningful(src, dest.ref, REG_Y)
|
||||
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_written(target)
|
||||
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
# TODO more sophisticated?
|
||||
|
||||
origin = context.get_assoc(src.ref)
|
||||
if not origin:
|
||||
raise UnmeaningfulReadError(instr, src.ref)
|
||||
context.assert_meaningful(origin)
|
||||
|
||||
context.set_written(dest)
|
||||
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
# TODO more sophisticated?
|
||||
context.set_written(dest.ref)
|
||||
context.assert_meaningful(src.ref, dest.ref, REG_Y)
|
||||
|
||||
origin = context.get_assoc(src.ref)
|
||||
if not origin:
|
||||
raise UnmeaningfulReadError(instr, src.ref)
|
||||
context.assert_meaningful(origin)
|
||||
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_written(target)
|
||||
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
||||
context.assert_meaningful(src, dest.ref, dest.index)
|
||||
context.set_written(dest.ref)
|
||||
|
@ -725,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)
|
||||
|
@ -733,59 +512,6 @@ class Analyzer(object):
|
|||
|
||||
context.set_touched(REG_A, FLAG_Z, FLAG_N)
|
||||
context.set_unmeaningful(REG_A, FLAG_Z, FLAG_N)
|
||||
elif opcode == 'goto':
|
||||
location = instr.location
|
||||
type_ = location.type
|
||||
|
||||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, location)
|
||||
|
||||
# assert that the dest routine's inputs are all initialized
|
||||
if isinstance(type_, VectorType):
|
||||
type_ = type_.of_type
|
||||
for ref in type_.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
|
||||
# and that this routine's trashes and output constraints are a
|
||||
# superset of the called routine's
|
||||
current_type = self.current_routine.location.type
|
||||
self.assert_affected_within('outputs', type_, current_type)
|
||||
self.assert_affected_within('trashes', type_, current_type)
|
||||
|
||||
context.encounter_gotos(set([instr.location]))
|
||||
|
||||
# Now that we have encountered a goto, we update the
|
||||
# context here to match what someone calling the goto'ed
|
||||
# function directly, would expect. (which makes sense
|
||||
# when you think about it; if this goto's F, then calling
|
||||
# this is like calling F, from the perspective of what is
|
||||
# returned.)
|
||||
#
|
||||
# However, this isn't the current context anymore. This
|
||||
# is an exit context of this routine.
|
||||
|
||||
exit_context = context.clone()
|
||||
|
||||
for ref in type_.outputs:
|
||||
exit_context.set_touched(ref) # ?
|
||||
exit_context.set_written(ref)
|
||||
|
||||
for ref in type_.trashes:
|
||||
exit_context.assert_writeable(ref)
|
||||
exit_context.set_touched(ref)
|
||||
exit_context.set_unmeaningful(ref)
|
||||
|
||||
self.exit_contexts.append(exit_context)
|
||||
|
||||
# When we get to the end, we'll check that all the
|
||||
# exit contexts are consistent with each other.
|
||||
|
||||
# We set the current context as having terminated.
|
||||
# If we are in a branch, the merge will deal with
|
||||
# having terminated. If we are at the end of the
|
||||
# routine, the routine end will deal with that.
|
||||
|
||||
context.set_terminated()
|
||||
|
||||
elif opcode == 'trash':
|
||||
context.set_touched(instr.dest)
|
||||
|
@ -795,6 +521,79 @@ class Analyzer(object):
|
|||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
def analyze_call(self, instr, context):
|
||||
type_ = self.get_type(instr.location)
|
||||
if not isinstance(type_, (RoutineType, VectorType)):
|
||||
raise TypeMismatchError(instr, instr.location.name)
|
||||
|
||||
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:
|
||||
context.set_written(ref)
|
||||
for ref in type_.trashes:
|
||||
context.assert_writeable(ref)
|
||||
context.set_touched(ref)
|
||||
context.set_unmeaningful(ref)
|
||||
|
||||
def analyze_goto(self, instr, context):
|
||||
location = instr.location
|
||||
type_ = self.get_type(instr.location)
|
||||
|
||||
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
|
||||
for ref in type_.inputs:
|
||||
context.assert_meaningful(ref)
|
||||
|
||||
# and that this routine's trashes and output constraints are a
|
||||
# superset of the called routine's
|
||||
current_type = self.get_type_for_name(self.current_routine.name)
|
||||
self.assert_affected_within('outputs', type_, current_type)
|
||||
self.assert_affected_within('trashes', type_, current_type)
|
||||
|
||||
context.encounter_gotos(set([instr.location]))
|
||||
|
||||
# Now that we have encountered a goto, we update the
|
||||
# context here to match what someone calling the goto'ed
|
||||
# function directly, would expect. (which makes sense
|
||||
# when you think about it; if this goto's F, then calling
|
||||
# this is like calling F, from the perspective of what is
|
||||
# returned.)
|
||||
#
|
||||
# However, this isn't the current context anymore. This
|
||||
# is an exit context of this routine.
|
||||
|
||||
exit_context = context.clone()
|
||||
|
||||
for ref in type_.outputs:
|
||||
exit_context.set_written(ref)
|
||||
|
||||
for ref in type_.trashes:
|
||||
exit_context.assert_writeable(ref)
|
||||
exit_context.set_touched(ref)
|
||||
exit_context.set_unmeaningful(ref)
|
||||
|
||||
self.exit_contexts.append(exit_context)
|
||||
|
||||
# When we get to the end, we'll check that all the
|
||||
# exit contexts are consistent with each other.
|
||||
|
||||
# We set the current context as having terminated.
|
||||
# If we are in a branch, the merge will deal with
|
||||
# having terminated. If we are at the end of the
|
||||
# routine, the routine end will deal with that.
|
||||
|
||||
context.set_terminated()
|
||||
|
||||
def analyze_if(self, instr, context):
|
||||
incoming_meaningful = set(context.each_meaningful())
|
||||
|
||||
|
@ -883,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:
|
||||
|
@ -905,3 +711,47 @@ class Analyzer(object):
|
|||
else:
|
||||
context.set_touched(REG_A)
|
||||
context.set_unmeaningful(REG_A)
|
||||
|
||||
def analyze_point_into(self, instr, context):
|
||||
if not isinstance(self.get_type(instr.pointer), PointerType):
|
||||
raise TypeMismatchError(instr, instr.pointer)
|
||||
if not TableType.is_a_table_type(self.get_type(instr.table), TYPE_BYTE):
|
||||
raise TypeMismatchError(instr, instr.table)
|
||||
|
||||
# check that pointer is not yet associated with any table.
|
||||
|
||||
if context.get_assoc(instr.pointer):
|
||||
raise ForbiddenWriteError(instr, instr.pointer)
|
||||
|
||||
# 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_unmeaningful(instr.pointer)
|
||||
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
|
||||
# unassociate pointer with table, mark as unmeaningful.
|
||||
|
||||
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):
|
||||
|
@ -54,12 +58,12 @@ class Program(AST):
|
|||
|
||||
|
||||
class Defn(AST):
|
||||
value_attrs = ('name', 'addr', 'initial', 'location',)
|
||||
value_attrs = ('name', 'addr', 'initial',)
|
||||
|
||||
|
||||
class Routine(AST):
|
||||
value_attrs = ('name', 'addr', 'initial', 'location',)
|
||||
children_attrs = ('statics',)
|
||||
value_attrs = ('name', 'addr', 'initial',)
|
||||
children_attrs = ('locals',)
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
|
@ -72,7 +76,19 @@ class Instr(AST):
|
|||
|
||||
|
||||
class SingleOp(Instr):
|
||||
value_attrs = ('opcode', 'dest', 'src', 'location',)
|
||||
value_attrs = ('opcode', 'dest', 'src',)
|
||||
|
||||
|
||||
class Reset(Instr):
|
||||
value_attrs = ('pointer', 'offset',)
|
||||
|
||||
|
||||
class Call(Instr):
|
||||
value_attrs = ('location',)
|
||||
|
||||
|
||||
class GoTo(Instr):
|
||||
value_attrs = ('location',)
|
||||
|
||||
|
||||
class If(Instr):
|
||||
|
@ -97,3 +113,8 @@ class WithInterruptsOff(Instr):
|
|||
class Save(Instr):
|
||||
value_attrs = ('locations',)
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
class PointInto(Instr):
|
||||
value_attrs = ('pointer', 'table',)
|
||||
child_attrs = ('block',)
|
||||
|
|
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,10 +1,16 @@
|
|||
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
|
||||
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
|
||||
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
|
||||
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import Program, Routine, Block, SingleOp, If, Repeat, For, WithInterruptsOff, Save
|
||||
from sixtypical.ast import (
|
||||
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef, AddressRef,
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef,
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
TableType, BufferType, PointerType, RoutineType, VectorType,
|
||||
TableType, PointerType, RoutineType, VectorType,
|
||||
REG_A, REG_X, REG_Y, FLAG_C
|
||||
)
|
||||
from sixtypical.emitter import Byte, Word, Table, Label, Offset, LowAddressByte, HighAddressByte
|
||||
|
@ -28,15 +34,29 @@ class UnsupportedOpcodeError(KeyError):
|
|||
|
||||
|
||||
class Compiler(object):
|
||||
def __init__(self, emitter):
|
||||
def __init__(self, symtab, emitter):
|
||||
self.symtab = symtab
|
||||
self.emitter = emitter
|
||||
self.routines = {} # routine.name -> Routine
|
||||
self.routine_statics = {} # routine.name -> { static.name -> Label }
|
||||
self.routine_locals = {} # routine.name -> { local.name -> Label }
|
||||
self.labels = {} # global.name -> Label ("global" includes routines)
|
||||
self.trampolines = {} # Location -> Label
|
||||
self.pointer_assoc = {} # pointer name -> table name (I'm not entirely happy about this)
|
||||
self.current_routine = None
|
||||
|
||||
# helper methods
|
||||
# - - - - helper methods - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
|
||||
return self.symtab.fetch_local_type(self.current_routine.name, name)
|
||||
return self.symtab.fetch_global_type(name)
|
||||
|
||||
def get_type(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.type
|
||||
if not isinstance(ref, LocationRef):
|
||||
raise NotImplementedError
|
||||
return self.get_type_for_name(ref.name)
|
||||
|
||||
def addressing_mode_for_index(self, index):
|
||||
if index == REG_X:
|
||||
|
@ -48,24 +68,22 @@ class Compiler(object):
|
|||
|
||||
def compute_length_of_defn(self, defn):
|
||||
length = None
|
||||
type_ = defn.location.type
|
||||
type_ = self.get_type_for_name(defn.name)
|
||||
if type_ == TYPE_BYTE:
|
||||
length = 1
|
||||
elif type_ == TYPE_WORD or isinstance(type_, (PointerType, VectorType)):
|
||||
length = 2
|
||||
elif isinstance(type_, TableType):
|
||||
length = type_.size * (1 if type_.of_type == TYPE_BYTE else 2)
|
||||
elif isinstance(type_, BufferType):
|
||||
length = type_.size
|
||||
if length is None:
|
||||
raise NotImplementedError("Need size for type {}".format(type_))
|
||||
return length
|
||||
|
||||
def get_label(self, name):
|
||||
if self.current_routine:
|
||||
static_label = self.routine_statics.get(self.current_routine.name, {}).get(name)
|
||||
if static_label:
|
||||
return static_label
|
||||
local_label = self.routine_locals.get(self.current_routine.name, {}).get(name)
|
||||
if local_label:
|
||||
return local_label
|
||||
return self.labels[name]
|
||||
|
||||
def absolute_or_zero_page(self, label):
|
||||
|
@ -74,18 +92,18 @@ class Compiler(object):
|
|||
else:
|
||||
return Absolute(label)
|
||||
|
||||
# visitor methods
|
||||
# - - - - visitor methods - - - -
|
||||
|
||||
def compile_program(self, program, compilation_roster=None):
|
||||
assert isinstance(program, Program)
|
||||
|
||||
defn_labels = []
|
||||
declarations = []
|
||||
|
||||
for defn in program.defns:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
self.labels[defn.name] = label
|
||||
defn_labels.append((defn, label))
|
||||
declarations.append((defn, self.symtab.fetch_global_type(defn.name), label))
|
||||
|
||||
for routine in program.routines:
|
||||
self.routines[routine.name] = routine
|
||||
|
@ -94,23 +112,25 @@ class Compiler(object):
|
|||
label.set_addr(routine.addr)
|
||||
self.labels[routine.name] = label
|
||||
|
||||
if hasattr(routine, 'statics'):
|
||||
static_labels = {}
|
||||
for defn in routine.statics:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
static_labels[defn.name] = label
|
||||
defn_labels.append((defn, label))
|
||||
self.routine_statics[routine.name] = static_labels
|
||||
self.current_routine = routine
|
||||
local_labels = {}
|
||||
for defn in routine.locals:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
local_labels[defn.name] = label
|
||||
declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label))
|
||||
self.routine_locals[routine.name] = local_labels
|
||||
self.current_routine = None
|
||||
|
||||
if compilation_roster is None:
|
||||
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
|
||||
|
||||
for roster_row in compilation_roster:
|
||||
for routine_name in roster_row[0:-1]:
|
||||
self.compile_routine(self.routines[routine_name], skip_final_goto=True)
|
||||
routine_name = roster_row[-1]
|
||||
self.compile_routine(self.routines[routine_name])
|
||||
for i, routine_name in enumerate(roster_row):
|
||||
if i < len(roster_row) - 1:
|
||||
self.compile_routine(self.routines[routine_name], next_routine=self.routines[roster_row[i + 1]])
|
||||
else:
|
||||
self.compile_routine(self.routines[routine_name])
|
||||
|
||||
for location, label in self.trampolines.items():
|
||||
self.emitter.resolve_label(label)
|
||||
|
@ -118,10 +138,9 @@ class Compiler(object):
|
|||
self.emitter.emit(RTS())
|
||||
|
||||
# initialized data
|
||||
for defn, label in defn_labels:
|
||||
for defn, type_, label in declarations:
|
||||
if defn.initial is not None:
|
||||
initial_data = None
|
||||
type_ = defn.location.type
|
||||
if type_ == TYPE_BYTE:
|
||||
initial_data = Byte(defn.initial)
|
||||
elif type_ == TYPE_WORD:
|
||||
|
@ -137,31 +156,57 @@ class Compiler(object):
|
|||
self.emitter.emit(initial_data)
|
||||
|
||||
# uninitialized, "BSS" data
|
||||
for defn, label in defn_labels:
|
||||
for defn, type_, label in declarations:
|
||||
if defn.initial is None and defn.addr is None:
|
||||
self.emitter.resolve_bss_label(label)
|
||||
|
||||
def compile_routine(self, routine, skip_final_goto=False):
|
||||
self.current_routine = routine
|
||||
self.skip_final_goto = skip_final_goto
|
||||
self.final_goto_seen = False
|
||||
def compile_routine(self, routine, next_routine=None):
|
||||
assert isinstance(routine, Routine)
|
||||
|
||||
self.current_routine = routine
|
||||
|
||||
if routine.block:
|
||||
self.emitter.resolve_label(self.get_label(routine.name))
|
||||
self.compile_block(routine.block)
|
||||
if not self.final_goto_seen:
|
||||
|
||||
needs_rts = True
|
||||
last_op = self.emitter.get_tail()
|
||||
|
||||
if isinstance(last_op, JSR):
|
||||
if isinstance(last_op.operand, Absolute):
|
||||
if isinstance(last_op.operand.value, Label):
|
||||
label = last_op.operand.value
|
||||
self.emitter.retract()
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
last_op = self.emitter.get_tail()
|
||||
|
||||
if isinstance(last_op, JMP):
|
||||
needs_rts = False
|
||||
if isinstance(last_op.operand, Absolute):
|
||||
if isinstance(last_op.operand.value, Label):
|
||||
if next_routine and last_op.operand.value.name == next_routine.name:
|
||||
self.emitter.retract()
|
||||
|
||||
if needs_rts:
|
||||
self.emitter.emit(RTS())
|
||||
|
||||
self.current_routine = None
|
||||
self.skip_final_goto = False
|
||||
|
||||
def compile_block(self, block):
|
||||
assert isinstance(block, Block)
|
||||
block.shallow_contains_goto = False
|
||||
for instr in block.instrs:
|
||||
self.compile_instr(instr)
|
||||
if isinstance(instr, GoTo):
|
||||
block.shallow_contains_goto = True
|
||||
|
||||
def compile_instr(self, instr):
|
||||
if isinstance(instr, SingleOp):
|
||||
return self.compile_single_op(instr)
|
||||
elif isinstance(instr, Call):
|
||||
return self.compile_call(instr)
|
||||
elif isinstance(instr, GoTo):
|
||||
return self.compile_goto(instr)
|
||||
elif isinstance(instr, If):
|
||||
return self.compile_if(instr)
|
||||
elif isinstance(instr, Repeat):
|
||||
|
@ -172,6 +217,10 @@ class Compiler(object):
|
|||
return self.compile_with_interrupts_off(instr)
|
||||
elif isinstance(instr, Save):
|
||||
return self.compile_save(instr)
|
||||
elif isinstance(instr, PointInto):
|
||||
return self.compile_point_into(instr)
|
||||
elif isinstance(instr, Reset):
|
||||
return self.compile_reset(instr)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
|
@ -190,10 +239,10 @@ class Compiler(object):
|
|||
elif isinstance(src, ConstantRef):
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.value))))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_X:
|
||||
self.emitter.emit(LDA(AbsoluteX(self.get_label(src.ref.name))))
|
||||
self.emitter.emit(LDA(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_Y:
|
||||
self.emitter.emit(LDA(AbsoluteY(self.get_label(src.ref.name))))
|
||||
elif isinstance(src, IndirectRef) and isinstance(src.ref.type, PointerType):
|
||||
self.emitter.emit(LDA(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
elif isinstance(src, IndirectRef) and isinstance(self.get_type(src.ref), PointerType):
|
||||
self.emitter.emit(LDA(IndirectY(self.get_label(src.ref.name))))
|
||||
else:
|
||||
self.emitter.emit(LDA(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||
|
@ -203,7 +252,7 @@ class Compiler(object):
|
|||
elif isinstance(src, ConstantRef):
|
||||
self.emitter.emit(LDX(Immediate(Byte(src.value))))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_Y:
|
||||
self.emitter.emit(LDX(AbsoluteY(self.get_label(src.ref.name))))
|
||||
self.emitter.emit(LDX(AbsoluteY(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(LDX(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||
elif dest == REG_Y:
|
||||
|
@ -212,7 +261,7 @@ class Compiler(object):
|
|||
elif isinstance(src, ConstantRef):
|
||||
self.emitter.emit(LDY(Immediate(Byte(src.value))))
|
||||
elif isinstance(src, IndexedRef) and src.index == REG_X:
|
||||
self.emitter.emit(LDY(AbsoluteX(self.get_label(src.ref.name))))
|
||||
self.emitter.emit(LDY(AbsoluteX(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(LDY(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||
else:
|
||||
|
@ -234,8 +283,8 @@ class Compiler(object):
|
|||
REG_X: AbsoluteX,
|
||||
REG_Y: AbsoluteY,
|
||||
}[dest.index]
|
||||
operand = mode_cls(self.get_label(dest.ref.name))
|
||||
elif isinstance(dest, IndirectRef) and isinstance(dest.ref.type, PointerType):
|
||||
operand = mode_cls(Offset(self.get_label(dest.ref.name), dest.offset.value))
|
||||
elif isinstance(dest, IndirectRef) and isinstance(self.get_type(dest.ref), PointerType):
|
||||
operand = IndirectY(self.get_label(dest.ref.name))
|
||||
else:
|
||||
operand = self.absolute_or_zero_page(self.get_label(dest.name))
|
||||
|
@ -250,10 +299,11 @@ class Compiler(object):
|
|||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(ADC(Immediate(Byte(src.value))))
|
||||
elif isinstance(src, IndexedRef):
|
||||
self.emitter.emit(ADC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||
mode = self.addressing_mode_for_index(src.index)
|
||||
self.emitter.emit(ADC(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(ADC(Absolute(self.get_label(src.name))))
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
|
||||
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_BYTE and self.get_type(dest) == TYPE_BYTE:
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
|
@ -267,7 +317,7 @@ class Compiler(object):
|
|||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and self.get_type(dest) == TYPE_WORD:
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
|
@ -287,7 +337,7 @@ class Compiler(object):
|
|||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and isinstance(dest.type, PointerType):
|
||||
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and isinstance(self.get_type(dest), PointerType):
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(ZeroPage(dest_label)))
|
||||
|
@ -316,10 +366,11 @@ class Compiler(object):
|
|||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(SBC(Immediate(Byte(src.value))))
|
||||
elif isinstance(src, IndexedRef):
|
||||
self.emitter.emit(SBC(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||
mode = self.addressing_mode_for_index(src.index)
|
||||
self.emitter.emit(SBC(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(SBC(Absolute(self.get_label(src.name))))
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_BYTE and dest.type == TYPE_BYTE:
|
||||
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_BYTE and self.get_type(dest) == TYPE_BYTE:
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
|
@ -333,7 +384,7 @@ class Compiler(object):
|
|||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
else:
|
||||
raise UnsupportedOpcodeError(instr)
|
||||
elif isinstance(dest, LocationRef) and src.type == TYPE_WORD and dest.type == TYPE_WORD:
|
||||
elif isinstance(dest, LocationRef) and self.get_type(src) == TYPE_WORD and self.get_type(dest) == TYPE_WORD:
|
||||
if isinstance(src, ConstantRef):
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
|
@ -367,7 +418,8 @@ class Compiler(object):
|
|||
if isinstance(src, ConstantRef):
|
||||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||
elif isinstance(src, IndexedRef):
|
||||
self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||
mode = self.addressing_mode_for_index(src.index)
|
||||
self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(src.name))))
|
||||
else:
|
||||
|
@ -384,34 +436,10 @@ class Compiler(object):
|
|||
if dest == REG_A:
|
||||
self.emitter.emit(cls())
|
||||
elif isinstance(dest, IndexedRef):
|
||||
self.emitter.emit(cls(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
|
||||
mode = self.addressing_mode_for_index(dest.index)
|
||||
self.emitter.emit(cls(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(self.absolute_or_zero_page(self.get_label(dest.name))))
|
||||
elif opcode == 'call':
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JSR(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
trampoline = self.trampolines.setdefault(
|
||||
location, Label(location.name + '_trampoline')
|
||||
)
|
||||
self.emitter.emit(JSR(Absolute(trampoline)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'goto':
|
||||
self.final_goto_seen = True
|
||||
if self.skip_final_goto:
|
||||
pass
|
||||
else:
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
if isinstance(location.type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location.type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError
|
||||
elif opcode == 'copy':
|
||||
self.compile_copy(instr, instr.src, instr.dest)
|
||||
elif opcode == 'trash':
|
||||
|
@ -421,9 +449,34 @@ class Compiler(object):
|
|||
else:
|
||||
raise NotImplementedError(opcode)
|
||||
|
||||
def compile_call(self, instr):
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
location_type = self.get_type(location)
|
||||
if isinstance(location_type, RoutineType):
|
||||
self.emitter.emit(JSR(Absolute(label)))
|
||||
elif isinstance(location_type, VectorType):
|
||||
trampoline = self.trampolines.setdefault(
|
||||
location, Label(location.name + '_trampoline')
|
||||
)
|
||||
self.emitter.emit(JSR(Absolute(trampoline)))
|
||||
else:
|
||||
raise NotImplementedError(location_type)
|
||||
|
||||
def compile_goto(self, instr):
|
||||
location = instr.location
|
||||
label = self.get_label(instr.location.name)
|
||||
location_type = self.get_type(location)
|
||||
if isinstance(location_type, RoutineType):
|
||||
self.emitter.emit(JMP(Absolute(label)))
|
||||
elif isinstance(location_type, VectorType):
|
||||
self.emitter.emit(JMP(Indirect(label)))
|
||||
else:
|
||||
raise NotImplementedError(location_type)
|
||||
|
||||
def compile_cmp(self, instr, src, dest):
|
||||
"""`instr` is only for reporting purposes"""
|
||||
if isinstance(src, LocationRef) and src.type == TYPE_WORD:
|
||||
if isinstance(src, LocationRef) and self.get_type(src) == TYPE_WORD:
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
|
@ -434,7 +487,7 @@ class Compiler(object):
|
|||
self.emitter.emit(CMP(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.resolve_label(end_label)
|
||||
return
|
||||
if isinstance(src, ConstantRef) and src.type == TYPE_WORD:
|
||||
if isinstance(src, ConstantRef) and self.get_type(src) == TYPE_WORD:
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(dest_label)))
|
||||
self.emitter.emit(CMP(Immediate(Byte(src.low_byte()))))
|
||||
|
@ -455,7 +508,8 @@ class Compiler(object):
|
|||
self.emitter.emit(cls(Immediate(Byte(src.value))))
|
||||
elif isinstance(src, IndexedRef):
|
||||
# FIXME might not work for some dest's (that is, cls's)
|
||||
self.emitter.emit(cls(self.addressing_mode_for_index(src.index)(self.get_label(src.ref.name))))
|
||||
mode = self.addressing_mode_for_index(src.index)
|
||||
self.emitter.emit(cls(mode(Offset(self.get_label(src.ref.name), src.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(cls(Absolute(self.get_label(src.name))))
|
||||
|
||||
|
@ -466,7 +520,8 @@ class Compiler(object):
|
|||
elif dest == REG_Y:
|
||||
self.emitter.emit(INY())
|
||||
elif isinstance(dest, IndexedRef):
|
||||
self.emitter.emit(INC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
|
||||
mode = self.addressing_mode_for_index(dest.index)
|
||||
self.emitter.emit(INC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(INC(Absolute(self.get_label(dest.name))))
|
||||
|
||||
|
@ -477,105 +532,115 @@ class Compiler(object):
|
|||
elif dest == REG_Y:
|
||||
self.emitter.emit(DEY())
|
||||
elif isinstance(dest, IndexedRef):
|
||||
self.emitter.emit(DEC(self.addressing_mode_for_index(dest.index)(self.get_label(dest.ref.name))))
|
||||
mode = self.addressing_mode_for_index(dest.index)
|
||||
self.emitter.emit(DEC(mode(Offset(self.get_label(dest.ref.name), dest.offset.value))))
|
||||
else:
|
||||
self.emitter.emit(DEC(Absolute(self.get_label(dest.name))))
|
||||
|
||||
def compile_copy(self, instr, src, dest):
|
||||
if isinstance(src, ConstantRef) and isinstance(dest, IndirectRef) and src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
|
||||
|
||||
if isinstance(src, (IndirectRef, IndexedRef)):
|
||||
src_ref_type = self.get_type(src.ref)
|
||||
else:
|
||||
src_type = self.get_type(src)
|
||||
|
||||
if isinstance(dest, (IndirectRef, IndexedRef)):
|
||||
dest_ref_type = self.get_type(dest.ref)
|
||||
else:
|
||||
dest_type = self.get_type(dest)
|
||||
|
||||
if isinstance(src, ConstantRef) and isinstance(dest, IndirectRef) and src_type == TYPE_BYTE and isinstance(dest_ref_type, PointerType):
|
||||
### copy 123, [ptr] + y
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.value))))
|
||||
self.emitter.emit(STA(IndirectY(dest_label)))
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndirectRef) and src.type == TYPE_BYTE and isinstance(dest.ref.type, PointerType):
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndirectRef) and src_type == TYPE_BYTE and isinstance(dest_ref_type, PointerType):
|
||||
### copy b, [ptr] + y
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(IndirectY(dest_label)))
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef) and dest.type == TYPE_BYTE and isinstance(src.ref.type, PointerType):
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef) and dest_type == TYPE_BYTE and isinstance(src_ref_type, PointerType):
|
||||
### copy [ptr] + y, b
|
||||
src_label = self.get_label(src.ref.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(IndirectY(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef) and isinstance(src.ref.type, PointerType) and isinstance(dest.ref.type, PointerType):
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef) and isinstance(src_ref_type, PointerType) and isinstance(dest_ref_type, PointerType):
|
||||
### copy [ptra] + y, [ptrb] + y
|
||||
src_label = self.get_label(src.ref.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
self.emitter.emit(LDA(IndirectY(src_label)))
|
||||
self.emitter.emit(STA(IndirectY(dest_label)))
|
||||
elif isinstance(src, AddressRef) and isinstance(dest, LocationRef) and isinstance(src.ref.type, BufferType) and isinstance(dest.type, PointerType):
|
||||
### copy ^buf, ptr
|
||||
src_label = self.get_label(src.ref.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and src_type == TYPE_WORD and TableType.is_a_table_type(dest_ref_type, TYPE_WORD):
|
||||
### copy w, wtab + y
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
mode = self.addressing_mode_for_index(dest.index)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, VectorType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src_type, VectorType) and isinstance(dest_ref_type, TableType) and isinstance(dest_ref_type.of_type, VectorType):
|
||||
### copy vec, vtab + y
|
||||
# FIXME this is the exact same as above - can this be simplified?
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
mode = self.addressing_mode_for_index(dest.index)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src.type, RoutineType) and isinstance(dest.ref.type, TableType) and isinstance(dest.ref.type.of_type, VectorType):
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef) and isinstance(src_type, RoutineType) and isinstance(dest_ref_type, TableType) and isinstance(dest_ref_type.of_type, VectorType):
|
||||
### copy routine, vtab + y
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
mode = self.addressing_mode_for_index(dest.index)
|
||||
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
|
||||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef) and src.type == TYPE_WORD and TableType.is_a_table_type(dest.ref.type, TYPE_WORD):
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
|
||||
elif isinstance(src, ConstantRef) and isinstance(dest, IndexedRef) and src_type == TYPE_WORD and TableType.is_a_table_type(dest_ref_type, TYPE_WORD):
|
||||
### copy 9999, wtab + y
|
||||
dest_label = self.get_label(dest.ref.name)
|
||||
mode = self.addressing_mode_for_index(dest.index)
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(dest_label)))
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value))))
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
|
||||
self.emitter.emit(STA(self.addressing_mode_for_index(dest.index)(Offset(dest_label, 256))))
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and TableType.is_a_table_type(src.ref.type, TYPE_WORD) and dest.type == TYPE_WORD:
|
||||
self.emitter.emit(STA(mode(Offset(dest_label, dest.offset.value + 256))))
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and TableType.is_a_table_type(src_ref_type, TYPE_WORD) and dest_type == TYPE_WORD:
|
||||
### copy wtab + y, w
|
||||
src_label = self.get_label(src.ref.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
|
||||
mode = self.addressing_mode_for_index(src.index)
|
||||
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
|
||||
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and isinstance(dest.type, VectorType) and isinstance(src.ref.type, TableType) and isinstance(src.ref.type.of_type, VectorType):
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef) and isinstance(dest_type, VectorType) and isinstance(src_ref_type, TableType) and isinstance(src_ref_type.of_type, VectorType):
|
||||
### copy vtab + y, vec
|
||||
# FIXME this is the exact same as above - can this be simplified?
|
||||
src_label = self.get_label(src.ref.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(src_label)))
|
||||
mode = self.addressing_mode_for_index(src.index)
|
||||
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(self.addressing_mode_for_index(src.index)(Offset(src_label, 256))))
|
||||
self.emitter.emit(LDA(mode(Offset(src_label, src.offset.value + 256))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif src.type == TYPE_BYTE and dest.type == TYPE_BYTE and not isinstance(src, ConstantRef):
|
||||
elif src_type == TYPE_BYTE and dest_type == TYPE_BYTE and not isinstance(src, ConstantRef):
|
||||
### copy b1, b2
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Absolute(src_label)))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
elif src.type == TYPE_WORD and dest.type == TYPE_WORD and isinstance(src, ConstantRef):
|
||||
elif src_type == TYPE_WORD and dest_type == TYPE_WORD and isinstance(src, ConstantRef):
|
||||
### copy 9999, w
|
||||
dest_label = self.get_label(dest.name)
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.low_byte()))))
|
||||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(Byte(src.high_byte()))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif src.type == TYPE_WORD and dest.type == TYPE_WORD and not isinstance(src, ConstantRef):
|
||||
elif src_type == TYPE_WORD and dest_type == TYPE_WORD and not isinstance(src, ConstantRef):
|
||||
### copy w1, w2
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
|
@ -583,7 +648,7 @@ class Compiler(object):
|
|||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(src.type, VectorType) and isinstance(dest.type, VectorType):
|
||||
elif isinstance(src_type, VectorType) and isinstance(dest_type, VectorType):
|
||||
### copy v1, v2
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
|
@ -591,7 +656,7 @@ class Compiler(object):
|
|||
self.emitter.emit(STA(Absolute(dest_label)))
|
||||
self.emitter.emit(LDA(Absolute(Offset(src_label, 1))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
elif isinstance(src.type, RoutineType) and isinstance(dest.type, VectorType):
|
||||
elif isinstance(src_type, RoutineType) and isinstance(dest_type, VectorType):
|
||||
### copy routine, vec
|
||||
src_label = self.get_label(src.name)
|
||||
dest_label = self.get_label(dest.name)
|
||||
|
@ -600,7 +665,7 @@ class Compiler(object):
|
|||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
self.emitter.emit(STA(Absolute(Offset(dest_label, 1))))
|
||||
else:
|
||||
raise NotImplementedError(src.type)
|
||||
raise NotImplementedError(src_type)
|
||||
|
||||
def compile_if(self, instr):
|
||||
cls = {
|
||||
|
@ -620,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)
|
||||
|
||||
|
@ -700,3 +770,18 @@ class Compiler(object):
|
|||
src_label = self.get_label(location.name)
|
||||
self.emitter.emit(PLA())
|
||||
self.emitter.emit(STA(Absolute(src_label)))
|
||||
|
||||
def compile_point_into(self, instr):
|
||||
self.pointer_assoc[instr.pointer.name] = instr.table.name
|
||||
self.compile_block(instr.block)
|
||||
del self.pointer_assoc[instr.pointer.name]
|
||||
|
||||
def compile_reset(self, instr):
|
||||
table_name = self.pointer_assoc[instr.pointer.name]
|
||||
src_label = Offset(self.get_label(table_name), instr.offset.value)
|
||||
dest_label = self.get_label(instr.pointer.name)
|
||||
|
||||
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
|
||||
|
|
332
src/sixtypical/context.py
Normal file
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
|
||||
|
@ -7,7 +11,8 @@ from sixtypical.model import RoutineType
|
|||
|
||||
class FallthruAnalyzer(object):
|
||||
|
||||
def __init__(self, debug=False):
|
||||
def __init__(self, symtab, debug=False):
|
||||
self.symtab = symtab
|
||||
self.debug = debug
|
||||
|
||||
def analyze_program(self, program):
|
||||
|
@ -16,7 +21,7 @@ class FallthruAnalyzer(object):
|
|||
self.fallthru_map = {}
|
||||
for routine in program.routines:
|
||||
encountered_gotos = list(routine.encountered_gotos)
|
||||
if len(encountered_gotos) == 1 and isinstance(encountered_gotos[0].type, RoutineType):
|
||||
if len(encountered_gotos) == 1 and isinstance(self.symtab.fetch_global_type(encountered_gotos[0].name), RoutineType):
|
||||
self.fallthru_map[routine.name] = encountered_gotos[0].name
|
||||
else:
|
||||
self.fallthru_map[routine.name] = None
|
||||
|
|
|
@ -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,53 +1,39 @@
|
|||
# Copyright (c) 2014-2024, Chris Pressey, Cat's Eye Technologies.
|
||||
# This file is distributed under a 2-clause BSD license. See LICENSES/ dir.
|
||||
# SPDX-License-Identifier: LicenseRef-BSD-2-Clause-X-SixtyPical
|
||||
|
||||
"""Data/storage model for SixtyPical."""
|
||||
|
||||
|
||||
class Type(object):
|
||||
def __init__(self, name, max_range=None):
|
||||
self.name = name
|
||||
self.max_range = max_range
|
||||
|
||||
def __repr__(self):
|
||||
return 'Type(%r)' % self.name
|
||||
|
||||
def __str__(self):
|
||||
return self.name
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, Type) and other.name == self.name
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name)
|
||||
from collections import namedtuple
|
||||
|
||||
|
||||
TYPE_BIT = Type('bit', max_range=(0, 1))
|
||||
TYPE_BYTE = Type('byte', max_range=(0, 255))
|
||||
TYPE_WORD = Type('word', max_range=(0, 65535))
|
||||
class BitType(namedtuple('BitType', ['typename'])):
|
||||
max_range = (0, 1)
|
||||
def __new__(cls):
|
||||
return super(BitType, cls).__new__(cls, 'bit')
|
||||
TYPE_BIT = BitType()
|
||||
|
||||
|
||||
class ByteType(namedtuple('ByteType', ['typename'])):
|
||||
max_range = (0, 255)
|
||||
def __new__(cls):
|
||||
return super(ByteType, cls).__new__(cls, 'byte')
|
||||
TYPE_BYTE = ByteType()
|
||||
|
||||
class RoutineType(Type):
|
||||
|
||||
class WordType(namedtuple('WordType', ['typename'])):
|
||||
max_range = (0, 65535)
|
||||
def __new__(cls):
|
||||
return super(WordType, cls).__new__(cls, 'word')
|
||||
TYPE_WORD = WordType()
|
||||
|
||||
|
||||
class RoutineType(namedtuple('RoutineType', ['typename', 'inputs', 'outputs', 'trashes'])):
|
||||
"""This memory location contains the code for a routine."""
|
||||
def __init__(self, inputs, outputs, trashes):
|
||||
self.name = 'routine'
|
||||
self.inputs = inputs
|
||||
self.outputs = outputs
|
||||
self.trashes = trashes
|
||||
max_range = (0, 0)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, inputs=%r, outputs=%r, trashes=%r)' % (
|
||||
self.__class__.__name__, self.name, self.inputs, self.outputs, self.trashes
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, RoutineType) and (
|
||||
other.name == self.name and
|
||||
other.inputs == self.inputs and
|
||||
other.outputs == self.outputs and
|
||||
other.trashes == self.trashes
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name) ^ hash(self.inputs) ^ hash(self.outputs) ^ hash(self.trashes)
|
||||
def __new__(cls, *args):
|
||||
return super(RoutineType, cls).__new__(cls, 'routine', *args)
|
||||
|
||||
@classmethod
|
||||
def executable_types_compatible(cls_, src, dest):
|
||||
|
@ -67,214 +53,68 @@ class RoutineType(Type):
|
|||
return False
|
||||
|
||||
|
||||
class VectorType(Type):
|
||||
class VectorType(namedtuple('VectorType', ['typename', 'of_type'])):
|
||||
"""This memory location contains the address of some other type (currently, only RoutineType)."""
|
||||
def __init__(self, of_type):
|
||||
self.name = 'vector'
|
||||
self.of_type = of_type
|
||||
max_range = (0, 65535)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (
|
||||
self.__class__.__name__, self.of_type
|
||||
)
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.name == other.name and self.of_type == other.of_type
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name) ^ hash(self.of_type)
|
||||
def __new__(cls, *args):
|
||||
return super(VectorType, cls).__new__(cls, 'vector', *args)
|
||||
|
||||
|
||||
class TableType(Type):
|
||||
def __init__(self, of_type, size):
|
||||
self.of_type = of_type
|
||||
self.size = size
|
||||
self.name = '{} table[{}]'.format(self.of_type.name, self.size)
|
||||
class TableType(namedtuple('TableType', ['typename', 'of_type', 'size'])):
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (
|
||||
self.__class__.__name__, self.of_type, self.size
|
||||
)
|
||||
def __new__(cls, *args):
|
||||
return super(TableType, cls).__new__(cls, 'table', *args)
|
||||
|
||||
@property
|
||||
def max_range(self):
|
||||
return (0, self.size - 1)
|
||||
|
||||
@classmethod
|
||||
def is_a_table_type(cls_, x, of_type):
|
||||
return isinstance(x, TableType) and x.of_type == of_type
|
||||
|
||||
|
||||
class BufferType(Type):
|
||||
def __init__(self, size):
|
||||
self.size = size
|
||||
self.name = 'buffer[%s]' % self.size
|
||||
class PointerType(namedtuple('PointerType', ['typename'])):
|
||||
max_range = (0, 65535)
|
||||
|
||||
def __new__(cls):
|
||||
return super(PointerType, cls).__new__(cls, 'pointer')
|
||||
|
||||
|
||||
class PointerType(Type):
|
||||
def __init__(self):
|
||||
self.name = 'pointer'
|
||||
# --------------------------------------------------------
|
||||
|
||||
|
||||
class Ref(object):
|
||||
def is_constant(self):
|
||||
"""read-only means that the program cannot change the value
|
||||
of a location. constant means that the value of the location
|
||||
will not change during the lifetime of the program."""
|
||||
raise NotImplementedError("class {} must implement is_constant()".format(self.__class__.__name__))
|
||||
|
||||
def max_range(self):
|
||||
raise NotImplementedError("class {} must implement max_range()".format(self.__class__.__name__))
|
||||
|
||||
|
||||
class LocationRef(Ref):
|
||||
def __init__(self, type, name):
|
||||
self.type = type
|
||||
self.name = name
|
||||
|
||||
def __eq__(self, other):
|
||||
# Ordinarily there will only be one ref with a given name,
|
||||
# but because we store the type in here and we want to treat
|
||||
# these objects as immutable, we compare the types, too,
|
||||
# just to be sure.
|
||||
equal = isinstance(other, self.__class__) and other.name == self.name
|
||||
if equal:
|
||||
assert other.type == self.type, repr((self, other))
|
||||
return equal
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.name + str(self.type))
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.name)
|
||||
|
||||
def __str__(self):
|
||||
return "{}:{}".format(self.name, self.type)
|
||||
|
||||
def is_constant(self):
|
||||
return isinstance(self.type, RoutineType)
|
||||
|
||||
def max_range(self):
|
||||
try:
|
||||
return self.type.max_range
|
||||
except:
|
||||
return (0, 0)
|
||||
class LocationRef(namedtuple('LocationRef', ['reftype', 'name'])):
|
||||
def __new__(cls, *args):
|
||||
return super(LocationRef, cls).__new__(cls, 'location', *args)
|
||||
|
||||
@classmethod
|
||||
def format_set(cls, location_refs):
|
||||
return '{%s}' % ', '.join([str(loc) for loc in sorted(location_refs, key=lambda x: x.name)])
|
||||
|
||||
|
||||
class IndirectRef(Ref):
|
||||
def __init__(self, ref):
|
||||
self.ref = ref
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__) and self.ref == other.ref
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__class__.name) ^ hash(self.ref)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, self.ref)
|
||||
class IndirectRef(namedtuple('IndirectRef', ['reftype', 'ref'])):
|
||||
def __new__(cls, *args):
|
||||
return super(IndirectRef, cls).__new__(cls, 'indirect', *args)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return '[{}]+y'.format(self.ref.name)
|
||||
|
||||
def is_constant(self):
|
||||
return False
|
||||
|
||||
|
||||
class IndexedRef(Ref):
|
||||
def __init__(self, ref, index):
|
||||
self.ref = ref
|
||||
self.index = index
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, self.__class__) and self.ref == other.ref and self.index == other.index
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__class__.name) ^ hash(self.ref) ^ hash(self.index)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.index)
|
||||
class IndexedRef(namedtuple('IndexedRef', ['reftype', 'ref', 'offset', 'index'])):
|
||||
def __new__(cls, *args):
|
||||
return super(IndexedRef, cls).__new__(cls, 'indexed', *args)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return '{}+{}'.format(self.ref.name, self.index.name)
|
||||
|
||||
def is_constant(self):
|
||||
return False
|
||||
return '{}+{}+{}'.format(self.ref.name, self.offset, self.index.name)
|
||||
|
||||
|
||||
class AddressRef(Ref):
|
||||
def __init__(self, ref):
|
||||
self.ref = ref
|
||||
|
||||
def __eq__(self, other):
|
||||
return self.ref == other.ref
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.__class__.name) ^ hash(self.ref)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r)' % (self.__class__.__name__, self.ref)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return '^{}'.format(self.ref.name)
|
||||
|
||||
def is_constant(self):
|
||||
return True
|
||||
|
||||
|
||||
class PartRef(Ref):
|
||||
"""For 'low byte of' location and 'high byte of' location modifiers.
|
||||
|
||||
height=0 = low byte, height=1 = high byte.
|
||||
|
||||
NOTE: Not actually used yet. Might require more thought before it's usable.
|
||||
"""
|
||||
def __init__(self, ref, height):
|
||||
assert isinstance(ref, Ref)
|
||||
assert ref.type == TYPE_WORD
|
||||
self.ref = ref
|
||||
self.height = height
|
||||
self.type = TYPE_BYTE
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, PartRef) and (
|
||||
other.height == self.height and other.ref == self.ref
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(self.ref) ^ hash(self.height) ^ hash(self.type)
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (self.__class__.__name__, self.ref, self.height)
|
||||
|
||||
def is_constant(self):
|
||||
return self.ref.is_constant()
|
||||
|
||||
|
||||
class ConstantRef(Ref):
|
||||
def __init__(self, type, value):
|
||||
self.type = type
|
||||
self.value = value
|
||||
|
||||
def __eq__(self, other):
|
||||
return isinstance(other, ConstantRef) and (
|
||||
other.type == self.type and other.value == self.value
|
||||
)
|
||||
|
||||
def __hash__(self):
|
||||
return hash(str(self.value) + str(self.type))
|
||||
|
||||
def __repr__(self):
|
||||
return '%s(%r, %r)' % (self.__class__.__name__, self.type, self.value)
|
||||
|
||||
def is_constant(self):
|
||||
return True
|
||||
|
||||
def max_range(self):
|
||||
return (self.value, self.value)
|
||||
class ConstantRef(namedtuple('ConstantRef', ['reftype', 'type', 'value'])):
|
||||
def __new__(cls, *args):
|
||||
return super(ConstantRef, cls).__new__(cls, 'constant', *args)
|
||||
|
||||
def high_byte(self):
|
||||
return (self.value >> 8) & 255
|
||||
|
@ -296,12 +136,16 @@ class ConstantRef(Ref):
|
|||
value -= 256
|
||||
return ConstantRef(self.type, value)
|
||||
|
||||
@property
|
||||
def name(self):
|
||||
return 'constant({})'.format(self.value)
|
||||
|
||||
REG_A = LocationRef(TYPE_BYTE, 'a')
|
||||
REG_X = LocationRef(TYPE_BYTE, 'x')
|
||||
REG_Y = LocationRef(TYPE_BYTE, 'y')
|
||||
|
||||
FLAG_Z = LocationRef(TYPE_BIT, 'z')
|
||||
FLAG_C = LocationRef(TYPE_BIT, 'c')
|
||||
FLAG_N = LocationRef(TYPE_BIT, 'n')
|
||||
FLAG_V = LocationRef(TYPE_BIT, 'v')
|
||||
REG_A = LocationRef('a')
|
||||
REG_X = LocationRef('x')
|
||||
REG_Y = LocationRef('y')
|
||||
|
||||
FLAG_Z = LocationRef('z')
|
||||
FLAG_C = LocationRef('c')
|
||||
FLAG_N = LocationRef('n')
|
||||
FLAG_V = LocationRef('v')
|
||||
|
|
|
@ -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,21 +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, If, Repeat, For, WithInterruptsOff, Save
|
||||
from sixtypical.ast import (
|
||||
Program, Defn, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
RoutineType, VectorType, TableType, BufferType, PointerType,
|
||||
LocationRef, ConstantRef, IndirectRef, IndexedRef, AddressRef,
|
||||
RoutineType, VectorType, TableType, PointerType,
|
||||
ConstantRef, IndirectRef, IndexedRef,
|
||||
)
|
||||
from sixtypical.scanner import Scanner
|
||||
|
||||
|
||||
class SymEntry(object):
|
||||
def __init__(self, ast_node, model):
|
||||
self.ast_node = ast_node
|
||||
self.model = model
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.model)
|
||||
from sixtypical.symtab import SymEntry
|
||||
|
||||
|
||||
class ForwardReference(object):
|
||||
|
@ -26,77 +24,62 @@ class ForwardReference(object):
|
|||
return "%s(%r)" % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
class ParsingContext(object):
|
||||
def __init__(self):
|
||||
self.symbols = {} # token -> SymEntry
|
||||
self.statics = {} # token -> SymEntry
|
||||
self.typedefs = {} # token -> Type AST
|
||||
self.consts = {} # token -> Loc
|
||||
|
||||
for token in ('a', 'x', 'y'):
|
||||
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BYTE, token))
|
||||
for token in ('c', 'z', 'n', 'v'):
|
||||
self.symbols[token] = SymEntry(None, LocationRef(TYPE_BIT, token))
|
||||
|
||||
def __str__(self):
|
||||
return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
|
||||
|
||||
def fetch(self, name):
|
||||
if name in self.statics:
|
||||
return self.statics[name].model
|
||||
if name in self.symbols:
|
||||
return self.symbols[name].model
|
||||
return None
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, context, text, filename):
|
||||
self.context = context
|
||||
def __init__(self, symtab, text, filename, include_path):
|
||||
self.symtab = symtab
|
||||
self.include_path = include_path
|
||||
self.scanner = Scanner(text, filename)
|
||||
self.current_routine_name = None
|
||||
|
||||
def syntax_error(self, msg):
|
||||
self.scanner.syntax_error(msg)
|
||||
|
||||
def lookup(self, name):
|
||||
model = self.context.fetch(name)
|
||||
def lookup(self, name, allow_forward=False, routine_name=None):
|
||||
model = self.symtab.fetch_global_ref(name)
|
||||
if model is None and routine_name:
|
||||
model = self.symtab.fetch_local_ref(routine_name, name)
|
||||
if model is None and allow_forward:
|
||||
return ForwardReference(name)
|
||||
if model is None:
|
||||
self.syntax_error('Undefined symbol "{}"'.format(name))
|
||||
return model
|
||||
|
||||
def declare(self, name, symentry, static=False):
|
||||
if self.context.fetch(name):
|
||||
def declare(self, name, ast_node, type_):
|
||||
if self.symtab.fetch_global_ref(name):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
if static:
|
||||
self.context.statics[name] = symentry
|
||||
else:
|
||||
self.context.symbols[name] = symentry
|
||||
self.symtab.symbols[name] = SymEntry(ast_node, type_)
|
||||
|
||||
def clear_statics(self):
|
||||
self.context.statics = {}
|
||||
def declare_local(self, routine_name, name, ast_node, type_):
|
||||
if self.symtab.fetch_local_ref(routine_name, name):
|
||||
self.syntax_error('Symbol "%s" already declared locally' % name)
|
||||
if self.symtab.fetch_global_ref(name):
|
||||
self.syntax_error('Symbol "%s" already declared globally' % name)
|
||||
self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
|
||||
|
||||
# ---- symbol resolution
|
||||
|
||||
def resolve_symbols(self, program):
|
||||
# This could stand to be better unified.
|
||||
|
||||
def backpatch_constraint_labels(type_):
|
||||
def resolve(w):
|
||||
if not isinstance(w, ForwardReference):
|
||||
return w
|
||||
return self.lookup(w.name)
|
||||
if isinstance(type_, TableType):
|
||||
backpatch_constraint_labels(type_.of_type)
|
||||
elif isinstance(type_, VectorType):
|
||||
backpatch_constraint_labels(type_.of_type)
|
||||
elif isinstance(type_, RoutineType):
|
||||
type_.inputs = set([resolve(w) for w in type_.inputs])
|
||||
type_.outputs = set([resolve(w) for w in type_.outputs])
|
||||
type_.trashes = set([resolve(w) for w in type_.trashes])
|
||||
def resolve(w):
|
||||
return self.lookup(w.name) if isinstance(w, ForwardReference) else w
|
||||
|
||||
for defn in program.defns:
|
||||
backpatch_constraint_labels(defn.location.type)
|
||||
for routine in program.routines:
|
||||
backpatch_constraint_labels(routine.location.type)
|
||||
def backpatched_type(type_):
|
||||
if isinstance(type_, TableType):
|
||||
return TableType(backpatched_type(type_.of_type), type_.size)
|
||||
elif isinstance(type_, VectorType):
|
||||
return VectorType(backpatched_type(type_.of_type))
|
||||
elif isinstance(type_, RoutineType):
|
||||
return RoutineType(
|
||||
frozenset([resolve(w) for w in type_.inputs]),
|
||||
frozenset([resolve(w) for w in type_.outputs]),
|
||||
frozenset([resolve(w) for w in type_.trashes]),
|
||||
)
|
||||
else:
|
||||
return type_
|
||||
|
||||
for name, symentry in self.symtab.symbols.items():
|
||||
symentry.type_ = backpatched_type(symentry.type_)
|
||||
|
||||
def resolve_fwd_reference(obj, field):
|
||||
field_value = getattr(obj, field, None)
|
||||
|
@ -108,35 +91,51 @@ class Parser(object):
|
|||
|
||||
for node in program.all_children():
|
||||
if isinstance(node, SingleOp):
|
||||
resolve_fwd_reference(node, 'location')
|
||||
resolve_fwd_reference(node, 'src')
|
||||
resolve_fwd_reference(node, 'dest')
|
||||
if isinstance(node, (Call, GoTo)):
|
||||
resolve_fwd_reference(node, 'location')
|
||||
|
||||
# --- grammar productions
|
||||
|
||||
def program(self):
|
||||
defns = []
|
||||
routines = []
|
||||
includes = []
|
||||
while self.scanner.consume('include'):
|
||||
filename = self.scanner.token
|
||||
self.scanner.scan()
|
||||
program = load_program(filename, self.symtab, self.include_path, include_file=True)
|
||||
includes.append(program)
|
||||
while self.scanner.on('typedef', 'const'):
|
||||
if self.scanner.on('typedef'):
|
||||
self.typedef()
|
||||
if self.scanner.on('const'):
|
||||
self.defn_const()
|
||||
typenames = ['byte', 'word', 'table', 'vector', 'buffer', 'pointer'] # 'routine',
|
||||
typenames.extend(self.context.typedefs.keys())
|
||||
typenames = ['byte', 'word', 'table', 'vector', 'pointer'] # 'routine',
|
||||
typenames.extend(self.symtab.typedefs.keys())
|
||||
while self.scanner.on(*typenames):
|
||||
defn = self.defn()
|
||||
self.declare(defn.name, SymEntry(defn, defn.location))
|
||||
type_, defn = self.defn()
|
||||
self.declare(defn.name, defn, type_)
|
||||
defns.append(defn)
|
||||
while self.scanner.consume('define'):
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
routine = self.routine(name)
|
||||
self.declare(name, SymEntry(routine, routine.location))
|
||||
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
|
||||
|
||||
|
@ -144,18 +143,18 @@ class Parser(object):
|
|||
self.scanner.expect('typedef')
|
||||
type_ = self.defn_type()
|
||||
name = self.defn_name()
|
||||
if name in self.context.typedefs:
|
||||
if name in self.symtab.typedefs:
|
||||
self.syntax_error('Type "%s" already declared' % name)
|
||||
self.context.typedefs[name] = type_
|
||||
self.symtab.typedefs[name] = type_
|
||||
return type_
|
||||
|
||||
def defn_const(self):
|
||||
self.scanner.expect('const')
|
||||
name = self.defn_name()
|
||||
if name in self.context.consts:
|
||||
if name in self.symtab.consts:
|
||||
self.syntax_error('Const "%s" already declared' % name)
|
||||
loc = self.const()
|
||||
self.context.consts[name] = loc
|
||||
self.symtab.consts[name] = loc
|
||||
return loc
|
||||
|
||||
def defn(self):
|
||||
|
@ -185,9 +184,7 @@ class Parser(object):
|
|||
if initial is not None and addr is not None:
|
||||
self.syntax_error("Definition cannot have both initial value and explicit address")
|
||||
|
||||
location = LocationRef(type_, name)
|
||||
|
||||
return Defn(self.scanner.line_number, name=name, addr=addr, initial=initial, location=location)
|
||||
return type_, Defn(self.scanner.line_number, name=name, addr=addr, initial=initial)
|
||||
|
||||
def const(self):
|
||||
if self.scanner.token in ('on', 'off'):
|
||||
|
@ -204,8 +201,8 @@ class Parser(object):
|
|||
loc = ConstantRef(TYPE_WORD, int(self.scanner.token))
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
elif self.scanner.token in self.context.consts:
|
||||
loc = self.context.consts[self.scanner.token]
|
||||
elif self.scanner.token in self.symtab.consts:
|
||||
loc = self.symtab.consts[self.scanner.token]
|
||||
self.scanner.scan()
|
||||
return loc
|
||||
else:
|
||||
|
@ -222,8 +219,8 @@ class Parser(object):
|
|||
|
||||
if self.scanner.consume('table'):
|
||||
size = self.defn_size()
|
||||
if size <= 0 or size > 256:
|
||||
self.syntax_error("Table size must be > 0 and <= 256")
|
||||
if size <= 0 or size > 65536:
|
||||
self.syntax_error("Table size must be > 0 and <= 65536")
|
||||
type_ = TableType(type_, size)
|
||||
|
||||
return type_
|
||||
|
@ -247,18 +244,15 @@ class Parser(object):
|
|||
type_ = VectorType(type_)
|
||||
elif self.scanner.consume('routine'):
|
||||
(inputs, outputs, trashes) = self.constraints()
|
||||
type_ = RoutineType(inputs=inputs, outputs=outputs, trashes=trashes)
|
||||
elif self.scanner.consume('buffer'):
|
||||
size = self.defn_size()
|
||||
type_ = BufferType(size)
|
||||
type_ = RoutineType(frozenset(inputs), frozenset(outputs), frozenset(trashes))
|
||||
elif self.scanner.consume('pointer'):
|
||||
type_ = PointerType()
|
||||
else:
|
||||
type_name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
if type_name not in self.context.typedefs:
|
||||
if type_name not in self.symtab.typedefs:
|
||||
self.syntax_error("Undefined type '%s'" % type_name)
|
||||
type_ = self.context.typedefs[type_name]
|
||||
type_ = self.symtab.typedefs[type_name]
|
||||
|
||||
return type_
|
||||
|
||||
|
@ -287,29 +281,18 @@ class Parser(object):
|
|||
def routine(self, name):
|
||||
type_ = self.defn_type()
|
||||
if not isinstance(type_, RoutineType):
|
||||
self.syntax_error("Can only define a routine, not %r" % type_)
|
||||
statics = []
|
||||
self.syntax_error("Can only define a routine, not {}".format(repr(type_)))
|
||||
locals_ = []
|
||||
if self.scanner.consume('@'):
|
||||
self.scanner.check_type('integer literal')
|
||||
block = None
|
||||
addr = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
else:
|
||||
statics = self.statics()
|
||||
|
||||
self.clear_statics()
|
||||
for defn in statics:
|
||||
self.declare(defn.name, SymEntry(defn, defn.location), static=True)
|
||||
locals_ = self.locals()
|
||||
block = self.block()
|
||||
self.clear_statics()
|
||||
|
||||
addr = None
|
||||
location = LocationRef(type_, name)
|
||||
return Routine(
|
||||
self.scanner.line_number,
|
||||
name=name, block=block, addr=addr,
|
||||
location=location, statics=statics
|
||||
)
|
||||
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_)
|
||||
|
||||
def labels(self):
|
||||
accum = []
|
||||
|
@ -333,16 +316,12 @@ class Parser(object):
|
|||
return accum
|
||||
|
||||
def locexpr(self):
|
||||
if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.context.consts or self.scanner.on_type('integer literal'):
|
||||
if self.scanner.token in ('on', 'off', 'word') or self.scanner.token in self.symtab.consts or self.scanner.on_type('integer literal'):
|
||||
return self.const()
|
||||
else:
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
loc = self.context.fetch(name)
|
||||
if loc:
|
||||
return loc
|
||||
else:
|
||||
return ForwardReference(name)
|
||||
return self.lookup(name, allow_forward=True, routine_name=self.current_routine_name)
|
||||
|
||||
def indlocexpr(self):
|
||||
if self.scanner.consume('['):
|
||||
|
@ -351,9 +330,6 @@ class Parser(object):
|
|||
self.scanner.expect('+')
|
||||
self.scanner.expect('y')
|
||||
return IndirectRef(loc)
|
||||
elif self.scanner.consume('^'):
|
||||
loc = self.locexpr()
|
||||
return AddressRef(loc)
|
||||
else:
|
||||
return self.indexed_locexpr()
|
||||
|
||||
|
@ -361,17 +337,28 @@ class Parser(object):
|
|||
loc = self.locexpr()
|
||||
if not isinstance(loc, str):
|
||||
index = None
|
||||
offset = ConstantRef(TYPE_BYTE, 0)
|
||||
if self.scanner.consume('+'):
|
||||
if self.scanner.token in self.symtab.consts or self.scanner.on_type('integer literal'):
|
||||
offset = self.const()
|
||||
self.scanner.expect('+')
|
||||
index = self.locexpr()
|
||||
loc = IndexedRef(loc, index)
|
||||
loc = IndexedRef(loc, offset, index)
|
||||
return loc
|
||||
|
||||
def statics(self):
|
||||
def locals(self):
|
||||
defns = []
|
||||
while self.scanner.consume('static'):
|
||||
defn = self.defn()
|
||||
type_, defn = self.defn()
|
||||
if defn.initial is None:
|
||||
self.syntax_error("Static definition {} must have initial value".format(defn))
|
||||
self.declare_local(self.current_routine_name, defn.name, defn, type_)
|
||||
defns.append(defn)
|
||||
while self.scanner.consume('local'):
|
||||
type_, defn = self.defn()
|
||||
if defn.initial is not None:
|
||||
self.syntax_error("Local definition {} may not have initial value".format(defn))
|
||||
self.declare_local(self.current_routine_name, defn.name, defn, type_)
|
||||
defns.append(defn)
|
||||
return defns
|
||||
|
||||
|
@ -380,7 +367,7 @@ class Parser(object):
|
|||
self.scanner.expect('{')
|
||||
while not self.scanner.on('}'):
|
||||
instrs.append(self.instr())
|
||||
if isinstance(instrs[-1], SingleOp) and instrs[-1].opcode == 'goto':
|
||||
if isinstance(instrs[-1], GoTo):
|
||||
break
|
||||
self.scanner.expect('}')
|
||||
return Block(self.scanner.line_number, instrs=instrs)
|
||||
|
@ -419,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
|
||||
|
@ -450,12 +441,15 @@ class Parser(object):
|
|||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
return SingleOp(self.scanner.line_number, opcode=opcode, dest=None, src=None)
|
||||
elif self.scanner.token in ("call", "goto"):
|
||||
opcode = self.scanner.token
|
||||
self.scanner.scan()
|
||||
elif self.scanner.consume("call"):
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
instr = SingleOp(self.scanner.line_number, opcode=opcode, location=ForwardReference(name), dest=None, src=None)
|
||||
instr = Call(self.scanner.line_number, location=ForwardReference(name))
|
||||
return instr
|
||||
elif self.scanner.consume("goto"):
|
||||
name = self.scanner.token
|
||||
self.scanner.scan()
|
||||
instr = GoTo(self.scanner.line_number, location=ForwardReference(name))
|
||||
return instr
|
||||
elif self.scanner.token in ("copy",):
|
||||
opcode = self.scanner.token
|
||||
|
@ -474,6 +468,12 @@ class Parser(object):
|
|||
locations = self.locexprs()
|
||||
block = self.block()
|
||||
return Save(self.scanner.line_number, locations=locations, block=block)
|
||||
elif self.scanner.consume("point"):
|
||||
pointer = self.locexpr()
|
||||
self.scanner.expect("into")
|
||||
table = self.locexpr()
|
||||
block = self.block()
|
||||
return PointInto(self.scanner.line_number, pointer=pointer, table=table, block=block)
|
||||
elif self.scanner.consume("trash"):
|
||||
dest = self.locexpr()
|
||||
return SingleOp(self.scanner.line_number, opcode='trash', src=None, dest=dest)
|
||||
|
@ -484,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.
|
||||
|
||||
|
@ -385,6 +416,68 @@ Some instructions on tables. (3/3)
|
|||
= $081B DEC $081F,X
|
||||
= $081E RTS
|
||||
|
||||
Using a constant offset, you can read and write entries in
|
||||
the table beyond the 256th.
|
||||
|
||||
| byte one
|
||||
| byte table[1024] many
|
||||
|
|
||||
| define main routine
|
||||
| inputs many
|
||||
| outputs many
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld a, many + x
|
||||
| st a, many + x
|
||||
| ld a, many + 999 + x
|
||||
| st a, many + 1000 + x
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA $081D,X
|
||||
= $0812 STA $081D,X
|
||||
= $0815 LDA $0C04,X
|
||||
= $0818 STA $0C05,X
|
||||
= $081B RTS
|
||||
|
||||
Instructions on tables, with constant offsets.
|
||||
|
||||
| byte table[256] many
|
||||
|
|
||||
| define main routine
|
||||
| inputs many
|
||||
| outputs many
|
||||
| trashes a, x, c, n, z, v
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld a, 0
|
||||
| st off, c
|
||||
| add a, many + 1 + x
|
||||
| sub a, many + 2 + x
|
||||
| cmp a, many + 3 + x
|
||||
| and a, many + 4 + x
|
||||
| or a, many + 5 + x
|
||||
| xor a, many + 6 + x
|
||||
| shl many + 7 + x
|
||||
| shr many + 8 + x
|
||||
| inc many + 9 + x
|
||||
| dec many + 10 + x
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA #$00
|
||||
= $0811 CLC
|
||||
= $0812 ADC $0832,X
|
||||
= $0815 SBC $0833,X
|
||||
= $0818 CMP $0834,X
|
||||
= $081B AND $0835,X
|
||||
= $081E ORA $0836,X
|
||||
= $0821 EOR $0837,X
|
||||
= $0824 ROL $0838,X
|
||||
= $0827 ROR $0839,X
|
||||
= $082A INC $083A,X
|
||||
= $082D DEC $083B,X
|
||||
= $0830 RTS
|
||||
|
||||
Compiling 16-bit `cmp`.
|
||||
|
||||
| word za @ 60001
|
||||
|
@ -551,7 +644,6 @@ Compiling `repeat forever`.
|
|||
= $080D LDY #$41
|
||||
= $080F INY
|
||||
= $0810 JMP $080F
|
||||
= $0813 RTS
|
||||
|
||||
The body of `repeat forever` can be empty.
|
||||
|
||||
|
@ -561,7 +653,6 @@ The body of `repeat forever` can be empty.
|
|||
| } forever
|
||||
| }
|
||||
= $080D JMP $080D
|
||||
= $0810 RTS
|
||||
|
||||
Compiling `for ... up to`.
|
||||
|
||||
|
@ -876,6 +967,42 @@ Copy routine (by forward reference) to vector.
|
|||
= $0818 INX
|
||||
= $0819 RTS
|
||||
|
||||
Copy byte to byte table and back, with both `x` and `y` as indexes,
|
||||
plus constant offsets.
|
||||
|
||||
| byte one
|
||||
| byte table[256] many
|
||||
|
|
||||
| define main routine
|
||||
| inputs one, many
|
||||
| outputs one, many
|
||||
| trashes a, x, y, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld y, 0
|
||||
| ld a, 77
|
||||
| st a, many + x
|
||||
| st a, many + y
|
||||
| st a, many + 1 + x
|
||||
| st a, many + 1 + y
|
||||
| ld a, many + x
|
||||
| ld a, many + y
|
||||
| ld a, many + 8 + x
|
||||
| ld a, many + 8 + y
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDY #$00
|
||||
= $0811 LDA #$4D
|
||||
= $0813 STA $082D,X
|
||||
= $0816 STA $082D,Y
|
||||
= $0819 STA $082E,X
|
||||
= $081C STA $082E,Y
|
||||
= $081F LDA $082D,X
|
||||
= $0822 LDA $082D,Y
|
||||
= $0825 LDA $0835,X
|
||||
= $0828 LDA $0835,Y
|
||||
= $082B RTS
|
||||
|
||||
Copy word to word table and back, with both `x` and `y` as indexes.
|
||||
|
||||
| word one
|
||||
|
@ -918,7 +1045,49 @@ Copy word to word table and back, with both `x` and `y` as indexes.
|
|||
= $0848 STA $084D
|
||||
= $084B RTS
|
||||
|
||||
Indirect call.
|
||||
Copy word to word table and back, with constant offsets.
|
||||
|
||||
| word one
|
||||
| word table[256] many
|
||||
|
|
||||
| define main routine
|
||||
| inputs one, many
|
||||
| outputs one, many
|
||||
| trashes a, x, y, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| ld y, 0
|
||||
| copy 777, one
|
||||
| copy one, many + 1 + x
|
||||
| copy one, many + 2 + y
|
||||
| copy many + 3 + x, one
|
||||
| copy many + 4 + y, one
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDY #$00
|
||||
= $0811 LDA #$09
|
||||
= $0813 STA $084C
|
||||
= $0816 LDA #$03
|
||||
= $0818 STA $084D
|
||||
= $081B LDA $084C
|
||||
= $081E STA $084F,X
|
||||
= $0821 LDA $084D
|
||||
= $0824 STA $094F,X
|
||||
= $0827 LDA $084C
|
||||
= $082A STA $0850,Y
|
||||
= $082D LDA $084D
|
||||
= $0830 STA $0950,Y
|
||||
= $0833 LDA $0851,X
|
||||
= $0836 STA $084C
|
||||
= $0839 LDA $0951,X
|
||||
= $083C STA $084D
|
||||
= $083F LDA $0852,Y
|
||||
= $0842 STA $084C
|
||||
= $0845 LDA $0952,Y
|
||||
= $0848 STA $084D
|
||||
= $084B RTS
|
||||
|
||||
Indirect call. TODO: we don't need the final RTS here, omit it.
|
||||
|
||||
| vector routine
|
||||
| outputs x
|
||||
|
@ -939,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`.
|
||||
|
||||
|
@ -1002,28 +1170,77 @@ 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
|
||||
= $0816 STA $0846
|
||||
= $0819 LDA #$3E
|
||||
= $081B STA $0847,X
|
||||
= $081E LDA #$08
|
||||
= $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.
|
||||
|
||||
| vector routine
|
||||
| outputs x
|
||||
| trashes a, z, n
|
||||
| one
|
||||
| vector routine
|
||||
| outputs x
|
||||
| trashes a, z, n
|
||||
| table[256] many
|
||||
|
|
||||
| define bar routine outputs x trashes a, z, n {
|
||||
| ld x, 200
|
||||
| }
|
||||
|
|
||||
| define main routine
|
||||
| inputs one, many
|
||||
| outputs one, many
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| copy bar, one
|
||||
| copy bar, many + 1 + x
|
||||
| copy one, many + 2 + x
|
||||
| copy many + 3 + x, one
|
||||
| call one
|
||||
| }
|
||||
= $080D LDX #$00
|
||||
= $080F LDA #$3E
|
||||
= $0811 STA $0845
|
||||
= $0814 LDA #$08
|
||||
= $0816 STA $0846
|
||||
= $0819 LDA #$3E
|
||||
= $081B STA $0848,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
|
||||
= $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
|
||||
|
||||
|
@ -1207,20 +1424,39 @@ Subtracting a word memory location from another word memory location.
|
|||
= $081D STA $0822
|
||||
= $0820 RTS
|
||||
|
||||
### Buffers and Pointers
|
||||
### Tables and Pointers
|
||||
|
||||
Load address into pointer.
|
||||
Associate pointer with table. Does nothing by itself.
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
|
|
||||
| define main routine
|
||||
| inputs buf
|
||||
| outputs buf, y
|
||||
| inputs tab
|
||||
| outputs tab, y
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| point ptr into tab {
|
||||
| }
|
||||
| }
|
||||
= $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
|
||||
|
@ -1231,17 +1467,19 @@ Load address into pointer.
|
|||
|
||||
Write literal through a pointer.
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
|
|
||||
| define main routine
|
||||
| inputs buf
|
||||
| outputs buf, y
|
||||
| inputs tab
|
||||
| outputs tab, y
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| copy 123, [ptr] + y
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$1C
|
||||
|
@ -1254,43 +1492,47 @@ Write literal through a pointer.
|
|||
|
||||
Write stored value through a pointer.
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
|
|
||||
| define main routine
|
||||
| inputs foo, buf
|
||||
| outputs y, buf
|
||||
| inputs foo, tab
|
||||
| outputs y, tab
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| copy foo, [ptr] + y
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy foo, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$1D
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$08
|
||||
= $0815 STA $FF
|
||||
= $0817 LDA $101D
|
||||
= $0817 LDA $091D
|
||||
= $081A STA ($FE),Y
|
||||
= $081C RTS
|
||||
|
||||
Read through a pointer, into a byte storage location, or the `a` register.
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
|
|
||||
| define main routine
|
||||
| inputs buf
|
||||
| inputs tab
|
||||
| outputs y, foo
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| copy [ptr] + y, foo
|
||||
| ld a, [ptr] + y
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy [ptr] + y, foo
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$1F
|
||||
|
@ -1298,25 +1540,62 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
|||
= $0813 LDA #$08
|
||||
= $0815 STA $FF
|
||||
= $0817 LDA ($FE),Y
|
||||
= $0819 STA $101F
|
||||
= $0819 STA $091F
|
||||
= $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.
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[256] tab
|
||||
| pointer ptra @ 252
|
||||
| pointer ptrb @ 254
|
||||
|
|
||||
| define main routine
|
||||
| inputs buf
|
||||
| outputs buf
|
||||
| inputs tab
|
||||
| outputs tab
|
||||
| trashes a, y, z, n, ptra, ptrb
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptra
|
||||
| copy ^buf, ptrb
|
||||
| copy [ptra] + y, [ptrb] + y
|
||||
| point ptra into tab {
|
||||
| reset ptra 0
|
||||
| point ptrb into tab {
|
||||
| reset ptrb 0
|
||||
| copy [ptra] + y, [ptrb] + y
|
||||
| }
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$24
|
||||
|
@ -1333,19 +1612,21 @@ Read and write through two pointers.
|
|||
|
||||
Write the `a` register through a pointer.
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
|
|
||||
| define main routine
|
||||
| inputs buf
|
||||
| outputs buf
|
||||
| inputs tab
|
||||
| outputs tab
|
||||
| trashes a, y, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| copy ^buf, ptr
|
||||
| ld a, 255
|
||||
| st a, [ptr] + y
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, 255
|
||||
| st a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$1C
|
||||
|
@ -1359,28 +1640,30 @@ Write the `a` register through a pointer.
|
|||
Add a word memory location, and a literal word, to a pointer, and then read through it.
|
||||
Note that this is *not* range-checked. (Yet.)
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
| word delta
|
||||
|
|
||||
| define main routine
|
||||
| inputs buf
|
||||
| inputs tab
|
||||
| outputs y, foo, delta
|
||||
| trashes a, c, v, z, n, ptr
|
||||
| {
|
||||
| copy 619, delta
|
||||
| ld y, 0
|
||||
| st off, c
|
||||
| copy ^buf, ptr
|
||||
| add ptr, delta
|
||||
| add ptr, word 1
|
||||
| copy [ptr] + y, foo
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| add ptr, delta
|
||||
| add ptr, word 1
|
||||
| copy [ptr] + y, foo
|
||||
| }
|
||||
| }
|
||||
= $080D LDA #$6B
|
||||
= $080F STA $1043
|
||||
= $080F STA $0943
|
||||
= $0812 LDA #$02
|
||||
= $0814 STA $1044
|
||||
= $0814 STA $0944
|
||||
= $0817 LDY #$00
|
||||
= $0819 CLC
|
||||
= $081A LDA #$42
|
||||
|
@ -1388,10 +1671,10 @@ Note that this is *not* range-checked. (Yet.)
|
|||
= $081E LDA #$08
|
||||
= $0820 STA $FF
|
||||
= $0822 LDA $FE
|
||||
= $0824 ADC $1043
|
||||
= $0824 ADC $0943
|
||||
= $0827 STA $FE
|
||||
= $0829 LDA $FF
|
||||
= $082B ADC $1044
|
||||
= $082B ADC $0944
|
||||
= $082E STA $FF
|
||||
= $0830 LDA $FE
|
||||
= $0832 ADC #$01
|
||||
|
@ -1400,7 +1683,7 @@ Note that this is *not* range-checked. (Yet.)
|
|||
= $0838 ADC #$00
|
||||
= $083A STA $FF
|
||||
= $083C LDA ($FE),Y
|
||||
= $083E STA $1042
|
||||
= $083E STA $0942
|
||||
= $0841 RTS
|
||||
|
||||
### Trash
|
||||
|
@ -1420,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
|
||||
|
@ -1443,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 {
|
||||
|
@ -163,6 +178,9 @@ Basic "open-faced for" loops, up and down.
|
|||
|
||||
Other blocks.
|
||||
|
||||
| byte table[256] tab
|
||||
| pointer ptr
|
||||
|
|
||||
| define main routine trashes a, x, c, z, v {
|
||||
| with interrupts off {
|
||||
| save a, x, c {
|
||||
|
@ -172,6 +190,10 @@ Other blocks.
|
|||
| save a, x, c {
|
||||
| ld a, 0
|
||||
| }
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
|
@ -180,7 +202,7 @@ User-defined memory addresses of different types.
|
|||
| byte byt
|
||||
| word wor
|
||||
| vector routine trashes a vec
|
||||
| buffer[2048] buf
|
||||
| byte table[2048] buf
|
||||
| pointer ptr
|
||||
|
|
||||
| define main routine {
|
||||
|
@ -192,6 +214,8 @@ Tables of different types and some operations on them.
|
|||
| byte table[256] many
|
||||
| word table[256] wmany
|
||||
| vector (routine trashes a) table[256] vmany
|
||||
| byte bval
|
||||
| word wval
|
||||
|
|
||||
| define main routine {
|
||||
| ld x, 0
|
||||
|
@ -207,11 +231,48 @@ Tables of different types and some operations on them.
|
|||
| shr many + x
|
||||
| inc many + x
|
||||
| dec many + x
|
||||
| ld a, many + x
|
||||
| st a, many + x
|
||||
| copy wval, wmany + x
|
||||
| copy wmany + x, wval
|
||||
| }
|
||||
= ok
|
||||
|
||||
Indexing with an offset in some tables.
|
||||
|
||||
| byte table[256] many
|
||||
| word table[256] wmany
|
||||
| byte bval
|
||||
| word wval
|
||||
|
|
||||
| define main routine {
|
||||
| ld x, 0
|
||||
| ld a, 0
|
||||
| st off, c
|
||||
| add a, many + 100 + x
|
||||
| sub a, many + 100 + x
|
||||
| cmp a, many + 100 + x
|
||||
| and a, many + 100 + x
|
||||
| or a, many + 100 + x
|
||||
| xor a, many + 100 + x
|
||||
| shl many + 100 + x
|
||||
| shr many + 100 + x
|
||||
| inc many + 100 + x
|
||||
| dec many + 100 + x
|
||||
| ld a, many + 100 + x
|
||||
| st a, many + 100 + x
|
||||
| copy wval, wmany + 100 + x
|
||||
| copy wmany + 100 + x, wval
|
||||
| }
|
||||
= ok
|
||||
|
||||
The number of entries in a table must be
|
||||
greater than 0 and less than or equal to 256.
|
||||
greater than 0 and less than or equal to 65536.
|
||||
|
||||
(In previous versions, a table could have at
|
||||
most 256 entries. They can now have more, however
|
||||
the offset-access syntax can only access the
|
||||
first 256. To access more, a pointer is required.)
|
||||
|
||||
| word table[512] many
|
||||
|
|
||||
|
@ -223,6 +284,30 @@ greater than 0 and less than or equal to 256.
|
|||
| ld x, 0
|
||||
| copy 9999, many + x
|
||||
| }
|
||||
= ok
|
||||
|
||||
| byte table[65536] many
|
||||
|
|
||||
| define main routine
|
||||
| inputs many
|
||||
| outputs many
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| copy 99, many + x
|
||||
| }
|
||||
= ok
|
||||
|
||||
| byte table[65537] many
|
||||
|
|
||||
| define main routine
|
||||
| inputs many
|
||||
| outputs many
|
||||
| trashes a, x, n, z
|
||||
| {
|
||||
| ld x, 0
|
||||
| copy 99, many + x
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
| word table[0] many
|
||||
|
@ -285,6 +370,19 @@ Constants.
|
|||
| }
|
||||
= ok
|
||||
|
||||
Named constants can be used as offsets.
|
||||
|
||||
| const lives 3
|
||||
| const w1 1000
|
||||
|
|
||||
| byte table[w1] those
|
||||
|
|
||||
| define main routine {
|
||||
| ld y, 0
|
||||
| ld a, those + lives + y
|
||||
| }
|
||||
= ok
|
||||
|
||||
Can't have two constants with the same name.
|
||||
|
||||
| const w1 1000
|
||||
|
@ -590,18 +688,20 @@ But you can't `goto` a label that never gets defined.
|
|||
| }
|
||||
? Expected '}', but found 'ld'
|
||||
|
||||
Buffers and pointers.
|
||||
Tables and pointers.
|
||||
|
||||
| buffer[2048] buf
|
||||
| byte table[2048] buf
|
||||
| pointer ptr
|
||||
| pointer ptrb
|
||||
| byte foo
|
||||
|
|
||||
| define main routine {
|
||||
| copy ^buf, ptr
|
||||
| copy 123, [ptr] + y
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, [ptrb] + y
|
||||
| point ptr into buf {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, [ptrb] + y
|
||||
| }
|
||||
| }
|
||||
= ok
|
||||
|
||||
|
@ -663,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
|
||||
|
@ -679,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
|
||||
|
@ -699,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
|
||||
|
|
||||
|
@ -707,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
|
||||
|
@ -719,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