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

Forbid nested with interrupts blocks, and more refactoring.

This commit is contained in:
Chris Pressey 2019-05-14 15:01:10 +01:00
parent 3f666f4385
commit 2e2e80664e
19 changed files with 463 additions and 171 deletions

View File

@ -12,10 +12,18 @@ History of SixtyPical
* Local locations need no longer be static. If they are not
static, they are considered uninitialized until assigned,
and they can be declared with an explicit fixed address.
* Along with `goto`, `call` and `with interrupts off` are
now forbidden inside a `with interrupts off` block.
* More tests to assure that using `call` inside a `point into`
block or inside a `for` block does not cause trouble,
particularly when the routine being called also uses the
variable named in that block.
* Fixed a bug where two local statics could be declared with
the same name.
* Split context off from analyzer and put it in its own module.
* Split the SixtyPical Analysis tests across three files.
* Split analysis context support off from analyzer, and
symbol table support from parse, and it their own modules.
* Split the SixtyPical Analysis tests across three files,
and placed test appliances for `sixtypical` in own file.
0.19
----

View File

@ -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

View File

@ -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)

162
TODO.md
View File

@ -1,6 +1,9 @@
TODO for SixtyPical
===================
Language
--------
### Save values to other-than-the-stack
Allow
@ -9,25 +12,109 @@ Allow
...
}
Which uses some other storage location instead of the stack. A local static
would be a good candidate for such.
Which uses some other storage location instead of the stack. A local non-static
would be a good candidate for such. At any rate, the location must not
be writeable by anything that is called from within the block. So, probably
just restrict this to local non-statics.
### Analyze `call` within blocks?
### Copy byte to/from table
What happens if you call another routine from inside a `with interrupts off` block?
Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
You have to `ld a`, `st a`. I think maybe we should have one.
What happens if you call another routine from inside a `save` block?
### Character literals
What happens if you call another routine from inside a `point into` block?
For goodness sake, let the programmer say `'A'` instead of `65`.
What happens if you call another routine from inside a `for` block?
### Character set mapping
Remember that any of these may have a `goto` ... and they may have a second
instance of the same block (e.g. `with interrupts off` nested within
`with interrupts off` shouldn't be allowed to turn them back on after the
inner block has finished -- even if there is no `call`.)
Not all computers think `'A'` should be `65`. Allow the character set to be
mapped. Probably copy what Ophis does.
These holes need to be plugged.
### "Include" directives
Search a searchlist of include paths. And use them to make libraries of routines.
One such library routine might be an `interrupt routine` type for various architectures.
Since "the supervisor" has stored values on the stack, we should be able to trash them
with impunity, in such a routine.
### Pointers into non-byte tables
Right now you cannot get a pointer into a non-byte (for instance, word or vector) table.
Word and vector tables are stored as two byte tables in memory. This is useful for
indexed access, but makes pointer access more difficult.
Laying them out for pointer access would make indexed access more difficult.
### Saving non-byte values
Right now you cannot save a word value.
There doesn't seem to be a hugely pressing reason why not.
Analysis
--------
### Forbid recursion
What happens if a routine calls itself, directly or indirectly? Many
constraints might be violated in this case. We should probably disallow
recursion by default. (Which means assembling the callgraph in all cases.)
### Analyze memory usage
If you define two variables that occupy the same address, an analysis error ought
to be raised. (But there should also be a way to annotate this as intentional.
Intentionally making two tables overlap could be valuable. However, the analysis
will probably completely miss this fact.)
Optimization
------------
### Space optimization of local non-statics
If there are two routines A and B, and A never calls B (even indirectly), and
B never calls A (even indirectly), then their non-static locals can
be allocated at the same space.
This is not just an impressive trick -- in the presence of local pointers, which
use up a word in zero-page, which we consider a precious resource, it allow those
zero-page locations to be re-used.
### Tail-call optimization
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
if the block is in tail position. The constraints should iron out the same both ways.
As long as the routine has consistent type context every place it exits, that should be fine.
### Branch optimization in `if`
Currently the `if` generator is not smart enough to avoid generating silly
jump instructions. (See the Fallthru tests.) Improve it.
### Dead code removal
Once we have a call graph we can omit routines that we're sure aren't called.
This would let us use include-files and standard-libraries nicely: any
routines they define, but that you don't use, don't get included.
Analyzing the set of possible routines that a vector can take on would help
this immensely.
Implementation
--------------
### Line numbers in analysis error messages
For analysis errors, there is a line number, but it's the line of the routine
after the routine in which the analysis error occurred. Fix this.
Blue-skying
-----------
### Pointers associated globally with a table(?)
@ -45,52 +132,5 @@ at different times.
These can co-exist with general, non-specific-table-linked `pointer` variables.
### Space optimization of local non-statics
If there are two routines A and B, and A never calls B (even indirectly), and
B never calls A (even indirectly), then their non-static locals can
be allocated at the same space.
This is more an impressive trick than a really useful feature, but still.
Impressive tricks are impressive.
### Copy byte to/from table
Do we want a `copy bytevar, table + x` instruction? We don't currently have one.
You have to `ld a`, `st a`. I think maybe we should have one.
### Analyze memory usage
If you define two variables that occupy the same address, an analysis error ought
to be raised. (But there should also be a way to annotate this as intentional.
Intentionally making two tables overlap could be valuable. However, the analysis
will probably completely miss this fact.)
### Character literals
For goodness sake, let the programmer say `'A'` instead of `65`.
### Character set mapping
Not all computers think `'A'` should be `65`. Allow the character set to be
mapped. Probably copy what Ophis does.
### Tail-call optimization
If a block ends in a `call` can that be converted to end in a `goto`? Why not? I think it can,
if the block is in tail position. The constraints should iron out the same both ways.
As long as the routine has consistent type context every place it exits, that should be fine.
### "Include" directives
Search a searchlist of include paths. And use them to make libraries of routines.
One such library routine might be an `interrupt routine` type for various architectures.
Since "the supervisor" has stored values on the stack, we should be able to trash them
with impunity, in such a routine.
### Line numbers in analysis error messages
For analysis errors, there is a line number, but it's the line of the routine
after the routine in which the analysis error occurred. Fix this.
If we have local pointers and space optimization for local non-statics, though,
these don't add as much.

View File

@ -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:])

View File

@ -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
.

View File

@ -269,36 +269,33 @@ define player_logic logic_routine
st off, c
add ptr, new_pos
ld y, 0
// check collision.
ld a, [ptr] + y
}
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 81
if z {
ld a, 32
}
cmp a, 32
if z {
point ptr into screen {
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 81
if z {
ld a, 32
}
cmp a, 32
if z {
reset ptr 0
st off, c
add ptr, pos
copy 32, [ptr] + y
}
copy new_pos, pos
copy new_pos, pos
point ptr into screen {
reset ptr 0
st off, c
add ptr, pos
copy 81, [ptr] + y
}
} else {
ld a, 1
st a, player_died
}
} else {
ld a, 1
st a, player_died
}
}
@ -314,26 +311,24 @@ define enemy_logic logic_routine
st off, c
add ptr, new_pos
ld y, 0
// check collision.
ld a, [ptr] + y
}
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 82
if z {
ld a, 32
}
cmp a, 32
if z {
point ptr into screen {
// if "collision" is with your own self, treat it as if it's blank space!
cmp a, 82
if z {
ld a, 32
}
cmp a, 32
if z {
reset ptr 0
st off, c
add ptr, pos
copy 32, [ptr] + y
}
copy new_pos, pos
copy new_pos, pos
point ptr into screen {
reset ptr 0
st off, c
add ptr, pos

View 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
}

View File

@ -230,9 +230,7 @@ class Analyzer(object):
elif isinstance(instr, For):
self.analyze_for(instr, context)
elif isinstance(instr, WithInterruptsOff):
self.analyze_block(instr.block, context)
if context.encountered_gotos():
raise IllegalJumpError(instr, instr)
self.analyze_with_interrupts_off(instr, context)
elif isinstance(instr, Save):
self.analyze_save(instr, context)
elif isinstance(instr, PointInto):
@ -290,7 +288,6 @@ class Analyzer(object):
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.set_touched(target)
context.set_written(target)
elif self.get_type(src) != self.get_type(dest):
@ -465,8 +462,6 @@ class Analyzer(object):
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.assert_writeable(target)
context.set_touched(target)
context.set_written(target)
elif isinstance(src, IndirectRef) and isinstance(dest, LocationRef):
@ -477,9 +472,8 @@ class Analyzer(object):
raise UnmeaningfulReadError(instr, src.ref)
context.assert_meaningful(origin)
context.assert_writeable(dest)
context.set_touched(dest)
context.set_written(dest)
elif isinstance(src, IndirectRef) and isinstance(dest, IndirectRef):
context.assert_meaningful(src.ref, dest.ref, REG_Y)
@ -491,8 +485,6 @@ class Analyzer(object):
target = context.get_assoc(dest.ref)
if not target:
raise ForbiddenWriteError(instr, dest.ref)
context.assert_writeable(target)
context.set_touched(target)
context.set_written(target)
elif isinstance(src, LocationRef) and isinstance(dest, IndexedRef):
@ -503,7 +495,6 @@ class Analyzer(object):
context.set_written(dest.ref)
elif isinstance(src, IndexedRef) and isinstance(dest, LocationRef):
context.assert_meaningful(src.ref, src.index)
context.set_touched(dest)
context.set_written(dest)
else:
context.assert_meaningful(src)
@ -569,7 +560,6 @@ class Analyzer(object):
exit_context = context.clone()
for ref in type_.outputs:
exit_context.set_touched(ref) # ?
exit_context.set_written(ref)
for ref in type_.trashes:
@ -677,6 +667,13 @@ class Analyzer(object):
context.set_range(instr.dest, instr.final, instr.final)
context.set_writeable(instr.dest)
def analyze_with_interrupts_off(self, instr, context):
block = instr.block
for instr in block.instrs:
if isinstance(instr, (Call, GoTo, WithInterruptsOff)):
raise IllegalJumpError(instr, instr)
self.analyze_instr(instr, context)
def analyze_save(self, instr, context):
batons = []
for location in instr.locations:

View File

@ -6,18 +6,10 @@ from sixtypical.ast import (
from sixtypical.model import (
TYPE_BIT, TYPE_BYTE, TYPE_WORD,
RoutineType, VectorType, TableType, PointerType,
LocationRef, ConstantRef, IndirectRef, IndexedRef,
ConstantRef, IndirectRef, IndexedRef,
)
from sixtypical.scanner import Scanner
class SymEntry(object):
def __init__(self, ast_node, type_):
self.ast_node = ast_node
self.type_ = type_
def __repr__(self):
return "%s(%r, %r)" % (self.__class__.__name__, self.ast_node, self.type_)
from sixtypical.symtab import SymEntry
class ForwardReference(object):
@ -28,42 +20,6 @@ class ForwardReference(object):
return "%s(%r)" % (self.__class__.__name__, self.name)
class SymbolTable(object):
def __init__(self):
self.symbols = {} # symbol name -> SymEntry
self.locals = {} # routine name -> (symbol name -> SymEntry)
self.typedefs = {} # type name -> Type AST
self.consts = {} # const name -> ConstantRef
for name in ('a', 'x', 'y'):
self.symbols[name] = SymEntry(None, TYPE_BYTE)
for name in ('c', 'z', 'n', 'v'):
self.symbols[name] = SymEntry(None, TYPE_BIT)
def __str__(self):
return "Symbols: {}\nLocals: {}\nTypedefs: {}\nConsts: {}".format(self.symbols, self.locals, self.typedefs, self.consts)
def has_local(self, routine_name, name):
return name in self.locals.get(routine_name, {})
def fetch_global_type(self, name):
return self.symbols[name].type_
def fetch_local_type(self, routine_name, name):
return self.locals[routine_name][name].type_
def fetch_global_ref(self, name):
if name in self.symbols:
return LocationRef(name)
return None
def fetch_local_ref(self, routine_name, name):
routine_locals = self.locals.get(routine_name, {})
if name in routine_locals:
return LocationRef(name)
return None
class Parser(object):
def __init__(self, symtab, text, filename):
self.symtab = symtab

50
src/sixtypical/symtab.py Normal file
View 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

View File

@ -1,6 +1,10 @@
#!/bin/sh
# This currently represents a lot of tests! If you only want to run a subset,
# it's probably best to run `falderal` manually on the file(s) you want to test.
falderal --substring-error \
"tests/appliances/sixtypical.md" \
"tests/SixtyPical Syntax.md" \
"tests/SixtyPical Analysis.md" \
"tests/SixtyPical Storage.md" \

View File

@ -10,9 +10,6 @@ For control flow, see [SixtyPical Control Flow](SixtyPical%20Control%20Flow.md).
[Falderal]: http://catseye.tc/node/Falderal
-> Functionality "Analyze SixtyPical program" is implemented by
-> shell command "bin/sixtypical --analyze-only --traceback %(test-body-file) && echo ok"
-> Tests for functionality "Analyze SixtyPical program"
### add ###
@ -754,6 +751,66 @@ A `goto` cannot appear within a `with interrupts` block.
| }
? IllegalJumpError
A `call` cannot appear within a `with interrupts` block.
| vector routine
| inputs x
| outputs x
| trashes z, n
| bar
|
| define foo routine
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| define other routine
| trashes bar, a, n, z
| {
| ld a, 0
| }
|
| define main routine
| trashes bar, a, n, z
| {
| with interrupts off {
| copy foo, bar
| call other
| }
| }
? IllegalJumpError
A `with interrupts` block cannot appear within a `with interrupts` block.
| vector routine
| inputs x
| outputs x
| trashes z, n
| bar
|
| define foo routine
| inputs x
| outputs x
| trashes z, n
| {
| inc x
| }
|
| define main routine
| trashes bar, a, n, z
| {
| with interrupts off {
| copy foo, bar
| with interrupts off {
| copy foo, bar
| }
| }
| }
? IllegalJumpError
### typedef ###
A typedef is a more-readable alias for a type. "Alias" means

View File

@ -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.

View File

@ -704,6 +704,82 @@ initialized at the start of that loop.
| }
? UnmeaningfulReadError: y
And in particular, you can't uninitialize the loop variable, in the loop.
| define foo routine
| trashes x
| {
| }
|
| define main routine
| outputs x, y, n, z
| trashes c
| {
| ld x, 0
| ld y, 15
| for x up to 15 {
| inc y
| call foo
| }
| }
? ForbiddenWriteError: x
So, if you call a routine from inside the loop, it better not also
loop on the same variable.
| define foo routine
| inputs y
| outputs x, y, n, z
| trashes c
| {
| ld x, 0
| for x up to 15 {
| inc y
| }
| }
|
| define main routine
| outputs x, y, n, z
| trashes c
| {
| ld x, 0
| ld y, 15
| for x up to 15 {
| inc y
| call foo
| }
| }
? ForbiddenWriteError: x
But if you take care to save and restore the loop variable in the
called routine, it will be okay.
| define foo routine
| inputs y
| outputs y, n, z
| trashes a, c
| {
| save x {
| ld x, 0
| for x up to 15 {
| inc y
| }
| }
| }
|
| define main routine
| outputs x, y, n, z
| trashes a, c
| {
| ld x, 0
| ld y, 15
| for x up to 15 {
| inc y
| call foo
| }
| }
= ok
The "for" loop does not preserve the `z` or `n` registers.
| define foo routine trashes x {

View File

@ -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

View File

@ -1589,6 +1589,68 @@ The offset in `reset` may not exceed the table's size.
| }
? RangeExceededError
### dynamic recurrence of `point ... into` blocks
You cannot call a routine which trashes the pointer from inside a
`point ... into` block. Remember that `point ... into` by
itself doesn't change anything, so doesn't trash anything.
| byte table[256] tab
| byte table[256] other
| pointer ptr
|
| define sub routine
| inputs other
| outputs other
| {
| point ptr into other {
| }
| }
|
| define main routine
| inputs tab, other
| outputs tab, other
| trashes a, y, c, z, n, v, ptr
| {
| ld y, 0
| point ptr into tab {
| reset ptr 0
| copy 123, [ptr] + y
| call sub
| copy 123, [ptr] + y
| }
| }
= ok
| byte table[256] tab
| byte table[256] other
| pointer ptr
|
| define sub routine
| inputs other
| outputs other
| trashes ptr
| {
| point ptr into other {
| reset ptr 0
| }
| }
|
| define main routine
| inputs tab, other
| outputs tab, other
| trashes a, y, c, z, n, v, ptr
| {
| ld y, 0
| point ptr into tab {
| reset ptr 0
| copy 123, [ptr] + y
| call sub
| copy 123, [ptr] + y
| }
| }
? UnmeaningfulReadError
### locals ###
When memory locations are defined static to a routine, they cannot be

View File

@ -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.

View 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"