This commit is contained in:
dschmenk 2016-01-29 13:46:03 -08:00
commit d975768c62
80 changed files with 10690 additions and 213 deletions

4
.gitignore vendored
View File

@ -16,10 +16,14 @@ mg
/Platform/Apple/tools/*/nbproject/private/
/Platform/Apple/tools/*/dist/
# Skip error files
/Platform/Apple/virtual/pack_error.txt
# Don't check in data specific to any particular game
/Platform/Apple/virtual/data/images/*.bin
/Platform/Apple/virtual/data/fonts/*.bin
/Platform/Apple/virtual/data/world/*.xml
/Platform/Apple/virtual/data/world/*.cache
/Platform/Apple/virtual/data/world/*.tsv
# Only check in sample.build.props; each person's build.props will be different.

View File

@ -0,0 +1,3 @@
!eof
This directory contains files that are useful when writing programs for the 6502 processor, but which are independent of the computer used.

View File

@ -0,0 +1,3 @@
!eof
This directory contains files that are useful when writing programs for the 65816 processor, but which are independent of the computer used.

View File

@ -0,0 +1,26 @@
!eof
If you have written files that you often need to include in your sources, you can store them in this directory.
This way they can be accessed in ACME sources using
!source <Own/insert_name_of_your_file_here>
or
!binary <Own/insert_name_of_your_file_here>
Please bear in mind that you cannot assemble such source codes on a machine that does *not* have your own include files. If you want to freely give away your programs though, there are two ways to do this:
a) Change those references to "normal" quotes and store your own library files in the same directory as the file that includes them. This means editing by hand, therefore the "Own" directory would lose its meaning.
b) Store your library files into a uniquely-named subdirectory inside the library tree and give away that part of the library as well. The position of such a personal directory inside the library tree should be chosen intelligently - it wouldn't be helpful if every single user of ACME would place his/her directory at the top level of the library. If your files are only of use for one specific computer, then you should create a new directory called "3rdParty" in that computer's directory and put your own directory inside it. The name of your own directory may be your name or IRC nick, for example.
Demo groups, software houses and the like should use this method as well, forming paths like this:
!binary <Acorn/3rdParty/BlackAdder/Baldrick.a>
!source <C64/3rdParty/WyldStallons/Scroller.a>
If you have a file that you think should be added to the "normal" library tree, just send me an e-mail and I'll include it in the next release. Well, at least I might consider it. :)

View File

@ -0,0 +1,5 @@
ACME - Release 0.91 (Linux/Unix/Platform independent version)
MultiPlatform 6502/65c02/65816 X-Assembler

View File

@ -0,0 +1,73 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- Linux/Unix/platform independent version ---
This file only describes stuff that is specific to the Linux / Unix
/ platform independent version of ACME. So if you are looking for more
general things, you should take a look at "docs/Help.txt".
*** Compiling and installing the executable:
Change into the directory "acme091/src" that was created when
unpacking the archive and simply type "make". This will compile the
sources and produce an executable file.
If you have root access:
Change into superuser mode using "su" and type "make install" to move
the executable to the appropriate directory (system-wide install).
If you don't have root access:
Type "make userinstall" to move the executable to your "~/bin"
directory (user-specific install).
Feel free to adjust the Makefile to your specific needs.
*** Installing the library:
The directory "ACME_Lib" contains a bunch of files that may be useful.
Okay, there's hardly anything in it at the moment, but it will
hopefully grow over time.
Copy the ACME_Lib directory to a place you think is appropriate. You
will have to set up an environment variable called "ACME" to allow the
main program to find this directory tree.
In bash:
set ACME="/my/path/to/ACME_Lib" ; export ACME
You don't *have* to install the library just to use ACME, but it
doesn't hurt to install it - one of the supplied example programs uses
the library as well.
*** Miscellaneous:
ACME was tested and prepared on Redhat 5.2 but it should work on any
Linux distribution and on other Unices aswell (if not, then let me
know and i'll fix it ASAP).
Krzysztof Dabrowski, current Linux port maintainer
mailto:brush@pol.pl
*** Changes:
Release 0.91: Merged Linux/Unix version and source-only version.
Release 0.04 beta: First Linux version

View File

@ -0,0 +1,87 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- 65816 support ---
This text contains information about the 65816-specific features of
ACME.
----------------------------------------------------------------------
Section: Command aliases for "long" JMPs and JSRs
----------------------------------------------------------------------
In addition to the commands JMP and JSR, the 65816 processor also
knows JML and JSL, which are JMP and JSR using new (long) addressing
modes. ACME also accepts the new addressing modes when using the old
mnemonics JMP and JSR, but the old addressing modes cannot be used
with the new mnemonics JML and JSL.
----------------------------------------------------------------------
Section: Argument order of MVN/MVP
----------------------------------------------------------------------
According to WDC's official syntax for 65816 assembly language, the
argument order of the MVN and MVP instructions differs between
assembly language and machine code.
To copy bytes from bank $ab to bank $cd, use the following statement:
mvn $ab, $cd ; source bank $ab, destination bank $cd
ACME will then produce the following machine code:
$54 $cd $ab ; opcode mvn, destination bank $cd, source bank $ab
ACME 0.05 and earlier did it the wrong way. Several other assemblers
still do. Make sure your sources are correct.
----------------------------------------------------------------------
Section: Register lengths
----------------------------------------------------------------------
When assembling "lda #5" for example, ACME has to know whether to
create an 8-bit argument or a 16-bit argument. This depends on the
current register length.
On startup, ACME assumes all registers are 8 bits wide. You can change
this at any time using the following pseudo opcodes:
!al ; switch to long accumulator
!as ; switch to short accumulator
!rl ; switch to long index registers
!rs ; switch to short index registers
Please note that ACME, unlike some other assemblers, does *not* track
SEP/REP commands: I don't like that method - it fails when
encountering PLPs, for example. So if it doesn't work reliably in the
first place, why use it? :)
If you don't like that you always have to use a pseudo opcode
alongside SEP/REP commands, then have a look at the file <65816/std.a>
(in the library). There are some predefined macros that you can use.
----------------------------------------------------------------------
Section: Postfixing stuff
----------------------------------------------------------------------
You can also use the postfix method (which is explained in the file
"AddrModes.txt") to specify the immediate argument's length:
ldx+2 #5
will always be assembled to a 16-bit argument, regardless of the
currently configured index register width. Use at your own risk - this
method obviously is not a good example on structured programming. :)
----------------------------------------------------------------------
Section: Miscellaneous
----------------------------------------------------------------------
Note that ACME cannot produce more than 64 KBytes of code. Also note
that though the 65816 CPU has an address space of 16 MB, ACME's
program counter is only sixteen bits wide. It shouldn't be too hard to
make any assembled code run in a non-zero bank, though.

View File

@ -0,0 +1,172 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- addressing modes ---
If a command can be used with different addressing modes, ACME has to
decide which one to use. Several commands of the 6502 CPU can be used
with either "absolute" addressing or "zeropage-absolute" addressing.
The former one means there's a 16-bit argument, the latter one means
there's an 8-bit argument.
And the 65816 CPU even knows some commands with 24-bit addressing...
So how does ACME know which addressing mode to use?
The simple approach is to always use the smallest possible argument,
of course: If the argument fits in a byte, use zeropage addressing. If
it doesn't, use absolute addressing. If it needs more than two bytes
and the 65816 CPU is chosen, use 24-bit addressing.
In most cases this works - with two exceptions. The remainder of this
text may sound somewhat confusing now, so if you don't have any
problems with addressing modes, then don't bother trying to understand
everything this texts says. :)
The two exceptions are:
*** 1) Labels are defined too late
If ACME cannot figure out the argument value in the first pass, it
assumes that the command uses 16-bit addressing.
If it later finds out that the argument only needs 8 bits, ACME gives
a warning ("using oversized addressing mode") and continues. However,
if it finds out that the argument needs 24 bits, it gives an error.
These problems can be solved by defining the labels *before* using
them, so that the value can be figured out in the first pass. If this
is not possible, you can use the postfix method, effectively exactly
defining what addressing mode to use. The postfix method is described
in a separate paragraph below.
*** 2) You *want* to use an oversized addressing mode
On the 65816 CPU, "zeropage addressing" is called "direct page
addressing". The difference is that the position of the "direct page"
can be changed. Then, "lda $fa" does not necessarily access the same
memory location as "lda $00fa" anymore. The same goes for 16- and 24-
bit addressing: "lda $fabc" does not necessarily access the same
memory location as "lda $00fabc", because the default bank can be set
to something other than zero.
But even on the plain 6502 CPU you might want to force ACME to use an
oversized addressing mode, for example because of timing issues.
Again there are two ways to solve the problem: You can define the
target location using leading zeros. ACME will then use an addressing
mode that is big enough even if the leading zeros would have been
other digits:
label1 = $fb
label2 = $00fd
label3 = $0000ff
lda $fa
sta $00fc
lda $0000fe
sta label1
lda label2
sta label3
will be assembled to
a5 fa ; lda $fa
8d fc 00 ; sta $00fc
af fe 00 00 ; lda $0000fe
85 fb ; sta $fb
ad fd 00 ; lda $00fd
8f ff 00 00 ; sta $0000ff
The other possibility is to use the postfix method (described in the
next paragraph).
*** The postfix method
Warning: This may sound very complicated at first, but I think that
once you get used to it you'll agree it's useful. If you don't want to
use this, stick to the "leading zeros" method and don't bother about
postfixes.
Still with me? Okay:
You can force ACME to use a specific addressing mode by adding "+1",
"+2" or "+3" to the assembler mnemonic. Each one of these postfixes
sets the relevant "Force Bit" in ACME's result. If Force Bit 3 is set,
ACME will use 24-bit addressing. Force Bit 2 means 16-bit addressing
and Force Bit 1 means 8-bit addressing. Higher Force Bits have higher
priorities.
Here's an (overly complicated) example:
label1 = $fb
label2 = $fd
label3+3 = $ff ; set Force Bit 3 and store in label's flags
ldx $fa
ldy+2 $fc ; set Force Bit 2 (16-bit addressing)
lda+3 $fe ; set Force Bit 3 (24-bit addressing)
stx label1
sty+2 label2 ; set Force Bit 2 (16-bit addressing)
sta label3 ; no need to set Force Bit 3 as it is
; already set in "label3".
will be assembled to
a6 fa ; ldx $fa
ac fc 00 ; ldy $00fc
af fe 00 00 ; lda $0000fe
86 fb ; stx $fb
8c fd 00 ; sty $00fd
8f ff 00 00 ; sta $0000ff
Postfixes given directly after the command have higher priority than
those given to the argument. As you can see, you can add the postfix
to the label definition as well (equivalent to leading zeros).
Applying the byte extraction operators ("<" gives the low byte, ">"
gives the high byte and "^" gives the bank byte of a value) to any
value will clear the argument's Force Bits 2 and 3 and set Force
Bit 1 instead. So "lda <label" will use 8-bit addressing, regardless
of the label's Force Bits. Of course, you can change this by
postfixing the command again... :)
*** The algorithm to find the right addressing mode
You don't need to read this paragraph just to use ACME, I only
included it for completeness' sake. This is a description of ACME's
strategy for finding the addressing mode to use:
First, ACME checks whether the command has any postfix. If it has,
ACME acts upon it. So postfixes have the highest priority.
Otherwise, ACME checks whether the argument has any Force Bits set
(because of leading zeros or byte extraction operators, for example)
or references to labels that have. If any Force Bit is set, ACME acts
upon it. This is the next priority level: Leading zeros or postfixes
added to the label definition.
Otherwise, ACME checks whether the argument could be figured out in
the first pass. If it couldn't, ACME tries to use a default addressing
mode (normally this will be 16-bit addressing).
In case the value could be figured out even in the first pass, then
everything's cool and froody: ACME just looks at the argument's value
and uses the smallest addressing mode that matches.
I admit that this algorithm sounds complicated, but it hasn't failed
yet. :) And in everyday usage, it does exactly what one expects.
If you want to take a closer look at the algorithm, examine the
"calc_arg_size" function in the "sources/mnemo.c" file.

View File

@ -0,0 +1,781 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- pseudo opcodes ---
This is a list of all the pseudo opcodes currently implemented.
Stuff in square brackets is optional, stuff followed by "*" may be
given more than once. This list is not sorted alphabetically, the
pseudo opcodes are grouped together according to their usage.
----------------------------------------------------------------------
Section: How to insert values
----------------------------------------------------------------------
Call: !8 EXPRESSION [, EXPRESSION]*
Purpose: Insert 8-bit values.
Parameters: EXPRESSION: Any formula the value parser accepts.
Aliases: "!08", "!by", "!byte"
Examples: !08 127, label, -128 ; output some values
!by 14, $3d, %0110, &304, <*, "c"
!byte 3 - 4, label1 EOR label2, 2 ^ tz, (3+4)*7
Call: !16 EXPRESSION [, EXPRESSION]*
Purpose: Insert 16-bit values.
Parameters: EXPRESSION: Any formula the value parser accepts.
Aliases: "!wo", "!word"
Examples: !16 65535, label, -32768 ; output some values
!wo 14, $4f35, %100101010010110, &36304, *, "c"
!word 3000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
Call: !24 EXPRESSION [, EXPRESSION]*
Purpose: Insert 24-bit values.
Parameters: EXPRESSION: Any formula the value parser accepts.
Examples: !24 16777215, label, -8388608, 14, $6a4f35
!24 %10010110100101010010110, &47336304, *, "c"
!24 300000 - 4, a1 AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
Call: !32 EXPRESSION [, EXPRESSION]*
Purpose: Insert 32-bit values.
Parameters: EXPRESSION: Any formula the value parser accepts.
Examples: !32 $7fffffff, label, -$80000000, 14, $46a4f35
!32 %1001011010010101001011010010, &4733630435, *, "c"
!32 300000 - 4, a AND a2, 2 ^ tz, (3+4)*70, l1 & .j2
Call: !fill AMOUNT [, VALUE]
Purpose: Fill amount of memory with value.
Parameters: AMOUNT: Any formula the value parser accepts, but it
must be solvable even in the first pass.
VALUE: Any formula the value parser accepts. If
omitted, a default value is used (currently zero).
Aliases: "!fi"
Examples: !fi 256, $ff ; reserve 256 bytes
!fill 2 ; reserve two bytes
Call: !align ANDVALUE, EQUALVALUE [, FILLVALUE]
Purpose: Fill memory until a matching address is reached. ACME
outputs FILLVALUE until "program counter AND ANDVALUE"
equals EQUALVALUE.
Parameters: ANDVALUE: Any formula the value parser accepts, but it
must be solvable even in the first pass.
EQUALVALUE: Any formula the value parser accepts, but
it must be solvable even in the first pass.
FILLVALUE: Any formula the value parser accepts. If it
is omitted, a default value is used (currently 234,
that's the 6502 CPU's NOP command).
Examples: ; eliminate the 6502's JMP()-Bug:
!align 1, 0 ; wait for even address
Label !word Pointer
; align code to page border for speed increase
!align 255, 0
----------------------------------------------------------------------
Section: How to insert text strings
----------------------------------------------------------------------
Call: !convtab KEYWORD [ { BLOCK } ]
or: !convtab FILENAME [ { BLOCK } ]
Purpose: Choose text conversion table.
Parameters: KEYWORD: Name of conversion table. Valid names are:
pet converts to PetSCII
raw doesn't convert at all
scr converts to C64 screencode
FILENAME: File name of conversion table, given in
"..." quoting (load from current directory) or in
<...> quoting (load from library). The file must hold
exactly 256 bytes.
BLOCK: A block of assembler statements
Before encountering this PO, ACME defaults to "raw".
This PO supersedes the now deprecated "!cbm".
Aliases: "!ct"
Examples: !convtab raw
!text "Test" ; outputs $54 $65 $73 $74
!ct pet
!tx "Test" ; outputs $d4 $45 $53 $54
!ct scr {
!tx "Test" ; outputs $54 $05 $13 $14
!ct "my_own_table_file"
!tx "äöüßÄÖÜ" ; whatever... :)
}
!tx "Test" ; outputs $d4 $45 $53 $54 again
Hint: If you don't want to fiddle with a hex editor to create a
conversion table file, try using ACME:
!to "asc2pet.ct", plain ; no load address
*=0 ; pc = table index
; first create "as-is" table
!for i, 256 {!byte i-1}
; now exchange upper and lower case characters
*=65
!for i, 91-65 {!byte *+128}
*=97
!for i, 123-97 {!byte *-32}
The resulting file can be used as a conversion table to convert to
PetSCII (which is useless, because ACME can do so anyway. But you get
the idea).
Call: !text STRING_VALUE [, STRING_VALUE]*
Purpose: Output the given string(s) using the current
conversion table.
Parameters: STRING_VALUE: Can be either a string given in double
quotes or any formula the value parser accepts.
Please note that formula results won't be converted,
but single characters involved in calculations will.
Aliases: "!tx"
Examples: !text "Loading...", Char_NewLine, "Filename:", 0
!tx "Offset character is ", offset-1+'a', 0
Call: !pet STRING_VALUE [, STRING_VALUE]*
Purpose: Output the given string(s) using the PetSCII
conversion table (This means to exchange the upper-
and lowercase characters; useful for C64 programs).
Parameters: STRING_VALUE: Can be either a string given in double
quotes or any formula the value parser accepts.
Please note that formula results won't be converted,
but single characters involved in calculations will.
Examples: !pet "Loading...", Char_NewLine, "Filename:", 0
!pet "Offset character is ", offset-1+'a', 0
Call: !raw STRING_VALUE [, STRING_VALUE]*
Purpose: Output the given string(s) without any conversion at
all.
Parameters: STRING_VALUE: Can be either a string given in double
quotes or any formula the value parser accepts.
Examples: !raw "Loading...", Char_NewLine, "Filename:", 0
!raw "Offset character is ", offset-1+'a', 0
Call: !scr STRING_VALUE [, STRING_VALUE]*
Purpose: Output the given string(s) using the C64 screen code
conversion table (useful for C64 programs, as you will
have guessed).
Parameters: STRING_VALUE: Can be either a string given in double
quotes or any formula the value parser accepts.
Please note that formula results won't be converted,
but single characters involved in calculations will.
Examples: !scr "Loading...", Char_NewLine, "Filename:", 0
!scr "Offset character is ", offset-1+'a', 0
Call: !scrxor XOR_VALUE, STRING_VALUE [, STRING_VALUE]*
Purpose: Output the given string(s) using the C64 screen code
conversion table and exclusive-OR-ing the results with
the given value (useful for C64 programs when inverse
video is needed, or EBC mode, etc.).
Parameters: XOR_VALUE: Any formula the value parser accepts.
STRING_VALUE: Can be either a string given in double
quotes or any formula the value parser accepts.
Please note that formula results will be neither
converted nor exclusive-OR-d.
Single characters involved in calculations will be
converted, but not exclusive-OR-d.
Examples: !scrxor $80, "Loading..."
!scrxor $a0, "Offset char is ", (offset-1+'a') EOR $a0
----------------------------------------------------------------------
Section: File stuff
----------------------------------------------------------------------
Call: !to FILENAME, FILEFORMAT
Purpose: Define the output file name and file type. If this
opcode isn't used, ACME still fully processes the
source code - as the resulting binary isn't stored,
this only serves to check for errors. Instead of using
this pseudo opcode, you can also use the command line
options "--outfile" and "--format".
Parameters: FILENAME: A file name given in "..." quoting.
FILEFORMAT: Name of file format. Valid names are:
cbm with load address (Commodore format)
plain without load address
If FILEFORMAT is omitted, ACME gives a warning and
then defaults to "cbm" (this can be changed using the
command line option "--format").
Examples: !to "eprom.p", plain ; don't add a load address
!to "demo.o", cbm ; add c64-style load address
Call: !source FILENAME
Purpose: Assemble another source code file. After having
processed the new file, ACME continues processing the
old one.
Parameters: FILENAME: A file name given in "..." quoting (load
from current directory) or in <...> quoting (load from
library).
Aliases: "!src"
Examples: !source <6502/std.a> ; Read library file
!src "Macros.a" ; Read file from current dir
Call: !binary FILENAME [, [SIZE] [, [SKIP]]]
Purpose: Insert binary file directly into output file.
Parameters: FILENAME: A file name given in "..." quoting (load
from current directory) or in <...> quoting (load from
library).
SIZE: Any formula the value parser accepts, but it
must be solvable even in the first pass. If SIZE is
given, it is used: If the file is longer, only SIZE
bytes are read; if it is shorter, ACME will use
padding until SIZE is reached. If SIZE is omitted,
ACME will include the file until EOF.
SKIP: Any formula the value parser accepts. If SKIP is
omitted, it defaults to zero. ACME will start loading
the file from file offset SKIP. So C64 coders wanting
to include C64 files without their load addresses
should use a SKIP value of 2.
Aliases: "!bin"
Examples: !binary <Own/menudata.b> ; insert library file
!bin "asc2pet.b", 256, 2 ; insert 256 bytes
; from file offset 2.
!bin "table", 2, 9 ; insert 2 bytes from offset 9
!bin "list",, 9 ; insert from offset 9 to EOF
----------------------------------------------------------------------
Section: Labels
----------------------------------------------------------------------
Call: !zone [TITLE] [ { BLOCK } ]
Purpose: Switch to new zone of local labels. Zones can either
be nested or used sequentially.
Parameters: TITLE: May consist of letters and digits. Its only
purpose is to be displayed in error messages, so it'll
be omitted in most cases.
BLOCK: A block of assembler statements
If no block is given, the previous zone is terminated
and the new zone is started.
If a block is given, the old zone continues after the
block.
Aliases: "!zn"
Examples: .backgroundcolor = 0 ; some local label
!zone File_IO ; new zone begins here
.backgroundcolor = 1 ; so this is a different label
!zn LinkedList_Init
.backgroundcolor = 2
!zone LinkedList { ; start of nested zone
; imagine some code here...
!zone LinkedList_Init
; imagine some more code here...
!zone LinkedList_Body {
; imagine yet some more code here...
!zone LinkedList_SecondPart
; imagine still some more code here...
}
!zone LinkedList_End
; you know what to imagine here...
}
.backgroundcolor = 3 ; => "Label already defined."
Call: !sl FILENAME
Purpose: Save all the global labels to the given file after the
assembly is finished. This table could be loaded
during another assembly session using the "!source"
pseudo opcode.
Parameters: FILENAME: A file name given in "..." quoting.
Examples: !sl "Labels.a" ; produce label dump after assembly
!sl "global" ; produce label dump after assembly
----------------------------------------------------------------------
Section: Flow control
----------------------------------------------------------------------
Call: !if CONDITION { BLOCK } [ else { BLOCK } ]
Purpose: Conditional assembly. If the given condition is true,
the first block of statements will be parsed;
if it isn't, the second block will be parsed instead
(if present).
Parameters: CONDITION: Any formula the value parser accepts, but
it must be solvable even in the first pass.
BLOCK: A block of assembler statements.
Examples: !text "Black", 0 ; Choose wording according to
!if country = uk { ; content of "country" label.
!text "Grey"
} else {
!text "Gray"
}
!byte 0
!text "White", 0
; Insert debug commands if label "debug" is not zero:
!if debug { lda #"z":jsr char_output }
Call: !ifdef LABEL { BLOCK } [ else { BLOCK } ]
or: !ifdef LABEL STATEMENT
Purpose: Conditional assembly, depending on whether a label is
already defined or not. If it is defined, the first
block of statements will be parsed; if it isn't, the
second block will be parsed instead (if present). This
opcode was only added to speed up parsing of library
files.
Only use it in your own files if you're sure you
*really* know what you are doing - using it in the
wrong place will result in loads of error messages.
Parameters: LABEL: Any valid label name.
BLOCK: A block of assembler statements.
STATEMENT: Any assembler statement.
Example: ; this was taken straight from <6502/std.a>:
!ifdef Lib_6502_std_a !eof ; parse this file once
Lib_6502_std_a = 1
Call: !for LABEL, TIMES { BLOCK }
Purpose: Looping assembly. The block of statements will be
parsed TIMES times. For a more flexible possibility,
have a look at "!do" below.
Parameters: LABEL: Any valid label name. The label's value will
show the number of the current loop cycle:
In the first cycle it will have the value 1, in the
last cycle it will have the value TIMES.
TIMES: Any formula the value parser accepts, but it
must be solvable even in the first pass. Negative
values are forbidden, zero causes the block to be
skipped.
BLOCK: A block of assembler statements.
Please note that it is impossible to change the number
of loop cycles "inside" the loop by fiddling with the
counter (using the "!set" pseudo opcode): The "!for"
routine keeps its own copy of the counter value and
only sets the label value, it never reads it back.
This was done to eliminate a possibility to hang ACME.
Examples: ; conversion table: integer to BCD
int2BCD !for Outer, 10 {
!for Inner, 10 {
!byte ((Outer-1) << 4) OR (Inner-1)
}
}
!fill 156, $ff ; values above 99 give 255 (invalid)
; conversion table: BCD to integer
BCD2int !for Outer, 10 {
!for Inner, 10 {
!byte 10 * (Outer-1) + (Inner-1)
}
!fill 6, $ff ; invalid BCD values give 255
}
!fill 96, $ff ; invalid BCD values give 255
Call: !set LABEL = VALUE
Purpose: Assign given value to label even if the label already
has a different value. Needed for loop counters when
using "!do", for example. Only use this opcode for
something else if you're sure you *really* know what
you are doing... :)
Parameters: LABEL: Any valid label name.
VALUE: Any formula the value parser accepts.
Example: see "!do" below
Call: !do [KEYWORD CONDITION] { BLOCK } [KEYWORD CONDITION]
Purpose: Looping assembly. The block of statements can be
parsed several times, depending on the given
condition(s).
Conditions may be placed before or after the block (or
even at both places), they are then parsed in every
repetition before or after the block respectively. If
there is a condition before the block and it isn't
met when first checked, the block will be skipped.
Parameters: KEYWORD: Either "until" or "while" (without quotes).
CONDITION: Any formula the value parser accepts, but
it must be solvable even in the first pass.
BLOCK: A block of assembler statements.
Examples: ; a loop with conditions at both start and end
!set a = 0 ; init loop counter
!do while loop_flag = TRUE {
lda #a
sta label+a
!set a = a + 1
} until a > 6
; a loop with a condition at the start
!do while * < $c000 { nop }
; a loop with a condition at the end
!do { !wo * + base } while * < base + 345
; a never ending loop - this will cause an error
!do while 3 < 4 { nop } until 3 = 4
; an empty loop - this will hang ACME
!do until 3 = 4 { } while 3 < 4
Call: !endoffile
Purpose: Stop processing the current source file. Using this
pseudo opcode you can add explanatory text inside your
source file without having to comment out every single
line of it.
Aliases: "!eof"
Example: rts ; some assembler mnemonic
!eof
Though this text isn't preceded by a semicolon, it is
treated as if it were a comment. In fact, ACME doesn't
even parse this anymore - the file gets closed when
"!eof" is reached.
Call: !warn STRING_VALUE
Purpose: Show a warning during assembly.
Parameters: STRING_VALUE: A string given in double quotes.
Example: !if * > $a000 {
!warn "Program reached ROM area."
}
Call: !error STRING_VALUE
Purpose: Generate an error during assembly (therefore, no
output file will be generated).
Parameters: STRING_VALUE: A string given in double quotes.
Example: rts ; end of some function
start !source "colors.a"
end !if end-start > 256 {
!error "Color strings exceed 256 chars!"
}
Call: !serious STRING_VALUE
Purpose: Generate a serious error, immediately stopping
assembly.
Parameters: STRING_VALUE: A string given in double quotes.
Example: !source "part1.a" ; sets part1_version
!source "part2.a" ; sets part2_version
!if part1_version != part2_version {
!serious "part1.a and part2.a don't match!"
}
----------------------------------------------------------------------
Section: Macro usage
----------------------------------------------------------------------
Call: !macro TITLE [[~]LABEL [, [~]LABEL]*] { BLOCK }
Purpose: Define a macro.
Parameters: TITLE: The macro's desired name (same rules as for
label names). If the title's first character is a dot
("."), the macro will be local (though why anyone
could want this is beyond me).
LABEL: The desired name for the parameter value at
call time. Normally, these parameter labels should be
local (first character a dot), as different macro
calls will almost for sure have different parameter
values.
If you prefix LABEL with a '~' character, it will be
called by reference, not by value: Changing the value
inside the macro will result in the "outer" label to
be changed as well.
BLOCK: A block of assembler statements.
Examples: ; far branch, as defined in <6502/std.a>
!macro bne .target {
beq * + 5
jmp .target
}
; increase 16-bit counters
!macro dinc .target {
inc .target
bne + ; "bne * + 5" would not work in zp
inc .target + 1
+
}
; Yes, anonymous label references can be used with
; macros (unlike several other assemblers). That's
; because ACME's macros are implemented more like
; real functions.
; load A and X
!macro ldax .target {
lda .target
ldx .target + 1
}
; store A and X
!macro stax .target {
sta .target
stx .target + 1
}
; use call-by-reference for return value
!macro reserve ~.address, .amount {
.address = external_pc
!set external_pc = external_pc + .amount
}
; define a pixel row of a C64 hardware sprite
!macro SpriteLine .v {
!by .v>>16, (.v>>8)&255, .v&255
}
Call: +TITLE [ARGUMENT [, ARGUMENT]*]
Purpose: Call a macro, using the given parameter values.
Parameters: TITLE: The macro's name as given in its definition.
ARGUMENT: This is either any formula the value parser
accepts, or (new in release 0.86) a '~' character
followed by a label name. The '~'-prefix indicates
call-by-reference semantics, which means that when the
macro changes the label's value, the "outer" label
value will change as well.
Examples: inc label
bne mark ; "near" branch
inc label2
+bne mark2 ; "far" branch
inc $fa ; increase 8-bit counter
+dinc $fb ; increase 16-bit counter
ldy label ; get byte
+ldax label2 ; get two bytes
; using macro calls in a macro definition
!macro cp16 .source, .target {
+ldax .source
+stax .target
}
; use call-by-reference for return value
!set external_pc = $0400
+reserve ~.line_buffer, 80
+reserve ~.in_buffer, 256
+reserve ~.out_buffer, 256
+reserve ~.byte_var, 1
; define a C64 hardware sprite
; 765432107654321076543210
+SpriteLine %........................
+SpriteLine %.#......................
+SpriteLine %.##.....................
+SpriteLine %.###....................
+SpriteLine %.####...................
+SpriteLine %.#####..................
+SpriteLine %.######.................
+SpriteLine %.#######................
+SpriteLine %.########...............
+SpriteLine %.#########..............
+SpriteLine %.########...............
+SpriteLine %.######.................
+SpriteLine %.######.................
+SpriteLine %.##..##.................
+SpriteLine %.#....##................
+SpriteLine %......##................
+SpriteLine %.......##...............
+SpriteLine %.......##...............
+SpriteLine %........##..............
+SpriteLine %........##..............
+SpriteLine %........................
!byte 0 ; pad to 64-byte block
Since release 0.86, different macros are allowed to have the same name
as long as their parameter lists differ in size (number of arguments)
or type (call-by-value vs. call-by-reference). So
!macro process_bytes b1,b2 {...whatever...}
!macro process_bytes b1,b2,b3 {...whatever...}
!macro process_bytes b1,b2,~b3 {...whatever...}
can *all* be used at the same time without any name clash.
----------------------------------------------------------------------
Section: Segment assembly
----------------------------------------------------------------------
Call: *= EXPRESSION
Purpose: Set program counter to given value and start new
segment. This opcode must be given at least once
(or the command line option "--setpc" must be used).
If segments overlap each other, warnings will be
issued (not errors, as some people do this overlapping
on purpose).
Parameters: EXPRESSION: Any formula the value parser accepts, but
it must be solvable even in the first pass.
Examples: !to "TinyDemo", cbm ; define output file + format
*= $0801 ; Start at C64 BASIC start
+basic_header ; Call program header macro
!src "main.a" ; include main program
*= $1000 ; jump to new segment
!bin "music.b" ; load music to $1000
*= $8000 ; jump to new segment
!bin "pic.b" ; load graphics to $8000
; After assembly, ACME will save everything from $0801
; up to the highest address written to. The resulting
; file will contain some big unused areas (zero'd),
; but demos will get compressed anyway... :)
Call: !initmem EXPRESSION
Purpose: Define "unchanged" memory. ACME will fill its output
buffer completely with the given value before storing
the assembled code. So gaps between segments will
contain the desired byte when writing the output file.
Instead of using this pseudo opcode, you can also use
the "--initmem" command line option. If neither is
used, the buffer is cleared.
Parameters: EXPRESSION: Any formula the value parser accepts, but
it must be solvable even in the first pass (because
this opcode will be ignored in all later passes).
Examples: !to "TinyDemo", cbm ; define output file + format
!initmem $ea ; Default memory content $ea.
*= $0801 ; Start at C64 BASIC start
+basic_header ; Call macro to create program header
!src "main.a" ; include main program
*= $1000 ; jump to new segment
!bin "music.b" ; load music to $1000
*= $8000 ; jump to new segment
!bin "pic.b" ; load graphics to $8000
; This is the same example as before, but now the big
; unused areas will contain the value $ea instead of
; zero.
!initmem $ff ; Default memory content is now $ff.
; Useful if you want to store your code in an EPROM.
----------------------------------------------------------------------
Section: Offset assembly
----------------------------------------------------------------------
Call: !pseudopc EXPRESSION [ { BLOCK } ]
Purpose: Assemble code as if the program counter had the given
value, effectively producing a program that has to be
copied to a different address space before being run.
After having processed the block of statements with
the new program counter, the updated (!) old program
counter is used again.
Thanks to the block syntax, offset assembly can now be
nested. Then the old program counter would not
necessarily be the *real* program counter, but could
be a pseudopc as well. ;)
Parameters: EXPRESSION: Any formula the value parser accepts, but
it must be solvable even in the first pass.
BLOCK: A block of assembler statements.
Examples: ldx #.shifted_end-.shifted_start
- lda .shifted_start-1,x
sta .target-1,x
dex
bne -
jmp .target
.shifted_start
!pseudopc $0400 {
.target ; imagine some code here...
; it should be copied to $0400 and executed *there*
}
.shifted_end
----------------------------------------------------------------------
Section: CPU support pseudo opcodes (especially 65816 support)
----------------------------------------------------------------------
Call: !cpu KEYWORD [ { BLOCK } ]
Purpose: Select the processor to produce code for. If this PO
isn't used, ACME defaults to the 6502 CPU (or to the
one selected by the "--cpu" command line option).
ACME will give errors if you try to assemble commands
the chosen CPU does not have. You can change the
chosen CPU at any time. When used with block syntax,
the previously chosen CPU value is restored
afterwards.
Parameters: KEYWORD: Currently valid keywords are:
6502 allows official mnemonics and addressing modes
6510 adds mnemonics for some undocumented opcodes
(but includes all the official 6502 stuff)
65c02 allows official 65c02 stuff (includes 6502)
65816 allows official 65816 stuff (includes 65c02)
BLOCK: A block of assembler statements.
Examples: !if cputype = $65c02 {
!cpu 65c02 { ; temporarily allow 65c02 stuff
stz .todelete
}
} else {
pha
lda #0
sta .todelete
pla
}
rts
!cpu 65816 ; allow 65816 commands from here on
Call: !al [ { BLOCK } ]
Purpose: Assume long (16 bits) accumulator. Only allowed when
producing code for the 65816 CPU. When used with block
syntax, the previous configuration is restored
afterwards.
Call: !as [ { BLOCK } ]
Purpose: Assume short (8 bits) accumulator. Only needed when
producing code for the 65816 CPU. When used with block
syntax, the previous configuration is restored
afterwards. Short accumulator is the default in every
pass.
Call: !rl [ { BLOCK } ]
Purpose: Assume long (16 bits) index registers. Only allowed
when producing code for the 65816 CPU. When used with
block syntax, the previous configuration is restored
afterwards.
Call: !rs [ { BLOCK } ]
Purpose: Assume short (8 bits) index registers. Only needed
when producing code for the 65816 CPU. When used with
block syntax, the previous configuration is restored
afterwards. Short registers are the default in every
pass.
----------------------------------------------------------------------
Section: Deprecated pseudo opcodes (they still work at the moment)
----------------------------------------------------------------------
Call: !cbm
Purpose: Use PetSCII as the text conversion table. Now
superseded by the "!convtab" pseudo opcode.
Old usage: !cbm ; gives "use !ct pet instead" warning
Now use: !convtab pet ; does the same without warning
Call: !subzone [TITLE] { BLOCK }
Purpose: Allows nesting of zones. Now superseded by "!zone"
because that allows nesting as well.
Parameters: TITLE: May consist of letters and digits. Its only
purpose is to be displayed in error messages, so it'll
be omitted in most cases.
BLOCK: A block of assembler statements.
Aliases: "!sz"
Old usage: !subzone graphics {
!source "graphics.a"
}
Now use: !zone graphics {
!source "graphics.a"
}
Call: !realpc
Purpose: Restore the program counter to its real value,
therefore finishing offset assembly. Because
"!pseudopc" now knows block syntax and can be nested,
there's no reason to use "!realpc" any more.
Old usage: !pseudopc $0400
; imagine some code here...
!realpc
Now use: !pseudopc $0400 {
; imagine some code here...
}

View File

@ -0,0 +1,340 @@
GNU GENERAL PUBLIC LICENSE
Version 2, June 1991
Copyright (C) 1989, 1991 Free Software Foundation, Inc.
59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
Preamble
The licenses for most software are designed to take away your
freedom to share and change it. By contrast, the GNU General Public
License is intended to guarantee your freedom to share and change free
software--to make sure the software is free for all its users. This
General Public License applies to most of the Free Software
Foundation's software and to any other program whose authors commit to
using it. (Some other Free Software Foundation software is covered by
the GNU Library General Public License instead.) You can apply it to
your programs, too.
When we speak of free software, we are referring to freedom, not
price. Our General Public Licenses are designed to make sure that you
have the freedom to distribute copies of free software (and charge for
this service if you wish), that you receive source code or can get it
if you want it, that you can change the software or use pieces of it
in new free programs; and that you know you can do these things.
To protect your rights, we need to make restrictions that forbid
anyone to deny you these rights or to ask you to surrender the rights.
These restrictions translate to certain responsibilities for you if you
distribute copies of the software, or if you modify it.
For example, if you distribute copies of such a program, whether
gratis or for a fee, you must give the recipients all the rights that
you have. You must make sure that they, too, receive or can get the
source code. And you must show them these terms so they know their
rights.
We protect your rights with two steps: (1) copyright the software, and
(2) offer you this license which gives you legal permission to copy,
distribute and/or modify the software.
Also, for each author's protection and ours, we want to make certain
that everyone understands that there is no warranty for this free
software. If the software is modified by someone else and passed on, we
want its recipients to know that what they have is not the original, so
that any problems introduced by others will not reflect on the original
authors' reputations.
Finally, any free program is threatened constantly by software
patents. We wish to avoid the danger that redistributors of a free
program will individually obtain patent licenses, in effect making the
program proprietary. To prevent this, we have made it clear that any
patent must be licensed for everyone's free use or not licensed at all.
The precise terms and conditions for copying, distribution and
modification follow.
GNU GENERAL PUBLIC LICENSE
TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
0. This License applies to any program or other work which contains
a notice placed by the copyright holder saying it may be distributed
under the terms of this General Public License. The "Program", below,
refers to any such program or work, and a "work based on the Program"
means either the Program or any derivative work under copyright law:
that is to say, a work containing the Program or a portion of it,
either verbatim or with modifications and/or translated into another
language. (Hereinafter, translation is included without limitation in
the term "modification".) Each licensee is addressed as "you".
Activities other than copying, distribution and modification are not
covered by this License; they are outside its scope. The act of
running the Program is not restricted, and the output from the Program
is covered only if its contents constitute a work based on the
Program (independent of having been made by running the Program).
Whether that is true depends on what the Program does.
1. You may copy and distribute verbatim copies of the Program's
source code as you receive it, in any medium, provided that you
conspicuously and appropriately publish on each copy an appropriate
copyright notice and disclaimer of warranty; keep intact all the
notices that refer to this License and to the absence of any warranty;
and give any other recipients of the Program a copy of this License
along with the Program.
You may charge a fee for the physical act of transferring a copy, and
you may at your option offer warranty protection in exchange for a fee.
2. You may modify your copy or copies of the Program or any portion
of it, thus forming a work based on the Program, and copy and
distribute such modifications or work under the terms of Section 1
above, provided that you also meet all of these conditions:
a) You must cause the modified files to carry prominent notices
stating that you changed the files and the date of any change.
b) You must cause any work that you distribute or publish, that in
whole or in part contains or is derived from the Program or any
part thereof, to be licensed as a whole at no charge to all third
parties under the terms of this License.
c) If the modified program normally reads commands interactively
when run, you must cause it, when started running for such
interactive use in the most ordinary way, to print or display an
announcement including an appropriate copyright notice and a
notice that there is no warranty (or else, saying that you provide
a warranty) and that users may redistribute the program under
these conditions, and telling the user how to view a copy of this
License. (Exception: if the Program itself is interactive but
does not normally print such an announcement, your work based on
the Program is not required to print an announcement.)
These requirements apply to the modified work as a whole. If
identifiable sections of that work are not derived from the Program,
and can be reasonably considered independent and separate works in
themselves, then this License, and its terms, do not apply to those
sections when you distribute them as separate works. But when you
distribute the same sections as part of a whole which is a work based
on the Program, the distribution of the whole must be on the terms of
this License, whose permissions for other licensees extend to the
entire whole, and thus to each and every part regardless of who wrote it.
Thus, it is not the intent of this section to claim rights or contest
your rights to work written entirely by you; rather, the intent is to
exercise the right to control the distribution of derivative or
collective works based on the Program.
In addition, mere aggregation of another work not based on the Program
with the Program (or with a work based on the Program) on a volume of
a storage or distribution medium does not bring the other work under
the scope of this License.
3. You may copy and distribute the Program (or a work based on it,
under Section 2) in object code or executable form under the terms of
Sections 1 and 2 above provided that you also do one of the following:
a) Accompany it with the complete corresponding machine-readable
source code, which must be distributed under the terms of Sections
1 and 2 above on a medium customarily used for software interchange; or,
b) Accompany it with a written offer, valid for at least three
years, to give any third party, for a charge no more than your
cost of physically performing source distribution, a complete
machine-readable copy of the corresponding source code, to be
distributed under the terms of Sections 1 and 2 above on a medium
customarily used for software interchange; or,
c) Accompany it with the information you received as to the offer
to distribute corresponding source code. (This alternative is
allowed only for noncommercial distribution and only if you
received the program in object code or executable form with such
an offer, in accord with Subsection b above.)
The source code for a work means the preferred form of the work for
making modifications to it. For an executable work, complete source
code means all the source code for all modules it contains, plus any
associated interface definition files, plus the scripts used to
control compilation and installation of the executable. However, as a
special exception, the source code distributed need not include
anything that is normally distributed (in either source or binary
form) with the major components (compiler, kernel, and so on) of the
operating system on which the executable runs, unless that component
itself accompanies the executable.
If distribution of executable or object code is made by offering
access to copy from a designated place, then offering equivalent
access to copy the source code from the same place counts as
distribution of the source code, even though third parties are not
compelled to copy the source along with the object code.
4. You may not copy, modify, sublicense, or distribute the Program
except as expressly provided under this License. Any attempt
otherwise to copy, modify, sublicense or distribute the Program is
void, and will automatically terminate your rights under this License.
However, parties who have received copies, or rights, from you under
this License will not have their licenses terminated so long as such
parties remain in full compliance.
5. You are not required to accept this License, since you have not
signed it. However, nothing else grants you permission to modify or
distribute the Program or its derivative works. These actions are
prohibited by law if you do not accept this License. Therefore, by
modifying or distributing the Program (or any work based on the
Program), you indicate your acceptance of this License to do so, and
all its terms and conditions for copying, distributing or modifying
the Program or works based on it.
6. Each time you redistribute the Program (or any work based on the
Program), the recipient automatically receives a license from the
original licensor to copy, distribute or modify the Program subject to
these terms and conditions. You may not impose any further
restrictions on the recipients' exercise of the rights granted herein.
You are not responsible for enforcing compliance by third parties to
this License.
7. If, as a consequence of a court judgment or allegation of patent
infringement or for any other reason (not limited to patent issues),
conditions are imposed on you (whether by court order, agreement or
otherwise) that contradict the conditions of this License, they do not
excuse you from the conditions of this License. If you cannot
distribute so as to satisfy simultaneously your obligations under this
License and any other pertinent obligations, then as a consequence you
may not distribute the Program at all. For example, if a patent
license would not permit royalty-free redistribution of the Program by
all those who receive copies directly or indirectly through you, then
the only way you could satisfy both it and this License would be to
refrain entirely from distribution of the Program.
If any portion of this section is held invalid or unenforceable under
any particular circumstance, the balance of the section is intended to
apply and the section as a whole is intended to apply in other
circumstances.
It is not the purpose of this section to induce you to infringe any
patents or other property right claims or to contest validity of any
such claims; this section has the sole purpose of protecting the
integrity of the free software distribution system, which is
implemented by public license practices. Many people have made
generous contributions to the wide range of software distributed
through that system in reliance on consistent application of that
system; it is up to the author/donor to decide if he or she is willing
to distribute software through any other system and a licensee cannot
impose that choice.
This section is intended to make thoroughly clear what is believed to
be a consequence of the rest of this License.
8. If the distribution and/or use of the Program is restricted in
certain countries either by patents or by copyrighted interfaces, the
original copyright holder who places the Program under this License
may add an explicit geographical distribution limitation excluding
those countries, so that distribution is permitted only in or among
countries not thus excluded. In such case, this License incorporates
the limitation as if written in the body of this License.
9. The Free Software Foundation may publish revised and/or new versions
of the General Public License from time to time. Such new versions will
be similar in spirit to the present version, but may differ in detail to
address new problems or concerns.
Each version is given a distinguishing version number. If the Program
specifies a version number of this License which applies to it and "any
later version", you have the option of following the terms and conditions
either of that version or of any later version published by the Free
Software Foundation. If the Program does not specify a version number of
this License, you may choose any version ever published by the Free Software
Foundation.
10. If you wish to incorporate parts of the Program into other free
programs whose distribution conditions are different, write to the author
to ask for permission. For software which is copyrighted by the Free
Software Foundation, write to the Free Software Foundation; we sometimes
make exceptions for this. Our decision will be guided by the two goals
of preserving the free status of all derivatives of our free software and
of promoting the sharing and reuse of software generally.
NO WARRANTY
11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY
FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN
OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES
PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED
OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS
TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE
PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING,
REPAIR OR CORRECTION.
12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR
REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES,
INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING
OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED
TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY
YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER
PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE
POSSIBILITY OF SUCH DAMAGES.
END OF TERMS AND CONDITIONS
How to Apply These Terms to Your New Programs
If you develop a new program, and you want it to be of the greatest
possible use to the public, the best way to achieve this is to make it
free software which everyone can redistribute and change under these terms.
To do so, attach the following notices to the program. It is safest
to attach them to the start of each source file to most effectively
convey the exclusion of warranty; and each file should have at least
the "copyright" line and a pointer to where the full notice is found.
<one line to give the program's name and a brief idea of what it does.>
Copyright (C) 19yy <name of author>
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
Also add information on how to contact you by electronic and paper mail.
If the program is interactive, make it output a short notice like this
when it starts in an interactive mode:
Gnomovision version 69, Copyright (C) 19yy name of author
Gnomovision comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
This is free software, and you are welcome to redistribute it
under certain conditions; type `show c' for details.
The hypothetical commands `show w' and `show c' should show the appropriate
parts of the General Public License. Of course, the commands you use may
be called something other than `show w' and `show c'; they could even be
mouse-clicks or menu items--whatever suits your program.
You should also get your employer (if you work as a programmer) or your
school, if any, to sign a "copyright disclaimer" for the program, if
necessary. Here is a sample; alter the names:
Yoyodyne, Inc., hereby disclaims all copyright interest in the program
`Gnomovision' (which makes passes at compilers) written by James Hacker.
<signature of Ty Coon>, 1 April 1989
Ty Coon, President of Vice
This General Public License does not permit incorporating your program into
proprietary programs. If your program is a subroutine library, you may
consider it more useful to permit linking proprietary applications with the
library. If this is what you want to do, use the GNU Library General
Public License instead of this License.

View File

@ -0,0 +1,296 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- change log ---
This text only contains descriptions of changes independent of the
platform used. There should be another help file in this archive
outlining the platform specific changes.
----------------------------------------------------------------------
Section: New in release 0.91
----------------------------------------------------------------------
Added anonymous labels (- + -- ++ --- +++ etc.). Every other assembler
seems to support them, so I added them to ACME as well... :)
New POs: "!warn MESSAGE", "!error MESSAGE", "!serious MESSAGE"
New CLI option: "--maxdepth NUMBER" sets maximum recursion depth for
macro calls and the "!source" pseudo opcode.
ACME now gives a warning when assembling JMP($xxff) on 6502/6510
because that instruction is broken on those CPUs.
After giving the error "Target out of range", the error "Number out of
range" is now suppressed.
Corrected code example in QuickRef.txt (why didn't anyone tell me? :))
Added additional example source code.
----------------------------------------------------------------------
Section: New in release 0.90
----------------------------------------------------------------------
Arithmetic shift right now has some watchdog code and should work
regardless of compiler.
Corrected some typos in error messages and docs.
New CLI option: "--cpu CPU_TYPE"
The output file format chosen with "--format FORMAT" is now used as
default when "!to" is used without format keyword.
Again: Tidier code.
----------------------------------------------------------------------
Section: New in release 0.89
----------------------------------------------------------------------
Support for more undocumented ("illegal") opcodes: anc, arr, asr, sbx,
dop, top, jam. See Illegals.txt for more info.
Change in shift operators: Logical shift right (">>" or "LSR") has on
most platforms actually been an arithmetic shift right all the
time! Therefore, ">>" now *officially* performs an arithmetic
shift right (can also be written as "ASR"), while ">>>" has been
added to perform a logical shift right (can also be written as
"LSR"). Note: This is about ACME's maths parser and has nothing to
do with the 6502 mnemonics "asl" and "lsr".
Finally added a "-o" command line option to set the output file! See
QuickRef.txt for info on the other new CLI options (--format,
--labeldump, --maxerrors, --setpc, --initmem, --version).
Fixed bug: "!align" could be used while program counter undefined.
Fixed bug: Numbers before mnemonics are no longer skipped (or rather,
implicit label definitions are no longer accepted if the label
name starts with a digit).
Change: Much better algorithm to compute to-the-power-of (read: it's
no longer braindead).
Some more internal tidying.
----------------------------------------------------------------------
Section: New in release 0.88
----------------------------------------------------------------------
Fixed architecture-dependent bug introduced in release 0.87.
Fixed bug: Unknown !cpu keywords could cause crashes.
Fixed bug in !ct "filename" nesting.
----------------------------------------------------------------------
Section: New in release 0.87
----------------------------------------------------------------------
Support for some undocumented ("illegal") opcodes: slo, rla, sre, rra,
sax, lax, dcp, isc. To use these, choose the 6510 cpu.
Two error messages gone: "Sorry, feature not yet implemented." and
"Chosen CPU does not support this command and/or addressing mode."
Explanation of new error message ("There's more than one character.")
added to docs.
----------------------------------------------------------------------
Section: New in release 0.86
----------------------------------------------------------------------
The "!convtab" pseudo opcode can now be given the file name of a
conversion table. The file must hold exactly 256 bytes.
Improved docs a bit (more and better examples, more info on verbosity
CLI switch).
If no "!to" pseudo opcode has been found, ACME will tell you so.
----------------------------------------------------------------------
Section: New in release 0.86 beta
----------------------------------------------------------------------
Macros can now be used with call-by-reference semantics, therefore
allowing some kind of return value. Call-by-reference is
indicated by prefixing the relevant parameter(s) with a '~'
character. This has to be done at both the macro definition and
the macro call.
Different macros are allowed to have the same name as long as their
parameter lists differ in size (number of arguments) or type
(call-by-value vs. call-by-reference)
Macros do not have a limit on parameter count anymore.
Macro size is unlimited now.
The expression parser does not have a limit on recursion depth
anymore, so you can use as many parentheses as you like.
Loop block size is unlimited now.
Label name and string lengths are unlimited now.
The recursion depth of "!source" and macro calls is set to 64. The
only reason there still *is* a limit is to be able to spot
infinite recursions.
Offset assembly now has block support and can be nested. Using the old
syntax still works, but gives a warning.
Pseudo opcodes "!convtab", "!cpu", "!al", "!as", "!rl" and "!rs" now
have block support and can be nested.
Using "!to" without file format indicator now gives a warning (but
still works).
Fixed bug: The statement
!to "outfile" ANY_SPECIAL_CHARACTER_BUT_COMMA GARBAGE
wasn't flagged as an error.
Fixed bug: The statement
!source "a file that cannot be opened"
did not give an error, but was just ignored.
If a global label starts with a shift-space character, a warning is
issued (because it is highly likely that it is a typing error).
*Much* cleaner internals. *Very* *much* cleaner internals actually.
More bug checking at runtime.
Tree lookups should be a bit faster.
Initialising the memory should be a bit faster.
Writing the output file should be a bit faster.
The expression parser now uses repeated multiplication instead of the
math library's pow() call, so it is no longer necessary to include
the C math library when compiling.
The number of errors displayed before assembly stops was reduced from
20 to 10. I really should make this configurable via a CLI switch.
----------------------------------------------------------------------
Section: New in release 0.85 alpha
----------------------------------------------------------------------
Fixed bug: Handling of parentheses in new expression parser was badly
screwed up. Thanks go to Nathan Smith for reporting that bug.
Verbosity messages for segments and output file now contain size info.
----------------------------------------------------------------------
Section: New in release 0.84 alpha
----------------------------------------------------------------------
Some changes in documentation (mainly corrected typos)
Usage count for labels (Unused ones are marked in label dump file)
New PO: "!8" (for 8-bit values, as "!byte" / "!by" / "!08")
Finally removed the dreaded only-two-input-files restriction
Improved PO: "!to" has parameter for choosing output file format
Fixed bug: Blanks after "!for"'s "}" character stopped assembly
Rewritten expression parser and label tree handler (should be faster)
Generally tidied up the source.
Skipped some version numbers to get a "less frightening" one. :)
----------------------------------------------------------------------
Section: New in release 0.08 beta
----------------------------------------------------------------------
Fixed really serious bug: The 65816's indirect DP addressing caused
wrong opcodes to be generated. Thanks to Doc Bacardi/The Dreams
for reporting it.
----------------------------------------------------------------------
Section: New in release 0.07 beta
----------------------------------------------------------------------
Fixed really serious bug: Indirect JMP / JSR were assembled without
target addresses. Thanks to YTM/Alliance for reporting that one.
Fixed bug in value parser's handling of parentheses: Expressions like
"a*(b-c)+d" gave "a*((b-c)+d)", obviously not the same.
Fixed bug: "!set LABEL = VALUE" now *really* works correctly.
Fixed bug: ACME gave "too late for postfix" error when reading a
predefined label of known size. Only occurred when using macros.
Fixed bug: Error messages given from within macro definitions used
truncated file names.
Fixed bug: Calling of local macros didn't work at all.
Fixed bug: "}" chars directly after macro calls were not found.
Fixed bug: Spaces after ":" and "{" gave syntax errors.
Fixed bug: Line counting inside loops was screwed up.
Fixed bug: Changed argument order of MVP and MVN (now it's "opcode,
source, target")
New PO: "!08" (for 8-bit values, as "!byte" / "!by")
New PO: "!16" (for 16-bit values, as "!word" / "!wo")
New PO: "!24" (for 24-bit values)
New PO: "!32" (for *signed* 32-bit values)
New PO: "!pseudopc" (starts offset assembly)
New PO: "!realpc" (ends offset assembly)
New PO: "!for LABEL, TIMES { LINES }" for easier loops.
New PO: "!initmem BYTE" to define empty memory.
New PO: "!endoffile" (short "!eof") replaces "!end".
New PO: "!ifdef" (only use this if you *really* know what you are
doing. Otherwise, just don't use it)
New PO: "!convtab CONVERSION" (short "!ct") selects the default
character conversion, making "!cbm" obsolete.
Improved PO: "!binary" now has "skip" parameter.
Change: "!cbm" outputs a warning when used - use "!ct pet" instead.
Change: "!end" no longer works - use "!eof" instead.
Change: "*=VALUE" is now segment change instead of offset assembly.
Change: Argument order of MVN/MVP is now as is standard.
The typecast system has been rewritten - now it works as intended.
BIT without any parameters no longer works - use a macro instead.
Leading zeros are stored in label structures and acted upon.
The documentation is in several files now.
Negative numbers are now handled much more sensibly.
'ACME' environment variable only needed when *really* needed.
----------------------------------------------------------------------
Section: New in release 0.05 beta
----------------------------------------------------------------------
Fixed bug: No more multiple error messages.
Fixed bug: Zone names now work correctly (First char wasn't stored).
Fixed bug: "!set label = label" now works correctly (I hope).
Fixed bug: "stz ...,y" gave "number too big" instead of "illegal
combination of command and addressing mode"
New PO: "!subzone" (short "!sz") for nested zones.
Added support for library tree when using "!source" or "!binary".
Single-character strings can now be given in single quotes as well.
Real icons.
Startup errors now exit correctly with EXIT_FAILURE code.
Example program now includes "Expected_Output" file.
Further tidied up the sources.
Tidied up the general help file:
-Changed "Freeware" to "free software"
-Corrected the information given on "!align".
-Added examples for most of the pseudo opcodes.
----------------------------------------------------------------------
Section: New in release 0.04 beta
----------------------------------------------------------------------
Corrected some small bugs.
New PO: "!zone" (short "!zn") replaces "!module" (short "!mod")
Tidied up the sources a lot.
Changed bad style C code reported by lint.
Added GNU GPL hint in every source file.
Added startup message in verbose mode.
Added "Error: " to startup error messages.
Added Amiga, Linux and OS/2 versions
----------------------------------------------------------------------
Section: New in release 0.03 beta
----------------------------------------------------------------------
Generally tidied up the source.
Moved RISC OS-specific CLI options to platform file.
Added pathname conversion from UNIX style to current platform style.
Added context variables (enabling "!source"s and macros).
Translated all documentation to english.
Changed string pseudo opcodes to allow numeric values.
Added verbose mode (CLI option "v").
Added output buffer, removing the need for additional output pass (and
now the "!to" pseudo opcode can be placed anywhere).
More than one "label = pc" definition per statement now illegal.
Instead added possibility to have several statements on a single line
by using ":" as a separator character.
Added new keywords: "!set", "!if", "else", "!do", "until", "while" and
"!macro"
Added support for "!source".
Added basic support for blocks.
Added support for "!if {...} else {...}".
Added support for zone titles.
Added support for loops (endless loops are only detected if producing
code).
Added support for macros (even nested definitions are possible now).
Added DOS version.
----------------------------------------------------------------------
Section: New in release 0.02 alpha
----------------------------------------------------------------------
Er, I don't know anymore. It was a bad ugly hack and it only ran on
RISC OS. :-)

View File

@ -0,0 +1,372 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- error messages ---
Here's a sorted list of all error messages ACME can give, possible
reasons and what you can do to sort it out.
----------------------------------------------------------------------
Section: Errors on startup
----------------------------------------------------------------------
Cannot open toplevel file "FILENAME".
Maybe you mistyped its name?
Error in CLI arguments: ...
There are several of these errors, but they should be quite self-
explanatory.
----------------------------------------------------------------------
Section: Warnings during assembly
----------------------------------------------------------------------
"!cbm" is deprecated; use "!ct pet" instead.
This is given when "!cbm" is encountered. It still works though.
"!pseudopc/!realpc" is deprecated; use "!pseudopc {}" instead.
"!pseudopc" can now be used with a block, so it can be nested.
So "!realpc" is no longer needed. It still works though.
"!subzone {}" is deprecated; use "!zone {}" instead.
"!zone" can now be used stand-alone (which just changes the
current zone) as well as with a block (which creates a subzone).
So "!subzone" is no longer needed. It still works though.
!warn: ...
This is given when the pseudo opcode "!warn" is executed. The
actual message varies according to the pseudo opcode's arguments.
Assembling buggy JMP($xxff) instruction
The original 6502 processor has a bug: When executing an indirect
JMP instruction where the low byte of the argument equals $ff, it
fetches the high byte of the jump target address not from memory
location ARGUMENT+1, but from ARGUMENT-255. Therefore ACME issues
this warning if you are about to generate such an instruction.
Note that this warning is only given for CPU types 6502 and 6510,
because 65c02 and 65816 have been fixed in this respect.
Bug in ACME, code follows
A situation has been encountered implying there is a bug in ACME.
See the last section in this file.
Implicit label definition not in leftmost column.
An implicit label definition has blanks before the label name.
Imagine this source code:
lda #00
imx
rts
Obviously, there's a typo in the middle line (imx instead of inx),
but ACME does not recognize this: It looks just like an implicit
label definition! Therefore releases 0.89 and higher warn you when
an implicit label does not start in column 1. Future versions may
support a command line option to switch this off again, though.
Label dump file already chosen.
The "!sl" command was given more than once (or in addition to the
"--labeldump" command line option). Only use it once.
Label name starts with a shift-space character.
The name of a global label starts with a shift-space character. It
is highly likely that this is a typing error, therefore this
warning is issued.
Memory already initialised.
The "!initmem" command was given more than once (or in addition to
the "--initmem" command line option). Only use it once.
Offset assembly still active at end of segment. Switched it off.
There's a "*=" command inside an offset assembly block. If you
know of a situation where it makes sense to do this, please tell
me.
Output file already chosen.
The "!to" command was given more than once (or in addition to the
"--outfile" command line option). Only use it once.
Segment reached another one, overwriting it.
The program counter has just reached the start of another segment.
Because some people might want to assemble "onto" a binary file
that was loaded before, this is given as a warning instead of as
an error.
Segment starts inside another one, overwriting it.
The given value in a "*=" command is located inside another
segment. Because some people might want to assemble "onto" a
binary file that was loaded before, this is given as a warning
instead of as an error.
Used "!to" without file format indicator. Defaulting to "cbm".
Now that "!to" can be given a file format keyword (either "plain"
or "cbm"), using "cbm" as default seems inappropriate. It still
works though.
Using oversized addressing mode.
ACME just assembled a command using an addressing mode that was
larger than needed. This only happens if ACME could not work out
the argument's value in the first pass, therefore assuming a 16-
bit addressing mode. If, in a later pass, ACME finds out that the
argument is small enough to fit in 8 bits, then this warning is
shown. If you define all your zeropage labels *before* they are
first used, this shouldn't happen. If you know that a specific
argument fits in 8 bits, you can force ACME to use 8 bits
addressing by postfixing the command with "+1". Example:
lda+1 label
ACME will then use an 8-bit addressing mode, regardless of whether
the label is known or not. If the label value happens to be too
large to fit in 8 bits, ACME will show an error of course (To
always truncate a value to 8 bits, use the '<' operator).
More about the postfixing method can be found in "AddrModes.txt".
----------------------------------------------------------------------
Section: Errors during assembly
----------------------------------------------------------------------
"ACME" environment variable not found.
This will be shown if the source code references any files from
the library, but the library location variable wasn't set. This
can only be given on systems using the said variable.
!error: ...
This is given when the pseudo opcode "!error" is executed. The
actual message varies according to the pseudo opcode's arguments.
Cannot open input file.
ACME had problems opening an input file ("!bin", "!convtab" or
"!src"). Maybe you mistyped its name.
Conversion table incomplete.
The conversion table file is too small. It needs to be exactly 256
bytes in size.
Division by zero.
Guess what - you attempted to divide by zero.
Exponent is negative.
Using negative exponents would only give sensible results when
using floating point maths.
File name quotes not found ("" or <>).
File names have to be given in quotes. Either "" quoting for files
located in the current directory or <> quoting for library files.
Found '}' instead of end-of-file.
ACME encountered a '}' character when it expected the file to end
instead (because no blocks were open).
Garbage data at end of statement.
There are still arguments when there should not be any more.
Illegal combination of command and addressing mode.
The given command cannot be used with the given addressing mode on
the CPU you have chosen.
Illegal combination of command and postfix.
The given command cannot be used with the addressing mode
indicated by the given postfix.
Illegal postfix.
You used a postfix other than "+1", "+2" or "+3".
Label already defined.
You defined a label that already had a different value. To change
a label's value, use the "!set" pseudo opcode.
Macro already defined.
Macros can only be defined once. If you define a macro twice, ACME
will help you find the definitions by giving a warning for the
first definition and a serious error (stopping assembly) for the
second definition.
Macro not defined (or wrong signature).
You tried to call a macro that either wasn't defined yet (always
define macros before using them) or was called with an illegal
argument list. There must be a 1:1 match between the definition's
formal parameters and the call's actual arguments.
Macro parameter twice.
The same label name is used two (or more) times in the same macro
parameter list.
Negative value - cannot choose addressing mode.
Because the argument is a negative value, ACME does not know what
addressing mode (8 bits, 16 bits, on a 65816 even 24 bits) to use.
You can overcome this problem using the postfix method. Or correct
your program to use positive addresses instead.
No string given.
ACME expects a string but doesn't find it.
Number out of range.
A value is too high or too low.
Program counter is unset.
You didn't set the program counter, so ACME didn't know where to
start.
Quotes still open at end of line.
You forgot the closing quotes.
Source file contains illegal character.
Your source code file contained a null byte.
Syntax error.
Guess what - there's a syntax error.
Target out of range.
A relative addressing (branch commands or PER) only has a limited
range. You exceeded it.
There's more than one character.
You used a text string in an arithmetic expression, but the string
contained more than a single character.
Too late for postfix.
You can only postfix labels at the start, before they are used for
the first time.
Too many '('.
A formula ends before all parentheses were closed.
Too many ')'.
There are more closing than opening parentheses in a formula.
Unknown encoding.
You used the "!convtab" command with a keyword ACME does not know.
Unknown operator.
You used an arithmetic/logical operator ACME does not know.
Remember that text versions of operators have to be given in upper
case.
Unknown output format.
You used the "!to" command with a keyword ACME does not know.
Unknown processor.
You used the "!cpu" command with a keyword ACME does not know.
Unknown pseudo opcode.
You have mistyped a "!" command.
Value not yet defined.
A value could not be worked out. Maybe you mistyped a label name.
Whether this is given as a "normal" or as a serious error depends
on the currently parsed pseudo opcode.
----------------------------------------------------------------------
Section: Serious errors (stopping assembly)
----------------------------------------------------------------------
!serious: ...
This is given when the pseudo opcode "!serious" is executed. The
actual message varies according to the pseudo opcode's arguments.
Found end-of-file instead of '}'.
The file ended when ACME expected the block to be closed instead
(because there was at least one block left open).
Loop count is negative.
You used the "!for" command with a negative loop count.
Macro already defined.
Macros can only be defined once. If you define a macro twice, ACME
will help you find both definitions by giving a warning for the
first definition and a serious error (stopping assembly) for the
second definition.
Missing '{'.
ACME didn't find the expected '{' character. Remember that '{'
characters must be given on the same line as the command they
belong to.
Out of memory.
When ACME runs out of memory, it stops assembly, giving this
error. Free some memory and try again. It's highly unlikely anyone
will ever see this error, though. ;)
Produced too much code.
The program counter reached address $10000, leaving the output
buffer. At the moment, ACME can only produce a maximum of 64 KB.
Syntax error.
This is only given as a _serious_ error if it's in a "!do" loop
condition.
Too deeply nested. Recursive macro calls?
The only reason for ACME to have a limit on macro call nesting
at all is to find infinite recursions. Current limit is 64.
Too deeply nested. Recursive "!source"?
The only reason for ACME to still have a limit on "!source"
nesting at all is to find infinite recursions. Current limit is
64.
Value not yet defined.
A value could not be worked out. Maybe you mistyped a label name.
Whether this is given as a "normal" or as a serious error depends
on the currently parsed pseudo opcode.
----------------------------------------------------------------------
Section: Errors on closedown
----------------------------------------------------------------------
Cannot open label dump file "FILENAME".
Cannot open output file "FILENAME".
Make sure the name doesn't contain wildcard characters and you
have write access to the directory.
No output file specified (use the "-o" option or the "!to" pseudo opcode).
You didn't specify the output file, so ACME did not create one.
----------------------------------------------------------------------
Section: Bugs in ACME
----------------------------------------------------------------------
The warning "Bug in ACME, code follows" is always followed by a
serious error message, stopping assembly. The second message
actually gives a hint about the bug's location in the source code.
If you ever get this combination of warning and serious error,
please send me an e-mail and tell me about it. If possible,
include a piece of source code that triggers it.
Please don't get this wrong - there are no known bugs. I just left
some debugging code in place in case there is a bug I failed to
notice during testing. In practice, this warning is not expected
to be given at all. That's the reason why I want to be notified if
it *does* decide to show up.
The hint messages are of no real interest to the end user, but here
they are for completeness' sake.
IllegalGroupIndex
The mnemonic tree contains a group that I didn't add.
IllegalBlockTerminator
A RAM block (macro or loop) was terminated incorrectly.
IllegalOperatorHandle
The expression parser found an operator that does not exist.
OperandStackNotEmpty
The expression parser has finished though there are still operands
left to parse.
OperatorStackNotEmpty
The expression parser has finished though there are still
operators left to parse.
StrangeInputMode
The input state machine has reached a state that does not exist.
StrangeParenthesis
The expression parser found a non-existing operator.

View File

@ -0,0 +1,31 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- the example source codes ---
To assemble the given example source code files, change to the
"examples" directory and type
acme ddrv.a
acme macedit.a
ACME will parse the source code files and will then produce files
called "ddrv64.prg" and "macedit.o". You may compare them to the files
called "ddrv64.exp" and "macedit.exp", to make sure ACME works as it
should do.
Just in case you wonder:
"ddrv64.prg" is a joystick/mouse driver for the C64. The source
code is fairly well documented. Have a look at it if you need more
examples on how ACME works. By changing one line in the source,
you can also generate "ddrv128.prg", a binary for the C128.
"macedit" is an unusably bad text editor for the C128. The source
code is not meant to be a good example of ACME's capabilities.
Please *don't* look at it. :)

View File

@ -0,0 +1,176 @@
ACME
...the ACME Crossassembler for Multiple Environments
Release 0.91
- free software -
(C) 1998-2006 Marco Baye
----------------------------------------------------------------------
Section: Copyright
----------------------------------------------------------------------
ACME - a crossassembler for producing 6502/6510/65c02/65816 code.
Copyright (C) 1998-2006 Marco Baye
The ACME icon was designed by Wanja "Brix" Gayk
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation; either version 2 of the License, or (at
your option) any later version.
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the
Free Software Foundation, Inc., 59 Temple Place, Suite 330,
Boston, MA 02111-1307 USA
----------------------------------------------------------------------
Section: Introduction
----------------------------------------------------------------------
ACME is a crossassembler for the 65xx range of processors. It knows
about the standard 6502, the 65c02 and the 65816. It also supports
the undocumented ("illegal") opcodes of the 6510 processor (a 6502-
variant that is used in the Commodore C=64).
This text and the other files in the same directory only describe the
basic functions independent of the platform used. There should be
another help file in this archive that outlines the features specific
to your platform.
The files in the docs directory and what they contain:
65816.txt Stuff specific to the 65816 processor
AddrModes.txt How to choose non-standard addressing modes
AllPOs.txt Lists ACME's pseudo opcodes. Use as a reference.
Changes.txt The change log.
COPYING Version 2 of the GNU General Public License
Errors.txt Lists ACME's error messages and what they mean.
Example.txt Information on how to assemble the example sources.
Help.txt ...is this text.
Illegals.txt Support for undocumented opcodes.
Lib.txt Information about the library.
QuickRef.txt All the basic stuff about ACME.
Source.txt How to compile ACME.
Upgrade.txt Incompatibilities to earlier versions.
IMPORTANT: If you upgrade from ACME 0.05 or earlier, don't forget to
read the file "Upgrade.txt" - release 0.07 and all later ones are
slightly incompatible to 0.05 and earlier.
If you want to start using ACME right away, read the file
"QuickRef.txt", it contains the main help text.
----------------------------------------------------------------------
Section: What it can and does
----------------------------------------------------------------------
ACME is a crossassembler.
ACME can produce code for the 6502, 6510, 65c02 and 65816 processors.
It does this *fast*.
It can produce at most 64 KBytes of code.
You can use global labels, local labels and anonymous labels.
It is fast.
You can use global and local macros.
You can use conditional assembly.
You can use looping assembly (There are two ways to do this; a very
simple and a very flexible one).
You can include other source files.
You can include binary files (either whole or parts) directly into the
output.
You can use offset assembly (code that is designed to run at a
different address).
It is fast.
ACME's maths parser uses operator priorities, so 1+2*3 will correctly
give 7 (unlike some other free assemblers that give 9 instead).
ACME's maths parser has no problems concerning parentheses and
indirect addressing modes.
ACME's maths parser knows a shit load of different operations.
You can dump the global labels into a file.
ACME supports a library of commonly used macros and labels.
It always takes as many passes as are needed.
ACME exists on several platforms, meaning you can easily exchange your
sources with other people (preferring other OSes).
ACME can convert its strings to PetSCII and screen code (Okay, this is
C64-specific).
Did I mention that it is fast?
----------------------------------------------------------------------
Section: What it can't and doesn't
----------------------------------------------------------------------
ACME cannot transfer data to a C64 or another computer.
ACME does not produce ".o65"-format linkable object files.
ACME cannot produce more than 64 KB (would be useful for the 65816).
ACME cannot disassemble or relocate given code files.
----------------------------------------------------------------------
Section: Platform independence
----------------------------------------------------------------------
ACME was initially developed under RISC OS. Currently there are
platform-specific versions available for AmigaOS, DOS, Linux, Windows
and RISC OS. The Linux sources should be ready to compile on most
other UNIX-like systems as well. In the future there will hopefully
also be a version that runs on the C64/128.
Though the source code does not exactly look like it *g*, ACME was
written with portability in mind: Some of its limitations were
included on purpose, just to allow a C64/128 version. To successfully
assemble multi-file source codes from other platforms, the file names
have to be altered as little as possible. Please name all your files
that may be distributed in a sensible way, for example by limiting
their file names to 8+3 format. I really hate this stupid will-it-
ever-die DOS convention, but using it is the only way to ensure
portability of files.
Please use ".a" as the file name extension of ACME source code files.
All file names used inside source code files have to be given in UNIX
style, ACME will convert them to the current host platform style if
needed.
There should be no problems concerning newline characters, ACME was
designed to cope with CR, LF and CRLF.
A minor problem is the different character tables used on different
systems. As all predefined ACME keywords only use 7-bit ASCII, the
assembler will work on any system that uses a superset of this
character table: UTF-8, ANSI, ISO 8859, etc.
Label names can contain top-bit-set characters - these may look
strange if the sources are edited on a different platform, but ACME
will still work.
If you want to port ACME to another platform, please inform me so that
I can add your version to the ones already present on the ACME
homepage. As the sources are released under the GNU General Public
License, you are not forced to do this; but it would help to make ACME
available to other users.
The same goes for any changes or enhancements to the sources: Please
send me a copy so that the changes can be incorporated into the next
"official" release on the ACME home page.
----------------------------------------------------------------------
Section: Contacting the author
----------------------------------------------------------------------
The newest version of ACME can be found at the ACME homepage:
http://home.pages.de/~mac_bacon/smorbrod/acme/
If you want to report a bug or make a suggestion, then simply send
me an email:
mailto:marco@baye.de

View File

@ -0,0 +1,130 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- Undocumented ("illegal") opcodes ---
Since release 0.87, ACME contains support for some of the undocumented
opcodes of the 6502 processor. Here they are:
| addressing mode |
Mnemo | 8 8,x 8,y 16 16,x 16,y (8,x) (8),y | performs:
------+--------------------------------------------------+-----------
slo | 07 17 -- 0f 1f 1b 03 13 | asl + ora
rla | 27 37 -- 2f 3f 3b 23 33 | rol + and
sre | 47 57 -- 4f 5f 5b 43 53 | lsr + eor
rra | 67 77 -- 6f 7f 7b 63 73 | ror + adc
sax | 87 -- 97 8f -- -- 83 -- | stx + sta
lax | a7 -- b7 af -- bf a3 b3 | ldx + lda
dcp | c7 d7 -- cf df db c3 d3 | dec + cmp
isc | e7 f7 -- ef ff fb e3 f3 | inc + sbc
In release 0.89, further ones were added:
| addressing mode |
Mnemo | implied #8 8 8,x 16 16,x | performs:
------+-------------------------------------+------------------------
anc | -- 2b -- -- -- -- | A = A & arg, then C=N
asr | -- 4b -- -- -- -- | A = A & arg, then lsr
arr | -- 6b -- -- -- -- | A = A & arg, then ror
sbx | -- cb -- -- -- -- | X = (A & X) - arg
dop | 80* 80 04 14 -- -- | skips next byte
top | 0c* -- -- -- 0c 1c | skips next two bytes
jam | 02 -- -- -- -- -- | crash (wait for reset)
Example:
!cpu 6510 ; activate additional mnemonics...
lax (some_zp_label,x) ; ...and use them. No, this
dcp (other_zp_label),y ; example does not make sense.
*) Note that "dop" and "top" can be used with implied addressing, but
the generated opcodes are those for immediate and 16-bit absolute
addressing, respectively. Using dop/top with x-indexed addressing
might have its uses when timing is critical (crossing a page border
adds a penalty cycle).
There is no guarantee that these opcodes actually work on a given 6502
(or 6510, or 8500, or 8502) CPU. But as far as I know, nobody ever
found an unmodified C64/C128 where these illegals didn't work. That's
why I used "6510" as the CPU keyword instead of "6502illegal" or
something like that.
These illegals will definitely *not* work on 65c02 and 65816 CPUs. But
I really should not have to tell you that ;)
Because there are no official mnemonics for these opcodes, different
people use different names for them. I hope my choices are not too
exotic for your taste.
Just for the sake of completeness: Here are all the remaining opcodes
(the ones ACME won't generate):
Opcode| Description
------+--------------------------------------------------------------
0b | same as 2b anc #8
12 | same as 02 and others jam CRASH
1a | same as (*legal*) ea nop
22 | same as 02 and others jam CRASH
32 | same as 02 and others jam CRASH
34 | same as 14 and others dop 8,x
3a | same as (*legal*) ea nop
3c | same as 1c and others top 16,x
42 | same as 02 and others jam CRASH
44 | same as 04 dop 8
52 | same as 02 and others jam CRASH
54 | same as 14 and others dop 8,x
5a | same as (*legal*) ea nop
5c | same as 1c and others top 16,x
62 | same as 02 and others jam CRASH
64 | same as 04 dop 8
72 | same as 02 and others jam CRASH
74 | same as 14 and others dop 8,x
7a | same as (*legal*) ea nop
7c | same as 1c and others top 16,x
82 | same as c2/e2 dop #8, but said to CRASH sometimes
89 | same as 80 dop #8
8b | see notes below
92 | same as 02 and others jam CRASH
93 | see notes below
9b | see notes below
9c | see notes below
9e | see notes below
9f | see notes below
ab | see notes below
b2 | same as 02 and others jam CRASH
bb | see notes below
c2 | same as 82/e2 dop #8, but said to CRASH sometimes
d2 | same as 02 and others jam CRASH
d4 | same as 14 and others dop 8,x
da | same as (*legal*) ea nop
dc | same as 1c and others top 16,x
e2 | same as 82/c2 dop #8, but said to CRASH sometimes
eb | same as (*legal*) e9 sbc #8
f2 | same as 02 and others jam CRASH
f4 | same as 14 and others dop 8,x
fa | same as (*legal*) ea nop
fc | same as 1c and others top 16,x
Concerning opcodes 8b, 93, 9b, 9c, 9e, 9f, ab, bb:
These opcodes are said to be unstable. For more information about what
they do, see these documents:
John West, Marko Mäkelä. '64doc' file, 1994/06/03.
Extra Instructions Of The 65XX Series CPU, Adam Vardy, 27 Sept. 1996
6502 Undocumented Opcodes, by Freddy Offenga, 5/17/1997
AAY64 (All About Your 64)
I did not see much point in assigning mnemonics for these opcodes. The
reference documents above call them:
8b: ane, xaa
93: sha, axa, ahx
9b: shs, tas, xas
9c: shy, say, sya
9e: shx, xas, sxa
9f: sha, axa, ahx
ab: lxa, oal, atx
bb: las, lar, lae

View File

@ -0,0 +1,21 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- the library ---
The files in the "ACME_Lib" directory tree can be accessed from
within ACME sources when using the pseudo opcodes "!source" or
"!binary":
If the file names are given using "..." quoting, ACME will look for
the files in the current directory.
If the file names are given using <...> quoting, ACME will look for
them in the ACME_Lib directory tree however.
All files in the ACME_Lib directory tree are in the public domain.
They are *NOT* covered by the GNU General Public License, under which
the actual ACME program is released.

View File

@ -0,0 +1,358 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- Quick reference ---
This file should give you a basic overview. More specialized stuff
like forcing a specific addressing mode is discussed in extra files
("AddrModes.txt" in this case).
----------------------------------------------------------------------
Section: Example of what an ACME source code file looks like
----------------------------------------------------------------------
;--- Example code fragment, start ---
!to "tiny.o", cbm ; set output file and format
*= $c000 ; set program counter
basout = $ffd2 ; explicit global label def.
; a string output loop:
ldx #0
beq + ; enter loop
- jsr basout ; output character
inx ; advance pointer
+ lda .string,x ; get character
bne - ; check whether last
rts
.string !pet "Dumb example", 13, 0
;--- Example code fragment, end ---
Here's the same fragment again, now with some additional info:
;--- Example code fragment, start ---
!to "tiny.o", cbm ; set output file and format
; This is a pseudo opcode to select the output filename and format.
; This can also be done using the command line options "-o" and "-f",
; respectively.
*= $c000 ; set program counter
; This can also be done using the command line option "--setpc".
basout = $ffd2 ; explicit global label def.
; Now "basout" is defined as a global label having the value $ffd2.
; a string output loop:
ldx #0
beq + ; enter loop
; "+" is an anonymous forward label. Other ones are "++", "+++", etc.
; They can be used like any other label, but they always reference
; their *NEXT* definition. This saves having to think of names for
; unimportant labels. As the label's value is not defined yet, ACME
; will need to perform a second pass.
- jsr basout ; output character
; "-" is an anonymous backward label. Other ones are "--", "---", etc.
; They can be used like any other label, but they always reference
; their *PREVIOUS* definition. This saves having to think of names for
; unimportant labels. In the line above, the value of "-" is set to
; the current program counter.
inx ; advance pointer
+ lda .string,x ; get character
; Here the value of "+" is set to the current program counter.
; ".string" is a local label (because its name starts with a '.'
; character), but as its value is not defined yet, ACME will need to
; perform a second pass.
bne - ; check whether last
; Here the last definition of the anonymous "-" label is referenced.
rts
.string !pet "Dumb example", 13, 0
; Now the value of the local label ".string" is set to the current
; program counter. All label values are defined now, so after having
; done the second pass, the binary will be saved. The "!pet" pseudo
; opcode stores its string argument in PetSCII encoding to memory,
; followed by the given byte values.
;--- Example code fragment, end ---
As you can see, pseudo opcodes are prefixed with an exclamation mark.
That's non-standard, but: Backwards compatibility is the root of all
evil. :)
Summary about labels:
There are global labels (their names starting with a letter or an
underscore character). These can be accessed throughout the whole
assembly.
Then there are local labels (their names starting with a '.'
character). These can only be accessed from inside the macro or zone
they were defined in (for more about macros and zones, see the file
"AllPOs.txt").
And then there are anonymous labels (their names being sequences of
either '-' or '+' characters). They are also local (bound to their
macro/zone), but in addition to that, the "-" labels can only be used
for backward references, while the "+" labels can only be used for
forward references.
In contrast to global and local labels, anonymous labels can not be
defined explicitly (as in LABEL=VALUE).
Save the given example source code to a file called "tiny.a" and start
acme by typing
acme tiny.a
ACME will then parse the file and report any errors. An output file
will only be generated if there were no errors and if an output
filename has been given.
After assembly, the example program can be run on a C64 using
LOAD "tiny.o",8,1
SYS 49152
Note that ACME does not include any routines for transferring data to
a C64. Such tools exist on almost every platform, and I didn't want
ACME to become bloatware.
----------------------------------------------------------------------
Section: The pseudo opcodes
----------------------------------------------------------------------
A list with information on how to use all the Pseudo Opcodes can be
found in the file "AllPOs.txt". Here's just a short overview:
!byte / !word / !24 / !32 / !fill / !align
...for directly placing values into the output file.
!zone / !sl
...for defining the scope of local labels and saving global labels.
!convtab / !pet / !raw / !scr / !scrxor / !text
...for converting and outputting strings.
!do / !endoffile / !for / !if / !ifdef / !set
...for flow control; looping assembly and conditional assembly.
!binary / !source / !to
...for handling input and output files.
!pseudopc
...for offset assembly.
!initmem *=
...for segment assembly.
!macro +
...for defining and calling macros.
!cpu !al !as !rl !rs
...for CPU support, especially the 65816 processor.
!warn !error !serious
...for generating warnings, errors and serious errors.
----------------------------------------------------------------------
Section: Command line arguments
----------------------------------------------------------------------
The command line syntax for calling acme is quite simple:
acme [options] [files]
Available options are:
-h, --help show this help and exit.
This is more or less useless, because the help is also shown
if ACME is run without any arguments at all.
-f, --format FORMAT select output format ("plain" or "cbm")
-o, --outfile FILE select output file.
Output filename and format can also be given using the "!to"
pseudo opcode. If the format is not specified, "!to" defaults
to "cbm", while the command line option defaults to "plain".
-l, --labeldump FILE select label dump file.
This can also be given using the "!sl" pseudo opcode.
--cpu CPU_TYPE set processor type.
This can be changed in the source code using the "!cpu" pseudo
opcode. Defaults to 6502.
--setpc NUMBER set program counter.
This can also be given in the source code using "*=NUMBER".
--initmem NUMBER define 'empty' memory.
This can also be given using the "!initmem" pseudo opcode.
Defaults to zero.
--maxerrors NUMBER set number of errors before exiting.
If not given, defaults to 10.
--maxdepth NUMBER set recursion depth for macro calls and the
"!source" pseudo opcode. If not given, defaults to 64.
-vDIGIT set verbosity level.
Sets how much additional informational output is generated.
Higher values mean more output:
acme -v0 source.a
This is the default: No additional output is generated,
ACME will only display warnings and errors.
acme -v1 source.a
Now the start and end addresses of the generated output
file are displayed, along with its size (a CBM-style
"load address" is *not* counted).
acme -v2 source.a
In addition to the "-v1" output, ACME will announce each
pass, will show amount and offset of "!binary" loads, and
show start and end addresses and size of each segment.
acme -v3 source.a
In addition to the "-v2" output, ACME will now announce
each source file.
-V, --version show version and exit.
Platform-specific versions of ACME might offer more options.
Since version 0.89, ACME accepts more than one top-level-filename
given on the command line.
----------------------------------------------------------------------
Section: The maths parser
----------------------------------------------------------------------
ACME has a relatively powerful maths parser. This parser is used
whenever ACME expects to read an integer value. Supported operations
include addition, subtraction, multiplication, divisions, comparisons,
shifts, negation, boolean operations and some assembler-specific stuff
like extracting the "low byte", the "high byte" or the "bank byte"
of a value.
All calculations are done using signed 32-bit integer arithmetic and
all label values are internally handled using 32 bits.
This is a list of the operators currently known by ACME:
Priority Example Meaning Alias
------------------------------------------------------------
13 ! v Complement of NOT
12 v ^ w To the power of
11 - v Negate
10 v * w Multiply
10 v / w Integer-Divide DIV
10 v % w Remainder of DIV MOD
9 v + w Add
9 v - w Subtract
8 v << w Shift left ASL, LSL
8 v >> w Arithmetic shift right ASR
8 v >>> w Logical shift right LSR
7 < v Lowbyte of
7 > v Highbyte of
7 ^ v Bankbyte of
6 v <= w Lower or equal
6 v < w Lower than
6 v >= w Higher or equal
6 v > w Higher than
5 v != w Not equal <>, ><
4 v = w Equal
3 v & w Bit-wise AND AND
2 Bit-wise exclusive OR EOR, XOR
1 v | w Bit-wise OR OR
Operations with higher priority are done first. Of course you can
change this using parentheses. If you prefer the aliases over the
shorthand characters, note that they must be written in capital
letters.
Note that though there are operators to extract the "low byte", the
"high byte" and the "bank byte", there is no operator to extract the
fourth byte. If you want to access that, shift it down using ">>>" or
"LSR".
In cases where it's not clear which operator was wanted, ACME takes
the longest possible one:
v<>w ...checks for "v not equal w"
v< >w ...checks for "v smaller than high byte of w"
So you may have to separate operators with spaces to make sure ACME
does what you want.
This is a list of the value formats currently known by ACME:
Example Meaning Prefix
---------------------------------------------------------------------
GetByte Global label None
.Loop Local label "."
9260 Decimal value None
$1a8e Hexadecimal value "$"
0x8b4f Hexadecimal value "0x"
&1054 Octal value "&"
%10010 Binary value "%"
In binary values you can substitute the characters "0" and "1" by "."
and "#" respectively. This way the values are much more readable.
"r" Character value Double quotes
'r' Character value Single quotes
The value depends on the current conversion table, chosen using the
"!ct" pseudo opcode.
* The current PC "*"
During offset assembly, "*" gives the value of the "Pseudo PC". Just
to make sure: The value of the program counter is always the value
that was valid at the start of the current statement, so
!word *, *, *, *
will give the same value four times. I think most assemblers do it
this way. Calculating 0^0 (zero to the power of zero) will give 1. If
you don't know why I'm telling you this, ask a mathematician. :)
----------------------------------------------------------------------
Section: Almost, but not quite, entirely useless syntax
----------------------------------------------------------------------
Every ACME source code file consists of a non-negative number of
"lines". The lines have to be separated from each other using CR, LF
or CRLF characters.
Every line consists of a non-negative number of "statements" and an
optional comment. Statements have to be separated from each other
using colon (":") characters, the comment has to be prefixed with a
semicolon (";") character.
Every statement consists of an optional "implicit label definition"
and an optional "command". These are separated from each other using
any number of SPACE or TAB characters. If an implicit label definition
has blanks before it, a warning is given (to spot typing errors - see
Errors.txt for more info).
Every label consists of these characters: "a" to "z", "A" to "Z", "0"
to "9", the underscore character "_" and all characters with values
beyond 127. The first character must not be a digit though. But it can
be a dot ("."), making the label a local one. Two other possibilities
for label names are "all-characters-are-minus" (then it's an anonymous
backward label) and "all-characters-are-plus" (then it's an anonymous
forward label).
Every command is one of the following:
An assembler opcode
A pseudo opcode, beginning with a "!" character
An explicit label definition (label=value)
A pc definition, beginning with a "*" character
A macro call, beginning with a "+" character
...and the syntax of those things varies. :)
Assembler mnemonics and pseudo opcodes are case insensitive, so
whether you write "LDA" or "lda" or "LdA" does not make a difference.
Arithmetic operators like MOD, XOR, LSL must be written in UPPER CASE;
this rule simply serves to make them stand out.
Label names are case sensitive, so "label" and "Label" are two
different things.

View File

@ -0,0 +1,19 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- source files ---
This program is free software, released under the terms of the GNU
General Public License. Therefore, the sources must be made publicly
accessible. If this archive does not contain the source file tree, you
can download it from the ACME web page at
http://home.pages.de/~mac_bacon/smorbrod/acme/
To build ACME, you need a recent C compiler and a "make" utility. On
most systems, typing "make" should suffice to build the binary. See
your platform's help file for more information.

View File

@ -0,0 +1,119 @@
ACME
...the ACME Crossassembler for Multiple Environments
--- compatibility problems ---
If you haven't used ACME before, you don't need to read this text.
It is only of use to people who upgraded from ACME 0.05 (or earlier)
to ACME 0.07 (or later).
You might encounter some slight incompatibilities: I have done a few
changes to ACME's workings.
Because backwards compatibility is the root of all evil (*g*), I did
not include any possibility to enforce the old behaviour. If one of
the following changes applies to your source files, assemble them with
this new release of ACME and then compare new and old output files.
Sorry for this inconvenience, but at least I think that there won't be
any further changes in the future.
----------------------------------------------------------------------
Section: Offset assembly / segment assembly
----------------------------------------------------------------------
Offset assembly is now done using a new pseudo opcode called
"!pseudopc". Have a look at "AllPOs.txt" for further information on
its syntax and usage.
The old way of just redefining the program counter by using more than
one "*= EXPRESSION" statements does something totally different now:
Whenever the program counter is redefined, ACME will actually change
its pointer into the output buffer, so you can write your code in
distinct segments. These segments can be given in any order. After
assembly, ACME stores everything from the lowest address used to the
highest address used. Have a look at "AllPOs.txt" for an example on
how to use this facility.
----------------------------------------------------------------------
Section: Argument order of MVP/MVN
----------------------------------------------------------------------
The syntax of the 65816 opcodes MVN and MVP is usually given as
MVN source_bank, destination_bank
All previous versions of ACME did it the other way round: First the
destination bank, then the source bank. This has been fixed, ACME now
uses the syntax given above.
----------------------------------------------------------------------
Section: Typecast
----------------------------------------------------------------------
You can use leading zeros to make ACME use a bigger addressing mode
than needed. Until now, this did not work when using labels. The
source code
label1 = $fa
label2 = $00fa
lda $fa
lda $00fa
lda label1
lda label2
was assembled to:
lda $fa
lda $00fa
lda $fa
lda $fa
Release 0.07 of ACME now correctly assembles the given source code to:
lda $fa
lda $00fa
lda $fa
lda $00fa
----------------------------------------------------------------------
Section: !endoffile
----------------------------------------------------------------------
Previous versions of ACME knew a pseudo opcode called "!end" that
marks the end of a source code file. Because the word "end" doesn't
actually specify *what* is about to end, I changed this to
"!endoffile". You can also use a short version, called "!eof". The old
PO "!end" no longer works.
----------------------------------------------------------------------
Section: Using the BIT command without parameters
----------------------------------------------------------------------
Release 0.07 of ACME will complain if you try to assemble BIT without
any parameter. Previous versions did just output the byte $2c - a
commonly known trick to mask the following 2-byte command on the 6502
processor. If you still want to do this, use
!src <6502/std.a> ; parse library file
to include some standard macros. Then you can use
+bit8 ; output $24 to mask following 1-byte command
and
+bit16 ; output $2c to mask following 2-byte command
respectively.
That's all. Again, sorry for the inconvenience...

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,66 @@
CFLAGS = -O3 -Wall
#LIBS = -lm
CC = gcc
RM = rm
#SRC =
PROGS = acme
BINDIR = /usr/local/bin
USERBIN = $(HOME)/bin
JAR = acme.jar
all: $(PROGS) $(JAR)
acme: acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o
$(CC) $(LIBS) $(CFLAGS) -o acme acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o
strip acme
$(JAR):
docker run -t -v "$(shell pwd):/project" mhaye/nestedvm:v4 /bin/bash -c "cd /project && nestedvm-c2jar $(JAR) acme.Acme acme.c alu.c cliargs.c cpu.c dynabuf.c encoding.c flow.c global.c input.c label.c macro.c mnemo.c output.c platform.c section.c tree.c"
acme.o: config.h acme.h alu.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h platform.h section.h acme.h acme.c
alu.o: config.h cpu.h dynabuf.h encoding.h global.h input.h label.h platform.h section.h tree.h alu.h alu.c
cliargs.o: cliargs.h cliargs.c
cpu.o: config.h alu.h dynabuf.h global.h input.h mnemo.h output.h tree.h cpu.h cpu.c
dynabuf.o: config.h acme.h global.h input.h dynabuf.h dynabuf.c
encoding.o: config.h alu.h acme.h dynabuf.h global.h output.h input.h tree.h encoding.h encoding.c
flow.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h macro.h mnemo.h tree.h flow.h flow.c
global.o: config.h acme.h cpu.h input.h label.h macro.h platform.h section.h global.h global.c
input.o: config.h dynabuf.h global.h section.h tree.h input.h input.c
label.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h mnemo.h section.h tree.h label.h label.c
macro.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h section.h tree.h macro.h macro.c
mnemo.o: config.h alu.h cpu.h dynabuf.h global.h input.h output.h tree.h mnemo.h mnemo.c
output.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h tree.h output.h output.c
platform.o: config.h platform.h platform.c
section.o: config.h dynabuf.h global.h section.h tree.h section.h section.c
tree.o: config.h dynabuf.h global.h label.h tree.h tree.c
clean:
-$(RM) -f *.o $(PROGS) *~ core *.jar
install: all
install -d $(BINDIR)
install $(PROGS) $(BINDIR)
userinstall: all
install -d $(USERBIN)
install $(PROGS) $(USERBIN)
# DO NOT DELETE

View File

@ -0,0 +1,64 @@
CFLAGS = -Wall -s
#LIBS = -lm
CC = gcc
RM = rm
#SRC =
PROGS = acme
#BINDIR = /usr/local/bin
#USERBIN = $(HOME)/bin
all: $(PROGS)
acme: acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o
$(CC) $(LIBS) $(CFLAGS) -o acme.out acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o
copy /b \djgpp\bin\pmodstub.exe + acme.out acmepmod.exe
djp acme.exe
djp acmepmod.exe
acme.o: config.h acme.h alu.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h platform.h section.h acme.h acme.c
alu.o: config.h cpu.h dynabuf.h encoding.h global.h input.h label.h platform.h section.h tree.h alu.h alu.c
cliargs.o: cliargs.h cliargs.c
cpu.o: config.h alu.h dynabuf.h global.h input.h mnemo.h output.h tree.h cpu.h cpu.c
dynabuf.o: config.h acme.h global.h input.h dynabuf.h dynabuf.c
encoding.o: config.h alu.h acme.h dynabuf.h global.h output.h input.h tree.h encoding.h encoding.c
flow.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h macro.h mnemo.h tree.h flow.h flow.c
global.o: config.h acme.h cpu.h input.h label.h macro.h platform.h section.h global.h global.c
input.o: config.h dynabuf.h global.h section.h tree.h input.h input.c
label.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h mnemo.h section.h tree.h label.h label.c
macro.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h section.h tree.h macro.h macro.c
mnemo.o: config.h alu.h cpu.h dynabuf.h global.h input.h output.h tree.h mnemo.h mnemo.c
output.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h tree.h output.h output.c
platform.o: config.h platform.h platform.c
section.o: config.h dynabuf.h global.h section.h tree.h section.h section.c
tree.o: config.h dynabuf.h global.h label.h tree.h tree.c
clean:
del *.o
# -$(RM) -f *.o $(PROGS) *~ core
#install: all
# install -d $(BINDIR)
# install $(PROGS) $(BINDIR)
#userinstall: all
# install -d $(USERBIN)
# install $(PROGS) $(USERBIN)
# DO NOT DELETE

View File

@ -0,0 +1,64 @@
CFLAGS = -O3 -mthrowback -mlibscl -Wall
#LIBS = -lm
CC = gcc
RM = rm
#SRC =
PROGS = acme
#BINDIR = /usr/local/bin
#USERBIN = $(HOME)/bin
all: $(PROGS)
acme: acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o
$(CC) $(LIBS) $(CFLAGS) -o !Unsqueezed acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o
Squeeze -f -v !Unsqueezed !ACME.!RunImage
acme.o: config.h acme.h alu.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h platform.h section.h acme.h acme.c
alu.o: config.h cpu.h dynabuf.h encoding.h global.h input.h label.h platform.h section.h tree.h alu.h alu.c
cliargs.o: cliargs.h cliargs.c
cpu.o: config.h alu.h dynabuf.h global.h input.h mnemo.h output.h tree.h cpu.h cpu.c
dynabuf.o: config.h acme.h global.h input.h dynabuf.h dynabuf.c
encoding.o: config.h alu.h acme.h dynabuf.h global.h output.h input.h tree.h encoding.h encoding.c
flow.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h macro.h mnemo.h tree.h flow.h flow.c
global.o: config.h acme.h cpu.h input.h label.h macro.h platform.h section.h global.h global.c
input.o: config.h dynabuf.h global.h section.h tree.h input.h input.c
label.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h mnemo.h section.h tree.h label.h label.c
macro.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h section.h tree.h macro.h macro.c
mnemo.o: config.h alu.h cpu.h dynabuf.h global.h input.h output.h tree.h mnemo.h mnemo.c
output.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h tree.h output.h output.c
platform.o: config.h platform.h platform.c
section.o: config.h dynabuf.h global.h section.h tree.h section.h section.c
tree.o: config.h dynabuf.h global.h label.h tree.h tree.c
clean:
wipe o.* ~c
# -$(RM) -f *.o $(PROGS) *~ core
#install: all
# install -d $(BINDIR)
# install $(PROGS) $(BINDIR)
#userinstall: all
# install -d $(USERBIN)
# install $(PROGS) $(USERBIN)
# DO NOT DELETE

View File

@ -0,0 +1,53 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff (in this case, for AmigaOS)
#ifndef platform_H
#define platform_H
// Symbolic constants and macros
// Called once on program startup (could register exit handler, if needed)
#define PLATFORM_INIT
// Convert UNIX-style pathname to Amiga-style pathname (no change)
#define PLATFORM_CONVERTPATHCHAR(a) (a)
// String containing the prefix for accessing files from the library tree
#define PLATFORM_LIBPREFIX "progdir:acme_lib/"
#define NO_NEED_FOR_ENV_VAR
// Setting file types of created files
#define PLATFORM_SETFILETYPE_CBM(a)
#define PLATFORM_SETFILETYPE_PLAIN(a)
#define PLATFORM_SETFILETYPE_TEXT(a)
// Platform specific message output
#define PLATFORM_WARNING(a)
#define PLATFORM_ERROR(a)
#define PLATFORM_SERIOUS(a)
// Integer-to-character conversion routine
#define PLATFORM_INT2BYTE(x) do {\
x ^= x >> 16;\
x ^= x >> 8;\
x &= 255;\
} while(0)
// Output of platform-specific command line switches
#define PLATFORM_OPTION_HELP
// Processing of platform-specific command line switches
#define PLATFORM_SHORTOPTION_CODE
#define PLATFORM_LONGOPTION_CODE
// other stuff
#ifdef __SASC__
#define inline
#endif
#endif

View File

@ -0,0 +1,47 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff (in this case, for DOS, OS/2 and Windows)
#ifndef platform_C
#define platform_C
#include <stdlib.h>
#include "dynabuf.h"
// Variables
char *DOS_lib_prefix = NULL; // header string of library tree
// Functions
// Called on program entry - set up DOS-specific stuff
void DOS_entry(void) {
char *env_var;
// Find out the path of ACME's library
env_var = getenv("ACME");
// if environment variable was found, make a copy
if(env_var) {
DYNABUF_CLEAR(GlobalDynaBuf);
// copy environment variable to global dynamic buffer
DynaBuf_add_string(GlobalDynaBuf, env_var);
DynaBuf_append(GlobalDynaBuf, '\\'); // add dir separator
DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator
DOS_lib_prefix = DynaBuf_get_copy(GlobalDynaBuf);
}
}
// Convert UNIX-style pathname to DOS-style pathname
char DOS_convert_path_char(char byte) {
if(byte == '/')
return('\\');
if(byte == '\\')
return('/');
return(byte);
}
#endif

View File

@ -0,0 +1,58 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff (in this case, for DOS, OS/2 and Windows)
#ifndef platform_H
#define platform_H
#include "config.h"
// Symbolic constants and macros
// Called once on program startup (could register exit handler, if needed)
#define PLATFORM_INIT DOS_entry()
// Convert UNIX-style pathname to DOS-style pathname
#define PLATFORM_CONVERTPATHCHAR(a) DOS_convert_path_char(a)
// String containing the prefix for accessing files from the library tree
#define PLATFORM_LIBPREFIX DOS_lib_prefix
// Setting file types of created files
#define PLATFORM_SETFILETYPE_CBM(a)
#define PLATFORM_SETFILETYPE_PLAIN(a)
#define PLATFORM_SETFILETYPE_TEXT(a)
// Platform specific message output
#define PLATFORM_WARNING(a)
#define PLATFORM_ERROR(a)
#define PLATFORM_SERIOUS(a)
// Integer-to-character conversion routine
#define PLATFORM_INT2BYTE(x) do {\
x ^= x >> 16;\
x ^= x >> 8;\
x &= 255;\
} while(0)
// Output of platform-specific command line switches
#define PLATFORM_OPTION_HELP
// Processing of platform-specific command line switches
#define PLATFORM_SHORTOPTION_CODE
#define PLATFORM_LONGOPTION_CODE
// Variables
extern char *DOS_lib_prefix; // header string of library tree
// Prototypes
extern void DOS_entry(void);
extern char DOS_convert_path_char(char);
#endif

View File

@ -0,0 +1,97 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff (in this case, for RISC OS)
#ifndef platform_C
#define platform_C
#include <stdlib.h>
#include <kernel.h>
#include "acme.h"
#include "input.h"
// Constants
// SWIs
#define OS_FILE 0x00008
#define XDDEUTILS_THROWBACKSTART 0x62587
#define XDDEUTILS_THROWBACKSEND 0x62588
#define XDDEUTILS_THROWBACKEND 0x62589
// Variables
int RISCOS_flags = 0; // Used to store platform-specific flags
// Functions
// Exit handler: If throwback was used, de-register now
void RISCOS_exit(void) {
_kernel_swi_regs regs;
if(RISCOS_flags & RISCOSFLAG_THROWN) {
_kernel_swi(XDDEUTILS_THROWBACKEND, &regs, &regs);
RISCOS_flags &= ~RISCOSFLAG_THROWN;
}
}
// Called on program entry - set up RISC OS-specific stuff
void RISCOS_entry(void) {
atexit(RISCOS_exit);
}
// Convert UNIX-style pathname to RISC OS-style pathname
char RISCOS_convert_path_char(char byte) {
if(byte == '.')
return('/');
if(byte == '/')
return('.');
if(byte == '?')
return('#');
if(byte == '#')
return('?');
return(byte);
}
// Setting the created files' types
void RISCOS_set_filetype(const char* filename, int file_type) {
_kernel_swi_regs regs;
regs.r[0] = 18; // reason code (set file type)
regs.r[1] = (int) filename;
regs.r[2] = file_type;
_kernel_swi(OS_FILE, &regs, &regs);
}
// Throwback protocol: "Type" can be 0, 1 or 2 (DDEUtils message types)
void RISCOS_throwback(const char* message, int type) {
_kernel_swi_regs regs;
// only use throwback protocol if wanted
if((RISCOS_flags & RISCOSFLAG_THROWBACK) == 0)
return;
// if this is the first throwback, set it up and send info
if((RISCOS_flags & RISCOSFLAG_THROWN) == 0) {
RISCOS_flags |= RISCOSFLAG_THROWN;
_kernel_swi(XDDEUTILS_THROWBACKSTART, &regs, &regs);
regs.r[0] = 0;
regs.r[1] = 0;
// regs.r[2] = (int) toplevel_source;
regs.r[2] = (int) Input_now->original_filename;
_kernel_swi(XDDEUTILS_THROWBACKSEND, &regs, &regs);
}
// send throwback message
regs.r[0] = 1;
regs.r[1] = 0;
regs.r[2] = (int) Input_now->original_filename;
regs.r[3] = Input_now->line_number;
regs.r[4] = type;
regs.r[5] = (int) message;
_kernel_swi(XDDEUTILS_THROWBACKSEND, &regs, &regs);
}
#endif

View File

@ -0,0 +1,69 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff (in this case, for RISC OS)
#ifndef platform_H
#define platform_H
#include "config.h"
// Symbolic constants and macros
// Called once on program startup (could register exit handler, if needed)
#define PLATFORM_INIT RISCOS_entry()
// Convert UNIX-style pathname to RISC OS-style pathname
#define PLATFORM_CONVERTPATHCHAR(a) RISCOS_convert_path_char(a)
// String containing the prefix for accessing files from the library tree
#define PLATFORM_LIBPREFIX "ACME_Lib:"
#define NO_NEED_FOR_ENV_VAR
// Setting file types of created files
#define PLATFORM_SETFILETYPE_CBM(a) RISCOS_set_filetype(a, 0x064)
#define PLATFORM_SETFILETYPE_PLAIN(a) RISCOS_set_filetype(a, 0xffd)
#define PLATFORM_SETFILETYPE_TEXT(a) RISCOS_set_filetype(a, 0xfff)
// Platform specific message output
#define PLATFORM_WARNING(a) RISCOS_throwback(a, 0)
#define PLATFORM_ERROR(a) RISCOS_throwback(a, 1)
#define PLATFORM_SERIOUS(a) RISCOS_throwback(a, 2)
// Integer-to-character conversion routine
#define PLATFORM_INT2BYTE(x) do {\
x ^= x >> 16;\
x ^= x >> 8;\
x &= 255;\
} while(0)
// Output of platform-specific command line switches
#define PLATFORM_OPTION_HELP \
" -t, --throwback use the DDEUtils module's \"throwback\" protocol.\n"
// Processing of platform-specific command line switches
#define PLATFORM_SHORTOPTION_CODE \
case 't': \
RISCOS_flags |= RISCOSFLAG_THROWBACK; \
break;
#define PLATFORM_LONGOPTION_CODE \
else if(strcmp(string, "throwback") == 0) \
RISCOS_flags |= RISCOSFLAG_THROWBACK;
// Variables
extern int RISCOS_flags; // Holds platform-specific flags
#define RISCOSFLAG_THROWBACK (1u << 0) // use throwback protocol
#define RISCOSFLAG_THROWN (1u << 1) // throwback is active
// Prototypes
extern void RISCOS_entry(void);
extern char RISCOS_convert_path_char(char);
extern void RISCOS_set_filetype(const char*, int);
extern void RISCOS_throwback(const char*, int);
#endif

View File

@ -0,0 +1,38 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff (in this case, for unknown OSes)
#ifndef platform_C
#define platform_C
#include <stdlib.h>
#include "dynabuf.h"
// Variables
char *AnyOS_lib_prefix = NULL; // header string of library tree
// Functions
// Called on program entry - set up platform-specific stuff
void AnyOS_entry(void) {
char *env_var;
// Find out the path of ACME's library
env_var = getenv("ACME");
// if environment variable was found, make a copy
if(env_var) {
DYNABUF_CLEAR(GlobalDynaBuf);
// copy environment variable to global dynamic buffer
DynaBuf_add_string(GlobalDynaBuf, env_var);
DynaBuf_append(GlobalDynaBuf, '/'); // add dir separator
DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator
AnyOS_lib_prefix = DynaBuf_get_copy(GlobalDynaBuf);
}
}
#endif

View File

@ -0,0 +1,55 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff (in this case, for unknown OSes)
#ifndef platform_H
#define platform_H
// Symbolic constants and macros
// Called once on program startup (could register exit handler, if needed)
#define PLATFORM_INIT AnyOS_entry()
// Convert UNIX-style pathname to AnyOS-style pathname (no change)
#define PLATFORM_CONVERTPATHCHAR(a) (a)
// String containing the prefix for accessing files from the library tree
#define PLATFORM_LIBPREFIX AnyOS_lib_prefix
// Setting the created files' types
#define PLATFORM_SETFILETYPE_CBM(a)
#define PLATFORM_SETFILETYPE_PLAIN(a)
#define PLATFORM_SETFILETYPE_TEXT(a)
// Platform specific message output
#define PLATFORM_WARNING(a)
#define PLATFORM_ERROR(a)
#define PLATFORM_SERIOUS(a)
// Integer-to-character conversion routine
#define PLATFORM_INT2BYTE(x) do {\
x ^= x >> 16;\
x ^= x >> 8;\
x &= 255;\
} while(0)
// Output of platform-specific command line switches
#define PLATFORM_OPTION_HELP
// Processing of platform-specific command line switches
#define PLATFORM_SHORTOPTION_CODE
#define PLATFORM_LONGOPTION_CODE
// Variables
extern char *AnyOS_lib_prefix; // header string of library tree
// Prototypes
extern void AnyOS_entry(void);
#endif

View File

@ -0,0 +1,376 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
//
// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU General Public License for more details.
//
// You should have received a copy of the GNU General Public License
// along with this program; if not, write to the Free Software
// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
#define RELEASE "0.91" // update before release (FIXME)
#define CODENAME "Gargravarr" // update before release
#define CHANGE_DATE "26 Mar" // update before release
#define CHANGE_YEAR "2006" // update before release
#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "acme.h"
#include "alu.h"
#include "cliargs.h"
#include "config.h"
#include "cpu.h"
#include "dynabuf.h"
#include "encoding.h"
#include "flow.h"
#include "global.h"
#include "input.h"
#include "label.h"
#include "macro.h"
#include "mnemo.h"
#include "output.h"
#include "platform.h"
#include "section.h"
// Constants
static const char FILE_WRITETEXT[] = "w";
static const char FILE_WRITEBINARY[] = "wb";
// names for error messages
static const char name_outfile[] = "output filename";
static const char name_dumpfile[] = "label dump filename";
// long options
#define OPTION_HELP "help"
#define OPTION_FORMAT "format"
#define OPTION_OUTFILE "outfile"
#define OPTION_LABELDUMP "labeldump"
#define OPTION_SETPC "setpc"
#define OPTION_CPU "cpu"
#define OPTION_INITMEM "initmem"
#define OPTION_MAXERRORS "maxerrors"
#define OPTION_MAXDEPTH "maxdepth"
#define OPTION_VERSION "version"
// Variables
static const char **toplevel_sources;
static int toplevel_src_count = 0;
static signed long starting_pc = PC_UNDEFINED;
static signed long fill_value = MEMINIT_USE_DEFAULT;
static struct cpu_t *default_cpu = NULL;
const char* labeldump_filename = NULL;
const char* output_filename = NULL;
// maximum recursion depth for macro calls and "!source"
signed long macro_recursions_left = MAX_NESTING;
signed long source_recursions_left = MAX_NESTING;
// Functions
// Show release and platform info (and exit, if wanted)
static void show_version(bool exit_after) {
puts(
"This is ACME, release " RELEASE " (\"" CODENAME "\"), " CHANGE_DATE " " CHANGE_YEAR "\n"
" " PLATFORM_VERSION
);
if(exit_after)
exit(EXIT_SUCCESS);
}
// Show full help (headline, release/platform/version, copyright, dedication,
// warranty disclaimer, usage) and exit program (SUCCESS)
static void show_help_and_exit(void) {
puts("\n"
"ACME - the ACME Crossassembler for Multiple Environments\n"
" Copyright (C) 1998-" CHANGE_YEAR " Marco Baye");
show_version(FALSE);
puts(
"ACME comes with ABSOLUTELY NO WARRANTY; for details read the help file.\n"
" This is free software, and you are welcome to redistribute it under\n"
" certain conditions; as outlined in the GNU General Public License.\n"
"Dedicated to the wisest being I ever had the pleasure of reading\n"
" books of (currently spending some time dead for tax reasons).\n"
"The newest version can be found at the ACME homepage:\n"
" " HOME_PAGE "\n"
"\n"
"Usage: acme [OPTION...] [FILE]...\n"
" -h, --" OPTION_HELP " show this help and exit.\n"
" -f, --" OPTION_FORMAT " FORMAT select output format.\n"
" -o, --" OPTION_OUTFILE " FILE select output file.\n"
" -l, --" OPTION_LABELDUMP " FILE select label dump file.\n"
" --" OPTION_SETPC " NUMBER set program counter.\n"
" --" OPTION_CPU " CPU select target processor.\n"
" --" OPTION_INITMEM " NUMBER define 'empty' memory.\n"
" --" OPTION_MAXERRORS " NUMBER set number of errors before exiting.\n"
" --" OPTION_MAXDEPTH " NUMBER set recursion depth for macro calls and !src.\n"
" -vDIGIT set verbosity level.\n"
PLATFORM_OPTION_HELP
" -V, --" OPTION_VERSION " show version and exit.\n"
);
exit(EXIT_SUCCESS);
}
// Error handling
// Tidy up before exiting by saving label dump
int ACME_finalize(int exit_code) {
FILE* fd;
if(labeldump_filename) {
fd = fopen(labeldump_filename, FILE_WRITETEXT);
if(fd) {
Label_dump_all(fd);
fclose(fd);
} else {
fprintf(stderr,
"Error: Cannot open label dump file \"%s\".\n",
labeldump_filename);
exit_code = EXIT_FAILURE;
}
}
return(exit_code);
}
// Save output file
static void save_output_file(void) {
FILE* fd;
// if no output file chosen, tell user and do nothing
if(output_filename == NULL) {
fputs("No output file specified (use the \"-o\" option or the \"!to\" pseudo opcode).", stderr);
return;
}
fd = fopen(output_filename, FILE_WRITEBINARY);
if(fd == NULL) {
fprintf(stderr, "Error: Cannot open output file \"%s\".\n",
output_filename);
return;
}
Output_save_file(fd, Mem_lowest_pc, Mem_highest_pc);
fclose(fd);
}
// Perform a single pass. Returns number of "NeedValue" type errors.
static int perform_pass(int flags) {
FILE* fd;
int i;
// be verbose
if(Process_verbosity > 1)
puts((flags & PASS_ISFIRST) ? "First pass." : (
(flags & PASS_ISERROR) ?
"Further pass needed to find error." :
"Further pass."
));
// call modules' "pass init" functions
CPU_passinit(default_cpu, starting_pc);// set default cpu values
Encoding_passinit(); // set default encoding
Section_passinit(); // set initial zone (untitled)
// init variables
pass_flags = flags;
pass_undefined_count = 0; // no "NeedValue" errors yet
pass_real_errors = 0; // no real errors yet
pc_inc = 0; // Increase PCs by this amount at end of line
// Process toplevel files
for(i = 0; i<toplevel_src_count; i++) {
if((fd = fopen(toplevel_sources[i], FILE_READBINARY)))
Parse_and_close_file(fd, toplevel_sources[i]);
else {
fprintf(stderr, "Error: Cannot open toplevel file \"%s\".\n", toplevel_sources[i]);
pass_real_errors++;
}
}
if(pass_real_errors)
exit(ACME_finalize(EXIT_FAILURE));
else
CPU_end_segment();
// free all memory blocks on "free-at-end-of-pass" list
autofree_free(&autofree_list_pass);
pass_count++;
return(pass_undefined_count);
}
// do passes until done (or errors occured). Return whether output is ready.
static bool do_actual_work(void) {
int undefined_prev, // "NeedValue" errors of previous pass
undefined_curr; // "NeedValue" errors of current pass
pass_count = 0;
undefined_curr = perform_pass(PASS_ISFIRST); // First pass
// now pretend there has been a pass before the first one
undefined_prev = undefined_curr + 1;
// As long as the number of "NeedValue" errors is decreasing but
// non-zero, keep doing passes.
while(undefined_curr && (undefined_curr < undefined_prev)) {
undefined_prev = undefined_curr;
undefined_curr = perform_pass(0);
}
// If still errors (unsolvable by doing further passes),
// perform additional pass to find and show them
if(undefined_curr == 0)
return(TRUE);
perform_pass(PASS_ISERROR);
return(FALSE);
}
// copy string to DynaBuf
static void keyword_to_dynabuf(const char keyword[]) {
DYNABUF_CLEAR(GlobalDynaBuf);
DynaBuf_add_string(GlobalDynaBuf, keyword);
DynaBuf_append(GlobalDynaBuf, '\0');
DynaBuf_to_lower(GlobalDynaBuf, GlobalDynaBuf);// convert to lower case
}
// check output format (the output format tree must be set up at this point!)
static void set_output_format(void) {
keyword_to_dynabuf(cliargs_get_string("output format"));
if(!Output_set_output_format()) {
fprintf(stderr, "%sUnknown output format (use 'cbm' or 'plain').\n", cliargs_error);
exit(EXIT_FAILURE);
}
}
// check CPU type (the cpu type tree must be set up at this point!)
static void set_starting_cpu(void) {
keyword_to_dynabuf(cliargs_get_string("CPU type"));
if(!CPU_find_cpu_struct(&default_cpu)) {
fprintf(stderr, "%sUnknown CPU type (use 6502, 6510, 65c02 or 65816).\n", cliargs_error);
exit(EXIT_FAILURE);
}
}
// set program counter
static void set_starting_pc(void) {
starting_pc = cliargs_get_long("program counter");
if(starting_pc > -1
&& starting_pc < 65536)
return;
fprintf(stderr, "%sProgram counter out of range (0-0xffff).\n", cliargs_error);
exit(EXIT_FAILURE);
}
// set initial memory contents
static void set_mem_contents(void) {
fill_value = cliargs_get_long("initmem value");
if(fill_value >= -128
&& fill_value <= 255)
return;
fprintf(stderr, "%sInitmem value out of range (0-0xff).\n", cliargs_error);
exit(EXIT_FAILURE);
}
// handle long options (like "--example"). Return unknown string.
static const char* long_option(const char* string) {
if(strcmp(string, OPTION_HELP) == 0)
show_help_and_exit();
else if(strcmp(string, OPTION_FORMAT) == 0)
set_output_format();
else if(strcmp(string, OPTION_OUTFILE) == 0)
output_filename = cliargs_get_string(name_outfile);
else if(strcmp(string, OPTION_LABELDUMP) == 0)
labeldump_filename = cliargs_get_string(name_dumpfile);
else if(strcmp(string, OPTION_SETPC) == 0)
set_starting_pc();
else if(strcmp(string, OPTION_CPU) == 0)
set_starting_cpu();
else if(strcmp(string, OPTION_INITMEM) == 0)
set_mem_contents();
else if(strcmp(string, OPTION_MAXERRORS) == 0)
max_errors = cliargs_get_long("maximum error count");
else if(strcmp(string, OPTION_MAXDEPTH) == 0)
macro_recursions_left = (source_recursions_left = cliargs_get_long("recursion depth"));
// else if(strcmp(string, "strictsyntax") == 0)
// strict_syntax = TRUE;
PLATFORM_LONGOPTION_CODE
else if(strcmp(string, OPTION_VERSION) == 0)
show_version(TRUE);
else return(string);
return(NULL);
}
// Handle short options (like "-e"). Return unknown character.
static char short_option(const char* argument) {
while(*argument) {
switch(*argument) {
case 'h': // "-h" shows help
show_help_and_exit();
case 'f': // "-f" selects output format
set_output_format();
break;
case 'o': // "-o" selects output filename
output_filename = cliargs_get_string(name_outfile);
break;
case 'l': // "-l" selects label dump filename
labeldump_filename = cliargs_get_string(name_dumpfile);
break;
case 'v': // "-v" changes verbosity
Process_verbosity++;
if((argument[1] >= '0') && (argument[1] <= '9'))
Process_verbosity = *(++argument) - '0';
break;
// platform specific switches are inserted here
PLATFORM_SHORTOPTION_CODE
case 'V': // "-V" shows version
show_version(TRUE);
break;
default: // unknown ones: program termination
return(*argument);
}
argument++;
}
return('\0');
}
// guess what
int main(int argc, const char *argv[]) {
// if called without any arguments, show usage info (not full help)
if(argc == 1)
show_help_and_exit();
cliargs_init(argc, argv);
DynaBuf_init();// inits *global* dynamic buffer - important, so first
// Init platform-specific stuff.
// For example, this could read the library path from an
// environment variable, which in turn may need DynaBuf already.
PLATFORM_INIT;
// init some keyword trees needed for argument handling
CPUtype_init();
Outputfile_init();
// handle command line arguments
cliargs_handle_options(short_option, long_option);
// generate list of files to process
cliargs_get_rest(&toplevel_src_count, &toplevel_sources,
"No top level sources given");
// Init modules (most of them will just build keyword trees)
ALU_init();
CPU_init();
Encoding_init();
Flow_init();
Input_init();
Label_init();
Macro_init();
Mnemo_init();
Output_init(fill_value);
Section_init();
if(do_actual_work())
save_output_file();
return(ACME_finalize(EXIT_SUCCESS)); // dump labels, if wanted
}

View File

@ -0,0 +1,23 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Main definitions
#ifndef acme_H
#define acme_H
#include "config.h"
// Variables
extern const char* labeldump_filename;
extern const char* output_filename;
// maximum recursion depth for macro calls and "!source"
extern signed long macro_recursions_left;
extern signed long source_recursions_left;
// Prototypes
extern int ACME_finalize(int exit_code);
#endif

Binary file not shown.

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,53 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// ALU stuff (the expression parser)
#ifndef alu_H
#define alu_H
#include "config.h"
// result structure type definition
struct result_t {
value_t value; // Expression value
int flags; // Expression flags
};
// Constants
// Meaning of bits in "Flags" of result_t structure
// Needless parentheses indicate use of indirect addressing
#define MVALUE_INDIRECT (1u << 7)
// There was *something* to parse
#define MVALUE_EXISTS (1u << 6)
// Value once was related to undefined expression
#define MVALUE_UNSURE (1u << 5)
// Value is defined (if this is cleared, the value will be zero)
#define MVALUE_DEFINED (1u << 4)
// Value is guaranteed to fit in one byte
#define MVALUE_ISBYTE (1u << 3)
// Value usage forces 24-bit usage
#define MVALUE_FORCE24 (1u << 2)
// Value usage forces 16-bit usage
#define MVALUE_FORCE16 (1u << 1)
// Value usage forces 8-bit usage
#define MVALUE_FORCE08 (1u << 0)
// Bit mask for force bits
#define MVALUE_FORCEBITS (MVALUE_FORCE08| MVALUE_FORCE16| MVALUE_FORCE24)
// Bit mask for fixed values (defined and existing)
#define MVALUE_GIVEN (MVALUE_DEFINED | MVALUE_EXISTS)
// Prototypes
extern void ALU_init(void);
extern void ALU_parse_expr_strict(result_t*);
extern void ALU_parse_expr_empty_strict(result_t*);
extern void ALU_parse_expr_medium(result_t*);
extern int ALU_parse_expr_liberal(result_t*);
#endif

View File

@ -0,0 +1,131 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// CLI argument stuff
#include <stdlib.h>
#include <stdio.h>
#include "config.h"
#include "cliargs.h"
// Constants
const char cliargs_error[] = "Error in CLI arguments: ";
// Variables
static int arguments_left; // number of CLI arguments left
static const char** next_argument; // next argument pointer
// Exported functions
// Return pointer to next command line argument (NULL if no more)
const char* cliargs_get_next(void) {
if(arguments_left == 0)
return(NULL);
arguments_left--;
return(*next_argument++);
}
// parse command line switches
void cliargs_handle_options(char (*fn_short)(const char*), const char* (*fn_long)(const char*)) {
const char *problem_string,
*argument;
char problem_char;
do {
// if there are no more arguments, return immediately
if(arguments_left == 0)
return;
// if next argument is not an option, return immediately
if((**next_argument) != '-')
return;
// officially fetch argument. We already know the
// first character is a '-', so advance pointer.
argument = cliargs_get_next() + 1;
// Check for "--"
if(*argument == '-') {
// long argument
if(argument[1] == '\0')
return; // when encountering "--", return
else {
problem_string = fn_long(argument + 1);
if(problem_string) {
fprintf(stderr, "%sUnknown option (--%s).\n", cliargs_error, problem_string);
exit(EXIT_FAILURE);
}
}
} else {
problem_char = fn_short(argument);
if(problem_char) {
fprintf(stderr, "%sUnknown switch (-%c).\n", cliargs_error, problem_char);
exit(EXIT_FAILURE);
}
}
} while(TRUE);
}
// Return next arg. If there is none, complain and exit
const char* cliargs_get_string(const char name[]) {
const char* string;
string = cliargs_get_next();
if(string)
return(string);
fprintf(stderr, "%sMissing %s.\n", cliargs_error, name);
exit(EXIT_FAILURE);
}
// Return signed long representation of next arg.
// If there is none, complain and exit.
// Copes with hexadecimal if prefixed with "$", "0x" or "0X".
// Copes with octal if prefixed with "&".
// Copes with binary if prefixed with "%".
// Assumes decimal otherwise.
signed long cliargs_get_long(const char name[]) {
signed long result;
const char *start;
char *end;
int base = 10;
start = cliargs_get_string(name);
if(*start == '%') {
base = 2;
start++;
} else if(*start == '&') {
base = 8;
start++;
} else if(*start == '$') {
base = 16;
start++;
} else if((*start == '0') && ((start[1] == 'x') || (start[1] == 'X'))) {
base = 16;
start += 2;
}
result = strtol(start, &end, base);
if(*end == '\0')
return(result);
fprintf(stderr, "%sCould not parse '%s'.\n", cliargs_error, end);
exit(EXIT_FAILURE);
}
// init command line handling stuff
const char* cliargs_init(int argc, const char *argv[]) {
arguments_left = argc;
next_argument = argv;
return(cliargs_get_next());
}
// return unhandled (non-option) arguments. Complains if none.
void cliargs_get_rest(int *argc, const char***argv, const char error[]) {
*argc = arguments_left;
*argv = next_argument;
if(error && (arguments_left == 0)) {
fprintf(stderr, "%s%s.\n", cliargs_error, error);
exit(EXIT_FAILURE);
}
}

View File

@ -0,0 +1,32 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// CLI argument stuff
#ifndef cliargs_H
#define cliargs_H
// Constants
extern const char cliargs_error[];
// Prototypes
// Handle options. Call fn_short for short options, fn_long for long ones.
extern void cliargs_handle_options(char (*fn_short)(const char*), const char* (*fn_long)(const char*));
// Return next argument.
extern const char* cliargs_get_next(void);
// Return next argument. If none left, complain with given name.
extern const char* cliargs_get_string(const char name[]);
// Return next argument as signed long. If no arguments left, complain with
// given name. On parse error, exit with error message.
extern signed long cliargs_get_long(const char name[]);
// Initialise argument handler. Returns program name (argv[0]).
extern const char* cliargs_init(int argc, const char *argv[]);
// Get unhandled args. If none left, complain with given error message.
extern void cliargs_get_rest(int *argc, const char***argv, const char error[]);
#endif

View File

@ -0,0 +1,45 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Configuration
#ifndef config_H
#define config_H
// types
typedef unsigned int zone_t;
typedef signed long value_t; // At least 32 bits
typedef unsigned long uvalue_t; // just for logical shift right
typedef struct label_t label_t;
typedef struct node_ra_t node_ra_t;
typedef struct node_t node_t;
typedef struct result_t result_t;
// Debugging flag, should be undefined in release version
// #define FDEBUG
// Maximum nesting depth of "!src" and macro calls
// Is not actually a limitation, but a means of finding recursions
#define MAX_NESTING 64
// Default value for output buffer
#define FILLVALUE_INITIAL 0
// Default value for "!fill"
#define FILLVALUE_FILL 0
// Default value for "!align" (234 = NOP)
#define FILLVALUE_ALIGN 234
// Nullpointer definition
#ifndef NULL
#define NULL ((void *)0)
#endif
// Boolean values
#ifndef FALSE
typedef int bool;
#define FALSE 0
#define TRUE 1
#endif
#endif

View File

@ -0,0 +1,319 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// CPU stuff
#include "alu.h"
#include "config.h"
#include "cpu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "mnemo.h"
#include "output.h"
#include "tree.h"
// Structure for linked list of segment data
typedef struct segment_t segment_t;
struct segment_t {
segment_t* next;
value_t start;
value_t length;
};
// Constants
static cpu_t CPU_6502 = {keyword_is_6502mnemo, CPU_FLAG_INDJMP_BUGGY};
static cpu_t CPU_6510 = {keyword_is_6510mnemo, CPU_FLAG_INDJMP_BUGGY};
static cpu_t CPU_65c02 = {keyword_is_65c02mnemo, 0};
//static cpu_t CPU_Rockwell65c02 = {keyword_is_Rockwell65c02mnemo, 0};
//static cpu_t CPU_WDC65c02 = {keyword_is_WDC65c02mnemo, 0};
static cpu_t CPU_65816 = {
keyword_is_65816mnemo,
CPU_FLAG_LONG_REGS | CPU_FLAG_IMM16
};
// Variables
cpu_t *CPU_now; // Struct of current CPU type (default 6502)
bool CPU65816_long_a;// Flag for long accumulator (default off)
bool CPU65816_long_r;// Flag for long index registers (default off)
value_t CPU_pc; // (Pseudo) program counter at start of statement
static segment_t* segment_list; // linked list of segment structures
// predefined stuff
static node_t* CPU_tree = NULL;// tree to hold CPU types
static node_t CPUs[] = {
// PREDEFNODE("z80", &CPU_Z80),
PREDEFNODE("6502", &CPU_6502),
PREDEFNODE("6510", &CPU_6510),
PREDEFNODE("65c02", &CPU_65c02),
// PREDEFNODE("Rockwell65c02", &CPU_Rockwell65c02),
// PREDEFNODE("WDC65c02", &CPU_WDC65c02),
PREDEFLAST("65816", &CPU_65816),
// ^^^^ this marks the last element
};
// init lowest and highest address
static void init_borders(value_t first_pc) {
Mem_lowest_pc = first_pc;
Mem_highest_pc = first_pc;
}
// set new program counter value
static void set_new_pc(value_t new_pc) {
segment_start = new_pc;
CPU_pc = new_pc;
Mem_current_pc = new_pc;
}
// Link segment data into segment chain
static void link_segment(value_t start, value_t length) {
segment_t *new_segment;
// may be faster if list is ordered!
new_segment = ALLOC_PASS(sizeof(segment_t));
new_segment->start = start;
new_segment->length = length;
new_segment->next = segment_list;
segment_list = new_segment;
}
// Show start and end of current segment
void CPU_end_segment(void) {
if(Mem_current_pc != CPU_pc)
Throw_warning("Offset assembly still active at end of segment. Switched it off.");
if(pass_flags & PASS_ISFIRST) {
link_segment(segment_start, Mem_current_pc - segment_start);
if(Process_verbosity > 1)
printf("Segment size is %ld ($%lx) bytes ($%lx - $%lx exclusive).\n", Mem_current_pc - segment_start, Mem_current_pc - segment_start, segment_start, Mem_current_pc);
}
}
// Set up new segment_max value according to the given program counter. Just
// find the next segment start and subtract 1.
void CPU_find_segment_max(value_t new_pc) {
segment_t* test_segment;
// may be faster if list is ordered!
segment_max = OUTBUFFERSIZE;// will be decremented later!
test_segment = segment_list;
while(test_segment) {
if((test_segment->start > new_pc)
&& (test_segment->start < segment_max))
segment_max = test_segment->start;
test_segment = test_segment->next;
}
segment_max--;// last free address available
}
// Check whether given PC is inside segment.
static void check_segment(value_t new_pc) {
segment_t* test_segment;
test_segment = segment_list;
while(test_segment) {
if((new_pc >= test_segment->start)
&& (new_pc < (test_segment->start) + (test_segment->length)))
Throw_warning("Segment starts inside another one, overwriting it.");
test_segment = test_segment->next;
}
}
// Parse (re-)definitions of program counter
void CPU_set_pc(void) {// Now GotByte = "*"
result_t new_pc;
NEXTANDSKIPSPACE(); // proceed with next char
// re-definitions of program counter change segment
if(GotByte == '=') {
GetByte();// proceed with next char
ALU_parse_expr_strict(&new_pc);
if(CPU_pc != PC_UNDEFINED) {
// It's a redefinition. Check some things:
// check whether new low
if(new_pc.value < Mem_lowest_pc)
Mem_lowest_pc = new_pc.value;
// show status of previous segment
CPU_end_segment();
// in first pass, maybe issue warning
if(pass_flags & PASS_ISFIRST) {
check_segment(new_pc.value);
CPU_find_segment_max(new_pc.value);
}
} else // it's the first pc definition
init_borders(new_pc.value);
set_new_pc(new_pc.value);
Input_ensure_EOS();
} else {
Throw_error(exception_syntax);
Input_skip_remainder();
}
}
// make sure PC is defined. If not, complain and set to dummy value
// FIXME - get rid of this function as it slows down Output_byte
inline void CPU_ensure_defined_pc(void) {
if(CPU_pc == PC_UNDEFINED) {
Throw_error("Program counter is unset.");
CPU_pc = PC_DUMMY;
}
}
// Insert byte until PC fits conditions
static enum eos_t PO_align(void) {
result_t and,
equal,
fill;
value_t test = CPU_pc;
CPU_ensure_defined_pc();
ALU_parse_expr_strict(&and);
fill.value = FILLVALUE_ALIGN;
if(!Input_accept_comma())
Throw_error(exception_syntax);
ALU_parse_expr_strict(&equal);
if(Input_accept_comma())
ALU_parse_expr_medium(&fill);
while((test++ & and.value) != equal.value)
Output_8b(fill.value);
return(ENSURE_EOS);
}
// Try to find CPU type held in DynaBuf. Returns whether succeeded.
bool CPU_find_cpu_struct(cpu_t** target) {
void* node_body;
if(!Tree_easy_scan(CPU_tree, &node_body, GlobalDynaBuf))
return(FALSE);
*target = node_body;
return(TRUE);
}
// Select CPU ("!cpu" pseudo opcode)
static enum eos_t PO_cpu(void) {
cpu_t* cpu_buffer = CPU_now; // remember current cpu
if(Input_read_and_lower_keyword())
if(!CPU_find_cpu_struct(&CPU_now))
Throw_error("Unknown processor.");
// If there's a block, parse that and then restore old value!
if(Parse_optional_block())
CPU_now = cpu_buffer;
return(ENSURE_EOS);
}
static const char Warning_old_offset_assembly[] =
"\"!pseudopc/!realpc\" is deprecated; use \"!pseudopc {}\" instead.";
// Start offset assembly
static enum eos_t PO_pseudopc(void) {
result_t result;
value_t offset = CPU_pc - Mem_current_pc;
// set new
ALU_parse_expr_strict(&result);
CPU_pc = result.value;
// If there's a block, parse that and then restore old value!
if(Parse_optional_block())
CPU_pc = (Mem_current_pc + offset) & 0xffff; // restore old
else if(pass_flags & PASS_ISFIRST)
Throw_warning(Warning_old_offset_assembly);
return(ENSURE_EOS);
}
// End offset assembly
static enum eos_t PO_realpc(void) {
if(pass_flags & PASS_ISFIRST)
Throw_warning(Warning_old_offset_assembly);
CPU_pc = Mem_current_pc;// deactivate offset assembly
return(ENSURE_EOS);
}
// If cpu type and value match, set register length variable to value.
// If cpu type and value don't match, complain instead.
static void check_and_set_reg_length(int *var, bool long_reg) {
if(long_reg && ((CPU_now->flags & CPU_FLAG_LONG_REGS) == 0))
Throw_error("Chosen CPU does not support long registers.");
else
*var = long_reg;
}
// Set register length, block-wise if needed.
static enum eos_t set_register_length(int* var, bool long_reg) {
int buffer = *var;
// Set new register length (or complain - whichever is more fitting)
check_and_set_reg_length(var, long_reg);
// If there's a block, parse that and then restore old value!
if(Parse_optional_block())
check_and_set_reg_length(var, buffer);// restore old length
return(ENSURE_EOS);
}
// Switch to long accu ("!al" pseudo opcode)
static enum eos_t PO_al(void) {
return(set_register_length(&CPU65816_long_a, TRUE));
}
// Switch to short accu ("!as" pseudo opcode)
static enum eos_t PO_as(void) {
return(set_register_length(&CPU65816_long_a, FALSE));
}
// Switch to long index registers ("!rl" pseudo opcode)
static enum eos_t PO_rl(void) {
return(set_register_length(&CPU65816_long_r, TRUE));
}
// Switch to short index registers ("!rs" pseudo opcode)
static enum eos_t PO_rs(void) {
return(set_register_length(&CPU65816_long_r, FALSE));
}
// pseudo opcode table
static node_t pseudo_opcodes[] = {
PREDEFNODE("align", PO_align),
PREDEFNODE("cpu", PO_cpu),
PREDEFNODE("pseudopc", PO_pseudopc),
PREDEFNODE("realpc", PO_realpc),
PREDEFNODE("al", PO_al),
PREDEFNODE("as", PO_as),
PREDEFNODE("rl", PO_rl),
PREDEFLAST("rs", PO_rs),
// ^^^^ this marks the last element
};
// Set default values for pass
void CPU_passinit(cpu_t* cpu_type, signed long starting_pc) {
// handle cpu type (default is 6502)
CPU_now = cpu_type ? cpu_type : &CPU_6502;
CPU65816_long_a = FALSE; // short accumulator
CPU65816_long_r = FALSE; // short index registers
// handle program counter
if(starting_pc == PC_UNDEFINED) {
init_borders(0); // set to _something_ (for !initmem)
segment_start = 0;
CPU_pc = PC_UNDEFINED;
Mem_current_pc = 0;
} else {
init_borders(starting_pc);
set_new_pc(starting_pc);
}
// other stuff
segment_list = NULL;
segment_max = OUTBUFFERSIZE-1;
}
// init cpu type tree (is done early)
void CPUtype_init(void) {
Tree_add_table(&CPU_tree, CPUs);
}
// init other stuff (done later)
void CPU_init(void) {
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}

View File

@ -0,0 +1,47 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// CPU stuff
#ifndef cpu_H
#define cpu_H
#include "config.h"
// CPU type structure definition
struct cpu_t {
// This routine is not allowed to change GlobalDynaBuf
// because that's where the mnemonic is stored!
bool (*keyword_is_mnemonic)(int);
int flags;
};
#define CPU_FLAG_LONG_REGS (1u << 0)
#define CPU_FLAG_IMM16 (1u << 1)
#define CPU_FLAG_INDJMP_BUGGY (1u << 2)
typedef struct cpu_t cpu_t;
// Variables
extern cpu_t *CPU_now; // Struct of current CPU type (default 6502)
extern bool CPU65816_long_a; // Flags for long accumulator and long
extern bool CPU65816_long_r; // index registers (both default off)
// FIXME - move the 65816 stuff out of general CPU stuff?
extern value_t CPU_pc; // Current program counter (pseudo value)
#define PC_UNDEFINED -1 // value of PC when undefined
#define PC_DUMMY 512 // dummy value to use when looking for errors
// Prototypes
extern void CPUtype_init(void);
extern void CPU_init(void);
extern void CPU_passinit(cpu_t* cpu_type, signed long starting_pc);
extern void CPU_end_segment(void);
extern void CPU_find_segment_max(value_t);
extern void CPU_set_pc(void);
extern inline void CPU_ensure_defined_pc(void);
extern bool CPU_find_cpu_struct(cpu_t** target);
#endif

View File

@ -0,0 +1,138 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Dynamic buffer stuff
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "acme.h"
#include "global.h"
#include "dynabuf.h"
#include "input.h"
// Constants and macros
// macro to grow dynabuf (CAUTION - fails if a < 1)
#define MAKE_LARGER_THAN(a) (2*(a))
// if someone requests a dynabuf smaller than this, use this size instead
#define DYNABUF_MINIMUM_INITIALSIZE 128 // should be >0 (see above)
// initial size for global dynabuf
// (as it holds macros, loop bodies, etc., make it large to begin with)
#define GLOBALDYNABUF_INITIALSIZE 1024 // should be >0 (see above)
// Variables
dynabuf_t* GlobalDynaBuf; // global dynamic buffer
// Functions
// get new buffer of given size
static void resize(dynabuf_t* db, size_t new_size) {
char* new_buf;
new_buf = realloc(db->buffer, new_size);
if(new_buf == NULL)
Throw_serious_error(exception_no_memory_left);
db->reserved = new_size;
db->buffer = new_buf;
}
// Exported functions
// Create and init a dynamic buffer and return pointer
dynabuf_t* DynaBuf_create(int initial_size) {
dynabuf_t* db;
if(initial_size < DYNABUF_MINIMUM_INITIALSIZE)
initial_size = DYNABUF_MINIMUM_INITIALSIZE;
if((db = malloc(sizeof(dynabuf_t)))) {
db->size = 0;
db->reserved = initial_size;
db->buffer = malloc(initial_size);
if(db->buffer)
return(db);// if both pointers are != NULL, no error
}
// otherwise, complain
fputs("Error: No memory for dynamic buffer.\n", stderr);
exit(EXIT_FAILURE);
}
// Enlarge buffer
void DynaBuf_enlarge(dynabuf_t* db) {
resize(db, MAKE_LARGER_THAN(db->reserved));
}
// Claim enough memory to hold a copy of the current buffer contents,
// make that copy and return it.
// The copy must be released by calling free().
char* DynaBuf_get_copy(dynabuf_t* db) {
char *copy;
copy = safe_malloc(db->size);
memcpy(copy, db->buffer, db->size);
return(copy);
}
// add char to buffer
void DynaBuf_append(dynabuf_t* db, char byte) {
DYNABUF_APPEND(db, byte);
}
// Append string to buffer (without terminator)
void DynaBuf_add_string(dynabuf_t* db, const char* string) {
char byte;
while((byte = *string++))
DYNABUF_APPEND(db, byte);
}
// add string version of value to buffer (without terminator)
void DynaBuf_add_value(dynabuf_t* db, value_t value) {
value_t header = value / 10,
last = value % 10;
// cater for negative values. "value = -value" would fail for -2^31
if(value < 0) {
DynaBuf_append(db, '-');
header = -header;
last = -last;
}
if(last < 0) {
last += 10;
header--;
}
if(header)
DynaBuf_add_value(db, header);
DynaBuf_append(db, "0123456789"[last]);
}
// Convert buffer contents to lower case (target and source may be identical)
void DynaBuf_to_lower(dynabuf_t* target, dynabuf_t* source) {
char *read,
*write;
// make sure target can take it
if(source->size > target->reserved)
resize(target, source->size);
// convert to lower case
read = source->buffer;// CAUTION - ptr may change when buf grows!
write = target->buffer;// CAUTION - ptr may change when buf grows!
while(*read)
*write++ = (*read++) | 32;
// Okay, so this method of converting to lowercase is lousy.
// But actually it doesn't matter, because only pre-defined
// keywords are converted, and all of those are plain
// old-fashioned 7-bit ASCII anyway. So I guess it'll do.
*write = '\0'; // terminate
}
// Initialisation - allocate global dynamic buffer
void DynaBuf_init(void) {
GlobalDynaBuf = DynaBuf_create(GLOBALDYNABUF_INITIALSIZE);
}

View File

@ -0,0 +1,59 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Dynamic buffer stuff
#ifndef dynabuf_H
#define dynabuf_H
#include "config.h"
// Macros
#define DYNABUF_CLEAR(db) {db->size = 0;}
#define DYNABUF_APPEND(db, byte) do {\
if(db->size == db->reserved)\
DynaBuf_enlarge(db);\
db->buffer[(db->size)++] = byte;\
} while(0)
// The next one is dangerous - the buffer location can change when a character
// is appended. So after calling this, don't change the buffer as long as you
// use the address.
#define GLOBALDYNABUF_CURRENT (GlobalDynaBuf->buffer)
// dynamic buffer structure
struct dynabuf_t {
char* buffer; // pointer to buffer
int size; // size of buffer's used portion
int reserved; // total size of buffer
};
typedef struct dynabuf_t dynabuf_t;
// Variables
extern dynabuf_t* GlobalDynaBuf; // global dynamic buffer
// Prototypes
// create global DynaBuf (call once on program startup)
extern void DynaBuf_init(void);
// create (private) DynaBuf
extern dynabuf_t* DynaBuf_create(int initial_size);
// call whenever buffer is too small
extern void DynaBuf_enlarge(dynabuf_t* db);
// returns copy of buffer's contents
extern char* DynaBuf_get_copy(dynabuf_t* db);
// copies string to buffer (without terminator)
extern void DynaBuf_add_string(dynabuf_t* db, const char*);
// add string version of value to buffer (without terminator)
extern void DynaBuf_add_value(dynabuf_t* db, value_t value);
// converts buffer contents to lower case
extern void DynaBuf_to_lower(dynabuf_t* db, dynabuf_t*);
// add char to buffer
extern void DynaBuf_append(dynabuf_t* db, char);
#endif

View File

@ -0,0 +1,245 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Character encoding stuff
#include <stdio.h>
#include <string.h>
#include "alu.h"
#include "acme.h"
#include "dynabuf.h"
#include "encoding.h"
#include "global.h"
#include "output.h"
#include "input.h"
#include "tree.h"
// Encoder function type definition
typedef char (*encoder_t)(char) ;
// Constants
static const char s_pet[] = "pet";
static const char s_raw[] = "raw";
static const char s_scr[] = "scr";
// Variables
// Conversion function pointer. No init needed: gets set before each pass.
char (*Encoding_encode_char)(char);
static char outermost_table[256]; // space for encoding table...
static char* loaded_table = outermost_table; // ...loaded from file
// predefined stuff
static node_t* encoder_tree = NULL; // tree to hold encoders
// Functions
// Insert string(s)
static enum eos_t encode_string(encoder_t inner_encoder, char eor) {
result_t result;
encoder_t outer_encoder = Encoding_encode_char;// buffer encoder
// make given encoder the current one (for ALU-parsed values)
Encoding_encode_char = inner_encoder;
do {
if(GotByte == '"') {
// read initial character
GetQuotedByte();
// send characters until closing quote is reached
while(GotByte && (GotByte != '"')) {
Output_8b(eor ^ Encoding_encode_char(GotByte));
GetQuotedByte();
}
if(GotByte == CHAR_EOS)
return(AT_EOS_ANYWAY);
// after closing quote, proceed with next char
GetByte();
} else {
// Parse value. No problems with single characters
// because the current encoding is
// temporarily set to the given one.
ALU_parse_expr_medium(&result);
Output_8b(result.value);
}
} while(Input_accept_comma());
Encoding_encode_char = outer_encoder; // reactivate buffered encoder
return(ENSURE_EOS);
}
// Insert text string (default format)
static enum eos_t PO_text(void) {
return(encode_string(Encoding_encode_char, 0));
}
// convert raw to raw (do not convert at all)
static char encoder_raw(char byte) {
return(byte);
}
// Insert raw string
static enum eos_t PO_raw(void) {
return(encode_string(encoder_raw, 0));
}
// convert raw to petscii
static char encoder_pet(char byte) {
if((byte >= 'A') && (byte <= 'Z'))
return((char) (byte | 0x80)); // FIXME - check why SAS-C
if((byte >= 'a') && (byte <= 'z')) // wants these casts.
return((char)(byte - 32)); // There are more below.
return(byte);
}
// Insert PetSCII string
static enum eos_t PO_pet(void) {
return(encode_string(encoder_pet, 0));
}
// convert raw to C64 screencode
static char encoder_scr(char byte) {
if((byte >= 'a') && (byte <= 'z'))
return((char)(byte - 96)); // shift uppercase down
if((byte >= '[') && (byte <= '_'))
return((char)(byte - 64)); // shift [\]^_ down
if(byte == '`')
return(64); // shift ` down
if(byte == '@')
return(0); // shift @ down
return(byte);
}
// Insert screencode string
static enum eos_t PO_scr(void) {
return(encode_string(encoder_scr, 0));
}
// Insert screencode string, EOR'd
static enum eos_t PO_scrxor(void) {
result_t result;
ALU_parse_expr_medium(&result);
if(Input_accept_comma())
return(encode_string(encoder_scr, result.value));
Throw_error(exception_syntax);
return(SKIP_REMAINDER);
}
// Switch to CBM mode ("!cbm" pseudo opcode)
static enum eos_t PO_cbm(void) {
Encoding_encode_char = encoder_pet;
// output deprecation warning
if(pass_flags & PASS_ISFIRST)
Throw_warning("\"!cbm\" is deprecated; use \"!ct pet\" instead.");
return(ENSURE_EOS);
}
//
static char encoder_file(char byte) {
return(loaded_table[(unsigned char) byte]);
}
// read encoding table from file
static enum eos_t user_defined_encoding(void) {
FILE* fd;
char *filename,
local_table[256],
*buffered_table = loaded_table;
encoder_t buffered_encoder = Encoding_encode_char;
// if file name is missing, don't bother continuing
if((filename = Input_read_filename(TRUE)) == NULL)
return(SKIP_REMAINDER);
fd = fopen(filename, FILE_READBINARY);
if(fd) {
if(fread(local_table, sizeof(char), 256, fd) != 256)
Throw_error("Conversion table incomplete.");
fclose(fd);
} else
Throw_error(exception_cannot_open_input_file);
free(filename); // filename no longer needed
Encoding_encode_char = encoder_file; // activate new encoding
loaded_table = local_table; // activate local table
// If there's a block, parse that and then restore old values
if(Parse_optional_block())
Encoding_encode_char = buffered_encoder;
else
// if there's *no* block, the table must be used from now on.
// copy the local table to the "outer" table
memcpy(buffered_table, local_table, 256);
// re-activate "outer" table (it might have been changed by memcpy())
loaded_table = buffered_table;
return(ENSURE_EOS);
}
// use one of the pre-defined encodings (raw, pet, scr)
static enum eos_t predefined_encoding(void) {
void* node_body;
char local_table[256],
*buffered_table = loaded_table;
encoder_t buffered_encoder = Encoding_encode_char;
// use one of the pre-defined encodings
if(Input_read_and_lower_keyword()) {
// search for tree item
if(Tree_easy_scan(encoder_tree, &node_body, GlobalDynaBuf))
Encoding_encode_char = (encoder_t) node_body;// activate new encoder
else
Throw_error("Unknown encoding.");
}
loaded_table = local_table; // activate local table
// If there's a block, parse that and then restore old values
if(Parse_optional_block())
Encoding_encode_char = buffered_encoder;
// re-activate "outer" table
loaded_table = buffered_table;
return(ENSURE_EOS);
}
// Set current encoding ("!convtab" pseudo opcode)
static enum eos_t PO_convtab(void) {
if((GotByte == '<') || (GotByte == '"'))
return(user_defined_encoding());
else
return(predefined_encoding());
}
// pseudo opcode table
static node_t pseudo_opcodes[] = {
PREDEFNODE(s_cbm, PO_cbm),
PREDEFNODE("ct", PO_convtab),
PREDEFNODE("convtab", PO_convtab),
PREDEFNODE(s_pet, PO_pet),
PREDEFNODE(s_raw, PO_raw),
PREDEFNODE(s_scr, PO_scr),
PREDEFNODE("scrxor", PO_scrxor),
PREDEFNODE("text", PO_text),
PREDEFLAST("tx", PO_text),
// ^^^^ this marks the last element
};
// keywords for "!convtab" pseudo opcode
static node_t encoders[] = {
PREDEFNODE(s_pet, encoder_pet),
PREDEFNODE(s_raw, encoder_raw),
PREDEFLAST(s_scr, encoder_scr),
// ^^^^ this marks the last element
};
// Exported functions
// Init (add to tree)
void Encoding_init(void) {
Tree_add_table(&encoder_tree, encoders);
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}
// Set default encoding (called before each pass)
void Encoding_passinit(void) {
Encoding_encode_char = encoder_raw;
}

View File

@ -0,0 +1,20 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Character encoding stuff
#ifndef encoding_H
#define encoding_H
// Variables
extern char (*Encoding_encode_char)(char); // conversion function pointer
// Prototypes
extern void Encoding_init(void);
extern void Encoding_passinit(void);
#endif

View File

@ -0,0 +1,403 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Flow control stuff (loops, conditional assembly etc.)
//
// Macros, conditional assembly, loops and sourcefile-includes are all based on
// parsing blocks of code. When defining macros or using loops or conditional
// assembly, the block starts with "{" and ends with "}". In the case of
// "!source", the given file is treated like a block - the outermost assembler
// routine uses the same technique to parse the top level file.
#include <string.h>
#include "acme.h"
#include "alu.h"
#include "config.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "label.h"
#include "macro.h"
#include "mnemo.h"
#include "tree.h"
// type definitions
enum cond_key_t {
ID_UNTIL, // Handles to store instead of
ID_WHILE, // the UNTIL and WHILE keywords
};
typedef struct {
enum cond_key_t type; // either ID_UNTIL or ID_WHILE
int line; // original line number
char* body; // pointer to actual expression
} loopcond_t;
// Variables
// predefined stuff
static node_t* condkey_tree = NULL;// tree to hold UNTIL and WHILE
static node_t condkeys[] = {
PREDEFNODE("until", ID_UNTIL),
PREDEFLAST("while", ID_WHILE),
// ^^^^ this marks the last element
};
// Helper functions for "!for" and "!do"
// Parse a loop body (FIXME - or a macro?)
static void parse_ram_block(int line_number, char* body) {
Input_now->line_number = line_number;// set line number to loop start
Input_now->src.ram_ptr = body;// set RAM read pointer to loop
// Parse loop body
Parse_until_eob_or_eof();
if(GotByte != CHAR_EOB)
Bug_found("IllegalBlockTerminator", GotByte);
}
// Try to read a condition into DynaBuf and store copy pointer in
// given loopcond_t structure.
// If no condition given, NULL is written to structure.
// Call with GotByte = first interesting character
static void store_condition(loopcond_t* condition, char terminator) {
void* node_body;
// write line number
condition->line = Input_now->line_number;
// Check for empty condition
if(GotByte == terminator) {
// Write NULL condition, then return
condition->body = NULL;
return;
}
// Seems as if there really *is* a condition.
// Read UNTIL/WHILE keyword
if(Input_read_and_lower_keyword()) {
// Search for new tree item
if(!Tree_easy_scan(condkey_tree, &node_body, GlobalDynaBuf)) {
Throw_error(exception_syntax);
condition->body = NULL;
return;
}
condition->type = (enum cond_key_t) node_body;
// Write given condition into buffer
SKIPSPACE();
DYNABUF_CLEAR(GlobalDynaBuf);
Input_until_terminator(terminator);
DynaBuf_append(GlobalDynaBuf, CHAR_EOS);// ensure terminator
condition->body = DynaBuf_get_copy(GlobalDynaBuf);
}
return;
}
// Check a condition expression
static bool check_condition(loopcond_t* condition) {
result_t expression;
// First, check whether there actually *is* a condition
if(condition->body == NULL)
return(TRUE); // non-existant conditions are always true
// set up input for expression evaluation
Input_now->line_number = condition->line;
Input_now->src.ram_ptr = condition->body;
GetByte(); // proceed with next char
ALU_parse_expr_strict(&expression);
if(GotByte)
Throw_serious_error(exception_syntax);
if(condition->type == ID_UNTIL)
return(!expression.value);
return(expression.value != FALSE);
}
// Looping assembly ("!do"). Has to be re-entrant.
static enum eos_t PO_do(void) { // Now GotByte = illegal char
loopcond_t condition1,
condition2;
input_t loop_input,
*outer_input;
char* loop_body;
bool go_on;
int loop_start;// line number of loop pseudo opcode
// Read head condition to buffer
SKIPSPACE();
store_condition(&condition1, CHAR_SOB);
if(GotByte != CHAR_SOB)
Throw_serious_error(exception_no_left_brace);
// Remember line number of loop body,
// then read block and get copy
loop_start = Input_now->line_number;
loop_body = Input_skip_or_store_block(TRUE); // changes line number!
// now GotByte = '}'
NEXTANDSKIPSPACE();// Now GotByte = first non-blank char after block
// Read tail condition to buffer
store_condition(&condition2, CHAR_EOS);
// now GotByte = CHAR_EOS
// set up new input
loop_input = *Input_now;// copy current input structure into new
loop_input.source_is_ram = TRUE; // set new byte source
// remember old input
outer_input = Input_now;
// activate new input (not useable yet, as pointer and
// line number are not yet set up)
Input_now = &loop_input;
do {
// Check head condition
go_on = check_condition(&condition1);
if(go_on) {
parse_ram_block(loop_start, loop_body);
// Check tail condition
go_on = check_condition(&condition2);
}
} while(go_on);
// Free memory
free(condition1.body);
free(loop_body);
free(condition2.body);
// restore previous input:
Input_now = outer_input;
GotByte = CHAR_EOS; // CAUTION! Very ugly kluge.
// But by switching input, we lost the outer input's GotByte. We know
// it was CHAR_EOS. We could just call GetByte() to get real input, but
// then the main loop could choke on unexpected bytes. So we pretend
// that we got the outer input's GotByte value magically back.
return(AT_EOS_ANYWAY);
}
// Looping assembly ("!for"). Has to be re-entrant.
static enum eos_t PO_for(void) {// Now GotByte = illegal char
input_t loop_input,
*outer_input;
result_t loop_counter;
value_t maximum;
char* loop_body;// pointer to loop's body block
label_t* label;
zone_t zone;
int force_bit,
loop_start;// line number of "!for" pseudo opcode
if(Input_read_zone_and_keyword(&zone) == 0) // skips spaces before
return(SKIP_REMAINDER);
// Now GotByte = illegal char
force_bit = Mnemo_get_force_bit(); // skips spaces after
label = Label_find(zone, 0); // FIXME - is it really useful that !for can set/clear force bits at will?
if(force_bit) {
label->flags &= ~MVALUE_FORCEBITS;// clear force bits
label->flags |= force_bit;// set desired force bit
}
if(Input_accept_comma() == FALSE) {
Throw_error(exception_syntax);
return(SKIP_REMAINDER);
}
ALU_parse_expr_strict(&loop_counter);
// prepare flags, size of *end* value doesn't matter for *all* loops
loop_counter.flags &= ~MVALUE_FORCEBITS; // clear force bits
if(loop_counter.value < 0)
Throw_serious_error("Loop count is negative.");
if(GotByte != CHAR_SOB)
Throw_serious_error(exception_no_left_brace);
// remember line number of loop pseudo opcode
loop_start = Input_now->line_number;
// read loop body into DynaBuf and get copy
loop_body = Input_skip_or_store_block(TRUE); // changes line number!
// switching input makes us lose GotByte. But we know it's '}' anyway!
// set up new input
loop_input = *Input_now;// copy current input structure into new
loop_input.source_is_ram = TRUE; // set new byte source
// remember old input
outer_input = Input_now;
// activate new input
// (not yet useable; pointer and line number are still missing)
Input_now = &loop_input;
if(loop_counter.value) {// skip loop if counter == 0
maximum = loop_counter.value;// remember maximum value
loop_counter.value = 1;// start counting
do {
// set counter
Label_set_value(label, &loop_counter, TRUE);
parse_ram_block(loop_start, loop_body);
// increase loop counter
loop_counter.value++;
} while(loop_counter.value <= maximum);
} else
Label_set_value(label, &loop_counter, TRUE); // set counter
// Free memory
free(loop_body);
// restore previous input:
Input_now = outer_input;
// GotByte of OuterInput would be '}' (if it would still exist)
GetByte(); // fetch next byte
return(ENSURE_EOS);
}
// Helper functions for "!if" and "!ifdef"
// Parse or skip a block. Returns whether block's '}' terminator was missing.
// Afterwards: GotByte = '}'
static bool skip_or_parse_block(bool parse) {
if(!parse) {
Input_skip_or_store_block(FALSE);
return(FALSE);
}
// if block was correctly terminated, return FALSE
Parse_until_eob_or_eof();
// if block isn't correctly terminated, complain and exit
if(GotByte != CHAR_EOB)
Throw_serious_error(exception_no_right_brace);
return(FALSE);
}
// Parse {block} [else {block}]
static void parse_block_else_block(bool parse_first) {
// Parse first block.
// If it's not correctly terminated, return immediately (because
// in that case, there's no use in checking for an "else" part).
if(skip_or_parse_block(parse_first))
return;
// now GotByte = '}'. Check for "else" part.
// If end of statement, return immediately.
NEXTANDSKIPSPACE();
if(GotByte == CHAR_EOS)
return;
// read keyword and check whether really "else"
if(Input_read_and_lower_keyword()) {
if(strcmp(GlobalDynaBuf->buffer, "else"))
Throw_error(exception_syntax);
else {
SKIPSPACE();
if(GotByte != CHAR_SOB)
Throw_serious_error(exception_no_left_brace);
skip_or_parse_block(!parse_first);
// now GotByte = '}'
GetByte();
}
}
Input_ensure_EOS();
}
// Conditional assembly ("!if"). Has to be re-entrant.
static enum eos_t PO_if(void) {// Now GotByte = illegal char
result_t result;
ALU_parse_expr_strict(&result);
if(GotByte != CHAR_SOB)
Throw_serious_error(exception_no_left_brace);
parse_block_else_block(!!(result.value));
return(ENSURE_EOS);
}
// Conditional assembly ("!ifdef"). Has to be re-entrant.
static enum eos_t PO_ifdef(void) {// Now GotByte = illegal char
node_ra_t* node;
label_t* label;
zone_t zone;
bool defined = FALSE;
if(Input_read_zone_and_keyword(&zone) == 0) // skips spaces before
return(SKIP_REMAINDER);
Tree_hard_scan(&node, Label_forest, zone, FALSE);
if(node) {
label = (label_t*) node->body;
// in first pass, count usage
if(pass_flags & PASS_ISFIRST)
label->usage++;
if(label->flags & MVALUE_DEFINED)
defined = TRUE;
}
SKIPSPACE();
if(GotByte == CHAR_SOB)
parse_block_else_block(defined);
else {
if(defined)
return(PARSE_REMAINDER);
return(SKIP_REMAINDER);
}
return(ENSURE_EOS);
}
// Macro definition ("!macro").
static enum eos_t PO_macro(void) {// Now GotByte = illegal char
// In first pass, parse. In all other passes, skip.
if(pass_flags & PASS_ISFIRST)
Macro_parse_definition(); // now GotByte = '}'
else {
// skip until CHAR_SOB ('{') is found.
// no need to check for end-of-statement, because such an
// error would already have been detected in first pass.
// for the same reason, there is no need to check for quotes.
while(GotByte != CHAR_SOB)
GetByte();
Input_skip_or_store_block(FALSE); // now GotByte = '}'
}
GetByte(); // Proceed with next character
return(ENSURE_EOS);
}
// Parse a whole source code file
void Parse_and_close_file(FILE* fd, const char* filename) {
// be verbose
if(Process_verbosity > 2)
printf("Parsing source file '%s'\n", filename);
// set up new input
Input_new_file(filename, fd);
// Parse block and check end reason
Parse_until_eob_or_eof();
if(GotByte != CHAR_EOF)
Throw_error("Found '}' instead of end-of-file.");
// close sublevel src
fclose(Input_now->src.fd);
}
// Include source file ("!source" or "!src"). Has to be re-entrant.
static enum eos_t PO_source(void) {// Now GotByte = illegal char
FILE* fd;
char local_gotbyte,
*filename;
input_t new_input,
*outer_input;
// Enter new nesting level.
// Quit program if recursion too deep.
if(--source_recursions_left < 0)
Throw_serious_error("Too deeply nested. Recursive \"!source\"?");
// Read file name. Quit function on error.
if((filename = Input_read_filename(TRUE)) == NULL) // uses copy
return(SKIP_REMAINDER);
// If file could be opened, parse it. Otherwise, complain.
if((fd = fopen(filename, FILE_READBINARY))) {
outer_input = Input_now;// remember old input
local_gotbyte = GotByte;// CAUTION - ugly kluge
Input_now = &new_input;// activate new input
Parse_and_close_file(fd, filename);
Input_now = outer_input;// restore previous input
GotByte = local_gotbyte;// CAUTION - ugly kluge
} else
Throw_error(exception_cannot_open_input_file);
// Release dynamically allocated file name
free(filename);
// Leave nesting level
source_recursions_left++;
return(ENSURE_EOS);
}
// pseudo opcode table
static node_t pseudo_opcodes[] = {
PREDEFNODE("do", PO_do),
PREDEFNODE("for", PO_for),
PREDEFNODE("if", PO_if),
PREDEFNODE("ifdef", PO_ifdef),
PREDEFNODE("macro", PO_macro),
PREDEFNODE("source", PO_source),
PREDEFLAST("src", PO_source),
// ^^^^ this marks the last element
};
// Init (add stuff to tree)
void Flow_init(void) {
Tree_add_table(&condkey_tree, condkeys);
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}

View File

@ -0,0 +1,19 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Flow control stuff (loops, conditional assembly etc.)
#ifndef flow_H
#define flow_H
#include <stdio.h>
#include "config.h"
// Prototypes
extern void Flow_init(void);
extern void Parse_and_close_file(FILE* fd, const char* filename);
#endif

View File

@ -0,0 +1,381 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Global stuff - things that are needed by several modules
#include <stdio.h>
#include "platform.h" // done first in case "inline" is redefined
#include "acme.h"
#include "cpu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "label.h"
#include "macro.h"
#include "output.h"
#include "section.h"
#include "tree.h"
// Constants
const char s_cbm[] = "cbm";
// Exception messages during assembly
const char exception_cannot_open_input_file[] = "Cannot open input file.";
const char exception_missing_string[] = "No string given.";
const char exception_no_left_brace[] = "Missing '{'.";
const char exception_no_memory_left[] = "Out of memory.";
const char exception_no_right_brace[]= "Found end-of-file instead of '}'.";
//const char exception_not_yet[] = "Sorry, feature not yet implemented.";
const char exception_syntax[] = "Syntax error.";
const char exception_number_out_of_range[] = "Number out of range.";
// default value for number of errors before exiting
#define MAXERRORS 10
// Flag table:
// This table contains flags for all the 256 possible byte values. The
// assembler reads the table whenever it needs to know whether a byte is
// allowed to be in a label name, for example.
// Bits Meaning when set
// 7....... Byte allowed to start keyword
// .6...... Byte allowed in keyword
// ..5..... Byte is upper case, can be lowercased by OR-ing this bit(!)
// ...4.... special character for input syntax: 0x00 TAB LF CR SPC : ; }
// ....3... preceding sequence of '-' characters is anonymous backward
// label. Currently only set for ')', ',' and CHAR_EOS.
// .....210 unused
const char Byte_flags[256] = {
/*$00*/ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// control characters
0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
/*$20*/ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// " !"#$%&'"
0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,// "()*+,-./"
0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,// "01234567"
0x40, 0x40, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,// "89:;<=>?"
/*$40*/ 0x00, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,// "@ABCDEFG"
0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,// "HIJKLMNO"
0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,// "PQRSTUVW"
0xe0, 0xe0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xc0,// "XYZ[\]^_"
/*$60*/ 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// "`abcdefg"
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// "hijklmno"
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// "pqrstuvw"
0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x10, 0x00, 0x00,// "yxz{|}~" BACKSPACE
/*$80*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// umlauts etc. ...
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
/*$a0*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
/*$c0*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
/*$e0*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,
};
// Variables
node_t* pseudo_opcode_tree = NULL; // tree to hold pseudo opcodes
int pass_flags; // Pass flags and its bitfields (FIXME - remove?)
int pass_count; // number of current pass (starts 0)
char GotByte; // Last byte read (processed)
int Process_verbosity = 0; // Level of additional output
// Global counters
int pass_undefined_count; // "NeedValue" type errors
int pass_real_errors; // Errors yet
value_t pc_inc; // Increase PC by this amount after statement
value_t segment_start; // Start of current segment
value_t segment_max; // Highest address segment may use
void* autofree_list_pass = NULL; // linked list, holds malloc blocks
signed long max_errors = MAXERRORS;// errors before giving up
// Functions
// Memory allocation stuff
// Allocate memory and die if not available
void* safe_malloc(size_t size) {
void* block;
if((block = malloc(size)) == NULL)
Throw_serious_error(exception_no_memory_left);
return(block);
}
// Allocate memory which gets auto-freed at end of pass
void* autofree_malloc(size_t size, void** list) {
void** block;
block = malloc(size + sizeof(void*));
if(block == NULL)
Throw_serious_error(exception_no_memory_left);
*block = *list;// make block point to current list
*list = block;// make list point to new block
return(block + 1);
}
// Free all linked memory blocks (called at end of pass)
void autofree_free(void* *head) {
void **p1,
**p2;
p1 = *head;// get pointer to list's first item
*head = NULL;// make start point to NULL
// free blocks until no more
while((p2 = p1)) {
p1 = *p2;
free(p2);
}
}
// Parser stuff
// Parse pseudo opcodes. Has to be re-entrant.
static inline void parse_pseudo_opcode(void) {// Now GotByte = "!"
void* node_body;
enum eos_t (*fn)(void);
enum eos_t then = SKIP_REMAINDER; // prepare for errors
GetByte();// read next byte
// on missing keyword, return (complaining will have been done)
if(Input_read_and_lower_keyword()) {
// search for tree item
if((Tree_easy_scan(pseudo_opcode_tree, &node_body, GlobalDynaBuf))
&& node_body) {
fn = (enum eos_t (*)(void)) node_body;
SKIPSPACE();
// call function
then = fn();
} else
Throw_error("Unknown pseudo opcode.");
}
if(then == SKIP_REMAINDER)
Input_skip_remainder();
else if(then == ENSURE_EOS)
Input_ensure_EOS();
// the other two possibilities (PARSE_REMAINDER and AT_EOS_ANYWAY)
// will lead to the remainder of the line being parsed by the mainloop.
}
// Check and return whether first label of statement. Complain if not.
static bool first_label_of_statement(int *statement_flags) {
if((*statement_flags) & SF_IMPLIED_LABEL) {
Throw_error(exception_syntax);
Input_skip_remainder();
return(FALSE);
}
(*statement_flags) |= SF_IMPLIED_LABEL; // now there has been one
return(TRUE);
}
// Parse global label definition or assembler mnemonic
static void parse_mnemo_or_global_label_def(int *statement_flags) {
// It is only a label if it isn't a mnemonic
if((CPU_now->keyword_is_mnemonic(Input_read_keyword()) == FALSE)
&& first_label_of_statement(statement_flags)) {
// Now GotByte = illegal char
// 04 Jun 2005 - this fix should help to
// explain "strange" error messages.
if((*GLOBALDYNABUF_CURRENT == ' ')
&& (pass_flags & PASS_ISFIRST))
Throw_warning("Label name starts with a shift-space character.");
Label_parse_definition(ZONE_GLOBAL, *statement_flags);
}
}
// Parse local label definition
static void parse_local_label_def(int *statement_flags) {
if(!first_label_of_statement(statement_flags))
return;
GetByte();// start after '.'
if(Input_read_keyword())
Label_parse_definition(Section_now->zone, *statement_flags);
}
// Parse anonymous backward label definition. Called with GotByte == '-'
static void parse_backward_anon_def(int *statement_flags) {
if(!first_label_of_statement(statement_flags))
return;
DYNABUF_CLEAR(GlobalDynaBuf);
do
DYNABUF_APPEND(GlobalDynaBuf, '-');
while(GetByte() == '-');
DynaBuf_append(GlobalDynaBuf, '\0');
Label_implicit_definition(Section_now->zone, *statement_flags, 0, TRUE);
}
// Parse anonymous forward label definition. Called with GotByte == ?
static void parse_forward_anon_def(int *statement_flags) {
label_t* counter_label;
if(!first_label_of_statement(statement_flags))
return;
DYNABUF_CLEAR(GlobalDynaBuf);
DynaBuf_append(GlobalDynaBuf, '+');
while(GotByte == '+') {
DYNABUF_APPEND(GlobalDynaBuf, '+');
GetByte();
}
counter_label = Label_fix_forward_name();
counter_label->value++;
DynaBuf_append(GlobalDynaBuf, '\0');
Label_implicit_definition(Section_now->zone, *statement_flags, 0, TRUE);
}
// Parse block, beginning with next byte.
// End reason (either CHAR_EOB or CHAR_EOF) can be found in GotByte afterwards
// Has to be re-entrant.
void Parse_until_eob_or_eof(void) {
int statement_flags;
// // start with next byte, don't care about spaces
// NEXTANDSKIPSPACE();
// start with next byte
GetByte();
// loop until end of block or end of file
while((GotByte != CHAR_EOB) && (GotByte != CHAR_EOF)) {
// process one statement
statement_flags = 0;// no "label = pc" definition yet
// Parse until end of statement. Only loops if statement
// contains "label = pc" definition and something else; or
// if "!ifdef" is true.
do {
switch(GotByte) {
case CHAR_EOS: // end of statement
// Ignore now, act later
// (stops from being "default")
break;
case ' ': // space
statement_flags |= SF_FOUND_BLANK;
/*FALLTHROUGH*/
case CHAR_SOL: // start of line
GetByte();// skip
break;
case '-':
parse_backward_anon_def(&statement_flags);
break;
case '+':
GetByte();
if((GotByte == '.')
|| (BYTEFLAGS(GotByte) & CONTS_KEYWORD))
Macro_parse_call();
else
parse_forward_anon_def(&statement_flags);
break;
case '!':
parse_pseudo_opcode();
break;
case '*':
CPU_set_pc();
break;
case '.':
parse_local_label_def(&statement_flags);
break;
default:
if(BYTEFLAGS(GotByte) & STARTS_KEYWORD) {
parse_mnemo_or_global_label_def(&statement_flags);
} else {
Throw_error(exception_syntax);
Input_skip_remainder();
}
}
} while(GotByte != CHAR_EOS); // until end-of-statement
// change program counter and memory pointer
CPU_pc += pc_inc;
Mem_current_pc += pc_inc;
// check for new max address
if(Mem_current_pc > Mem_highest_pc)
Mem_highest_pc = Mem_current_pc;
pc_inc = 0;
// go on with next byte
GetByte();//NEXTANDSKIPSPACE();
}
}
// Skip space. If GotByte is CHAR_SOB ('{'), parse block and return TRUE.
// Otherwise (if there is no block), return FALSE.
// Don't forget to call EnsureEOL() afterwards.
bool Parse_optional_block(void) {
SKIPSPACE();
if(GotByte != CHAR_SOB)
return(FALSE);
Parse_until_eob_or_eof();
if(GotByte != CHAR_EOB)
Throw_serious_error(exception_no_right_brace);
GetByte();
return(TRUE);
}
// Error handling
// This routine will do the actual output for warnings, errors and serious
// errors. It shows the given message string, as well as the current
// context: file name, line number, source type and source title.
static void throw_message(const char* message, const char* type) {
fprintf(stderr, "%s - File %s, line %d (%s %s): %s\n", type,
Input_now->original_filename, Input_now->line_number,
Section_now->type, Section_now->title,
message
);
}
// Output a warning.
// This means the produced code looks as expected. But there has been a
// situation that should be reported to the user, for example ACME may have
// assembled a 16-bit parameter with an 8-bit value.
void Throw_warning(const char* message) {
PLATFORM_WARNING(message);
throw_message(message, "Warning");
}
// Output an error.
// This means something went wrong in a way that implies that the output
// almost for sure won't look like expected, for example when there was a
// syntax error. The assembler will try to go on with the assembly though, so
// the user gets to know about more than one of his typos at a time.
void Throw_error(const char* message) {
PLATFORM_ERROR(message);
throw_message(message, "Error");
pass_real_errors++;
if(pass_real_errors >= max_errors)
exit(ACME_finalize(EXIT_FAILURE));
}
// Output a serious error, stopping assembly.
// Serious errors are those that make it impossible to go on with the
// assembly. Example: "!fill" without a parameter - the program counter cannot
// be set correctly in this case, so proceeding would be of no use at all.
void Throw_serious_error(const char* message) {
PLATFORM_SERIOUS(message);
throw_message(message, "Serious error");
exit(ACME_finalize(EXIT_FAILURE));
}
// Handle bugs
void Bug_found(const char* message, int code) {
Throw_warning("Bug in ACME, code follows");
fprintf(stderr, "(0x%x:)", code);
Throw_serious_error(message);
}

View File

@ -0,0 +1,88 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Global stuff - things that are needed by several modules
#ifndef global_H
#define global_H
#include <stdio.h>
#include <stdlib.h>
#include "config.h"
// Constants
#define SF_FOUND_BLANK (1u) // statement had space or tab
#define SF_IMPLIED_LABEL (2u) // statement had implied label def
extern const char s_cbm[];
// Error messages during assembly
extern const char exception_cannot_open_input_file[];
extern const char exception_missing_string[];
extern const char exception_no_left_brace[];
extern const char exception_no_memory_left[];
extern const char exception_no_right_brace[];
//extern const char exception_not_yet[];
extern const char exception_syntax[];
extern const char exception_number_out_of_range[];
// Byte flags table
extern const char Byte_flags[];
#define BYTEFLAGS(c) (Byte_flags[(unsigned char) c])
#define STARTS_KEYWORD (1u << 7) // Byte is allowed to start a keyword
#define CONTS_KEYWORD (1u << 6) // Byte is allowed in a keyword
#define BYTEIS_UPCASE (1u << 5) // Byte is upper case and can be
// converted to lower case by OR-ing this bit(!)
#define BYTEIS_SYNTAX (1u << 4) // special character for input syntax
#define FOLLOWS_ANON (1u << 3) // preceding '-' are backward label
// bits 2, 1 and 0 are unused
// Variables
extern node_t* pseudo_opcode_tree;// tree to hold pseudo opcodes
// structures
enum eos_t {
SKIP_REMAINDER, // skip remainder of line - (after errors)
ENSURE_EOS, // make sure there's nothing left in statement
PARSE_REMAINDER, // parse what's left
AT_EOS_ANYWAY, // actually, same as PARSE_REMAINDER
};
extern int pass_flags; // Pass flags and its bitfields
// Do stuff that only has to be done once (only in first pass)
#define PASS_ISFIRST (1u << 0)
// Show errors when values are undefined (only in additional pass)
#define PASS_ISERROR (1u << 1)
extern int pass_count;
extern int Process_verbosity;// Level of additional output
extern char GotByte;// Last byte read (processed)
// Global counters
extern int pass_undefined_count;// "NeedValue" type errors in current pass
extern int pass_real_errors; // Errors yet
extern value_t pc_inc; // Increase PC by this amount after statement
extern value_t segment_start; // Start of current segment
extern value_t segment_max; // Highest address segment may use
extern signed long max_errors; // errors before giving up
extern void* autofree_list_pass; // linked list, holds malloc blocks
// Macros for skipping a single space character
#define SKIPSPACE() do {if(GotByte == ' ') GetByte();} while(0)
#define NEXTANDSKIPSPACE() do {if(GetByte() == ' ') GetByte();} while(0)
// Prototypes
extern inline void* safe_malloc(size_t);
extern void* autofree_malloc(size_t, void**);
// Macro for claiming auto-freed memory blocks
#define ALLOC_PASS(Size) autofree_malloc(Size, &autofree_list_pass)
extern void autofree_free(void**);
extern void Parse_until_eob_or_eof(void);
extern int Parse_optional_block(void);
extern void Throw_warning(const char*);
extern void Throw_error(const char*);
extern void Throw_serious_error(const char*);
extern void Bug_found(const char*, int);
#endif

View File

@ -0,0 +1,495 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Input stuff
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "platform.h"
#include "section.h"
#include "tree.h"
// Constants
const char FILE_READBINARY[] = "rb";
#define CHAR_TAB (9) // Tab character
#define CHAR_LF (10) // line feed (in file)
// (10) // start of line (in high-level format)
#define CHAR_CR (13) // carriage return (in file)
// (13) // end of file (in high-level format)
#define CHAR_STATEMENT_DELIMITER ':'
#define CHAR_COMMENT_SEPARATOR ';'
// If the characters above are changed, don't forget to adjust ByteFlags[]!
// fake input structure (for error msgs before any real input is established)
static input_t outermost = {
"<none>", // file name
0, // line number
FALSE, // Faked file access, so no RAM read
INPUTSTATE_EOF, // state of input
{
NULL // RAM read pointer or file handle
}
};
// Variables
input_t* Input_now = &outermost; // current input structure
// End of source file ("!endoffile" or "!eof")
static enum eos_t PO_eof(void) {
// Well, it doesn't end right here and now, but at end-of-line! :-)
Input_ensure_EOS();
Input_now->state = INPUTSTATE_EOF;
return(AT_EOS_ANYWAY);
}
// predefined stuff
static node_t pseudo_opcodes[] = {
PREDEFNODE("eof", PO_eof),
PREDEFLAST("endoffile", PO_eof),
// ^^^^ this marks the last element
};
// Functions
// Init (add to tree)
void Input_init(void) {
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}
// Let current input point to start of file
void Input_new_file(const char* filename, FILE* fd) {
Input_now->original_filename = filename;
Input_now->line_number = 1;
Input_now->source_is_ram = FALSE;
Input_now->state = INPUTSTATE_NORMAL;
Input_now->src.fd = fd;
}
// Deliver source code from current file (!) in shortened high-level format
static char get_processed_from_file(void) {
int from_file;
do {
switch(Input_now->state) {
case INPUTSTATE_NORMAL:
// fetch a fresh byte from the current source file
from_file = getc(Input_now->src.fd);
// now process it
/*FALLTHROUGH*/
case INPUTSTATE_AGAIN:
// Process the latest byte again. Of course, this only
// makes sense if the loop has executed at least once,
// otherwise the contents of from_file are undefined.
// If the source is changed so there is a possibility
// to enter INPUTSTATE_AGAIN mode without first having
// defined "from_file", trouble may arise...
Input_now->state = INPUTSTATE_NORMAL;
// EOF must be checked first because it cannot be used
// as an index into Byte_flags[]
if(from_file == EOF) {
// remember to send an end-of-file
Input_now->state = INPUTSTATE_EOF;
return(CHAR_EOS);// end of statement
}
// check whether character is special one
// if not, everything's cool and froody, so return it
if((BYTEFLAGS(from_file) & BYTEIS_SYNTAX) == 0)
return((char) from_file);
// check special characters ("0x00 TAB LF CR SPC :;}")
switch(from_file) {
case CHAR_TAB:// TAB character
case ' ':
// remember to skip all following blanks
Input_now->state = INPUTSTATE_SKIPBLANKS;
return(' ');
case CHAR_LF:// LF character
// remember to send a start-of-line
Input_now->state = INPUTSTATE_LF;
return(CHAR_EOS);// end of statement
case CHAR_CR:// CR character
// remember to check CRLF + send start-of-line
Input_now->state = INPUTSTATE_CR;
return(CHAR_EOS);// end of statement
case CHAR_EOB:
// remember to send an end-of-block
Input_now->state = INPUTSTATE_EOB;
return(CHAR_EOS);// end of statement
case CHAR_STATEMENT_DELIMITER:
// just deliver an EOS instead
return(CHAR_EOS);// end of statement
case CHAR_COMMENT_SEPARATOR:
// remember to skip remainder of line
Input_now->state = INPUTSTATE_COMMENT;
return(CHAR_EOS);// end of statement
default:
// complain if byte is 0
Throw_error("Source file contains illegal character.");
return((char) from_file);
}
case INPUTSTATE_SKIPBLANKS:
// read until non-blank, then deliver that
do
from_file = getc(Input_now->src.fd);
while((from_file == CHAR_TAB) || (from_file == ' '));
// re-process last byte
Input_now->state = INPUTSTATE_AGAIN;
break;
case INPUTSTATE_LF:
// return start-of-line, then continue in normal mode
Input_now->state = INPUTSTATE_NORMAL;
return(CHAR_SOL);// new line
case INPUTSTATE_CR:
// return start-of-line, remember to check for LF
Input_now->state = INPUTSTATE_SKIPLF;
return(CHAR_SOL);// new line
case INPUTSTATE_SKIPLF:
from_file = getc(Input_now->src.fd);
// if LF, ignore it and fetch another byte
// otherwise, process current byte
if(from_file == CHAR_LF)
Input_now->state = INPUTSTATE_NORMAL;
else
Input_now->state = INPUTSTATE_AGAIN;
break;
case INPUTSTATE_COMMENT:
// read until end-of-line or end-of-file
do
from_file = getc(Input_now->src.fd);
while((from_file != EOF) && (from_file != CHAR_CR) && (from_file != CHAR_LF));
// re-process last byte
Input_now->state = INPUTSTATE_AGAIN;
break;
case INPUTSTATE_EOB:
// deliver EOB
Input_now->state = INPUTSTATE_NORMAL;
return(CHAR_EOB);// end of block
case INPUTSTATE_EOF:
// deliver EOF
Input_now->state = INPUTSTATE_NORMAL;
return(CHAR_EOF);// end of file
default:
Bug_found("StrangeInputMode", Input_now->state);
}
} while(TRUE);
}
// This function delivers the next byte from the currently active byte source
// in shortened high-level format.
// When inside quotes, use GetQuotedByte() instead!
char GetByte(void) {
// do {
// If byte source is RAM, then no conversions are
// necessary, because in RAM the source already has
// high-level format
// Otherwise, the source is a file. This means we will call
// GetFormatted() which will do a shit load of conversions.
if(Input_now->source_is_ram)
GotByte = *(Input_now->src.ram_ptr++);
else
GotByte = get_processed_from_file();
// // if start-of-line was read, increment line counter and repeat
// if(GotByte != CHAR_SOL)
// return(GotByte);
// Input_now->line_number++;
// } while(TRUE);
if(GotByte == CHAR_SOL)
Input_now->line_number++;
return(GotByte);
}
// This function delivers the next byte from the currently active byte source
// in un-shortened high-level format.
// This function complains if CHAR_EOS (end of statement) is read.
char GetQuotedByte(void) {
int from_file; // must be an int to catch EOF
// If byte source is RAM, then no conversion is necessary,
// because in RAM the source already has high-level format
if(Input_now->source_is_ram)
GotByte = *(Input_now->src.ram_ptr++);
// Otherwise, the source is a file.
else {
// fetch a fresh byte from the current source file
from_file = getc(Input_now->src.fd);
switch(from_file) {
case EOF:
// remember to send an end-of-file
Input_now->state = INPUTSTATE_EOF;
GotByte = CHAR_EOS; // end of statement
break;
case CHAR_LF:// LF character
// remember to send a start-of-line
Input_now->state = INPUTSTATE_LF;
GotByte = CHAR_EOS; // end of statement
break;
case CHAR_CR:// CR character
// remember to check for CRLF + send a start-of-line
Input_now->state = INPUTSTATE_CR;
GotByte = CHAR_EOS; // end of statement
break;
default:
GotByte = from_file;
}
}
// now check for end of statement
if(GotByte == CHAR_EOS)
Throw_error("Quotes still open at end of line.");
return(GotByte);
}
// Skip remainder of statement, for example on error
void Input_skip_remainder(void) {
while(GotByte)
GetByte();// Read characters until end-of-statement
}
// Ensure that the remainder of the current statement is empty, for example
// after mnemonics using implied addressing.
void Input_ensure_EOS(void) {// Now GotByte = first char to test
SKIPSPACE();
if(GotByte) {
Throw_error("Garbage data at end of statement.");
Input_skip_remainder();
}
}
// Skip or store block (starting with next byte, so call directly after
// reading opening brace).
// If "Store" is TRUE, the block is read into GlobalDynaBuf, then a copy
// is made and a pointer to that is returned.
// If "Store" is FALSE, NULL is returned.
// After calling this function, GotByte holds '}'. Unless EOF was found first,
// but then a serious error would have been thrown.
char* Input_skip_or_store_block(bool store) {
char byte;
int depth = 1; // to find matching block end
// prepare global dynamic buffer
DYNABUF_CLEAR(GlobalDynaBuf);
do {
byte = GetByte();
// if wanted, store
if(store)
DYNABUF_APPEND(GlobalDynaBuf, byte);
// now check for some special characters
switch(byte) {
case CHAR_EOF: // End-of-file in block? Sorry, no way.
Throw_serious_error(exception_no_right_brace);
case '"': // Quotes? Okay, read quoted stuff.
case '\'':
do {
GetQuotedByte();
// if wanted, store
if(store)
DYNABUF_APPEND(GlobalDynaBuf, GotByte);
} while((GotByte != CHAR_EOS) && (GotByte != byte));
break;
case CHAR_SOB:
depth++;
break;
case CHAR_EOB:
depth--;
break;
}
} while(depth);
// in case of skip, return now
if(!store)
return(NULL);
// otherwise, prepare to return copy of block
// add EOF, just to make sure block is never read too far
DynaBuf_append(GlobalDynaBuf, CHAR_EOS);
DynaBuf_append(GlobalDynaBuf, CHAR_EOF);
// return pointer to copy
return(DynaBuf_get_copy(GlobalDynaBuf));
}
// Read bytes and add to GlobalDynaBuf until the given terminator (or CHAR_EOS)
// is found. Act upon single and double quotes by entering (and leaving) quote
// mode as needed (So the terminator does not terminate when inside quotes).
void Input_until_terminator(char terminator) {
char byte = GotByte;
do {
// Terminator? Exit. EndOfStatement? Exit.
if((byte == terminator) || (byte == CHAR_EOS))
return;
// otherwise, append to GlobalDynaBuf and check for quotes
DYNABUF_APPEND(GlobalDynaBuf, byte);
if((byte == '"') || (byte == '\'')) {
do {
// Okay, read quoted stuff.
GetQuotedByte();// throws error on EOS
DYNABUF_APPEND(GlobalDynaBuf, GotByte);
} while((GotByte != CHAR_EOS) && (GotByte != byte));
// on error, exit now, before calling GetByte()
if(GotByte != byte)
return;
}
byte = GetByte();
} while(TRUE);
}
// Append to GlobalDynaBuf while characters are legal for keywords.
// Throws "missing string" error if none.
// Returns number of characters added.
int Input_append_keyword_to_global_dynabuf(void) {
int length = 0;
// add characters to buffer until an illegal one comes along
while(BYTEFLAGS(GotByte) & CONTS_KEYWORD) {
DYNABUF_APPEND(GlobalDynaBuf, GotByte);
length++;
GetByte();
}
if(length == 0)
Throw_error(exception_missing_string);
return(length);
}
// Check whether GotByte is a dot.
// If not, store global zone value.
// If yes, store current zone value and read next byte.
// Then jump to Input_read_keyword(), which returns length of keyword.
int Input_read_zone_and_keyword(zone_t *zone) {
SKIPSPACE();
if(GotByte == '.') {
GetByte();
*zone = Section_now->zone;
} else
*zone = ZONE_GLOBAL;
return(Input_read_keyword());
}
// Clear dynamic buffer, then append to it until an illegal (for a keyword)
// character is read. Zero-terminate the string. Return its length (without
// terminator).
// Zero lengths will produce a "missing string" error.
int Input_read_keyword(void) {
int length;
DYNABUF_CLEAR(GlobalDynaBuf);
length = Input_append_keyword_to_global_dynabuf();
// add terminator to buffer (increments buffer's length counter)
DynaBuf_append(GlobalDynaBuf, '\0');
return(length);
}
// Clear dynamic buffer, then append to it until an illegal (for a keyword)
// character is read. Zero-terminate the string, then convert to lower case.
// Return its length (without terminator).
// Zero lengths will produce a "missing string" error.
int Input_read_and_lower_keyword(void) {
int length;
DYNABUF_CLEAR(GlobalDynaBuf);
length = Input_append_keyword_to_global_dynabuf();
// add terminator to buffer (increments buffer's length counter)
DynaBuf_append(GlobalDynaBuf, '\0');
DynaBuf_to_lower(GlobalDynaBuf, GlobalDynaBuf);// convert to lower case
return(length);
}
// Try to read a file name. If "allow_library" is TRUE, library access by using
// <...> quoting is possible as well. The file name given in the assembler
// source code is converted from UNIX style to platform style.
// Returns: A pointer to the resulting file name string (NULL on error).
// Errors are handled and reported, but caller should call
// Input_skip_remainder() then.
// Note: The file name is malloc()'d and therefore should be free()'d when no
// longer used.
char* Input_read_filename(bool allow_library) {
char *lib_prefix,
end_quote;
DYNABUF_CLEAR(GlobalDynaBuf);
SKIPSPACE();
// check for library access
if(GotByte == '<') {
// if library access forbidden, complain
if(allow_library == FALSE) {
Throw_error("Writing to library not supported.");
return(NULL);
}
// read platform's lib prefix
lib_prefix = PLATFORM_LIBPREFIX;
#ifndef NO_NEED_FOR_ENV_VAR
// if lib prefix not set, complain
if(lib_prefix == NULL) {
Throw_error("\"ACME\" environment variable not found.");
return(NULL);
}
#endif
// copy lib path and set quoting char
DynaBuf_add_string(GlobalDynaBuf, lib_prefix);
end_quote = '>';
} else {
if(GotByte == '"')
end_quote = '"';
else {
Throw_error("File name quotes not found (\"\" or <>).");
return(NULL);
}
}
// read first character, complain if closing quote
if(GetQuotedByte() == end_quote) {
Throw_error("No file name given.");
return(NULL);
}
// read characters until closing quote (or EOS) is reached
// append platform-converted characters to current string
while((GotByte != CHAR_EOS) && (GotByte != end_quote)) {
DYNABUF_APPEND(GlobalDynaBuf, PLATFORM_CONVERTPATHCHAR(GotByte));
GetQuotedByte();
}
// on error, return NULL
if(GotByte == CHAR_EOS)
return(NULL);
GetByte(); // fetch next to forget closing quote
// terminate string and return copy
DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator
return(DynaBuf_get_copy(GlobalDynaBuf));
}
// Try to read a comma, skipping spaces before and after. Return TRUE if comma
// found, otherwise FALSE.
bool Input_accept_comma(void) {
SKIPSPACE();
if(GotByte != ',')
return(FALSE);
NEXTANDSKIPSPACE();
return(TRUE);
}

View File

@ -0,0 +1,72 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Input stuff
#ifndef input_H
#define input_H
#include <stdio.h>
// type definitions
// values for input_t component "Src.State"
enum inputstate_t {
INPUTSTATE_NORMAL, // everything's fine
INPUTSTATE_AGAIN, // re-process last byte
INPUTSTATE_SKIPBLANKS, // shrink multiple spaces
INPUTSTATE_LF, // send start-of-line after end-of-statement
INPUTSTATE_CR, // same, but also remember to skip LF
INPUTSTATE_SKIPLF, // skip LF if that's next
INPUTSTATE_COMMENT, // skip characters until newline or EOF
INPUTSTATE_EOB, // send end-of-block after end-of-statement
INPUTSTATE_EOF, // send end-of-file after end-of-statement
};
typedef struct {
const char* original_filename;// during RAM reads, too
int line_number; // in file (on RAM reads, too)
bool source_is_ram; // TRUE if RAM, FALSE if file
enum inputstate_t state; // state of input
union {
FILE* fd; // file descriptor
char* ram_ptr; // RAM read ptr (loop or macro block)
} src;
} input_t;
// Constants
extern const char FILE_READBINARY[];
// Special characters
// The program *heavily* relies on CHAR_EOS (end of statement) being 0x00!
#define CHAR_EOS (0) // end of statement (in high-level format)
#define CHAR_SOB '{' // start of block
#define CHAR_EOB '}' // end of block
#define CHAR_SOL (10) // start of line (in high-level format)
#define CHAR_EOF (13) // end of file (in high-level format)
// If the characters above are changed, don't forget to adjust Byte_flags[]!
// Variables
extern input_t* Input_now; // current input structure
// Prototypes
extern void Input_init(void);
extern void Input_new_file(const char* filename, FILE* fd);
extern char GetByte(void);
extern char GetQuotedByte(void);
extern void Input_skip_remainder(void);
extern void Input_ensure_EOS(void);
extern char* Input_skip_or_store_block(bool store);
extern void Input_until_terminator(char terminator);
extern int Input_append_keyword_to_global_dynabuf(void);// returns length
extern int Input_read_zone_and_keyword(zone_t*);
extern int Input_read_keyword(void);// returns length
extern int Input_read_and_lower_keyword(void);// returns length
extern char* Input_read_filename(bool library_allowed);
extern bool Input_accept_comma(void);// skips spaces before and after
#endif

View File

@ -0,0 +1,251 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Label stuff
#include <stdio.h>
#include "acme.h"
#include "alu.h"
#include "cpu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "label.h"
#include "mnemo.h"
#include "platform.h"
#include "section.h"
#include "tree.h"
// Variables
node_ra_t* Label_forest[256];// ... (because of 8-bit hash)
// Dump label value and flags to dump file
static void dump_one_label(node_ra_t* node, FILE* fd) {
label_t* label = node->body;
// output name
fprintf(fd, "%s", node->id_string);
switch(label->flags & MVALUE_FORCEBITS) {
case MVALUE_FORCE16:
fprintf(fd, "+2=");
break;
case MVALUE_FORCE16 | MVALUE_FORCE24:
/*FALLTHROUGH*/
case MVALUE_FORCE24:
fprintf(fd, "+3=");
break;
default:
fprintf(fd, " =");
}
if(label->flags & MVALUE_DEFINED)
fprintf(fd, "$%x", (unsigned int) label->value);
else
fprintf(fd, " ?");
if(label->flags & MVALUE_UNSURE)
fprintf(fd, "; ?");
if(label->usage == 0)
fprintf(fd, "; unused");
fprintf(fd, "\n");
}
// Search for label. Create if nonexistant. If created, give it flags "Flags".
// The label name must be held in GlobalDynaBuf.
label_t* Label_find(zone_t zone, int flags) {
node_ra_t* node;
label_t* label;
bool node_created;
int force_bits = flags & MVALUE_FORCEBITS;
node_created = Tree_hard_scan(&node, Label_forest, zone, TRUE);
// if node has just been created, create label as well
if(node_created) {
// Create new label structure
label = safe_malloc(sizeof(label_t));
// Finish empty label item
label->flags = flags;
label->value = 0;
label->usage = 0; // usage count
label->pass = pass_count;
node->body = label;
} else
label = node->body;
// make sure the force bits don't clash
if((node_created == FALSE) && force_bits)
if((label->flags & MVALUE_FORCEBITS) != force_bits)
Throw_error("Too late for postfix.");
return(label);
}
// Assign value to label. The routine acts upon the label's flag bits and
// produces an error if needed.
void Label_set_value(label_t* label, result_t* new, bool change) {
int flags = label->flags;
// value stuff
if((flags & MVALUE_DEFINED) && (change == FALSE)) {
// Label is already defined, so compare new and old values
if(new->value != label->value)
Throw_error("Label already defined.");
} else
// Label is not defined yet OR redefinitions are allowed
label->value = new->value;
// flags stuff
// Ensure that "unsure" labels without "isByte" state don't get that
if((flags & (MVALUE_UNSURE | MVALUE_ISBYTE)) == MVALUE_UNSURE)
new->flags &= ~MVALUE_ISBYTE;
if(change)
flags = (flags & MVALUE_UNSURE) | new->flags;
else {
if((flags & MVALUE_FORCEBITS) == 0)
if((flags & (MVALUE_UNSURE | MVALUE_DEFINED)) == 0)
flags |= new->flags & MVALUE_FORCEBITS;
flags |= new->flags & ~MVALUE_FORCEBITS;
}
label->flags = flags;
}
// (Re)set label
static enum eos_t PO_set(void) {// Now GotByte = illegal char
result_t result;
int force_bit;
label_t* label;
zone_t zone;
if(Input_read_zone_and_keyword(&zone) == 0) // skips spaces before
// Now GotByte = illegal char
return(SKIP_REMAINDER);
force_bit = Mnemo_get_force_bit();// skips spaces after
label = Label_find(zone, force_bit);
if(GotByte != '=') {
Throw_error(exception_syntax);
return(SKIP_REMAINDER);
}
// label = parsed value
GetByte();// proceed with next char
ALU_parse_expr_medium(&result);
// clear label's force bits and set new ones
label->flags &= ~(MVALUE_FORCEBITS | MVALUE_ISBYTE);
if(force_bit) {
label->flags |= force_bit;
result.flags &= ~(MVALUE_FORCEBITS | MVALUE_ISBYTE);
}
Label_set_value(label, &result, TRUE);
return(ENSURE_EOS);
}
// Select dump file
static enum eos_t PO_sl(void) {
// only process this pseudo opcode in the first pass
if((pass_flags & PASS_ISFIRST) == 0)
return(SKIP_REMAINDER);
// if label dump file already chosen, complain and exit
if(labeldump_filename) {
Throw_warning("Label dump file already chosen.");
return(SKIP_REMAINDER);
}
// read filename (returns a malloc'd copy)
// if no file name given, exit (complaining will have been done)
if((labeldump_filename = Input_read_filename(FALSE)) == NULL)
return(SKIP_REMAINDER);
// ensure there's no garbage at end of line
return(ENSURE_EOS);
}
// predefined stuff
static node_t pseudo_opcodes[] = {
PREDEFNODE("set", PO_set),
PREDEFLAST("sl", PO_sl),
// ^^^^ this marks the last element
};
// Parse implicit label definition (can be either global or local).
// GlobalDynaBuf holds the label name.
void Label_implicit_definition(zone_t zone, int stat_flags, int force_bit, bool change) {
result_t result;
label_t* label;
label = Label_find(zone, force_bit);
// implicit label definition (label)
if((stat_flags & SF_FOUND_BLANK)
&& (pass_flags & PASS_ISFIRST)
&& (1 == 1)) // FIXME - make configurable via CLI switch
Throw_warning("Implicit label definition not in leftmost column.");
CPU_ensure_defined_pc();
result.value = CPU_pc;
result.flags = MVALUE_DEFINED;
Label_set_value(label, &result, change);
}
// Parse label definition (can be either global or local).
// GlobalDynaBuf holds the label name.
void Label_parse_definition(zone_t zone, int stat_flags) {
result_t result;
label_t* label;
int force_bit = Mnemo_get_force_bit();// skips spaces after
if(GotByte == '=') {
// explicit label definition (label = <something>)
label = Label_find(zone, force_bit);
// label = parsed value
GetByte(); // skip '='
ALU_parse_expr_medium(&result);
Label_set_value(label, &result, FALSE);
Input_ensure_EOS();
} else
Label_implicit_definition(zone, stat_flags, force_bit, FALSE);
}
// Dump global labels into file
void Label_dump_all(FILE* fd) {
Tree_dump_forest(Label_forest, ZONE_GLOBAL, dump_one_label, fd);
PLATFORM_SETFILETYPE_TEXT(labeldump_filename);
}
// Init (add to tree and clear pointer table)
void Label_init(void) {
node_ra_t** ptr;
int i;
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
// Cut down all the trees (clear pointer table)
ptr = Label_forest;
// Clear pointertable
for(i = 255; i >= 0; i--)
*ptr++ = NULL;
}
// Fix name of anonymous forward label (held in DynaBuf, NOT TERMINATED!) so it
// references the *next* anonymous forward label definition. The tricky bit is,
// each name length would need its own counter. But hey, ACME's real quick in
// finding labels, so I'll just abuse the label system to store those counters.
label_t* Label_fix_forward_name(void) {
label_t* counter_label;
unsigned long number;
// terminate name, find "counter" label and read value
DynaBuf_append(GlobalDynaBuf, '\0');
counter_label = Label_find(Section_now->zone, 0);
// make sure it gets reset to zero in each new pass
if(counter_label->pass != pass_count) {
counter_label->pass = pass_count;
counter_label->value = 0;
}
number = (unsigned long) counter_label->value;
// now append to the name to make it unique
GlobalDynaBuf->size--; // forget terminator, we want to append
do {
DYNABUF_APPEND(GlobalDynaBuf, "0123456789abcdef"[number & 15]);
number >>= 4;
} while(number);
DynaBuf_append(GlobalDynaBuf, '\0');
return(counter_label);
}

View File

@ -0,0 +1,37 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Label stuff
#ifndef label_H
#define label_H
#include <stdio.h>
// "label" structure type definition
struct label_t {
value_t value; // Expression value
int flags; // Expression flags
// FIXME - integrate value and flags to result_t struct!
int usage; // usage count
int pass; // set to pass number on creation (for anon counters)
};
// Variables
extern node_ra_t* Label_forest[]; // trees (because of 8-bit hash)
// Prototypes
extern void Label_init(void);
extern void Label_set_value(label_t*, result_t*, bool change);
extern void Label_implicit_definition(zone_t zone, int stat_flags, int force_bit, bool change);
extern void Label_parse_definition(zone_t zone, int stat_flags);
extern label_t* Label_find(zone_t, int);
extern void Label_dump_all(FILE* fd);
extern label_t* Label_fix_forward_name(void);
#endif

View File

@ -0,0 +1,346 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Macro stuff
#include <string.h> // needs strlen() + memcpy()
#include "platform.h" // done first in case "inline" is redefined
#include "acme.h"
#include "alu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "label.h"
#include "section.h"
#include "tree.h"
#include "macro.h"
// Constants
#define MACRONAME_DYNABUF_INITIALSIZE 128
#define ARG_SEPARATOR ' ' // separates macro title from arg types
#define ARGTYPE_NUM_VAL 'v'
#define ARGTYPE_NUM_REF 'V'
//#define ARGTYPE_STR_VAL 's'
//#define ARGTYPE_STR_REF 'S'
#define REFERENCE_CHAR '~' // prefix for call-by-reference
#define HALF_INITIAL_ARG_TABLE_SIZE 4
static const char exception_macro_twice[] = "Macro already defined.";
// macro struct type definition
struct macro_t {
int def_line_number;// line number of definition for error msgs
char* def_filename; // file name of definition for error msgs
char* original_name; // user-supplied name for error msgs
char* parameter_list; // parameters (whole line)
char* body; // RAM block containing macro body
};
// there's no need to make this a struct and add a type component:
// when the macro has been found, accessing its parameter_list component
// gives us the possibility to find out which args are call-by-value and
// which ones are call-by-reference.
union macro_arg_t {
result_t result; // value and flags (call by value)
label_t* label; // pointer to label struct (call by reference)
};
// Variables
static dynabuf_t* user_macro_name; // original macro title
static dynabuf_t* internal_name; // plus param type chars
static node_ra_t* macro_forest[256]; // trees (because of 8b hash)
// Dynamic argument table
static union macro_arg_t* arg_table = NULL;
static int argtable_size = HALF_INITIAL_ARG_TABLE_SIZE;
// Functions
// Enlarge the argument table
static void enlarge_arg_table(void) {
argtable_size *= 2;
arg_table =
realloc(arg_table, argtable_size * sizeof(union macro_arg_t));
if(arg_table == NULL)
Throw_serious_error(exception_no_memory_left);
}
// Initialisation - allocate dynamic buffer for macro name and create arg table
void Macro_init(void) {
user_macro_name = DynaBuf_create(MACRONAME_DYNABUF_INITIALSIZE);
internal_name = DynaBuf_create(MACRONAME_DYNABUF_INITIALSIZE);
enlarge_arg_table();
}
// Read macro zone and title. Title is read to GlobalDynaBuf and then copied
// over to internal_name DynaBuf, where ARG_SEPARATOR is added.
// In user_macro_name DynaBuf, the original name is reconstructed (even with
// '.' prefix) so a copy can be linked to the resulting macro struct.
static zone_t get_zone_and_title(void) {
zone_t macro_zone;
Input_read_zone_and_keyword(&macro_zone); // skips spaces before
// now GotByte = illegal character after title
// copy macro title to private dynabuf and add separator character
DYNABUF_CLEAR(user_macro_name);
DYNABUF_CLEAR(internal_name);
if(macro_zone != ZONE_GLOBAL)
DynaBuf_append(user_macro_name, '.');
DynaBuf_add_string(user_macro_name, GLOBALDYNABUF_CURRENT);
DynaBuf_add_string(internal_name, GLOBALDYNABUF_CURRENT);
DynaBuf_append(user_macro_name, '\0');
DynaBuf_append(internal_name, ARG_SEPARATOR);
SKIPSPACE();// done here once so it's not necessary at two callers
return(macro_zone);
}
// Check for comma. If there, append to GlobalDynaBuf.
static inline bool pipe_comma(void) {
bool result;
result = Input_accept_comma();
if(result)
DYNABUF_APPEND(GlobalDynaBuf, ',');
return(result);
}
// Return malloc'd copy of string
static char* get_string_copy(const char* original) {
size_t size;
char* copy;
size = strlen(original) + 1;
copy = safe_malloc(size);
memcpy(copy, original, size);
return(copy);
}
// This function is called from both macro definition and macro call.
// Terminate macro name and copy from internal_name to GlobalDynaBuf
// (because that's where Tree_hard_scan() looks for the search string).
// Then try to find macro and return whether it was created.
static bool search_for_macro(node_ra_t** result, zone_t zone, bool create) {
DynaBuf_append(internal_name, '\0'); // terminate macro name
// now internal_name = macro_title SPC argument_specifiers NUL
DYNABUF_CLEAR(GlobalDynaBuf);
DynaBuf_add_string(GlobalDynaBuf, internal_name->buffer);
DynaBuf_append(GlobalDynaBuf, '\0');
return(Tree_hard_scan(result, macro_forest, zone, create));
}
// This function is called when an already existing macro is re-defined.
// It first outputs a warning and then a serious error, stopping assembly.
// Showing the first message as a warning guarantees that ACME does not reach
// the maximum error limit inbetween.
static void report_redefinition(node_ra_t* macro_node) {
struct macro_t* original_macro = macro_node->body;
// show warning with location of current definition
Throw_warning(exception_macro_twice);
// CAUTION, ugly kluge: fiddle with Input_now and Section_now
// data to generate helpful error messages
Input_now->original_filename = original_macro->def_filename;
Input_now->line_number = original_macro->def_line_number;
Section_now->type = "original";
Section_now->title = "definition";
// show serious error with location of original definition
Throw_serious_error(exception_macro_twice);
}
// This function is only called during the first pass, so there's no need to
// check whether to skip the definition or not.
// Return with GotByte = '}'
void Macro_parse_definition(void) {// Now GotByte = illegal char after "!macro"
char* formal_parameters;
node_ra_t* macro_node;
struct macro_t* new_macro;
zone_t macro_zone = get_zone_and_title();
// now GotByte = first non-space after title
DYNABUF_CLEAR(GlobalDynaBuf); // prepare to hold formal parameters
// GlobalDynaBuf = "" (will hold formal parameter list)
// user_macro_name = ['.'] MacroTitle NUL
// internal_name = MacroTitle ARG_SEPARATOR (grows to signature)
// Accept n>=0 comma-separated formal parameters before CHAR_SOB ('{').
// Valid argument formats are:
// .LOCAL_LABEL_BY_VALUE
// ~.LOCAL_LABEL_BY_REFERENCE
// GLOBAL_LABEL_BY_VALUE global args are very uncommon,
// ~GLOBAL_LABEL_BY_REFERENCE but not forbidden
// now GotByte = non-space
if(GotByte != CHAR_SOB) { // any at all?
do {
// handle call-by-reference character ('~')
if(GotByte != REFERENCE_CHAR)
DynaBuf_append(internal_name, ARGTYPE_NUM_VAL);
else {
DynaBuf_append(internal_name, ARGTYPE_NUM_REF);
DynaBuf_append(GlobalDynaBuf, REFERENCE_CHAR);
GetByte();
}
// handle prefix for local labels ('.')
if(GotByte == '.') {
DynaBuf_append(GlobalDynaBuf, '.');
GetByte();
}
// handle label name
Input_append_keyword_to_global_dynabuf();
} while(pipe_comma());
// ensure CHAR_SOB ('{')
if(GotByte != CHAR_SOB)
Throw_serious_error(exception_no_left_brace);
}
DynaBuf_append(GlobalDynaBuf, CHAR_EOS); // terminate param list
// now GlobalDynaBuf = comma-separated parameter list without spaces,
// but terminated with CHAR_EOS.
formal_parameters = DynaBuf_get_copy(GlobalDynaBuf);
// now GlobalDynaBuf = unused
// Reading the macro body would change the line number. To have correct
// error messages, we're checking for "macro twice" *now*.
// Search for macro. Create if not found.
// But if found, complain (macro twice).
if(search_for_macro(&macro_node, macro_zone, TRUE) == FALSE)
report_redefinition(macro_node);// quits with serious error
// Create new macro struct and set it up. Finally we'll read the body.
new_macro = safe_malloc(sizeof(struct macro_t));
new_macro->def_line_number = Input_now->line_number;
new_macro->def_filename = get_string_copy(Input_now->original_filename);
new_macro->original_name = get_string_copy(user_macro_name->buffer);
new_macro->parameter_list = formal_parameters;
new_macro->body = Input_skip_or_store_block(TRUE);// changes LineNumber
macro_node->body = new_macro; // link macro struct to tree node
// and that about sums it up
}
// Parse macro call ("+MACROTITLE"). Has to be re-entrant.
void Macro_parse_call(void) { // Now GotByte = dot or first char of macro name
char local_gotbyte;
label_t* label;
section_t new_section,
*outer_section;
input_t new_input,
*outer_input;
struct macro_t* actual_macro;
node_ra_t *macro_node,
*label_node;
zone_t macro_zone,
label_zone;
int arg_count = 0;
// Enter deeper nesting level
// Quit program if recursion too deep.
if(--macro_recursions_left < 0)
Throw_serious_error("Too deeply nested. Recursive macro calls?");
macro_zone = get_zone_and_title();
// now GotByte = first non-space after title
// internal_name = MacroTitle ARG_SEPARATOR (grows to signature)
// Accept n>=0 comma-separated arguments before CHAR_EOS.
// Valid argument formats are:
// EXPRESSION (everything that does NOT start with '~'
// ~.LOCAL_LABEL_BY_REFERENCE
// ~GLOBAL_LABEL_BY_REFERENCE
// now GotByte = non-space
if(GotByte != CHAR_EOS) { // any at all?
do {
// if arg table cannot take another element, enlarge
if(argtable_size <= arg_count)
enlarge_arg_table();
// Decide whether call-by-reference or call-by-value
// In both cases, GlobalDynaBuf may be used.
if(GotByte == REFERENCE_CHAR) {
// read call-by-reference arg
DynaBuf_append(internal_name, ARGTYPE_NUM_REF);
GetByte(); // skip '~' character
Input_read_zone_and_keyword(&label_zone);
// GotByte = illegal char
arg_table[arg_count].label =
Label_find(label_zone, 0);
} else {
// read call-by-value arg
DynaBuf_append(internal_name, ARGTYPE_NUM_VAL);
ALU_parse_expr_medium(
&(arg_table[arg_count].result));
}
arg_count++;
} while(Input_accept_comma());
}
// now arg_table contains the arguments
// now GlobalDynaBuf = unused
// check for "unknown macro"
// Search for macro. Do not create if not found.
search_for_macro(&macro_node, macro_zone, FALSE);
if(macro_node == NULL) {
Throw_error("Macro not defined (or wrong signature).");
Input_skip_remainder();
} else {
// make macro_node point to the macro struct
actual_macro = macro_node->body;
local_gotbyte = GotByte;// CAUTION - ugly kluge
// set up new input
new_input.original_filename = actual_macro->def_filename;
new_input.line_number = actual_macro->def_line_number;
new_input.source_is_ram = TRUE;
new_input.state = INPUTSTATE_NORMAL; // FIXME - fix others!
new_input.src.ram_ptr = actual_macro->parameter_list;
// remember old input
outer_input = Input_now;
// activate new input
Input_now = &new_input;
// remember old section
outer_section = Section_now;
// start new section (with new zone)
// FALSE = title mustn't be freed
Section_new_zone(&new_section, "Macro",
actual_macro->original_name, FALSE);
GetByte(); // fetch first byte of parameter list
// assign arguments
if(GotByte != CHAR_EOS) { // any at all?
arg_count = 0;
do {
// Decide whether call-by-reference
// or call-by-value
// In both cases, GlobalDynaBuf may be used.
if(GotByte == REFERENCE_CHAR) {
// assign call-by-reference arg
GetByte(); // skip '~' character
Input_read_zone_and_keyword(&label_zone);
if((Tree_hard_scan(&label_node, Label_forest, label_zone, TRUE) == FALSE)
&& (pass_flags & PASS_ISFIRST))
Throw_error("Macro parameter twice.");
label_node->body = arg_table[arg_count].label;
} else {
// assign call-by-value arg
Input_read_zone_and_keyword(&label_zone);
label = Label_find(label_zone, 0);
// FIXME - add a possibility to Label_find to make it possible to find out
// whether label was just created. Then check for the same error message here
// as above ("Macro parameter twice.").
label->value = arg_table[arg_count].result.value;
label->flags = arg_table[arg_count].result.flags;
}
arg_count++;
} while(Input_accept_comma());
}
// and now, finally, parse the actual macro body
Input_now->state = INPUTSTATE_NORMAL; // FIXME - fix others!
Input_now->src.ram_ptr = actual_macro->body;
Parse_until_eob_or_eof();
if(GotByte != CHAR_EOB)
Bug_found("IllegalBlockTerminator", GotByte);
// FIXME - call parse_ram_block(actual_macro->def_line_number,
// actual_macro->body) instead?
// end section (free title memory, if needed)
Section_finalize(&new_section);
// restore previous section
Section_now = outer_section;
// restore previous input:
Input_now = outer_input;
// restore old Gotbyte context
GotByte = local_gotbyte;// CAUTION - ugly kluge
Input_ensure_EOS();
}
macro_recursions_left++; // leave this nesting level
}

View File

@ -0,0 +1,19 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Macro stuff
#ifndef macro_H
#define macro_H
#include "config.h"
// Prototypes
extern void Macro_init(void); // create private dynabuf
extern void Macro_parse_definition(void);
extern void Macro_parse_call(void);
#endif

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,22 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Mnemonic definitions
#ifndef mnemo_H
#define mnemo_H
// Prototypes
extern void Mnemo_init(void);
extern int Mnemo_get_force_bit(void);
extern bool keyword_is_6502mnemo(int length);
extern bool keyword_is_6510mnemo(int length);
extern bool keyword_is_65c02mnemo(int length);
//extern bool keyword_is_Rockwell65c02mnemo(int length);
//extern bool keyword_is_WDC65c02mnemo(int length);
extern bool keyword_is_65816mnemo(int length);
#endif

View File

@ -0,0 +1,427 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Output stuff
#include <stdlib.h>
#include <stdio.h>
#include <string.h> // for memset()
#include "acme.h"
#include "alu.h"
#include "config.h"
#include "cpu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "output.h"
#include "platform.h"
#include "tree.h"
// Constants
#define USERMSG_DYNABUF_INITIALSIZE 80
// Variables
static bool memory_initialised = FALSE;
value_t Mem_current_pc; // Current program counter (real memory address)
value_t Mem_lowest_pc; // Start address of program (first PC given)
value_t Mem_highest_pc; // End address of program plus one
static char* output_buffer = NULL; // to hold assembled code
static dynabuf_t* user_message; // dynamic buffer (!warn/error/serious)
// Chosen output file format
static enum out_format_t output_format = OUTPUT_FORMAT_UNSPECIFIED;
// predefined stuff
static node_t* file_format_tree = NULL;// tree to hold output formats
// Possible output file formats
static node_t file_formats[] = {
PREDEFNODE(s_cbm, OUTPUT_FORMAT_CBM),
// PREDEFNODE("o65", OUTPUT_FORMAT_O65), FIXME - add!
PREDEFLAST("plain", OUTPUT_FORMAT_PLAIN),
// ^^^^ this marks the last element
};
static const char s_08[] = "08";
#define s_8 (&s_08[1]) // Yes, I know I'm sick
// Functions
// Send low byte to output file, automatically increasing program counter
void Output_byte(value_t byte) {
int offset;
CPU_ensure_defined_pc();
offset = Mem_current_pc + pc_inc;
if(offset == segment_max + 1) {
if(offset == OUTBUFFERSIZE)
Throw_serious_error("Produced too much code.");
Throw_warning("Segment reached another one, overwriting it.");
if(pass_flags & PASS_ISFIRST)
CPU_find_segment_max(offset + 1);
}
output_buffer[offset] = byte & 255;
pc_inc++;
}
// Output 8-bit value with range check
void Output_8b(value_t value) {
if((value <= 255) && (value >= -128))
Output_byte(value);
else
Throw_error(exception_number_out_of_range);
}
// Output 16-bit values with range check
void Output_16b(value_t value) {
if((value <= 65535) && (value >= -32768)) {
Output_byte(value);
Output_byte(value >> 8);
} else
Throw_error(exception_number_out_of_range);
}
// Output 24-bit values with range check
void Output_24b(value_t value) {
if((value <= 0xffffff) && (value >= -0x800000)) {
Output_byte(value);
Output_byte(value >> 8);
Output_byte(value >> 16);
} else
Throw_error(exception_number_out_of_range);
}
// Output 32-bit values (without range check)
void Output_32b(value_t value) {
// if((Value <= 0x7fffffff) && (Value >= -0x80000000)) {
Output_byte(value);
Output_byte(value >> 8);
Output_byte(value >> 16);
Output_byte(value >> 24);
// } else
// Throw_error(exception_number_out_of_range);
}
// Helper function for !8, !16, !24 and !32 pseudo opcodes
static enum eos_t output_objects(void (*fn)(value_t)) {
result_t result;
do {
ALU_parse_expr_medium(&result);
fn(result.value);
} while(Input_accept_comma());
return(ENSURE_EOS);
}
// Insert 8-bit values ("!08" / "!8" / "!by" / "!byte" pseudo opcode)
static enum eos_t PO_08(void) {
return(output_objects(Output_8b));
}
// Insert 16-bit values ("!16" / "!wo" / "!word" pseudo opcode)
static enum eos_t PO_16(void) {
return(output_objects(Output_16b));
}
// Insert 24-bit values ("!24" pseudo opcode)
static enum eos_t PO_24(void) {
return(output_objects(Output_24b));
}
// Insert 32-bit values ("!32" pseudo opcode)
static enum eos_t PO_32(void) {
return(output_objects(Output_32b));
}
// Helper function for PO_binary()
static FILE* open_bin_and_skip(char* filename, int skip) {
FILE* fd = fopen(filename, FILE_READBINARY);
if(fd)
fseek(fd, skip, SEEK_SET); // skip "Skip" bytes
else
Throw_error(exception_cannot_open_input_file);// on error, complain
return(fd);
}
// Include binary file
static enum eos_t PO_binary(void) {
result_t result;
FILE* fd;
char* filename;
int byte,
size_given = 0;
value_t size,
skip = 0;
// if file name is missing, don't bother continuing
if((filename = Input_read_filename(TRUE)) == NULL) // uses copy
return(SKIP_REMAINDER);
// read arguments, if any
if(Input_accept_comma()) {
ALU_parse_expr_empty_strict(&result);// read size argument
size = result.value;
size_given = result.flags & MVALUE_EXISTS;
// read skip argument, if given
if(Input_accept_comma()) {
ALU_parse_expr_empty_strict(&result);
if(result.flags & MVALUE_EXISTS)
skip = result.value;
}
}
if(size_given) {
// explicit size info, so truncate or pad file
if(pass_undefined_count || pass_real_errors)
// don't insert file if it's just a waste of time
pc_inc += size;
else {
// really try to open file and insert it
fd = open_bin_and_skip(filename, skip);
free(filename); // file name no longer needed
if(fd == NULL)
return(SKIP_REMAINDER);
// copy "Size" bytes from file to output
while(size--) {
byte = getc(fd);
if(byte == EOF)
byte = 0;
Output_8b(byte);
}
fclose(fd);
}
} else {
// no explicit size info, so read file until EOF
fd = open_bin_and_skip(filename, skip);
free(filename); // file name no longer needed
if(fd == NULL)
return(SKIP_REMAINDER);
// copy bytes from file to output until EOF
while((byte = getc(fd)) != EOF)
Output_8b(byte);
fclose(fd);
}
// if verbose, produce some output
if((pass_flags & PASS_ISFIRST) && (Process_verbosity > 1))
printf("Loaded %ld ($%lx) bytes from file offset %ld ($%lx).\n",
pc_inc, pc_inc, skip, skip);
return(ENSURE_EOS);
}
// Reserve space by sending bytes of given value ("!fi" / "!fill" pseudo opcode)
static enum eos_t PO_fill(void) {
result_t size;
result_t fill;
fill.value = FILLVALUE_FILL;
ALU_parse_expr_strict(&size);
if(Input_accept_comma())
ALU_parse_expr_medium(&fill);
while(size.value--)
Output_8b(fill.value);
return(ENSURE_EOS);
}
// Fill output buffer with given byte value
static void fill_completely(char value) {
memset(output_buffer, value, OUTBUFFERSIZE);
}
// Define default value for empty memory ("!initmem" pseudo opcode)
static enum eos_t PO_initmem(void) {
result_t result;
if((pass_flags & PASS_ISFIRST) == 0)
return(SKIP_REMAINDER);
// if MemInit flag is already set, complain
if(memory_initialised) {
Throw_warning("Memory already initialised.");
return(SKIP_REMAINDER);
}
// set MemInit flag
memory_initialised = TRUE;
// get value and init memory
ALU_parse_expr_strict(&result);
if((result.value > 255) || (result.value < -128))
Throw_error(exception_number_out_of_range);
// init memory
fill_completely(result.value & 0xff);
// tricky bit
// enforce another pass
if((pass_undefined_count == 0)
&& (Mem_lowest_pc < Mem_highest_pc))
pass_undefined_count = 1;
return(ENSURE_EOS);
}
// show user-defined message
static enum eos_t throw_string(const char prefix[], void (*fn)(const char*)) {
result_t result;
DYNABUF_CLEAR(user_message);
DynaBuf_add_string(user_message, prefix);
do {
if(GotByte == '"') {
// parse string
GetQuotedByte(); // read initial character
// send characters until closing quote is reached
while(GotByte && (GotByte != '"')) {
DYNABUF_APPEND(user_message, GotByte);
GetQuotedByte();
}
if(GotByte == CHAR_EOS)
return(AT_EOS_ANYWAY);
// after closing quote, proceed with next char
GetByte();
} else {
// parse value
ALU_parse_expr_medium(&result);
if(result.flags & MVALUE_DEFINED)
DynaBuf_add_value(user_message, result.value);
else
DynaBuf_add_string(user_message, "<UNDEFINED>");
}
} while(Input_accept_comma());
DynaBuf_append(user_message, '\0');
fn(user_message->buffer);
return(ENSURE_EOS);
}
////
//static enum eos_t PO_print(void) {
// return(throw_string(FIXME));
//}
// throw warning as given in source code
static enum eos_t PO_warn(void) {
return(throw_string("!warn: ", Throw_warning));
}
// throw error as given in source code
static enum eos_t PO_error(void) {
return(throw_string("!error: ", Throw_error));
}
// throw serious error as given in source code
static enum eos_t PO_serious(void) {
return(throw_string("!serious: ", Throw_serious_error));
}
// Try to set output format in DynaBuf. Returns whether succeeded.
bool Output_set_output_format(void) {
void* node_body;
if(!Tree_easy_scan(file_format_tree, &node_body, GlobalDynaBuf))
return(FALSE);
output_format = (enum out_format_t) node_body;
return(TRUE);
}
// Select output file and format ("!to" pseudo opcode)
static enum eos_t PO_to(void) {
// only act upon this pseudo opcode in first pass
if((pass_flags & PASS_ISFIRST) == 0)
return(SKIP_REMAINDER);
// if output file already chosen, complain
if(output_filename) {
Throw_warning("Output file already chosen.");
return(SKIP_REMAINDER);
}
// on file name error, give up
if((output_filename = Input_read_filename(FALSE)) == NULL) // uses copy
return(SKIP_REMAINDER);
// select output format
// if no comma found, use default file format
if(Input_accept_comma() == FALSE) {
if(output_format == OUTPUT_FORMAT_UNSPECIFIED) {
output_format = OUTPUT_FORMAT_CBM;
// output deprecation warning
if(pass_flags & PASS_ISFIRST)
Throw_warning("Used \"!to\" without file format indicator. Defaulting to \"cbm\".");
}
return(ENSURE_EOS);
}
// parse output format name
// if no keyword given, give up
if(Input_read_and_lower_keyword() == 0)
return(SKIP_REMAINDER);
if(Output_set_output_format())
return(ENSURE_EOS); // success
// error occurred
Throw_error("Unknown output format.");
return(SKIP_REMAINDER);
}
// pseudo ocpode table
static node_t pseudo_opcodes[] = {
PREDEFNODE(s_08, PO_08),
PREDEFNODE(s_8, PO_08),
PREDEFNODE("by", PO_08),
PREDEFNODE("byte", PO_08),
PREDEFNODE("16", PO_16),
PREDEFNODE("wo", PO_16),
PREDEFNODE("word", PO_16),
PREDEFNODE("24", PO_24),
PREDEFNODE("32", PO_32),
PREDEFNODE("bin", PO_binary),
PREDEFNODE("binary", PO_binary),
PREDEFNODE("fi", PO_fill),
PREDEFNODE("fill", PO_fill),
PREDEFNODE("initmem", PO_initmem),
// PREDEFNODE("print", PO_print),
PREDEFNODE("warn", PO_warn),
PREDEFNODE("error", PO_error),
PREDEFNODE("serious", PO_serious),
PREDEFLAST("to", PO_to),
// ^^^^ this marks the last element
};
// Init file format tree (is done early)
void Outputfile_init(void) {
Tree_add_table(&file_format_tree, file_formats);
}
// Init other stuff (done later)
void Output_init(signed long fill_value) {
// FIXME - call safe_malloc instead (after having it fixed so it can
// be called before a section is set up)
output_buffer = malloc(OUTBUFFERSIZE);
if(output_buffer == NULL) {
fputs("Error: No memory for output buffer.\n", stderr);
exit(EXIT_FAILURE);
}
if(fill_value == MEMINIT_USE_DEFAULT)
fill_value = FILLVALUE_INITIAL;
else
memory_initialised = TRUE;
user_message = DynaBuf_create(USERMSG_DYNABUF_INITIALSIZE);
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
// init output buffer (fill memory with initial value)
fill_completely(fill_value & 0xff);
}
// Dump memory buffer into output file
void Output_save_file(FILE* fd, value_t start, value_t end) {
if(Process_verbosity)
printf("Saving %ld ($%lx) bytes ($%lx - $%lx exclusive).\n",
end - start, end - start, start, end);
// Output file header according to file format
switch(output_format) {
case OUTPUT_FORMAT_UNSPECIFIED:
case OUTPUT_FORMAT_PLAIN:
PLATFORM_SETFILETYPE_PLAIN(output_filename);
break;
case OUTPUT_FORMAT_CBM:
PLATFORM_SETFILETYPE_CBM(output_filename);
// output 16-bit load address in little-endian byte order
putc(start & 255, fd);
putc(start >> 8, fd);
break;
}
// Dump output buffer to file
fwrite(output_buffer + start, sizeof(char), end - start, fd);
}

View File

@ -0,0 +1,42 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Output stuff
#ifndef output_H
#define output_H
#include <stdio.h>
#include "config.h"
// Constants
#define OUTBUFFERSIZE 65536
#define MEMINIT_USE_DEFAULT 256
enum out_format_t {
OUTPUT_FORMAT_UNSPECIFIED, // default value. (results in
OUTPUT_FORMAT_PLAIN, // "plain" being used)
OUTPUT_FORMAT_CBM, // default for "!to" pseudo opcode
};
// Variables
extern value_t Mem_current_pc; // Current memory pointer
extern value_t Mem_lowest_pc; // Start address of used memory
extern value_t Mem_highest_pc; // End address of used memory plus one
// Prototypes
extern void Outputfile_init(void);
extern void Output_init(signed long fill_value);
extern void Output_8b(value_t);
extern void Output_16b(value_t);
extern void Output_24b(value_t);
extern void Output_32b(value_t);
extern void Output_byte(value_t);
extern bool Output_set_output_format(void);
extern void Output_save_file(FILE* fd, value_t, value_t);
#endif

View File

@ -0,0 +1,39 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff
#include "config.h"
#include "platform.h"
// Amiga
#ifdef _AMIGA
#ifndef platform_C
#define platform_C
// Nothing here - Amigas don't need no stinkin' platform-specific stuff!
#endif
#endif
// DOS and OS/2
#ifdef __DJGPP__
#include "_dos.c"
#endif
#ifdef __OS2__
#include "_dos.c"
#endif
//#ifdef __Windows__
//#include "_dos.c"
//#endif
// RISC OS
#ifdef __riscos__
#include "_riscos.c"
#endif
// add further platform files here
// Unix/Linux/others (surprisingly also works on Windows)
#include "_std.c"

View File

@ -0,0 +1,41 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Platform specific stuff
// Amiga
#ifdef _AMIGA
#define PLATFORM_VERSION "Ported to AmigaOS by Christoph Mammitzsch."
#include "_amiga.h"
#endif
// DOS and OS/2
#ifdef __DJGPP__
#define PLATFORM_VERSION "Ported to DOS by Marco Baye."
#include "_dos.h"
#endif
#ifdef __OS2__
#define PLATFORM_VERSION "Ported to OS/2 by Malte Eckhardt."
#include "_dos.h"
#endif
//#ifdef __Windows__
//#define PLATFORM_VERSION "Ported to Windows by ?"
//#include "_dos.h"
//#endif
// RISC OS
#ifdef __riscos__
#define PLATFORM_VERSION "Ported to RISC OS by Marco Baye."
#include "_riscos.h"
#endif
// add further platform files here
// Unix/Linux/others (surprisingly also works on Windows)
#ifndef PLATFORM_VERSION
#define PLATFORM_VERSION "Platform independent version.\nCurrent maintainer Krzysztof Dabrowski aka BruSH/ElysiuM"
#endif
#include "_std.h"

View File

@ -0,0 +1,117 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Section stuff
#include "config.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "tree.h"
#include "section.h"
// Constants
static const char type_zone[] = "Zone";
static const char s_subzone[] = "subzone";
#define s_zone (&s_subzone[3]) // Yes, I know I'm sick
static char untitled[] = "<untitled>";
// ...is actually constant, but flagging it "const" results in heap of warnings
// fake section structure (for error msgs before any real section is in use)
static section_t initial_section = {
0, // zone value
"during", // "type" => normally "zone Title" or
"init", // "title" => "macro test", now "during init"
FALSE, // no, title was not malloc'd
};
// Variables
section_t* Section_now = &initial_section;// current section
static section_t outer_section; // outermost section struct
static zone_t zone_max; // Highest zone number yet
// Write given info into given zone structure and activate it
void Section_new_zone(section_t* section, const char* type, char* title, bool allocated) {
section->zone = ++zone_max;
section->type = type;
section->title = title;
section->allocated = allocated;
// activate new section
Section_now = section;
}
// Tidy up: If necessary, release section title.
// Warning - the state of the component "Allocd" may have
// changed in the meantime, so don't rely on a local variable.
void Section_finalize(section_t* section) {
if(section->allocated)
free(section->title);
}
// Switch to new zone ("!zone" or "!zn"). Has to be re-entrant.
static enum eos_t PO_zone(void) {
section_t entry_values;// buffer for outer zone
char* new_title;
bool allocated;
// remember everything about current structure
entry_values = *Section_now;
// set default values in case there is no valid title
new_title = untitled;
allocated = FALSE;
// Check whether a zone title is given. If yes and it can be read,
// get copy, remember pointer and remember to free it later on.
if(BYTEFLAGS(GotByte) & CONTS_KEYWORD) {
// Because we know of one character for sure,
// there's no need to check the return value.
Input_read_keyword();
new_title = DynaBuf_get_copy(GlobalDynaBuf);
allocated = TRUE;
}
// setup new section
// section type is "subzone", just in case a block follows
Section_new_zone(Section_now, "Subzone", new_title, allocated);
if(Parse_optional_block()) {
// Block has been parsed, so it was a SUBzone.
Section_finalize(Section_now);// end inner zone
*Section_now = entry_values;// restore entry values
} else {
// no block found, so it's a normal zone change
Section_finalize(&entry_values);// end outer zone
Section_now->type = type_zone;// change type to "zone"
}
return(ENSURE_EOS);
}
// Start subzone ("!subzone" or "!sz"). Has to be re-entrant.
static enum eos_t PO_subzone(void) {
// output deprecation warning
if(pass_flags & PASS_ISFIRST)
Throw_warning("\"!subzone {}\" is deprecated; use \"!zone {}\" instead.");
// call "!zone" instead
return(PO_zone());
}
// predefined stuff
static node_t pseudo_opcodes[] = {
PREDEFNODE(s_zone, PO_zone),
PREDEFNODE("zn", PO_zone),
PREDEFNODE(s_subzone, PO_subzone),
PREDEFLAST("sz", PO_subzone),
// ^^^^ this marks the last element
};
// Init (add to tree and register function to call at start of each pass)
void Section_init(void) {
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}
// Setup outermost section (called before each pass)
void Section_passinit(void) {
zone_max = ZONE_GLOBAL;// will be incremented by next line
Section_new_zone(&outer_section, type_zone, untitled, FALSE);
}

View File

@ -0,0 +1,37 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Section stuff
#ifndef section_H
#define section_H
#include "config.h"
// "section" structure type definition
typedef struct {
zone_t zone; // current zone value
const char* type; // "Zone", "Subzone" or "Macro"
char* title; // zone title, subzone title or macro title
int allocated; // whether title was malloc()'d
} section_t;
// Constants
#define ZONE_GLOBAL 0 // Number of "global zone"
// Variables
extern section_t *Section_now; // current section structure
// Prototypes
extern void Section_new_zone(section_t*, const char* type, char* title, bool allocated);
extern void Section_init(void);
extern void Section_passinit(void);
extern void Section_finalize(section_t* section);
#endif

View File

@ -0,0 +1,198 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Tree stuff
#include "config.h"
#include "dynabuf.h"
#include "global.h"
#include "tree.h"
#include "platform.h"
// Functions
// Compute hash value by exclusive ORing the node's ID string and write
// output to struct.
// This routine is not allowed to change GlobalDynaBuf!
hash_t make_hash(node_t* node) {
register char byte;
register const char* read;
register hash_t tmp = 0;
read = node->id_string;
while((byte = *read++))
tmp = ((tmp << 7) | (tmp >> (8 * sizeof(hash_t) - 7))) ^ byte;
node->hash_value = tmp;
return(tmp);
}
// Link a predefined data set to a tree
void add_node_to_tree(node_t** tree, node_t* node_to_add) {
hash_t hash;
// compute hash value
hash = make_hash(node_to_add);
while(*tree) {
// compare HashValue
if(hash > (*tree)->hash_value)
tree = &((*tree)->greater_than);
else
tree = &((*tree)->less_than_or_equal);
}
*tree = node_to_add;// add new leaf to tree
// New nodes are always added as leaves, so there's no need to copy a second
// pointer. And because the PREDEF* macros contain NULL as init values, it is
// not necessary to clear the new node's greater_than and less_than_or_equal
// fields.
}
// Add predefined tree items to given tree. The PREDEF* macros set HashValue
// to 1 in all entries but the last. The last entry contains 0.
void Tree_add_table(node_t** tree, node_t* table_to_add) {
// Caution when trying to optimise this. :)
while(table_to_add->hash_value)
add_node_to_tree(tree, table_to_add++);
add_node_to_tree(tree, table_to_add);
}
// Search for a given ID string in a given tree.
// Compute the hash of the given string and then use that to try to find a
// tree item that matches the given data (HashValue and DynaBuf-String).
// Store "Body" component in NodeBody and return TRUE.
// Return FALSE if no matching item found.
bool Tree_easy_scan(node_t* tree, void** node_body, struct dynabuf_t* dyna_buf) {
node_t wanted;// temporary storage
const char *p1,
*p2;
char b1,
b2;
hash_t hash;
wanted.id_string = dyna_buf->buffer;
hash = make_hash(&wanted);
while(tree) {
// compare HashValue
if(hash > tree->hash_value) {
// wanted hash is bigger than current, so go
// to tree branch with bigger hashes
tree = tree->greater_than;
continue;
}
if(hash == tree->hash_value) {
p1 = wanted.id_string;
p2 = tree->id_string;
do {
b1 = *p1++;
b2 = *p2++;
} while((b1 == b2) && b1);
if(b1 == b2) {
// store body data
*node_body = tree->body;
return(TRUE);
}
}
// either the wanted hash is smaller or
// it was exact but didn't match
tree = tree->less_than_or_equal;
}
return(FALSE); // indicate failure
}
// Search for a "RAM tree" item. Compute the hash of string in GlobalDynaBuf
// and then use that to try to find a tree item that matches the given data
// (HashValue, ID_Number, GlobalDynaBuf-String). Save pointer to found tree
// item in given location.
// If no matching item is found, check the "Create" flag. If it is set, create
// a new tree item, link to tree, fill with data and store its pointer. If the
// "Create" flag is clear, store NULL as result.
// Returns whether item was created.
bool Tree_hard_scan(node_ra_t** result, node_ra_t** forest, int id_number, bool create) {
node_t wanted; // temporary storage
node_ra_t **current_node;
node_ra_t *new_leaf_node;
const char *p1,
*p2;
char b1,
b2;
hash_t byte_hash;
wanted.id_string = GLOBALDYNABUF_CURRENT;
// incorporate ID number into hash value
byte_hash = make_hash(&wanted) ^ id_number;
wanted.hash_value = byte_hash;// correct struct's hash
PLATFORM_INT2BYTE(byte_hash);// transform into byte
current_node = &(forest[byte_hash]);// point into table
while(*current_node) {
// compare HashValue
if(wanted.hash_value > (*current_node)->hash_value) {
// wanted hash is bigger than current, so go
// to tree branch with bigger hashes
current_node = &((*current_node)->greater_than);
continue;
}
if(wanted.hash_value == (*current_node)->hash_value) {
if(id_number == (*current_node)->id_number) {
p1 = wanted.id_string;
p2 = (*current_node)->id_string;
do {
b1 = *p1++;
b2 = *p2++;
} while((b1 == b2) && b1);
if(b1 == b2) {
// store node pointer
*result = *current_node;
// return FALSE because node
// was not created
return(FALSE);
}
}
}
// either the wanted hash is smaller or
// it was exact but didn't match
current_node = &((*current_node)->less_than_or_equal);
}
// node wasn't found. Check whether to create it
if(create == FALSE) {
*result = NULL;// indicate failure
return(FALSE);// return FALSE because node was not created
}
// create new node
new_leaf_node = safe_malloc(sizeof(node_ra_t));
new_leaf_node->greater_than = NULL;
new_leaf_node->less_than_or_equal = NULL;
new_leaf_node->hash_value = wanted.hash_value;
new_leaf_node->id_number = id_number;
new_leaf_node->id_string = DynaBuf_get_copy(GlobalDynaBuf);// make permanent copy
// add new leaf to tree
*current_node = new_leaf_node;
// store pointer to new node in result location
*result = new_leaf_node;
return(TRUE);// return TRUE because node was created
}
// Call given function for each object of matching type in the given tree.
// Calls itself recursively.
void dump_tree(node_ra_t* node, int id_number, void (*fn)(node_ra_t*, FILE*), FILE* env) {
if(node->id_number == id_number)
fn(node, env);
if(node->greater_than)
dump_tree(node->greater_than, id_number, fn, env);
if(node->less_than_or_equal)
dump_tree(node->less_than_or_equal, id_number, fn, env);
}
// Calls Tree_dump_tree for each non-zero entry of the given tree table.
void Tree_dump_forest(node_ra_t** forest, int id_number, void (*fn)(node_ra_t*, FILE*), FILE* env) {
int i;
for(i = 255; i >= 0; i--) {
if(*forest)
dump_tree(*forest, id_number, fn, env);
forest++;
}
}

View File

@ -0,0 +1,50 @@
//
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2006 Marco Baye
// Have a look at "acme.c" for further info
//
// Tree stuff
#ifndef tree_H
#define tree_H
#include "config.h"
// Macros
#define PREDEFNODE(s, v) {NULL, NULL, 1, s, (void*) (v)}
#define PREDEFLAST(s, v) {NULL, NULL, 0, s, (void*) (v)}
// type definitions
typedef unsigned int hash_t;
// Must be unsigned, otherwise the hash algorithm won't be very useful!
// tree node structure type definition (for easy lookups)
struct node_t {
node_t* greater_than; // pointer to sub-tree
node_t* less_than_or_equal;// pointer to sub-tree
hash_t hash_value;
const char* id_string; // name, zero-terminated
void* body; // bytes, handles or handler function
};
// tree node structure type definition (for macros/labels)
struct node_ra_t {
node_ra_t* greater_than; // pointer to sub-tree
node_ra_t* less_than_or_equal;// pointer to sub-tree
hash_t hash_value;
char* id_string; // name, zero-terminated
void* body; // macro/label body
unsigned int id_number; // zone number
};
// Prototypes
extern void Tree_add_table(node_t** tree, node_t* table_to_add);
extern bool Tree_easy_scan(node_t* tree, void** node_body, struct dynabuf_t* dyna_buf);
extern bool Tree_hard_scan(node_ra_t**, node_ra_t**, int, bool);
extern void Tree_dump_forest(node_ra_t**, int, void (*)(node_ra_t*, FILE*), FILE*);
#endif

View File

@ -26,6 +26,7 @@ void emit_def(char *name, int is_bytecode);
int emit_data(int vartype, int consttype, long constval, int constsize);
void emit_codetag(int tag);
void emit_const(int cval);
void emit_conststr(long conststr, int strsize);
void emit_lb(void);
void emit_lw(void);
void emit_llb(int index);

View File

@ -75,6 +75,18 @@ void parse_error(char *errormsg)
fprintf(stderr, "^\nError: %s\n", errormsg);
exit(1);
}
int hexdigit(char ch)
{
ch = toupper(ch);
if (ch >= '0' && ch <= '9')
return ch - '0';
else if (ch >= 'A' && ch <= 'F')
return ch - 'A' + 10;
else
return -1;
}
t_token scan(void)
{
prevtoken = scantoken;
@ -152,12 +164,8 @@ t_token scan(void)
constval = 0;
while (scanpos++)
{
if (*scanpos >= '0' && *scanpos <= '9')
constval = constval * 16 + *scanpos - '0';
else if (*scanpos >= 'A' && *scanpos <= 'F')
constval = constval * 16 + *scanpos - 'A' + 10;
else if (*scanpos >= 'a' && *scanpos <= 'f')
constval = constval * 16 + *scanpos - 'a' + 10;
if (hexdigit(*scanpos) >= 0)
constval = constval * 16 + hexdigit(*scanpos);
else
break;
}
@ -216,6 +224,7 @@ t_token scan(void)
else if (scanpos[0] == '\"')
{
char *scanshift;
int scanoffset;
/*
* String constant.
*/
@ -225,6 +234,7 @@ t_token scan(void)
{
if (*scanpos == '\\')
{
scanoffset = 1;
switch (scanpos[1])
{
case 'n':
@ -248,12 +258,20 @@ t_token scan(void)
case '0':
*scanpos = '\0';
break;
case '$':
if (hexdigit(scanpos[2]) < 0 || hexdigit(scanpos[3]) < 0) {
parse_error("Bad string constant");
return (-1);
}
*scanpos = hexdigit(scanpos[2]) * 16 + hexdigit(scanpos[3]);
scanoffset = 3;
break;
default:
parse_error("Bad string constant");
return (-1);
}
for (scanshift = scanpos + 1; *scanshift; scanshift++)
scanshift[0] = scanshift[1];
scanshift[0] = scanshift[scanoffset];
}
scanpos++;
}

View File

@ -5,8 +5,9 @@ PLVM = plvm
PLVM02 = PLVM02.SYSTEM.sys#2000
CMD = CMD\#FF2000
PLASM = plasm
JAR = $(PLASM).jar
INCS = tokens.h symbols.h lex.h parse.h codegen.h
OBJS = plasm.c parse.o lex.o codegen.o
SRCS = plasm.c parse.c lex.c codegen.c
#
# Image filetypes for Virtual ][
#
@ -22,13 +23,18 @@ TXTTYPE = .TXT
#SYSTYPE = \#ff0000
#TXTTYPE = \#040000
all: $(PLASM) $(PLVM) $(PLVM02) $(CMD)
all: $(PLASM) $(JAR) $(PLVM) $(PLVM02) $(CMD)
clean:
-rm *.o *~ *.a *FE1000 $(PLVM02) $(CMD) $(PLASM) $(PLVM)
$(PLASM): $(OBJS) $(INCS)
cc $(OBJS) -o $(PLASM)
jar: $(JAR)
$(PLASM): $(SRCS) $(INCS)
cc $(SRCS) -o $(PLASM)
$(JAR): $(SRCS) $(INCS)
docker run -t -v "$(shell pwd):/project" mhaye/nestedvm:v4 /bin/bash -c "cd /project && nestedvm-c2jar $(JAR) plasma.Plasma $(SRCS)"
$(PLVM): plvm.c
cc plvm.c -o $(PLVM)

Binary file not shown.

View File

@ -125,4 +125,5 @@ puti(mystruc)
putln
puts(@constr); puti(constval); putln
puts("Hello from in-line string!\n")
puts("Hi \$41pple.\n");
done

View File

@ -27,12 +27,20 @@ dist.jar=${dist.dir}/PackPartitions.jar
dist.javadoc.dir=${dist.dir}/javadoc
endorsed.classpath=
excludes=
file.reference.A2Copy.jar=../A2Copy/dist/A2Copy.jar
file.reference.ac.jar=../A2Copy/dist/lib/ac.jar
file.reference.acme.jar=../ACME/src/acme.jar
file.reference.lz4-1.1.1.jar=/Users/mhaye/LL/repo/Platform/Apple/tools/PackPartitions/lib/lz4-1.1.1.jar
file.reference.plasm.jar=../PLASMA/src/plasm.jar
includes=**
jar.compress=false
javac.classpath=\
${file.reference.lz4-1.1.1.jar}:\
${libs.groovy-all.classpath}
${libs.groovy-all.classpath}:\
${file.reference.plasm.jar}:\
${file.reference.acme.jar}:\
${file.reference.A2Copy.jar}:\
${file.reference.ac.jar}
# Space-separated list of extra javac options
javac.compilerargs=
javac.deprecation=false

View File

@ -18,6 +18,11 @@ package org.demo
import java.nio.ByteBuffer
import java.nio.channels.Channels
import net.jpountz.lz4.LZ4Factory
import java.nio.charset.StandardCharsets
import java.nio.file.Files
import java.util.zip.GZIPInputStream
import java.security.MessageDigest
import javax.xml.bind.DatatypeConverter
/**
*
@ -40,6 +45,7 @@ class PackPartitions
def TYPE_PORTRAIT = 11
def mapNames = [:] // map name (and short name also) to map.2dor3d, map.num
def sysCode = [:] // memory manager
def code = [:] // code name to code.num, code.buf
def maps2D = [:] // map name to map.num, map.buf
def maps3D = [:] // map name to map.num, map.buf
@ -59,11 +65,12 @@ class PackPartitions
def debugCompression = false
def javascriptOut = null
def currentContext = []
def nWarnings = 0
def binaryStubsOnly = false
def cache = [:]
/**
* Keep track of context within the XML file, so we can spit out more useful
* error and warning messages.
@ -116,6 +123,14 @@ class PackPartitions
}
}
def calcImageHash(imgEl)
{
def data = imgEl.displayData?.find { it.@platform == "AppleII" }
assert data : "image '${imgEl.@name}' missing AppleII platform data"
byte[] bytes = MessageDigest.getInstance("MD5").digest(data.toString().getBytes())
return DatatypeConverter.printHexBinary(bytes)
}
def pixelize(dataEl)
{
def nBytes = dataEl.@width as int
@ -524,54 +539,6 @@ class PackPartitions
}
}
/**
* Dump map data to Javascript code, to help in debugging the raycaster. This way,
* the Javascript version can run the same map, and we can compare its results to
* the 6502 results.
*/
def dumpJsMap(rows, texMap)
{
def width = rows[0].size()+2
// Write the map data. First comes the sentinel row.
javascriptOut.println("var map = [")
javascriptOut.print(" [")
(0..<width).each { javascriptOut.print("-1,") }
javascriptOut.println("],")
// Now the real map data
rows.each { row ->
javascriptOut.print(" [-1,")
row.each { tile ->
def b = texMap[tile?.@id]
if ((b & 0x80) == 0)
javascriptOut.format("%2d,", b)
else
javascriptOut.print(" 0,")
}
javascriptOut.println("-1,],")
}
// Finish the map data with another sentinel row
javascriptOut.print(" [")
(0..<width).each { javascriptOut.print("-1,") }
javascriptOut.println("]")
javascriptOut.println("];\n")
// Then write out the sprites
javascriptOut.println("var allSprites = [")
rows.eachWithIndex { row, y ->
row.eachWithIndex { tile, x ->
def b = texMap[tile?.@id]
if ((b & 0x80) != 0) {
// y+1 below to account for initial sentinel row
javascriptOut.format(" {type:%2d, x:%2d.5, y:%2d.5},\n", b & 0x7f, x, y+1)
}
}
}
javascriptOut.println("];\n")
}
def write3DMap(buf, mapName, rows, scriptModule, locationsWithTriggers) // [ref BigBlue1_50]
{
def width = rows[0].size() + 2 // Sentinel $FF at start and end of each row
@ -639,9 +606,6 @@ class PackPartitions
// Sentinel row of $FF at end of map
(0..<width).each { buf.put((byte)0xFF) }
if (javascriptOut)
dumpJsMap(rows, texMap)
}
// The renderer wants bits of the two pixels interleaved in a special way.
@ -676,26 +640,77 @@ class PackPartitions
{
return name.toLowerCase().replaceAll(" ", "")
}
def grabFromCache(kind, addTo, name, hash)
{
def key = kind + ":" + name
if (cache.containsKey(key) && cache[key].hash == hash) {
def num = addTo.size() + 1
addTo[name] = [num:num, buf:wrapByteArray(cache[key].data)]
return true
}
return false
}
def addToCache(kind, addTo, name, hash, buf)
{
def num = addTo.size() + 1
addTo[name] = [num:num, buf:buf]
def uncompressedLen = buf.position()
def uncompressedData = new byte[uncompressedLen]
buf.position(0)
buf.get(uncompressedData)
def key = kind + ":" + name
cache[key] = [hash:hash, data:uncompressedData]
}
def grabEntireFromCache(kind, addTo, hash)
{
if (cache.containsKey(kind) && cache[kind].hash == hash) {
cache[kind].ents.each { ent ->
def num = addTo.size() + 1
addTo[ent.name] = [num: ent.num, buf: wrapByteArray(ent.data)]
}
return true
}
return false
}
def addEntireToCache(kind, addTo, hash)
{
def ents = []
addTo.each { name, ent ->
def buf = ent.buf
def uncompressedLen = buf.position()
def uncompressedData = new byte[uncompressedLen]
buf.position(0)
buf.get(uncompressedData)
ents << [name:name, num:ent.num, data:uncompressedData]
}
cache[kind] = [hash:hash, ents:ents]
}
def packTexture(imgEl)
{
def num = textures.size() + 1
def name = imgEl.@name ?: "img$num"
//println "Packing texture #$num named '${imgEl.@name}'."
def pixels = parseTexture(imgEl)
calcTransparency(pixels)
def buf = ByteBuffer.allocate(50000)
writeTexture(buf, pixels)
textures[stripName(imgEl.@name)] = [num:num, buf:buf]
def name = stripName(imgEl.@name)
def hash = calcImageHash(imgEl)
if (!grabFromCache("texture", textures, name, hash)) {
def pixels = parseTexture(imgEl)
calcTransparency(pixels)
def buf = ByteBuffer.allocate(50000)
writeTexture(buf, pixels)
addToCache("texture", textures, name, hash, buf)
}
}
def packFrameImage(imgEl)
{
def num = frames.size() + 1
def name = imgEl.@name ?: "img$num"
//println "Packing frame image #$num named '${imgEl.@name}'."
def buf = parseFrameData(imgEl)
frames[imgEl.@name] = [num:num, buf:buf]
def hash = calcImageHash(imgEl)
if (!grabFromCache("frame", frames, name, hash))
addToCache("frame", frames, name, hash, parseFrameData(imgEl))
}
def packPortrait(imgEl)
@ -905,7 +920,9 @@ class PackPartitions
{
def inBuf = new byte[256]
def outBuf = ByteBuffer.allocate(50000)
def stream = new File(path).withInputStream { stream ->
if (binaryStubsOnly)
return outBuf
new File(path).withInputStream { stream ->
while (true) {
def got = stream.read(inBuf)
if (got < 0) break
@ -927,6 +944,10 @@ class PackPartitions
def num = modules.size() + 1
//println "Reading module #$num from '$path'."
def bufObj = readBinary(path)
if (binaryStubsOnly) {
modules[name] = [num:num, buf:bufObj]
return
}
def bufLen = bufObj.position()
def buf = new byte[bufLen]
@ -1044,9 +1065,7 @@ class PackPartitions
}
newFixup.add((byte)0xFF)
modules[name] = [num:num, buf:wrapByteArray(newAsmCode)]
bytecodes[name] = [num:num, buf:wrapByteList(byteCode)]
fixups[name] = [num:num, buf:wrapByteList(newFixup)]
return [wrapByteArray(newAsmCode), wrapByteList(byteCode), wrapByteList(newFixup)]
}
def wrapByteArray(array) {
@ -1258,34 +1277,184 @@ class PackPartitions
stream.write(it.buf.data, 0, it.buf.len)
}
}
def runNestedvm(programClass, programName, args, inDir, inFile, outFile)
{
def prevStdin = System.in
def prevStdout = System.out
def prevUserDir = System.getProperty("user.dir")
def result
try
{
System.setProperty("user.dir", new File(inDir).getAbsolutePath())
if (inFile) {
inFile.withInputStream { inStream ->
System.in = inStream
outFile.withOutputStream { outStream ->
System.out = new PrintStream(outStream)
result = programClass.newInstance().run(args)
}
}
}
else {
result = programClass.newInstance().run(args)
}
} finally {
System.in = prevStdin
System.out = prevStdout
System.setProperty("user.dir", prevUserDir)
}
if (result != 0)
throw new Exception("$programName failed with code $result")
}
def getCodeDeps(codeFile)
{
def baseDir = codeFile.getParentFile()
// If we've cached deps for this file, just return that.
def key = "codeDeps:" + codeFile.toString()
def hash = codeFile.lastModified()
def deps = []
if (cache.containsKey(key) && cache[key].hash == hash)
deps = cache[key].deps
else {
codeFile.eachLine { line ->
def m = line =~ /^\s*include\s+"([^"]+)"\s*$/
if (m)
deps << new File(baseDir, m.group(1))
m = line =~ /\s*!source "([^"]+)"\s*$/
if (m) {
if (codeFile ==~ /.*\.pla$/) {
// Asm includes inside a plasma file have an extra level of ".."
// because they end up getting processed within the "build" dir.
deps << new File(new File(baseDir, "build"), m.group(1)).getCanonicalFile()
}
else
deps << new File(baseDir, m.group(1)).getCanonicalFile()
}
}
cache[key] = [hash:hash, deps:deps]
}
// Recursively calc deps of the deps
deps.each { dep -> getCodeDeps(dep) }
// And return everything we found.
return deps
}
def getLastDep(codeFile)
{
codeFile = codeFile.getCanonicalFile()
def time = codeFile.lastModified()
getCodeDeps(codeFile).each { dep ->
time = Math.max(time, getLastDep(dep))
}
return time
}
def assembleCode(codeName, inDir)
{
if (binaryStubsOnly)
return addToCache("code", code, codeName, 1, ByteBuffer.allocate(1))
def hash = getLastDep(new File(inDir, codeName + ".s"))
if (grabFromCache("code", code, codeName, hash))
return
println "Assembling ${codeName}.s"
new File(inDir + "build").mkdir()
String[] args = ["acme", "-f", "plain",
"-o", "build/" + codeName + ".b",
codeName + ".s"]
runNestedvm(acme.Acme.class, "ACME assembler", args, inDir, null, null)
addToCache("code", code, codeName, hash, readBinary(inDir + "build/" + codeName + ".b"))
}
def assembleCore(inDir)
{
if (binaryStubsOnly)
return addToCache("sysCode", sysCode, "mem", 1, ByteBuffer.allocate(1))
def hash = getLastDep(new File(inDir, "mem.s"))
if (grabFromCache("sysCode", sysCode, "mem", hash))
return
println "Assembling mem.s"
new File(inDir + "build").mkdir()
String[] args = ["acme", "-o", "build/cmd.sys#2000", "mem.s"]
runNestedvm(acme.Acme.class, "ACME assembler", args, inDir, null, null)
addToCache("sysCode", sysCode, "mem", hash, readBinary(inDir + "build/cmd.sys#2000"))
}
def compileModule(moduleName, codeDir)
{
if (binaryStubsOnly)
return addToCache("modules", modules, moduleName, 1, ByteBuffer.allocate(1))
def hash = getLastDep(new File(codeDir + moduleName + ".pla"))
if (grabFromCache("modules", modules, moduleName, hash) &&
grabFromCache("bytecodes", bytecodes, moduleName, hash) &&
grabFromCache("fixups", fixups, moduleName, hash))
{
return
}
println "Compiling ${moduleName}.pla"
String[] args = ["plasm", "-AM"]
new File(codeDir + "build").mkdir()
runNestedvm(plasma.Plasma.class, "PLASMA compiler", args, codeDir,
new File(codeDir + moduleName + ".pla"),
new File(codeDir + "build/" + moduleName + ".a"))
args = ["acme", "--setpc", "4096",
"-o", moduleName + ".b",
moduleName + ".a"]
runNestedvm(acme.Acme.class, "ACME assembler", args, codeDir + "build/", null, null)
def module, bytecode, fixup
(module, bytecode, fixup) = readModule(moduleName, codeDir + "build/" + moduleName + ".b")
addToCache("modules", modules, moduleName, hash, module)
addToCache("bytecodes", bytecodes, moduleName, hash, bytecode)
addToCache("fixups", fixups, moduleName, hash, fixup)
}
def readAllCode()
{
readCode("render", "src/raycast/build/render.b")
readCode("expand", "src/raycast/build/expand.b")
readCode("fontEngine", "src/font/build/fontEngine.b")
readCode("tileEngine", "src/tile/build/tile.b")
assembleCore("src/core/")
assembleCode("render", "src/raycast/")
assembleCode("expand", "src/raycast/")
assembleCode("fontEngine", "src/font/")
assembleCode("tileEngine", "src/tile/")
readModule("gameloop", "src/plasma/build/gameloop.b")
readModule("globalScripts", "src/plasma/build/globalScripts.b")
readModule("combat", "src/plasma/build/combat.b")
readModule("gen_enemies", "src/plasma/build/gen_enemies.b")
compileModule("gameloop", "src/plasma/")
compileModule("globalScripts", "src/plasma/")
compileModule("combat", "src/plasma/")
compileModule("gen_enemies", "src/plasma/")
}
def pack(xmlPath, binPath, javascriptPath)
def pack(xmlPath)
{
// Save time by using cache of previous run
File cacheFile = new File(xmlPath.toString()+".cache")
if (cacheFile.exists()) {
ObjectInputStream out = new ObjectInputStream(new FileInputStream(cacheFile));
cache = out.readObject();
out.close()
}
// Read in code chunks. For now these are hard coded, but I guess they ought to
// be configured in a config file somewhere...?
//
println "Reading code resources."
readAllCode()
// We have only one font, for now at least.
println "Reading fonts."
readFont("font", "data/fonts/font.bin")
// Open the XML data file produced by Outlaw Editor
def dataIn = new XmlParser().parse(xmlPath)
def xmlLastMod = xmlPath.lastModified()
// Pre-pack the data for each tile
println "Packing tiles."
@ -1296,66 +1465,52 @@ class PackPartitions
// Pack the global tile set before other tile sets (contains the player avatar, etc.)
packGlobalTileSet(dataIn)
// Pack each image, which has the side-effect of filling in the
// image name map. Handle frame images separately.
//
def titleFound = false
def uiFramesFound = 0
def imageNamesPacked = [:]
for (def pass = 0; pass < 5; pass++)
{
switch (pass) {
case 0: println "Packing title screen."; break
case 1: println "Packing UI frames."; break
case 2: println "Packing other frame images."; break
case 3: println "Packing textures."; break
case 4: println "Packing portraits."; break
}
dataIn.image.sort{it.@name.toLowerCase()}.each { image ->
def category = image.@category?.toLowerCase()
def name = image.@name.toLowerCase()
if (category == "fullscreen" && name == "title") {
if (pass == 0) {
packFrameImage(image)
titleFound = true
imageNamesPacked[name] = true
}
}
else if (category == "uiframe") {
if (pass == 1) {
packFrameImage(image)
++uiFramesFound
imageNamesPacked[name] = true
}
}
else if (category == "fullscreen") {
if (pass == 2) {
packFrameImage(image)
imageNamesPacked[name] = true
}
}
else if (category == "wall" || category == "sprite") {
if (pass == 3) {
packTexture(image)
imageNamesPacked[name] = true
}
}
else if (category == "portrait") {
if (pass == 4) {
packPortrait(image)
imageNamesPacked[name] = true
}
}
}
}
assert titleFound : "Couldn't find title image. Should be category='FULLSCREEN', name='title'"
assert uiFramesFound == 2 : "Need exactly 2 UI frames, found $uiFramesFound instead."
// Divvy up the images by category
def titleImgs = []
def uiFrameImgs = []
def fullscreenImgs = []
def textureImgs = []
def portraitImgs = []
dataIn.image.each { image ->
if (!(image.@name.toLowerCase() in imageNamesPacked))
println "Warning: couldn't classify image named '${image.@name}', category '${image.@category}'."
dataIn.image.sort{it.@name.toLowerCase()}.each { image ->
def category = image.@category?.toLowerCase()
def name = image.@name.toLowerCase()
if (category == "fullscreen" && name == "title")
titleImgs << image
else if (category == "uiframe")
uiFrameImgs << image
else if (category == "fullscreen")
fullscreenImgs << image
else if (category == "wall" || category == "sprite")
textureImgs << image
else if (category == "portrait")
portraitImgs << image
else
println "Warning: couldn't classify image named '${name}', category '${category}'."
}
assert titleImgs.size() == 1 : "Couldn't find title image. Should be category='FULLSCREEN', name='title'"
assert uiFrameImgs.size() == 2 : "Need exactly 2 UI frames, found ${uiFramesImgs.size()} instead."
// Pack each image, which has the side-effect of filling in the image name map.
println "Packing title screen."
titleImgs.each { image -> packFrameImage(image) }
println "Packing UI frames."
uiFrameImgs.each { image -> packFrameImage(image) }
println "Packing other frame images."
fullscreenImgs.each { image -> packFrameImage(image) }
println "Packing textures."
textureImgs.each { image -> packTexture(image) }
println "Packing portraits."
if (!grabEntireFromCache("portraits", portraits, xmlLastMod)) {
portraitImgs.each { image -> packPortrait(image) }
addEntireToCache("portraits", portraits, xmlLastMod)
}
// Number all the maps and record them with names
def num2D = 0
def num3D = 0
@ -1387,15 +1542,13 @@ class PackPartitions
printWarning "map name '${map?.@name}' should contain '2D' or '3D'. Skipping."
}
// Ready to start writing the output file.
// Ready to write the output file.
println "Writing output file."
new File("build/root").mkdir()
def binPath = new File("build/root/game.part.0.bin").path
new File(binPath).withOutputStream { stream -> writePartition(stream) }
// Finish up Javacript if necessary
if (javascriptPath)
javascriptOut.close()
// Lastly, print stats
// Print stats
println "Compression saved $compressionSavings bytes."
if (compressionSavings > 0) {
def endSize = new File(binPath).length()
@ -1404,7 +1557,12 @@ class PackPartitions
println "Size $origSize -> $endSize ($savPct% savings)"
}
println "Done."
// Write a new cache file
File newCacheFile = new File(xmlPath.toString()+".cache.new")
ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(newCacheFile));
out.writeObject(cache);
out.close()
newCacheFile.renameTo(cacheFile)
}
def isAlnum(ch)
@ -1540,13 +1698,31 @@ class PackPartitions
out.println("end")
}
def replaceIfDiff(oldFile)
{
def newFile = new File(oldFile + ".new")
oldFile = new File(oldFile)
def newText = newFile.text
def oldText = oldFile.text
if (newText == oldText) {
//println "Same text, deleting $newFile"
newFile.delete()
}
else {
//println "Changed text, renaming $newFile to $oldFile"
newFile.renameTo(oldFile)
}
}
def dataGen(xmlPath)
{
// Open the XML data file produced by Outlaw Editor
def dataIn = new XmlParser().parse(xmlPath)
// Translate image names to constants
new File("src/plasma/gen_images.plh").withWriter { out ->
new File("src/plasma/gen_images.plh.new").withWriter { out ->
def portraitNum = 0
dataIn.image.sort{it.@name.toLowerCase()}.each { image ->
def category = image.@category?.toLowerCase()
@ -1567,17 +1743,19 @@ class PackPartitions
}
}
}
replaceIfDiff("src/plasma/gen_images.plh")
// Translate enemies to code
def enemyLines = new File("data/world/enemies.tsv").readLines()
new File("src/plasma/gen_enemies.plh").withWriter { out ->
new File("src/plasma/gen_enemies.plh.new").withWriter { out ->
out.println("// Generated code - DO NOT MODIFY BY HAND\n")
enemyLines[1..-1].eachWithIndex { line, index ->
out.println("const CE${humanNameToSymbol(line.split("\t")[0], false)} = ${index*2}")
}
out.println("const NUM_ENEMIES = ${enemyLines.size - 1}")
}
new File("src/plasma/gen_enemies.pla").withWriter { out ->
replaceIfDiff("src/plasma/gen_enemies.plh")
new File("src/plasma/gen_enemies.pla.new").withWriter { out ->
out.println("// Generated code - DO NOT MODIFY BY HAND")
out.println()
out.println("include \"gamelib.plh\"")
@ -1600,10 +1778,13 @@ class PackPartitions
out.println("return @funcTbl")
out.println("done")
}
replaceIfDiff("src/plasma/gen_enemies.pla")
// Produce a list of assembly and PLASMA code segments
binaryStubsOnly = true
readAllCode()
new File("src/plasma/gen_modules.plh").withWriter { out ->
binaryStubsOnly = false
new File("src/plasma/gen_modules.plh.new").withWriter { out ->
code.each { k, v ->
out.println "const CODE_${humanNameToSymbol(k, true)} = ${v.num}"
}
@ -1611,6 +1792,23 @@ class PackPartitions
out.println "const MODULE_${humanNameToSymbol(k, true)} = ${v.num}"
}
}
replaceIfDiff("src/plasma/gen_modules.plh")
}
def createImage()
{
// Copy the PLASMA VM file to the output directory
Files.copy(new File("PLVM02.SYSTEM.sys").toPath(), new File("build/root/PLVM02.SYSTEM.sys").toPath())
// Copy the memory manager to the output directory
Files.copy(new File("src/core/build/cmd.sys#2000").toPath(), new File("build/root/cmd.sys#2000").toPath())
// Decompress the base image
Files.copy(new GZIPInputStream(new FileInputStream("data/disks/base.2mg.gz")), new File("build/game.2mg").toPath())
// Now put the files into the image
String[] args = ["-put", "build/game.2mg", "/", "build/root"]
new a2copy.A2Copy().main(args)
}
static void main(String[] args)
@ -1632,12 +1830,11 @@ class PackPartitions
}
// Check the arguments
if (!(args.size() == 2 || args.size() == 3)) {
println "Usage: convert yourOutlawFile.xml game.part.0.bin [intcastMap.js]"
println " (where intcastMap.js is to aid in debugging the Javascript raycaster)"
println " or: convert yourOutlawFile.xml -dataGen"
if (args.size() != 1) {
println "Usage: packPartitions yourOutlawFile.xml"
System.exit(1);
}
def xmlFile = new File(args[0])
// If there's an existing error file, remote it.
def errorFile = new File("pack_error.txt")
@ -1647,10 +1844,20 @@ class PackPartitions
// Go for it.
def inst = new PackPartitions()
try {
if (args[1] == "-dataGen")
inst.dataGen(args[0])
else
inst.pack(args[0], args[1], args.size() > 2 ? args[2] : null)
// Blow away everything in the build directory, and recreate it
def buildDir = new File("build")
if (buildDir.exists())
buildDir.deleteDir()
buildDir.mkdirs()
// Create PLASMA headers
new PackPartitions().dataGen(xmlFile)
// Pack everything into a binary file
inst.pack(xmlFile)
// And create the final disk image
inst.createImage()
}
catch (Throwable t) {
errorFile.withWriter { out ->

View File

@ -0,0 +1 @@
../tools/PLASMA/src/PLVM02.SYSTEM.sys

View File

@ -29,55 +29,11 @@
<!-- Create build directory -->
<mkdir dir="${build.dir}"/>
<!-- Generate code from tables -->
<echo>Generating code from tables.</echo>
<java jar="${pack.dir}/PackPartitions.jar" fork="true" failonerror="true">
<arg value="data/world/world.xml"/>
<arg value="-dataGen"/>
</java>
<!-- Build sub-projects -->
<echo>Building core.</echo>
<ant dir="${src.dir}/core" target="build" useNativeBasedir="true" inheritAll="false"/>
<echo>Building game loop.</echo>
<ant dir="${src.dir}/plasma" target="build" useNativeBasedir="true" inheritAll="false"/>
<echo>Building raycast.</echo>
<ant dir="${src.dir}/raycast" target="build" useNativeBasedir="true" inheritAll="false"/>
<echo>Building font engine.</echo>
<ant dir="${src.dir}/font" target="build" useNativeBasedir="true" inheritAll="false"/>
<echo>Building tile engine.</echo>
<ant dir="${src.dir}/tile" target="build" useNativeBasedir="true" inheritAll="false"/>
<!-- Pack the game data -->
<echo>Packing game and code resources.</echo>
<java jar="${pack.dir}/PackPartitions.jar" fork="true" failonerror="true">
<arg value="data/world/world.xml"/>
<arg value="build/game.part.0.bin"/>
</java>
<!-- Construct a directory to put on the Apple -->
<delete failonerror="false" dir="${build.dir}/root"/>
<mkdir dir="${build.dir}/root"/>
<copy todir="${build.dir}/root">
<fileset dir="${plasma.dir}" includes="PLVM02.SYSTEM*"/>
<fileset dir="${src.dir}/core/build" includes="*.sys*"/>
<fileset dir="./build" includes="game.part*.bin"/>
</copy>
<mkdir dir="${build.dir}/root/"/>
<!-- Make a new base image file -->
<delete failonerror="false" file="${build.dir}/${projName}.2mg"/>
<bunzip2 src="./data/disks/base.2mg.bz2" dest="${build.dir}/${projName}.2mg"/>
<!-- And stuff the directory into it -->
<echo>Adding files to image.</echo>
<java jar="${a2copy.dir}/a2copy.jar" fork="true" failonerror="true">
<arg value="-put"/>
<arg value="${build.dir}/${projName}.2mg"/>
<arg value="/"/>
<arg value="${build.dir}/root"/>
</java>
</target>
</project>

Binary file not shown.

View File

@ -20,6 +20,9 @@
<target name="clean">
<delete failonerror="false" dir="${build.dir}"/>
<delete>
<fileset dir="." includes="gen_*.pl?"/>
</delete>
</target>
<target name="build">