This commit is contained in:
Irmen de Jong 2017-12-28 23:03:59 +01:00
parent 5e16b82418
commit e30ba696db
12 changed files with 128 additions and 100 deletions

View File

@ -13,7 +13,7 @@ import sys
import shutil
import enum
from collections import defaultdict
from typing import Set, List, Tuple, Optional, Any, Dict, Union
from typing import Set, List, Tuple, Optional, Any, Dict, Union, Generator
from .astparse import ParseError, parse_expr_as_int, parse_expr_as_number, parse_expr_as_primitive,\
parse_expr_as_string, parse_arguments, parse_expr_as_comparison
from .symbols import SourceRef, SymbolTable, DataType, SymbolDefinition, SubroutineDef, LabelDef, \
@ -38,6 +38,12 @@ class ParseResult:
self.blocks = [] # type: List['ParseResult.Block']
self.subroutine_usage = defaultdict(set) # type: Dict[Tuple[str, str], Set[str]]
def all_blocks(self) -> Generator['ParseResult.Block', None, None]:
for block in self.blocks:
yield block
for sub in block.symbols.iter_subroutines(True):
yield sub.sub_block
class Block:
_unnamed_block_labels = {} # type: Dict[ParseResult.Block, str]
@ -75,18 +81,12 @@ class ParseResult:
except (SymbolError, LookupError):
return None, None
def flatten_statement_list(self) -> None:
if all(isinstance(stmt, ParseResult._AstNode) for stmt in self.statements):
# this is the common case
return
statements = []
def all_statements(self) -> Generator[Tuple['ParseResult.Block', Optional[SubroutineDef], 'ParseResult._AstNode'], None, None]:
for stmt in self.statements:
if isinstance(stmt, (tuple, list)):
statements.extend(stmt)
else:
assert isinstance(stmt, ParseResult._AstNode)
statements.append(stmt)
self.statements = statements
yield self, None, stmt
for sub in self.symbols.iter_subroutines(True):
for stmt in sub.sub_block.statements:
yield sub.sub_block, sub, stmt
class Value:
def __init__(self, datatype: DataType, name: str=None, constant: bool=False) -> None:
@ -606,7 +606,10 @@ class Parser:
if sys.stderr.isatty():
print("\x1b[1m", file=sys.stderr)
print("\nERROR: internal parser error: ", x, file=sys.stderr)
print(" file:", self.sourceref.file, "block:", self.cur_block.name, "line:", self.sourceref.line, file=sys.stderr)
if self.cur_block:
print(" file:", self.sourceref.file, "block:", self.cur_block.name, "line:", self.sourceref.line, file=sys.stderr)
else:
print(" file:", self.sourceref.file, file=sys.stderr)
if sys.stderr.isatty():
print("\x1b[0m", file=sys.stderr)
raise # XXX temporary solution to get stack trace info in the event of parse errors
@ -697,37 +700,30 @@ class Parser:
self.sourceref.line = -1
self.sourceref.column = 0
def desugar_immediate_strings(stmt: ParseResult._AstNode) -> None:
if isinstance(stmt, ParseResult.CallStmt):
for s in stmt.desugared_call_arguments:
self.sourceref.line = s.lineno
self.sourceref.column = 0
s.desugar_immediate_string(self)
for s in stmt.desugared_output_assignments:
self.sourceref.line = s.lineno
self.sourceref.column = 0
s.desugar_immediate_string(self)
if isinstance(stmt, ParseResult.AssignmentStmt):
self.sourceref.line = stmt.lineno
self.sourceref.column = 0
stmt.desugar_immediate_string(self)
for block in self.result.blocks:
self.cur_block = block
# create parameter loads for calls
for index, stmt in enumerate(list(block.statements)):
self.sourceref = block.sourceref.copy()
self.sourceref.column = 0
for block, sub, stmt in block.all_statements():
if isinstance(stmt, ParseResult.CallStmt):
self.sourceref.line = stmt.lineno
self.sourceref.column = 0
stmt.desugar_call_arguments_and_outputs(self)
# create parameter loads for calls, in subroutine blocks
for sub in block.symbols.iter_subroutines(True):
for stmt in sub.sub_block.statements:
if isinstance(stmt, ParseResult.CallStmt):
self.sourceref.line = stmt.lineno
self.sourceref.column = 0
stmt.desugar_call_arguments_and_outputs(self)
block.flatten_statement_list()
# desugar immediate string value assignments
for index, stmt in enumerate(list(block.statements)):
if isinstance(stmt, ParseResult.CallStmt):
for s in stmt.desugared_call_arguments:
self.sourceref.line = s.lineno
self.sourceref.column = 0
s.desugar_immediate_string(self)
for s in stmt.desugared_output_assignments:
self.sourceref.line = s.lineno
self.sourceref.column = 0
s.desugar_immediate_string(self)
if isinstance(stmt, ParseResult.AssignmentStmt):
self.sourceref.line = stmt.lineno
self.sourceref.column = 0
stmt.desugar_immediate_string(self)
desugar_immediate_strings(stmt)
def next_line(self) -> str:
self._cur_lineidx += 1
@ -787,38 +783,50 @@ class Parser:
self.result.with_sys = False
self.result.format = ProgramFormat.RAW
output_specified = False
zp_specified = False
while True:
self._parse_comments()
line = self.next_line()
if line.startswith("output"):
if line.startswith(("output ", "output\t")):
if output_specified:
raise self.PError("multiple occurrences of 'output'")
raise self.PError("can only specify output options once")
output_specified = True
_, _, arg = line.partition(" ")
arg = arg.lstrip()
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
self.result.with_sys = False
self.result.format = ProgramFormat.RAW
if arg == "raw":
pass
elif arg == "prg":
if "raw" in options:
options.remove("raw")
if "prg" in options:
options.remove("prg")
self.result.format = ProgramFormat.PRG
elif arg.replace(' ', '') == "prg,sys":
self.result.with_sys = True
self.result.format = ProgramFormat.PRG
else:
raise self.PError("invalid output format")
elif line.startswith("clobberzp"):
if self.result.clobberzp:
raise self.PError("multiple occurrences of 'clobberzp'")
self.result.clobberzp = True
_, _, arg = line.partition(" ")
arg = arg.lstrip()
if arg == "restore":
self.result.restorezp = True
elif arg == "":
pass
else:
raise self.PError("invalid arg for clobberzp")
if "basic" in options:
options.remove("basic")
if self.result.format == ProgramFormat.PRG:
self.result.with_sys = True
else:
raise self.PError("can only use basic output option with prg, not raw")
if options:
raise self.PError("invalid output option(s): " + str(options))
elif line.startswith(("zp ", "zp\t")):
if zp_specified:
raise self.PError("can only specify ZP options once")
zp_specified = True
_, _, optionstr = line.partition(" ")
options = set(optionstr.replace(' ', '').split(','))
self.result.clobberzp = False
self.result.restorezp = False
if "clobber" in options:
options.remove("clobber")
self.result.clobberzp = True
if "restore" in options:
options.remove("restore")
if self.result.clobberzp:
self.result.restorezp = True
else:
raise self.PError("can only use restore zp option if clobber zp is used as well")
if options:
raise self.PError("invalid zp option(s): " + str(options))
elif line.startswith("address"):
if self.result.start_address:
raise self.PError("multiple occurrences of 'address'")
@ -1644,17 +1652,12 @@ class Optimizer:
def optimize(self) -> ParseResult:
print("\noptimizing parse tree")
for block in self.parsed.blocks:
for block in self.parsed.all_blocks():
self.remove_identity_assigns(block)
self.combine_assignments_into_multi(block)
self.optimize_multiassigns(block)
self.remove_unused_subroutines(block)
self.optimize_compare_with_zero(block)
for sub in block.symbols.iter_subroutines(True):
self.remove_identity_assigns(sub.sub_block)
self.combine_assignments_into_multi(sub.sub_block)
self.optimize_multiassigns(sub.sub_block)
self.optimize_compare_with_zero(sub.sub_block)
return self.parsed
def optimize_compare_with_zero(self, block: ParseResult.Block) -> None:

View File

@ -165,13 +165,36 @@ The default format of the generated program is a "raw" binary where code starts
You can generate other types of programs as well, by telling IL65 via an output mode statement
at the beginning of your program:
| mode declaration | meaning |
|--------------------|------------------------------------------------------------------------------------|
| ``output raw`` | no load address bytes |
| ``output prg`` | include the first two load address bytes, (default is ``$0801``), no BASIC program |
| ``output prg,sys`` | include the first two load address bytes, BASIC start program with sys call to code, default code start is immediately after the BASIC program at ``$081d``, or beyond |
| | |
| ``address $0801`` | override program start address (default is set to ``$c000`` for raw mode and ``$0801`` for C-64 prg mode). Cannot be used if output mode is ``prg,sys`` because BASIC programs always have to start at ``$0801``. |
| mode declaration | meaning |
|----------------------|------------------------------------------------------------------------------------|
| ``output raw`` | no load address bytes |
| ``output prg`` | include the first two load address bytes, (default is ``$0801``), no BASIC program |
| ``output prg,basic`` | as 'prg', but include a BASIC start program with SYS call, default code start is immediately after the BASIC program at ``$081d``, or after that. |
| | |
| ``address $0801`` | override program start address (default is set to ``$c000`` for raw mode and ``$0801`` for C-64 prg mode). Cannot be used if output mode is ``prg,basic`` because BASIC programs always have to start at ``$0801``. |
### ZeroPage Options
You can tell the compiler how to treat the *zero page*. Normally it is considered a 'no-go' area
except for the frew free locations mentioned under "Memory Model".
However you can specify some options globally in your program to change this behavior:
- ``zp clobber``
Use the whole zeropage for variables. It is not possible to exit your program
correctly back to BASIC, other than resetting the machine.
It does make your program smaller and faster because many more variables can
be stored in the ZP, which is more efficient.
- ``zp clobber, restore``
Use the whole zeropage, but make a backup copy of the original values at program start.
When your program exits, the original ZP is restored and you drop back to the BASIC prompt.
If you use ``zp clobber``, you can no longer use BASIC or KERNAL routines,
because these depend on most of the locations in the ZP. This includes most of the floating-point
logic and several utility routines that do I/O, such as ``print_string``.
@todo default IRQ handling will still change certain values in ZP...
### Program Entry Point

View File

@ -1,4 +1,4 @@
output prg,sys
output prg,basic
import "c64lib"

View File

@ -1,7 +1,8 @@
; var definitions and immediate primitive data type tests
output raw
clobberzp
zp clobber
import "c64lib"

View File

@ -1,6 +1,6 @@
; floating point tests
output prg, sys
output prg, basic
import "c64lib"

View File

@ -1,4 +1,4 @@
output prg,sys
output prg,basic
import "c64lib"

View File

@ -1,7 +1,7 @@
; var definitions and immediate primitive data type tests
output prg, sys
clobberzp
output prg, basic
zp clobber
import "c64lib"

View File

@ -1,4 +1,4 @@
output prg,sys ; @todo basic
output prg,basic
;reg_preserve off ; @todo global option
import "c64lib"
@ -40,7 +40,13 @@ start
printloop
c64util.print_string("\nYou have ")
c64util.print_byte_decimal(attempts_left)
c64util.print_string(" guesses left.\nWhat is your next guess? ")
c64util.print_string(" guess")
A = attempts_left
A -= 1 ; @todo wrong instruction adc
if_zero A goto ask_guess
c64util.print_string("es")
ask_guess
c64util.print_string(" left.\nWhat is your next guess? ")
A = c64util.input_chars(guess)
c64.CHROUT('\n')
[$22.word] = guess
@ -54,8 +60,8 @@ printloop
goto continue
correct_guess
c64util.print_string("\nImpressive!\n")
bye()
c64util.print_string("\nThat's my number, impressive!\n")
goodbye()
return
too_high
@ -71,16 +77,15 @@ game_over
c64util.print_string("\nToo bad! It was: ")
c64util.print_byte_decimal(secretnumber)
c64.CHROUT('\n')
bye()
goodbye()
return
sub bye ()->() {
sub goodbye ()->() {
;var x ; @todo vars in sub
;memory y = $c000 ; @todo vars in sub
;const q = 22 ; @todo const in sub
c64.CHROUT('\n')
;c64util.print_string("Thanks for playing. Bye!\n\n") ;@todo string values should work in sub too
c64util.print_string("\nThanks for playing. Bye!\n")
return
}

View File

@ -4,9 +4,8 @@
; line 3 comment
output prg,sys ; create a c-64 program with basic SYS call to launch it
;clobberzp restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
output basic , prg ; create a c-64 program with basic SYS call to launch it
zp restore , clobber ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
~main $0a00

View File

@ -1,8 +1,8 @@
; source IL file
; these are comments
output prg,sys ; create a c-64 program with basic SYS call to launch it
clobberzp restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
output prg, basic ; create a c-64 program with basic SYS call to launch it
zp clobber,restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
~main
{

View File

@ -1,8 +1,5 @@
; source IL file
; these are comments
output prg,sys ; create a c-64 program with basic SYS call to launch it
; clobberzp restore ; clobber over the zp memory normally used by basic/kernel rom, frees up more zp
output prg,basic ; create a c-64 program with basic SYS call to launch it
import "c64lib" ; searched in several locations and with .ill file extension added

View File

@ -1,4 +1,4 @@
output prg,sys ; create a c-64 program with basic SYS to() launch it
output prg,basic ; create a c-64 program with basic SYS to() launch it
import "c64lib.ill"