mirror of
https://github.com/catseye/SixtyPical.git
synced 2024-11-29 03:51:35 +00:00
commit
8467cd947e
24
HISTORY.md
24
HISTORY.md
@ -1,6 +1,30 @@
|
||||
History of SixtyPical
|
||||
=====================
|
||||
|
||||
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
|
||||
----
|
||||
|
||||
|
4
LICENSE
4
LICENSE
@ -7,7 +7,7 @@ covered by the following BSD-compatible license, modelled after the
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Copyright (c)2014-2018 Chris Pressey, Cat's Eye Technologies.
|
||||
Copyright (c)2014-2019 Chris Pressey, Cat's Eye Technologies.
|
||||
|
||||
The authors intend this Report to belong to the entire SixtyPical
|
||||
community, and so we grant permission to copy and distribute it for
|
||||
@ -24,7 +24,7 @@ The source code for the reference implementation and supporting tools (in the
|
||||
|
||||
-----------------------------------------------------------------------------
|
||||
|
||||
Copyright (c)2014-2018, Chris Pressey, Cat's Eye Technologies.
|
||||
Copyright (c)2014-2019, Chris Pressey, Cat's Eye Technologies.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without
|
||||
|
@ -1,7 +1,7 @@
|
||||
SixtyPical
|
||||
==========
|
||||
|
||||
_Version 0.19. Work-in-progress, everything is subject to change._
|
||||
_Version 0.20. Work-in-progress, everything is subject to change._
|
||||
|
||||
**SixtyPical** is a [low-level](#low-level) programming language
|
||||
supporting a sophisticated [static analysis](#static-analysis).
|
||||
@ -109,7 +109,9 @@ In order to run the tests for compilation, [dcc6502][] needs to be installed.
|
||||
|
||||
* [SixtyPical specification](doc/SixtyPical.md)
|
||||
* [Literate test suite for SixtyPical syntax](tests/SixtyPical%20Syntax.md)
|
||||
* [Literate test suite for SixtyPical analysis](tests/SixtyPical%20Analysis.md)
|
||||
* [Literate test suite for SixtyPical analysis (operations)](tests/SixtyPical%20Analysis.md)
|
||||
* [Literate test suite for SixtyPical analysis (storage)](tests/SixtyPical%20Storage.md)
|
||||
* [Literate test suite for SixtyPical analysis (control flow)](tests/SixtyPical%20Control%20Flow.md)
|
||||
* [Literate test suite for SixtyPical compilation](tests/SixtyPical%20Compilation.md)
|
||||
* [Literate test suite for SixtyPical fallthru optimization](tests/SixtyPical%20Fallthru.md)
|
||||
|
||||
|
180
TODO.md
180
TODO.md
@ -1,6 +1,9 @@
|
||||
TODO for SixtyPical
|
||||
===================
|
||||
|
||||
Language
|
||||
--------
|
||||
|
||||
### Save values to other-than-the-stack
|
||||
|
||||
Allow
|
||||
@ -9,35 +12,111 @@ Allow
|
||||
...
|
||||
}
|
||||
|
||||
Which uses some other storage location instead of the stack. A local static
|
||||
would be a good candidate for such.
|
||||
Which uses some other storage location instead of the stack. A local non-static
|
||||
would be a good candidate for such. At any rate, the location must not
|
||||
be writeable by anything that is called from within the block. So, probably
|
||||
just restrict this to local non-statics.
|
||||
|
||||
### Analyze `call` within blocks?
|
||||
### Copy byte to/from table
|
||||
|
||||
What happens if you call another routine from inside a `with interrupts off` block?
|
||||
Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
|
||||
You have to `ld a`, `st a`. I think maybe we should have one.
|
||||
|
||||
What happens if you call another routine from inside a `save` block?
|
||||
### Character literals
|
||||
|
||||
What happens if you call another routine from inside a `point into` block?
|
||||
For goodness sake, let the programmer say `'A'` instead of `65`.
|
||||
|
||||
What happens if you call another routine from inside a `for` block?
|
||||
### Character set mapping
|
||||
|
||||
Remember that any of these may have a `goto` ... and they may have a second
|
||||
instance of the same block (e.g. `with interrupts off` nested within
|
||||
`with interrupts off` shouldn't be allowed to turn them back on after the
|
||||
inner block has finished -- even if there is no `call`.)
|
||||
Not all computers think `'A'` should be `65`. Allow the character set to be
|
||||
mapped. Probably copy what Ophis does.
|
||||
|
||||
These holes need to be plugged.
|
||||
### "Include" directives
|
||||
|
||||
### Reset pointer in `point into` blocks
|
||||
Search a searchlist of include paths. And use them to make libraries of routines.
|
||||
|
||||
We have `point into` blocks, but maybe the action when entering such a
|
||||
block shouldn't always be to set the given pointer to the start of the given table.
|
||||
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.
|
||||
|
||||
That is, sometimes we would like to start at some fixed offset. And
|
||||
sometimes we want to (re)set the pointer, without closing and starting a new block.
|
||||
### Pointers into non-byte tables
|
||||
|
||||
### Pointers associated globally with a table
|
||||
Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
|
||||
|
||||
Word and vector tables are stored as two byte tables in memory. This is useful for
|
||||
indexed access, but makes pointer access more difficult.
|
||||
|
||||
Laying them out for pointer access would make indexed access more difficult.
|
||||
|
||||
### Saving non-byte values
|
||||
|
||||
Right now you cannot save a word value.
|
||||
|
||||
There doesn't seem to be a hugely pressing reason why not.
|
||||
|
||||
Analysis
|
||||
--------
|
||||
|
||||
### Forbid recursion
|
||||
|
||||
What happens if a routine calls itself, directly or indirectly? Many
|
||||
constraints might be violated in this case. We should probably disallow
|
||||
recursion by default. (Which means assembling the callgraph in all cases.)
|
||||
|
||||
### Analyze memory usage
|
||||
|
||||
If you define two variables that occupy the same address, an analysis error ought
|
||||
to be raised. (But there should also be a way to annotate this as intentional.
|
||||
Intentionally making two tables overlap could be valuable. However, the analysis
|
||||
will probably completely miss this fact.)
|
||||
|
||||
Optimization
|
||||
------------
|
||||
|
||||
### Space optimization of local non-statics
|
||||
|
||||
If there are two routines A and B, and A never calls B (even indirectly), and
|
||||
B never calls A (even indirectly), then their non-static locals can
|
||||
be allocated at the same space.
|
||||
|
||||
This is not just an impressive trick -- in the presence of local pointers, which
|
||||
use up a word in zero-page, which we consider a precious resource, it allow those
|
||||
zero-page locations to be re-used.
|
||||
|
||||
### Tail-call optimization
|
||||
|
||||
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
|
||||
if the block is in tail position. The constraints should iron out the same both ways.
|
||||
|
||||
As long as the routine has consistent type context every place it exits, that should be fine.
|
||||
|
||||
### Branch optimization in `if`
|
||||
|
||||
Currently the `if` generator is not smart enough to avoid generating silly
|
||||
jump instructions. (See the Fallthru tests.) Improve it.
|
||||
|
||||
### Dead code removal
|
||||
|
||||
Once we have a call graph we can omit routines that we're sure aren't called.
|
||||
|
||||
This would let us use include-files and standard-libraries nicely: any
|
||||
routines they define, but that you don't use, don't get included.
|
||||
|
||||
Analyzing the set of possible routines that a vector can take on would help
|
||||
this immensely.
|
||||
|
||||
Implementation
|
||||
--------------
|
||||
|
||||
### Line numbers in analysis error messages
|
||||
|
||||
For analysis errors, there is a line number, but it's the line of the routine
|
||||
after the routine in which the analysis error occurred. Fix this.
|
||||
|
||||
Blue-skying
|
||||
-----------
|
||||
|
||||
### Pointers associated globally with a table(?)
|
||||
|
||||
We have `point into` blocks, but we would also like to sometimes pass a pointer
|
||||
around to different routines, and have them all "know" what table it operates on.
|
||||
@ -53,66 +132,5 @@ at different times.
|
||||
|
||||
These can co-exist with general, non-specific-table-linked `pointer` variables.
|
||||
|
||||
### Local non-statics
|
||||
|
||||
Somewhat related to the above, it should be possible to declare a local storage
|
||||
location which is not static.
|
||||
|
||||
In this case, it would be considered uninitialized each time the routine was
|
||||
entered.
|
||||
|
||||
So, you do not have a guarantee that it has a valid value. But you are guaranteed
|
||||
that no other routine can read or modify it.
|
||||
|
||||
It also enables a trick: if there are two routines A and B, and A never calls B
|
||||
(even indirectly), and B never calls A (even indirectly), then their locals can
|
||||
be allocated at the same space.
|
||||
|
||||
A local could also be given an explicit address. In this case, two locals in
|
||||
different routines could be given the same address, and as long as the condition
|
||||
in the above paragraph holds, that's okay. (If it doesn't, the analyzer should
|
||||
detect it.)
|
||||
|
||||
This would permit local pointers, which would be one way of addressing the
|
||||
"same pointer to different tables" problem.
|
||||
|
||||
### Copy byte to/from table
|
||||
|
||||
Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
|
||||
You have to `ld a`, `st a`. I think maybe we should have one.
|
||||
|
||||
### Analyze memory usage
|
||||
|
||||
If you define two variables that occupy the same address, an analysis error ought
|
||||
to be raised. (But there should also be a way to annotate this as intentional.
|
||||
Intentionally making two tables overlap could be valuable. However, the analysis
|
||||
will probably completely miss this fact.)
|
||||
|
||||
### Character literals
|
||||
|
||||
For goodness sake, let the programmer say `'A'` instead of `65`.
|
||||
|
||||
### Character set mapping
|
||||
|
||||
Not all computers think `'A'` should be `65`. Allow the character set to be
|
||||
mapped. Probably copy what Ophis does.
|
||||
|
||||
### Tail-call optimization
|
||||
|
||||
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
|
||||
if the block is in tail position. The constraints should iron out the same both ways.
|
||||
|
||||
As long as the routine has consistent type context every place it exits, that should be fine.
|
||||
|
||||
### "Include" directives
|
||||
|
||||
Search a searchlist of include paths. And use them to make libraries of routines.
|
||||
|
||||
One such library routine might be an `interrupt routine` type for various architectures.
|
||||
Since "the supervisor" has stored values on the stack, we should be able to trash them
|
||||
with impunity, in such a routine.
|
||||
|
||||
### Line numbers in analysis error messages
|
||||
|
||||
For analysis errors, there is a line number, but it's the line of the routine
|
||||
after the routine in which the analysis error occurred. Fix this.
|
||||
If we have local pointers and space optimization for local non-statics, though,
|
||||
these don't add as much.
|
||||
|
@ -16,7 +16,8 @@ import sys
|
||||
from tempfile import NamedTemporaryFile
|
||||
import traceback
|
||||
|
||||
from sixtypical.parser import Parser, SymbolTable, merge_programs
|
||||
from sixtypical.symtab import SymbolTable
|
||||
from sixtypical.parser import Parser, merge_programs
|
||||
from sixtypical.analyzer import Analyzer
|
||||
from sixtypical.outputter import outputter_class_for
|
||||
from sixtypical.compiler import Compiler
|
||||
@ -184,7 +185,7 @@ if __name__ == '__main__':
|
||||
argparser.add_argument(
|
||||
"--version",
|
||||
action="version",
|
||||
version="%(prog)s 0.19"
|
||||
version="%(prog)s 0.20"
|
||||
)
|
||||
|
||||
options, unknown = argparser.parse_known_args(sys.argv[1:])
|
||||
|
@ -1,7 +1,7 @@
|
||||
SixtyPical
|
||||
==========
|
||||
|
||||
This document describes the SixtyPical programming language version 0.19,
|
||||
This document describes the SixtyPical programming language version 0.20,
|
||||
both its static semantics (the capabilities and limits of the static
|
||||
analyses it defines) and its runtime semantics (with reference to the
|
||||
semantics of 6502 machine code.)
|
||||
@ -196,7 +196,8 @@ table pointed to is implemented with "indirect indexed" addressing, as in
|
||||
There are extended instruction modes for using these types of memory location.
|
||||
See `copy` below, but here is some illustrative example code:
|
||||
|
||||
point ptr into buf { // this is the only way to initialize a pointer
|
||||
point ptr into buf { // this associates this pointer with this table
|
||||
reset ptr 0 // this is the only way to initialize a pointer
|
||||
add ptr, 4 // note, this is unchecked against table's size!
|
||||
ld y, 0 // you must set this to something yourself
|
||||
copy [ptr] + y, byt // read memory through pointer, into byte
|
||||
@ -658,4 +659,6 @@ Grammar
|
||||
| "repeat" Block ("until" ["not"] LocExpr | "forever")
|
||||
| "for" LocExpr ("up"|"down") "to" Const Block
|
||||
| "with" "interrupts" LitBit Block
|
||||
| "point" LocExpr "into" LocExpr Block
|
||||
| "reset" LocExpr Const
|
||||
.
|
||||
|
@ -265,37 +265,37 @@ define player_logic logic_routine
|
||||
|
||||
if c {
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
// check collision.
|
||||
ld a, [ptr] + y
|
||||
}
|
||||
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 81
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
point ptr into screen {
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 81
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
}
|
||||
|
||||
copy new_pos, pos
|
||||
copy new_pos, pos
|
||||
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 81, [ptr] + y
|
||||
}
|
||||
} else {
|
||||
ld a, 1
|
||||
st a, player_died
|
||||
}
|
||||
} else {
|
||||
ld a, 1
|
||||
st a, player_died
|
||||
}
|
||||
}
|
||||
|
||||
@ -307,28 +307,29 @@ define enemy_logic logic_routine
|
||||
|
||||
if c {
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, new_pos
|
||||
ld y, 0
|
||||
|
||||
// check collision.
|
||||
ld a, [ptr] + y
|
||||
}
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 82
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
point ptr into screen {
|
||||
|
||||
// if "collision" is with your own self, treat it as if it's blank space!
|
||||
cmp a, 82
|
||||
if z {
|
||||
ld a, 32
|
||||
}
|
||||
cmp a, 32
|
||||
if z {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 32, [ptr] + y
|
||||
}
|
||||
|
||||
copy new_pos, pos
|
||||
copy new_pos, pos
|
||||
|
||||
point ptr into screen {
|
||||
reset ptr 0
|
||||
st off, c
|
||||
add ptr, pos
|
||||
copy 82, [ptr] + y
|
||||
|
@ -11,10 +11,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
|
||||
|
33
eg/rudiments/nested-for.60p
Normal file
33
eg/rudiments/nested-for.60p
Normal file
@ -0,0 +1,33 @@
|
||||
// Include `support/${PLATFORM}.60p` before this source
|
||||
// Should print H (being ASCII 72 = 8 * 9)
|
||||
|
||||
// Increase y by 7, circuitously
|
||||
//
|
||||
define foo routine
|
||||
inputs y
|
||||
outputs y, n, z
|
||||
trashes a, c
|
||||
{
|
||||
save x {
|
||||
ld x, 0
|
||||
for x up to 6 {
|
||||
inc y
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Each iteration increases y by 8; there are 9 iterations
|
||||
//
|
||||
define main routine
|
||||
outputs x, y, n, z
|
||||
trashes a, c
|
||||
{
|
||||
ld x, 0
|
||||
ld y, 0
|
||||
for x up to 8 {
|
||||
inc y
|
||||
call foo
|
||||
}
|
||||
ld a, y
|
||||
call chrout
|
||||
}
|
@ -1,8 +1,9 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.context import AnalysisContext
|
||||
from sixtypical.model import (
|
||||
TYPE_BYTE, TYPE_WORD,
|
||||
TableType, PointerType, VectorType, RoutineType,
|
||||
@ -80,316 +81,6 @@ class IncompatibleConstraintsError(ConstraintsError):
|
||||
pass
|
||||
|
||||
|
||||
class AnalysisContext(object):
|
||||
"""
|
||||
A location is touched if it was changed (or even potentially
|
||||
changed) during this routine, or some routine called by this routine.
|
||||
|
||||
A location is meaningful if it was an input to this routine,
|
||||
or if it was set to a meaningful value by some operation in this
|
||||
routine (or some routine called by this routine).
|
||||
|
||||
If a location is meaningful, it has a range. This range represents
|
||||
the lowest and highest values that it might possibly be (i.e. we know
|
||||
it cannot possibly be below the lowest or above the highest.) In the
|
||||
absence of any usage information, the range of a byte, is 0..255 and
|
||||
the range of a word is 0..65535.
|
||||
|
||||
A location is writeable if it was listed in the outputs and trashes
|
||||
lists of this routine. A location can also be temporarily marked
|
||||
unwriteable in certain contexts, such as `for` loops.
|
||||
"""
|
||||
def __init__(self, symtab, routine, inputs, outputs, trashes):
|
||||
self.symtab = symtab
|
||||
self.routine = routine # Routine (AST node)
|
||||
self._touched = set() # {LocationRef}
|
||||
self._range = dict() # LocationRef -> (Int, Int)
|
||||
self._writeable = set() # {LocationRef}
|
||||
self._terminated = False
|
||||
self._gotos_encountered = set()
|
||||
self._pointer_assoc = dict()
|
||||
|
||||
for ref in inputs:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
self._range[ref] = self.max_range(ref)
|
||||
output_names = set()
|
||||
for ref in outputs:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
output_names.add(ref.name)
|
||||
self._writeable.add(ref)
|
||||
for ref in trashes:
|
||||
if self.is_constant(ref):
|
||||
raise ConstantConstraintError(self.routine, ref.name)
|
||||
if ref.name in output_names:
|
||||
raise InconsistentConstraintsError(self.routine, ref.name)
|
||||
self._writeable.add(ref)
|
||||
|
||||
def __str__(self):
|
||||
return "{}(\n _touched={},\n _range={},\n _writeable={}\n)".format(
|
||||
self.__class__.__name__,
|
||||
LocationRef.format_set(self._touched), LocationRef.format_set(self._range), LocationRef.format_set(self._writeable)
|
||||
)
|
||||
|
||||
def to_json_data(self):
|
||||
type_ = self.symtab.fetch_global_type(self.routine.name)
|
||||
return {
|
||||
'routine_inputs': ','.join(sorted(loc.name for loc in type_.inputs)),
|
||||
'routine_outputs': ','.join(sorted(loc.name for loc in type_.outputs)),
|
||||
'routine_trashes': ','.join(sorted(loc.name for loc in type_.trashes)),
|
||||
'touched': ','.join(sorted(loc.name for loc in self._touched)),
|
||||
'range': dict((loc.name, '{}-{}'.format(rng[0], rng[1])) for (loc, rng) in self._range.items()),
|
||||
'writeable': ','.join(sorted(loc.name for loc in self._writeable)),
|
||||
'terminated': self._terminated,
|
||||
'gotos_encountered': ','.join(sorted(loc.name for loc in self._gotos_encountered)),
|
||||
}
|
||||
|
||||
def clone(self):
|
||||
c = AnalysisContext(self.symtab, self.routine, [], [], [])
|
||||
c._touched = set(self._touched)
|
||||
c._range = dict(self._range)
|
||||
c._writeable = set(self._writeable)
|
||||
c._pointer_assoc = dict(self._pointer_assoc)
|
||||
c._gotos_encountered = set(self._gotos_encountered)
|
||||
return c
|
||||
|
||||
def update_from(self, other):
|
||||
"""Replaces the information in this context, with the information from the other context.
|
||||
This is an overwriting action - it does not attempt to merge the contexts.
|
||||
|
||||
We do not replace the gotos_encountered for technical reasons. (In `analyze_if`,
|
||||
we merge those sets afterwards; at the end of `analyze_routine`, they are not distinct in the
|
||||
set of contexts we are updating from, and we want to retain our own.)"""
|
||||
self.routine = other.routine
|
||||
self._touched = set(other._touched)
|
||||
self._range = dict(other._range)
|
||||
self._writeable = set(other._writeable)
|
||||
self._terminated = other._terminated
|
||||
self._pointer_assoc = dict(other._pointer_assoc)
|
||||
|
||||
def each_meaningful(self):
|
||||
for ref in self._range.keys():
|
||||
yield ref
|
||||
|
||||
def each_touched(self):
|
||||
for ref in self._touched:
|
||||
yield ref
|
||||
|
||||
def each_writeable(self):
|
||||
for ref in self._writeable:
|
||||
yield ref
|
||||
|
||||
def assert_meaningful(self, *refs, **kwargs):
|
||||
exception_class = kwargs.get('exception_class', UnmeaningfulReadError)
|
||||
for ref in refs:
|
||||
# statics are always meaningful
|
||||
if self.symtab.has_static(self.routine.name, ref.name):
|
||||
continue
|
||||
if self.is_constant(ref):
|
||||
pass
|
||||
elif isinstance(ref, LocationRef):
|
||||
if ref not in self._range:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
elif isinstance(ref, IndexedRef):
|
||||
self.assert_meaningful(ref.ref, **kwargs)
|
||||
self.assert_meaningful(ref.index, **kwargs)
|
||||
else:
|
||||
raise NotImplementedError(ref)
|
||||
|
||||
def assert_writeable(self, *refs, **kwargs):
|
||||
exception_class = kwargs.get('exception_class', ForbiddenWriteError)
|
||||
for ref in refs:
|
||||
# statics are always writeable
|
||||
if self.symtab.has_static(self.routine.name, ref.name):
|
||||
continue
|
||||
if ref not in self._writeable:
|
||||
message = ref.name
|
||||
if kwargs.get('message'):
|
||||
message += ' (%s)' % kwargs['message']
|
||||
raise exception_class(self.routine, message)
|
||||
|
||||
def assert_in_range(self, inside, outside, offset):
|
||||
"""Given two locations, assert that the first location, offset by the given offset,
|
||||
is contained 'inside' the second location."""
|
||||
assert isinstance(inside, LocationRef)
|
||||
assert isinstance(outside, LocationRef)
|
||||
|
||||
# inside should always be meaningful
|
||||
inside_range = self._range[inside]
|
||||
|
||||
# outside might not be meaningful, so default to max range if necessary
|
||||
if outside in self._range:
|
||||
outside_range = self._range[outside]
|
||||
else:
|
||||
outside_range = self.max_range(outside)
|
||||
|
||||
if (inside_range[0] + offset.value) < outside_range[0] or (inside_range[1] + offset.value) > outside_range[1]:
|
||||
raise RangeExceededError(self.routine,
|
||||
"Possible range of {} {} (+{}) exceeds acceptable range of {} {}".format(
|
||||
inside, inside_range, offset, outside, outside_range
|
||||
)
|
||||
)
|
||||
|
||||
def set_touched(self, *refs):
|
||||
for ref in refs:
|
||||
self._touched.add(ref)
|
||||
# TODO: it might be possible to invalidate the range here
|
||||
|
||||
def set_meaningful(self, *refs):
|
||||
for ref in refs:
|
||||
if ref not in self._range:
|
||||
self._range[ref] = self.max_range(ref)
|
||||
|
||||
def set_top_of_range(self, ref, top):
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_bottom_of_range(self, ref, bottom):
|
||||
self.assert_meaningful(ref)
|
||||
(top, _) = self._range[ref]
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def set_range(self, ref, bottom, top):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = (bottom, top)
|
||||
|
||||
def get_top_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
self.assert_meaningful(ref)
|
||||
(_, top) = self._range[ref]
|
||||
return top
|
||||
|
||||
def get_bottom_of_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.value
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, _) = self._range[ref]
|
||||
return bottom
|
||||
|
||||
def get_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
self.assert_meaningful(ref)
|
||||
(bottom, top) = self._range[ref]
|
||||
return bottom, top
|
||||
|
||||
def copy_range(self, src, dest):
|
||||
self.assert_meaningful(src)
|
||||
if src in self._range:
|
||||
src_range = self._range[src]
|
||||
else:
|
||||
src_range = self.max_range(src)
|
||||
self._range[dest] = src_range
|
||||
|
||||
def invalidate_range(self, ref):
|
||||
self.assert_meaningful(ref)
|
||||
self._range[ref] = self.max_range(ref)
|
||||
|
||||
def set_unmeaningful(self, *refs):
|
||||
for ref in refs:
|
||||
if ref in self._range:
|
||||
del self._range[ref]
|
||||
|
||||
def set_written(self, *refs):
|
||||
"""A "helper" method which does the following common sequence for
|
||||
the given refs: asserts they're all writable, and sets them all
|
||||
as touched and meaningful."""
|
||||
self.assert_writeable(*refs)
|
||||
self.set_touched(*refs)
|
||||
self.set_meaningful(*refs)
|
||||
|
||||
def set_unwriteable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`."""
|
||||
for ref in refs:
|
||||
self._writeable.remove(ref)
|
||||
|
||||
def set_writeable(self, *refs):
|
||||
"""Intended to be used for implementing analyzing `for`, but also used in `save`."""
|
||||
for ref in refs:
|
||||
self._writeable.add(ref)
|
||||
|
||||
def encounter_gotos(self, gotos):
|
||||
self._gotos_encountered |= gotos
|
||||
|
||||
def encountered_gotos(self):
|
||||
return self._gotos_encountered
|
||||
|
||||
def set_terminated(self):
|
||||
# Having a terminated context and having encountered gotos is not the same thing.
|
||||
self._terminated = True
|
||||
|
||||
def has_terminated(self):
|
||||
return self._terminated
|
||||
|
||||
def extract(self, location):
|
||||
"""Sets the given location as writeable in the context, and returns a 'baton' representing
|
||||
the previous state of context for that location. This 'baton' can be used to later restore
|
||||
this state of context."""
|
||||
# Used in `save`.
|
||||
baton = (
|
||||
location,
|
||||
location in self._touched,
|
||||
self._range.get(location, None),
|
||||
location in self._writeable,
|
||||
)
|
||||
self.set_writeable(location)
|
||||
return baton
|
||||
|
||||
def re_introduce(self, baton):
|
||||
"""Given a 'baton' produced by `extract()`, restores the context for that saved location
|
||||
to what it was before `extract()` was called."""
|
||||
# Used in `save`.
|
||||
location, was_touched, was_range, was_writeable = baton
|
||||
|
||||
if was_touched:
|
||||
self._touched.add(location)
|
||||
elif location in self._touched:
|
||||
self._touched.remove(location)
|
||||
|
||||
if was_range is not None:
|
||||
self._range[location] = was_range
|
||||
elif location in self._range:
|
||||
del self._range[location]
|
||||
|
||||
if was_writeable:
|
||||
self._writeable.add(location)
|
||||
elif location in self._writeable:
|
||||
self._writeable.remove(location)
|
||||
|
||||
def get_assoc(self, pointer):
|
||||
return self._pointer_assoc.get(pointer)
|
||||
|
||||
def set_assoc(self, pointer, table):
|
||||
self._pointer_assoc[pointer] = table
|
||||
|
||||
def is_constant(self, ref):
|
||||
"""read-only means that the program cannot change the value
|
||||
of a location. constant means that the value of the location
|
||||
will not change during the lifetime of the program."""
|
||||
if isinstance(ref, ConstantRef):
|
||||
return True
|
||||
if isinstance(ref, (IndirectRef, IndexedRef)):
|
||||
return False
|
||||
if isinstance(ref, LocationRef):
|
||||
type_ = self.symtab.fetch_global_type(ref.name)
|
||||
return isinstance(type_, RoutineType)
|
||||
raise NotImplementedError
|
||||
|
||||
def max_range(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return (ref.value, ref.value)
|
||||
elif self.symtab.has_static(self.routine.name, ref.name):
|
||||
return self.symtab.fetch_static_type(self.routine.name, ref.name).max_range
|
||||
else:
|
||||
return self.symtab.fetch_global_type(ref.name).max_range
|
||||
|
||||
|
||||
class Analyzer(object):
|
||||
|
||||
def __init__(self, symtab, debug=False):
|
||||
@ -401,15 +92,15 @@ class Analyzer(object):
|
||||
# - - - - helper methods - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
|
||||
return self.symtab.fetch_static_type(self.current_routine.name, name)
|
||||
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
|
||||
return self.symtab.fetch_local_type(self.current_routine.name, name)
|
||||
return self.symtab.fetch_global_type(name)
|
||||
|
||||
def get_type(self, ref):
|
||||
if isinstance(ref, ConstantRef):
|
||||
return ref.type
|
||||
if not isinstance(ref, LocationRef):
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError(str(ref))
|
||||
return self.get_type_for_name(ref.name)
|
||||
|
||||
def assert_type(self, type_, *locations):
|
||||
@ -462,6 +153,14 @@ class Analyzer(object):
|
||||
self.current_routine = routine
|
||||
type_ = self.get_type_for_name(routine.name)
|
||||
context = AnalysisContext(self.symtab, routine, type_.inputs, type_.outputs, type_.trashes)
|
||||
|
||||
# register any local statics as already-initialized
|
||||
for local_name, local_symentry in self.symtab.locals.get(routine.name, {}).items():
|
||||
ref = self.symtab.fetch_local_ref(routine.name, local_name)
|
||||
if local_symentry.ast_node.initial is not None:
|
||||
context.set_meaningful(ref)
|
||||
context.set_range(ref, local_symentry.ast_node.initial, local_symentry.ast_node.initial)
|
||||
|
||||
self.exit_contexts = []
|
||||
|
||||
self.analyze_block(routine.block, context)
|
||||
@ -505,7 +204,7 @@ class Analyzer(object):
|
||||
|
||||
# if something was touched, then it should have been declared to be writable.
|
||||
for ref in context.each_touched():
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_static(routine.name, ref.name):
|
||||
if ref not in type_.outputs and ref not in type_.trashes and not self.symtab.has_local(routine.name, ref.name):
|
||||
raise ForbiddenWriteError(routine, ref.name)
|
||||
|
||||
self.exit_contexts = None
|
||||
@ -531,15 +230,15 @@ class Analyzer(object):
|
||||
elif isinstance(instr, For):
|
||||
self.analyze_for(instr, context)
|
||||
elif isinstance(instr, WithInterruptsOff):
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
raise IllegalJumpError(instr, instr)
|
||||
self.analyze_with_interrupts_off(instr, context)
|
||||
elif isinstance(instr, Save):
|
||||
self.analyze_save(instr, context)
|
||||
elif isinstance(instr, PointInto):
|
||||
self.analyze_point_into(instr, context)
|
||||
elif isinstance(instr, Reset):
|
||||
self.analyze_reset(instr, context)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
raise NotImplementedError(str(instr))
|
||||
|
||||
def analyze_single_op(self, instr, context):
|
||||
|
||||
@ -589,7 +288,6 @@ class Analyzer(object):
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
elif self.get_type(src) != self.get_type(dest):
|
||||
@ -759,12 +457,11 @@ class Analyzer(object):
|
||||
# 2. check that the context is meaningful
|
||||
|
||||
if isinstance(src, (LocationRef, ConstantRef)) and isinstance(dest, IndirectRef):
|
||||
context.assert_meaningful(src, REG_Y)
|
||||
context.assert_meaningful(src, dest.ref, REG_Y)
|
||||
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
|
||||
@ -775,10 +472,10 @@ class Analyzer(object):
|
||||
raise UnmeaningfulReadError(instr, src.ref)
|
||||
context.assert_meaningful(origin)
|
||||
|
||||
context.set_touched(dest)
|
||||
context.set_written(dest)
|
||||
|
||||
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
|
||||
context.assert_meaningful(src.ref, REG_Y)
|
||||
context.assert_meaningful(src.ref, dest.ref, REG_Y)
|
||||
|
||||
origin = context.get_assoc(src.ref)
|
||||
if not origin:
|
||||
@ -788,7 +485,6 @@ class Analyzer(object):
|
||||
target = context.get_assoc(dest.ref)
|
||||
if not target:
|
||||
raise ForbiddenWriteError(instr, dest.ref)
|
||||
context.set_touched(target)
|
||||
context.set_written(target)
|
||||
|
||||
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
|
||||
@ -799,7 +495,6 @@ class Analyzer(object):
|
||||
context.set_written(dest.ref)
|
||||
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
|
||||
context.assert_meaningful(src.ref, src.index)
|
||||
context.set_touched(dest)
|
||||
context.set_written(dest)
|
||||
else:
|
||||
context.assert_meaningful(src)
|
||||
@ -865,7 +560,6 @@ class Analyzer(object):
|
||||
exit_context = context.clone()
|
||||
|
||||
for ref in type_.outputs:
|
||||
exit_context.set_touched(ref) # ?
|
||||
exit_context.set_written(ref)
|
||||
|
||||
for ref in type_.trashes:
|
||||
@ -973,6 +667,13 @@ class Analyzer(object):
|
||||
context.set_range(instr.dest, instr.final, instr.final)
|
||||
context.set_writeable(instr.dest)
|
||||
|
||||
def analyze_with_interrupts_off(self, instr, context):
|
||||
block = instr.block
|
||||
for instr in block.instrs:
|
||||
if isinstance(instr, (Call, GoTo, WithInterruptsOff)):
|
||||
raise IllegalJumpError(instr, instr)
|
||||
self.analyze_instr(instr, context)
|
||||
|
||||
def analyze_save(self, instr, context):
|
||||
batons = []
|
||||
for location in instr.locations:
|
||||
@ -1007,11 +708,11 @@ class Analyzer(object):
|
||||
if context.get_assoc(instr.pointer):
|
||||
raise ForbiddenWriteError(instr, instr.pointer)
|
||||
|
||||
# associate pointer with table, mark it as meaningful.
|
||||
# associate pointer with table
|
||||
# (do not mark it as meaningful yet - that's reset's job.)
|
||||
|
||||
context.set_assoc(instr.pointer, instr.table)
|
||||
context.set_meaningful(instr.pointer)
|
||||
context.set_touched(instr.pointer)
|
||||
context.set_unmeaningful(instr.pointer)
|
||||
|
||||
self.analyze_block(instr.block, context)
|
||||
if context.encountered_gotos():
|
||||
@ -1021,3 +722,21 @@ class Analyzer(object):
|
||||
|
||||
context.set_assoc(instr.pointer, None)
|
||||
context.set_unmeaningful(instr.pointer)
|
||||
|
||||
def analyze_reset(self, instr, context):
|
||||
type = self.get_type(instr.pointer)
|
||||
if not isinstance(type, (PointerType)):
|
||||
raise TypeMismatchError(instr, instr.pointer.name)
|
||||
|
||||
table = context.get_assoc(instr.pointer)
|
||||
if not table:
|
||||
raise ForbiddenWriteError(instr, '{} is not associated with any table'.format(instr.pointer.name))
|
||||
context.assert_meaningful(table)
|
||||
low_limit, high_limit = context.get_range(table)
|
||||
|
||||
assert isinstance(instr.offset, ConstantRef)
|
||||
if instr.offset.value < low_limit or instr.offset.value > high_limit:
|
||||
raise RangeExceededError(instr, instr.pointer.name)
|
||||
|
||||
context.set_meaningful(instr.pointer)
|
||||
context.set_touched(instr.pointer)
|
||||
|
@ -59,7 +59,7 @@ class Defn(AST):
|
||||
|
||||
class Routine(AST):
|
||||
value_attrs = ('name', 'addr', 'initial',)
|
||||
children_attrs = ('statics',)
|
||||
children_attrs = ('locals',)
|
||||
child_attrs = ('block',)
|
||||
|
||||
|
||||
@ -75,6 +75,10 @@ class SingleOp(Instr):
|
||||
value_attrs = ('opcode', 'dest', 'src',)
|
||||
|
||||
|
||||
class Reset(Instr):
|
||||
value_attrs = ('pointer', 'offset',)
|
||||
|
||||
|
||||
class Call(Instr):
|
||||
value_attrs = ('location',)
|
||||
|
||||
|
@ -1,7 +1,7 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
ConstantRef, LocationRef, IndexedRef, IndirectRef,
|
||||
@ -34,16 +34,17 @@ class Compiler(object):
|
||||
self.symtab = symtab
|
||||
self.emitter = emitter
|
||||
self.routines = {} # routine.name -> Routine
|
||||
self.routine_statics = {} # routine.name -> { static.name -> Label }
|
||||
self.routine_locals = {} # routine.name -> { local.name -> Label }
|
||||
self.labels = {} # global.name -> Label ("global" includes routines)
|
||||
self.trampolines = {} # Location -> Label
|
||||
self.pointer_assoc = {} # pointer name -> table name (I'm not entirely happy about this)
|
||||
self.current_routine = None
|
||||
|
||||
# - - - - helper methods - - - -
|
||||
|
||||
def get_type_for_name(self, name):
|
||||
if self.current_routine and self.symtab.has_static(self.current_routine.name, name):
|
||||
return self.symtab.fetch_static_type(self.current_routine.name, name)
|
||||
if self.current_routine and self.symtab.has_local(self.current_routine.name, name):
|
||||
return self.symtab.fetch_local_type(self.current_routine.name, name)
|
||||
return self.symtab.fetch_global_type(name)
|
||||
|
||||
def get_type(self, ref):
|
||||
@ -76,9 +77,9 @@ class Compiler(object):
|
||||
|
||||
def get_label(self, name):
|
||||
if self.current_routine:
|
||||
static_label = self.routine_statics.get(self.current_routine.name, {}).get(name)
|
||||
if static_label:
|
||||
return static_label
|
||||
local_label = self.routine_locals.get(self.current_routine.name, {}).get(name)
|
||||
if local_label:
|
||||
return local_label
|
||||
return self.labels[name]
|
||||
|
||||
def absolute_or_zero_page(self, label):
|
||||
@ -107,16 +108,15 @@ class Compiler(object):
|
||||
label.set_addr(routine.addr)
|
||||
self.labels[routine.name] = label
|
||||
|
||||
if hasattr(routine, 'statics'):
|
||||
self.current_routine = routine
|
||||
static_labels = {}
|
||||
for defn in routine.statics:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
static_labels[defn.name] = label
|
||||
declarations.append((defn, self.symtab.fetch_static_type(routine.name, defn.name), label))
|
||||
self.routine_statics[routine.name] = static_labels
|
||||
self.current_routine = None
|
||||
self.current_routine = routine
|
||||
local_labels = {}
|
||||
for defn in routine.locals:
|
||||
length = self.compute_length_of_defn(defn)
|
||||
label = Label(defn.name, addr=defn.addr, length=length)
|
||||
local_labels[defn.name] = label
|
||||
declarations.append((defn, self.symtab.fetch_local_type(routine.name, defn.name), label))
|
||||
self.routine_locals[routine.name] = local_labels
|
||||
self.current_routine = None
|
||||
|
||||
if compilation_roster is None:
|
||||
compilation_roster = [['main']] + [[routine.name] for routine in program.routines if routine.name != 'main']
|
||||
@ -192,6 +192,8 @@ class Compiler(object):
|
||||
return self.compile_save(instr)
|
||||
elif isinstance(instr, PointInto):
|
||||
return self.compile_point_into(instr)
|
||||
elif isinstance(instr, Reset):
|
||||
return self.compile_reset(instr)
|
||||
else:
|
||||
raise NotImplementedError
|
||||
|
||||
@ -742,12 +744,16 @@ class Compiler(object):
|
||||
self.emitter.emit(STA(Absolute(src_label)))
|
||||
|
||||
def compile_point_into(self, instr):
|
||||
src_label = self.get_label(instr.table.name)
|
||||
self.pointer_assoc[instr.pointer.name] = instr.table.name
|
||||
self.compile_block(instr.block)
|
||||
del self.pointer_assoc[instr.pointer.name]
|
||||
|
||||
def compile_reset(self, instr):
|
||||
table_name = self.pointer_assoc[instr.pointer.name]
|
||||
src_label = Offset(self.get_label(table_name), instr.offset.value)
|
||||
dest_label = self.get_label(instr.pointer.name)
|
||||
|
||||
self.emitter.emit(LDA(Immediate(HighAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(dest_label)))
|
||||
self.emitter.emit(LDA(Immediate(LowAddressByte(src_label))))
|
||||
self.emitter.emit(STA(ZeroPage(Offset(dest_label, 1))))
|
||||
|
||||
self.compile_block(instr.block)
|
||||
|
328
src/sixtypical/context.py
Normal file
328
src/sixtypical/context.py
Normal file
@ -0,0 +1,328 @@
|
||||
# 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
|
@ -128,7 +128,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 +143,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):
|
||||
|
@ -1,23 +1,15 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.ast import (
|
||||
Program, Defn, Routine, Block, SingleOp, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
Program, Defn, Routine, Block, SingleOp, Reset, Call, GoTo, If, Repeat, For, WithInterruptsOff, Save, PointInto
|
||||
)
|
||||
from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
|
||||
RoutineType, VectorType, TableType, PointerType,
|
||||
LocationRef, ConstantRef, IndirectRef, IndexedRef,
|
||||
ConstantRef, IndirectRef, IndexedRef,
|
||||
)
|
||||
from sixtypical.scanner import Scanner
|
||||
|
||||
|
||||
class SymEntry(object):
|
||||
def __init__(self, ast_node, type_):
|
||||
self.ast_node = ast_node
|
||||
self.type_ = type_
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_)
|
||||
from sixtypical.symtab import SymEntry
|
||||
|
||||
|
||||
class ForwardReference(object):
|
||||
@ -28,42 +20,6 @@ class ForwardReference(object):
|
||||
return "%s(%r)" % (self.__class__.__name__, self.name)
|
||||
|
||||
|
||||
class SymbolTable(object):
|
||||
def __init__(self):
|
||||
self.symbols = {} # symbol name -> SymEntry
|
||||
self.statics = {} # routine name -> (symbol name -> SymEntry)
|
||||
self.typedefs = {} # type name -> Type AST
|
||||
self.consts = {} # const name -> ConstantRef
|
||||
|
||||
for name in ('a', 'x', 'y'):
|
||||
self.symbols[name] = SymEntry(None, TYPE_BYTE)
|
||||
for name in ('c', 'z', 'n', 'v'):
|
||||
self.symbols[name] = SymEntry(None, TYPE_BIT)
|
||||
|
||||
def __str__(self):
|
||||
return "Symbols: {}\nStatics: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.statics, self.typedefs, self.consts)
|
||||
|
||||
def has_static(self, routine_name, name):
|
||||
return name in self.statics.get(routine_name, {})
|
||||
|
||||
def fetch_global_type(self, name):
|
||||
return self.symbols[name].type_
|
||||
|
||||
def fetch_static_type(self, routine_name, name):
|
||||
return self.statics[routine_name][name].type_
|
||||
|
||||
def fetch_global_ref(self, name):
|
||||
if name in self.symbols:
|
||||
return LocationRef(name)
|
||||
return None
|
||||
|
||||
def fetch_static_ref(self, routine_name, name):
|
||||
routine_statics = self.statics.get(routine_name, {})
|
||||
if name in routine_statics:
|
||||
return LocationRef(name)
|
||||
return None
|
||||
|
||||
|
||||
class Parser(object):
|
||||
def __init__(self, symtab, text, filename):
|
||||
self.symtab = symtab
|
||||
@ -76,7 +32,7 @@ class Parser(object):
|
||||
def lookup(self, name, allow_forward=False, routine_name=None):
|
||||
model = self.symtab.fetch_global_ref(name)
|
||||
if model is None and routine_name:
|
||||
model = self.symtab.fetch_static_ref(routine_name, name)
|
||||
model = self.symtab.fetch_local_ref(routine_name, name)
|
||||
if model is None and allow_forward:
|
||||
return ForwardReference(name)
|
||||
if model is None:
|
||||
@ -88,10 +44,12 @@ class Parser(object):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
self.symtab.symbols[name] = SymEntry(ast_node, type_)
|
||||
|
||||
def declare_static(self, routine_name, name, ast_node, type_):
|
||||
def declare_local(self, routine_name, name, ast_node, type_):
|
||||
if self.symtab.fetch_local_ref(routine_name, name):
|
||||
self.syntax_error('Symbol "%s" already declared locally' % name)
|
||||
if self.symtab.fetch_global_ref(name):
|
||||
self.syntax_error('Symbol "%s" already declared' % name)
|
||||
self.symtab.statics.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
|
||||
self.syntax_error('Symbol "%s" already declared globally' % name)
|
||||
self.symtab.locals.setdefault(routine_name, {})[name] = SymEntry(ast_node, type_)
|
||||
|
||||
# ---- symbol resolution
|
||||
|
||||
@ -306,17 +264,17 @@ class Parser(object):
|
||||
type_ = self.defn_type()
|
||||
if not isinstance(type_, RoutineType):
|
||||
self.syntax_error("Can only define a routine, not {}".format(repr(type_)))
|
||||
statics = []
|
||||
locals_ = []
|
||||
if self.scanner.consume('@'):
|
||||
self.scanner.check_type('integer literal')
|
||||
block = None
|
||||
addr = int(self.scanner.token)
|
||||
self.scanner.scan()
|
||||
else:
|
||||
statics = self.statics()
|
||||
locals_ = self.locals()
|
||||
block = self.block()
|
||||
addr = None
|
||||
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, statics=statics)
|
||||
return type_, Routine(self.scanner.line_number, name=name, block=block, addr=addr, locals=locals_)
|
||||
|
||||
def labels(self):
|
||||
accum = []
|
||||
@ -370,13 +328,19 @@ class Parser(object):
|
||||
loc = IndexedRef(loc, offset, index)
|
||||
return loc
|
||||
|
||||
def statics(self):
|
||||
def locals(self):
|
||||
defns = []
|
||||
while self.scanner.consume('static'):
|
||||
type_, defn = self.defn()
|
||||
if defn.initial is None:
|
||||
self.syntax_error("Static definition {} must have initial value".format(defn))
|
||||
self.declare_static(self.current_routine_name, defn.name, defn, type_)
|
||||
self.declare_local(self.current_routine_name, defn.name, defn, type_)
|
||||
defns.append(defn)
|
||||
while self.scanner.consume('local'):
|
||||
type_, defn = self.defn()
|
||||
if defn.initial is not None:
|
||||
self.syntax_error("Local definition {} may not have initial value".format(defn))
|
||||
self.declare_local(self.current_routine_name, defn.name, defn, type_)
|
||||
defns.append(defn)
|
||||
return defns
|
||||
|
||||
@ -424,6 +388,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
|
||||
|
50
src/sixtypical/symtab.py
Normal file
50
src/sixtypical/symtab.py
Normal file
@ -0,0 +1,50 @@
|
||||
# encoding: UTF-8
|
||||
|
||||
from sixtypical.model import (
|
||||
TYPE_BIT, TYPE_BYTE, LocationRef,
|
||||
)
|
||||
|
||||
|
||||
class SymEntry(object):
|
||||
def __init__(self, ast_node, type_):
|
||||
self.ast_node = ast_node
|
||||
self.type_ = type_
|
||||
|
||||
def __repr__(self):
|
||||
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_)
|
||||
|
||||
|
||||
class SymbolTable(object):
|
||||
def __init__(self):
|
||||
self.symbols = {} # symbol name -> SymEntry
|
||||
self.locals = {} # routine name -> (symbol name -> SymEntry)
|
||||
self.typedefs = {} # type name -> Type AST
|
||||
self.consts = {} # const name -> ConstantRef
|
||||
|
||||
for name in ('a', 'x', 'y'):
|
||||
self.symbols[name] = SymEntry(None, TYPE_BYTE)
|
||||
for name in ('c', 'z', 'n', 'v'):
|
||||
self.symbols[name] = SymEntry(None, TYPE_BIT)
|
||||
|
||||
def __str__(self):
|
||||
return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts)
|
||||
|
||||
def has_local(self, routine_name, name):
|
||||
return name in self.locals.get(routine_name, {})
|
||||
|
||||
def fetch_global_type(self, name):
|
||||
return self.symbols[name].type_
|
||||
|
||||
def fetch_local_type(self, routine_name, name):
|
||||
return self.locals[routine_name][name].type_
|
||||
|
||||
def fetch_global_ref(self, name):
|
||||
if name in self.symbols:
|
||||
return LocationRef(name)
|
||||
return None
|
||||
|
||||
def fetch_local_ref(self, routine_name, name):
|
||||
routine_locals = self.locals.get(routine_name, {})
|
||||
if name in routine_locals:
|
||||
return LocationRef(name)
|
||||
return None
|
14
test.sh
14
test.sh
@ -1,7 +1,13 @@
|
||||
#!/bin/sh
|
||||
|
||||
# This currently represents a lot of tests! If you only want to run a subset,
|
||||
# it's probably best to run `falderal` manually on the file(s) you want to test.
|
||||
|
||||
falderal --substring-error \
|
||||
tests/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 Compilation.md"
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -6,9 +6,6 @@ SixtyPical to 6502 machine code.
|
||||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Functionality "Compile SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||
|
||||
-> Tests for functionality "Compile SixtyPical program"
|
||||
|
||||
Null program.
|
||||
@ -1400,7 +1397,7 @@ Subtracting a word memory location from another word memory location.
|
||||
|
||||
### Tables and Pointers
|
||||
|
||||
Load address of table into pointer.
|
||||
Associate pointer with table. Does nothing by itself.
|
||||
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
@ -1415,6 +1412,24 @@ Load address of table into pointer.
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F RTS
|
||||
|
||||
Reset pointer to table.
|
||||
|
||||
| byte table[256] tab
|
||||
| pointer ptr @ 254
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| outputs tab, y
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$18
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$08
|
||||
@ -1433,6 +1448,7 @@ Write literal through a pointer.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
@ -1458,6 +1474,7 @@ Write stored value through a pointer.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy foo, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
@ -1483,6 +1500,7 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| copy [ptr] + y, foo
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
@ -1497,6 +1515,39 @@ Read through a pointer, into a byte storage location, or the `a` register.
|
||||
= $081C LDA ($FE),Y
|
||||
= $081E RTS
|
||||
|
||||
Multiple `reset`s may occur inside the same block.
|
||||
|
||||
| byte table[256] tab @ 1024
|
||||
| pointer ptr @ 254
|
||||
| byte foo
|
||||
|
|
||||
| define main routine
|
||||
| inputs tab
|
||||
| outputs y, foo
|
||||
| trashes a, z, n, ptr
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 16
|
||||
| copy [ptr] + y, foo
|
||||
| reset ptr 18
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
= $080D LDY #$00
|
||||
= $080F LDA #$10
|
||||
= $0811 STA $FE
|
||||
= $0813 LDA #$04
|
||||
= $0815 STA $FF
|
||||
= $0817 LDA ($FE),Y
|
||||
= $0819 STA $0827
|
||||
= $081C LDA #$12
|
||||
= $081E STA $FE
|
||||
= $0820 LDA #$04
|
||||
= $0822 STA $FF
|
||||
= $0824 LDA ($FE),Y
|
||||
= $0826 RTS
|
||||
|
||||
Read and write through two pointers.
|
||||
|
||||
| byte table[256] tab
|
||||
@ -1510,7 +1561,9 @@ Read and write through two pointers.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptra into tab {
|
||||
| reset ptra 0
|
||||
| point ptrb into tab {
|
||||
| reset ptrb 0
|
||||
| copy [ptra] + y, [ptrb] + y
|
||||
| }
|
||||
| }
|
||||
@ -1541,6 +1594,7 @@ Write the `a` register through a pointer.
|
||||
| {
|
||||
| ld y, 0
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, 255
|
||||
| st a, [ptr] + y
|
||||
| }
|
||||
@ -1571,6 +1625,7 @@ Note that this is *not* range-checked. (Yet.)
|
||||
| ld y, 0
|
||||
| st off, c
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| add ptr, delta
|
||||
| add ptr, word 1
|
||||
| copy [ptr] + y, foo
|
||||
@ -1619,9 +1674,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
|
||||
@ -1651,3 +1706,66 @@ just the same as initialized global storage locations are.
|
||||
= $081D RTS
|
||||
= $081E .byte $FF
|
||||
= $081F .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 $0821
|
||||
= $0812 JSR $0816
|
||||
= $0815 RTS
|
||||
= $0816 STX $0820
|
||||
= $0819 INC $0820
|
||||
= $081C LDX $0820
|
||||
= $081F 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 JSR $0816
|
||||
= $0815 RTS
|
||||
= $0816 STX $0400
|
||||
= $0819 INC $0400
|
||||
= $081C LDX $0400
|
||||
= $081F RTS
|
||||
|
1845
tests/SixtyPical Control Flow.md
Normal file
1845
tests/SixtyPical Control Flow.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -61,12 +61,6 @@ to pass these tests to be considered an implementation of SixtyPical.
|
||||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Functionality "Dump fallthru info for SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --optimize-fallthru --dump-fallthru-info --analyze-only --traceback %(test-body-file)"
|
||||
|
||||
-> Functionality "Compile SixtyPical program with fallthru optimization" is implemented by
|
||||
-> shell command "bin/sixtypical --output-format=c64-basic-prg --optimize-fallthru --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </tmp/foo"
|
||||
|
||||
-> Tests for functionality "Dump fallthru info for SixtyPical program"
|
||||
|
||||
A single routine, obviously, falls through to nothing and has nothing fall
|
||||
|
1984
tests/SixtyPical Storage.md
Normal file
1984
tests/SixtyPical Storage.md
Normal file
File diff suppressed because it is too large
Load Diff
@ -9,9 +9,6 @@ but not necessarily sensible programs.
|
||||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Functionality "Check syntax of SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok"
|
||||
|
||||
-> Tests for functionality "Check syntax of SixtyPical program"
|
||||
|
||||
Rudimentary program.
|
||||
@ -176,6 +173,7 @@ Other blocks.
|
||||
| ld a, 0
|
||||
| }
|
||||
| point ptr into tab {
|
||||
| reset ptr 0
|
||||
| ld a, [ptr] + y
|
||||
| }
|
||||
| }
|
||||
@ -681,6 +679,7 @@ Tables and pointers.
|
||||
|
|
||||
| define main routine {
|
||||
| point ptr into buf {
|
||||
| reset ptr 0
|
||||
| copy 123, [ptr] + y
|
||||
| copy [ptr] + y, foo
|
||||
| copy [ptr] + y, [ptrb] + y
|
||||
@ -746,13 +745,14 @@ Only routines can be defined in the new style.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Memory locations can be defined static to a routine.
|
||||
Memory locations can be defined local to a routine.
|
||||
|
||||
| define foo routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t : 0
|
||||
| local word w
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
@ -762,13 +762,14 @@ Memory locations can be defined static to a routine.
|
||||
| define main routine
|
||||
| trashes a, x, z, n
|
||||
| static byte t : 0
|
||||
| local word w
|
||||
| {
|
||||
| ld x, t
|
||||
| call foo
|
||||
| }
|
||||
= ok
|
||||
|
||||
Static memory locations must always be given an initial value.
|
||||
Local static memory locations must always be given an initial value.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
@ -782,7 +783,49 @@ Static memory locations must always be given an initial value.
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Name of a static cannot shadow an existing global or static.
|
||||
Local static memory locations may not be given an address.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t @ 1024
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Local dynamic memory locations may not be given an initial value.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t : 10
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
? SyntaxError
|
||||
|
||||
Local dynamic memory locations may be given an address.
|
||||
|
||||
| define main routine
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| local byte t @ 1024
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
| ld x, t
|
||||
| }
|
||||
= ok
|
||||
|
||||
Name of a local cannot shadow an existing global or local.
|
||||
|
||||
| byte t
|
||||
|
|
||||
@ -790,7 +833,7 @@ Name of a static cannot shadow an existing global or static.
|
||||
| inputs x
|
||||
| outputs x
|
||||
| trashes z, n
|
||||
| static byte t
|
||||
| static byte t : 10
|
||||
| {
|
||||
| st x, t
|
||||
| inc t
|
||||
@ -802,11 +845,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
|
||||
|
20
tests/appliances/sixtypical.md
Normal file
20
tests/appliances/sixtypical.md
Normal file
@ -0,0 +1,20 @@
|
||||
This file contains only the [Falderal][] directives that define the different
|
||||
functionalities tested by the test suite, assuming that it's the reference
|
||||
implementation, `sixtypical`, that is going to implement these functionalities.
|
||||
|
||||
[Falderal]: http://catseye.tc/node/Falderal
|
||||
|
||||
-> Functionality "Check syntax of SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --parse-only --traceback %(test-body-file) && echo ok"
|
||||
|
||||
-> Functionality "Analyze SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok"
|
||||
|
||||
-> Functionality "Compile SixtyPical program" is implemented by
|
||||
-> shell command "bin/sixtypical --output-format=c64-basic-prg --traceback %(test-body-file) --output /tmp/foo && tests/appliances/bin/dcc6502-adapter </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