Initial commit, ACME 0.94.2 - one or two bugs fixed and one feature added since the last release, but no third-party patches included yet.

git-svn-id: https://svn.code.sf.net/p/acme-crossass/code-0/trunk@2 4df02467-bbd4-4a76-a152-e7ce94205b78
This commit is contained in:
marcobaye 2012-02-27 21:14:46 +00:00
parent d1045a9474
commit db3c69a77b
76 changed files with 15230 additions and 0 deletions

73
trunk/ACME_Lib/6502/std.a Normal file
View File

@ -0,0 +1,73 @@
;ACME 0.07
!ifdef Lib_6502_std_a !eof
Lib_6502_std_a = 1
; Labels and macros for plain 6502 processor
cpu_nmi = $fffa
cpu_reset = $fffc
cpu_irq = $fffe
; skip byte
!macro bit8 {
!byte $24; opcode of BIT $.. command
}
; skip word
!macro bit16 {
!byte $2c; opcode of BIT $.... command
}
; increase 16-bit counter
!macro inc16 .t {
inc .t
bne .j; "*" syntax not used here because size of ".t" is unknown
inc .t + 1
.j
}
; far branches
!macro bcc .t {
bcs * + 5
jmp .t
}
!macro bcs .t {
bcc * + 5
jmp .t
}
!macro beq .t {
bne * + 5
jmp .t
}
!macro bne .t {
beq * + 5
jmp .t
}
!macro bmi .t {
bpl * + 5
jmp .t
}
!macro bpl .t {
bmi * + 5
jmp .t
}
!macro bvc .t {
bvs * + 5
jmp .t
}
!macro bvs .t {
bvc * + 5
jmp .t
}

View File

@ -0,0 +1,80 @@
;ACME 0.07
!ifdef Lib_65816_std_a !eof
Lib_65816_std_a = 1
; Labels and macros for Western Digital's 65c816 processor
cpu_e_cop = $fff4
cpu_e_abort = $fff8
cpu_e_nmi = $fffa
cpu_e_reset = $fffc
cpu_e_irq = $fffe
cpu_n_cop = $fff4
cpu_n_brk = $fff6
cpu_n_abort = $fff8
cpu_n_nmi = $fffa
cpu_n_irq = $fffe
!macro cpu_emu {; switch to emulation mode
sec
xce
}
!macro cpu_native {; switch to native mode
clc
xce
}
!macro a8 {; switch A to 8 bit
sep #%..#.....
!as
}
!macro a16 {; switch A to 16 bit
rep #%..#.....
!al
}
!macro i8 {; switch X/Y to 8 bit
sep #%...#....
!rs
}
!macro i16 {; switch X/Y to 16 bit
rep #%...#....
!rl
}
!macro ai8 {; switch A/X/Y to 8 bit
sep #%..##....
!as
!rs
}
!macro ai16 {; switch A/X/Y to 16 bit
rep #%..##....
!al
!rl
}
!macro a8i16 {; switch A to 8, X/Y to 16 bit
+a8
+i16
}
!macro a16i8 {; switch A to 16, X/Y to 8 bit
+a16
+i8
}
!macro inc24 .t {; increase 24-bit counter
inc .t
bne .j; "*" syntax not used here because size of ".t" is unknown
inc .t + 1
bne .j
inc .t + 2
.j
}

87
trunk/docs/65816.txt Normal file
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.

172
trunk/docs/AddrModes.txt Normal file
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.

818
trunk/docs/AllPOs.txt Normal file
View File

@ -0,0 +1,818 @@
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 XOR 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 "abcdefg" ; 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') XOR $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
Call: !ifndef LABEL { BLOCK } [ else { BLOCK } ]
or: !ifndef LABEL STATEMENT
Purpose: Conditional assembly, depending on whether a label is
already defined or not.
With "ifdef", if the label is defined, the first block
of statements will be parsed; if it isn't, the second
block will be parsed instead (if present).
With "ifndef", it's the other way around: If the label
isn't defined, the first block of statements will be
parsed; if it is defined, the second block will be
parsed instead (if present).
CAUTION: These opcodes were added to speed up parsing
of library files (see example below). They can be used
to tell passes apart, therefore only use them in your
own files if you're sure you *really* know what you
are doing - using them in the wrong way will result in
loads of error messages.
Parameters: LABEL: Any valid label name.
BLOCK: A block of assembler statements.
STATEMENT: Any assembler statement.
Examples: ; this was taken from <6502/std.a>:
!ifdef Lib_6502_std_a !eof ; in later passes,
Lib_6502_std_a = 1 ; skip this file.
; During the first pass, the label is not defined,
; therefore the file will get parsed. During all
; further passes, the label is already defined,
; therefore the file will be skipped.
; if the following code gets included several times,
; only assemble it at the first location:
!ifndef my_label {my_label} ; only define if undefined
!if *=my_label {
; imagine some code here...
; this block will only be assembled at the
; first location where it is included. all
; further instances will be skipped.
}
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 caller's label's
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 [,MODIFIER]*
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. Because some people do this overlapping
on purpose, the warnings can be suppressed using
modifier keywords.
Future versions of ACME may issue errors instead of
warnings.
Parameters: EXPRESSION: Any formula the value parser accepts, but
it must be solvable even in the first pass.
MODIFIER: "overlay" or "invisible" (without quotes):
"overlay" suppresses the warning "Segment starts
inside another one, overwriting it".
"invisible" makes the new segment invisible, so that
_other_ segments will never raise the warning "Segment
reached another one, overwriting it".
Examples: !to "TinyDemo", cbm ; define output file + format
*= $0801 ; start at C64 BASIC start
!src "basicmacros.a" ; include macro definitions
+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
*= $8010, overlay, invisible ; go back and patch
; the graphics, suppressing warnings
; 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
!src "basicmacros.a" ; include macro definitions
+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
*= $8010, overlay, invisible ; go back and patch
; the graphics, suppressing warnings
; 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...
}

340
trunk/docs/COPYING Normal file
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.

348
trunk/docs/Changes.txt Normal file
View File

@ -0,0 +1,348 @@
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.94.2
----------------------------------------------------------------------
----------------------------------------------------------------------
Section: New in release 0.94.1
----------------------------------------------------------------------
New CLI switch: "-D" allows to set global labels via the command line.
New CLI switch: "-Wno-label-indent" switches off warnings about
indented implicit label definitions.
New PO: "!ifndef" (finally a companion for "!ifdef"...)
When setting the program counter via "*=", modifiers ("overlay" and
"invisible") allow to suppress warnings about segment overlap.
Float values without leading digits are now accepted.
----------------------------------------------------------------------
Section: New in release 0.93
----------------------------------------------------------------------
Change: If "Offset assembly still active at end of segment", it no
longer gets switched off.
Change: Operators ASR and LSL/ASL now can also handle FP (LSR still
makes no sense).
Change: Added distinction between '/' and "DIV" operators: DIV always
gives integer results, while '/' depends on operands.
New functions: added int() and float() functions.
Internal change: default fill value for !align is now CPU-specific
(but still 234)
New CLI switch: "--use-stdout" prints errors to stdout instead of
stderr (a fix for the "Relaunch64" IDE I have nothing to do with)
----------------------------------------------------------------------
Section: New in release 0.92
----------------------------------------------------------------------
Text versions of arithmetic/logic operators (XOR, DIV, MOD, etc.) no
longer need to be in upper case.
Experimental support for floating point maths.
Support for mathematical functions:
sin(), cos(), tan(), arcsin(), arccos(), arctan()
New errors:
"Argument out of range.", "Unknown function."
These operators always deliver ints:
not, and, or, xor, lowbyteof, highbyteof, bankbyteof, mod, asl,
lsl, asr, lsr
----------------------------------------------------------------------
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. :-)

380
trunk/docs/Errors.txt Normal file
View File

@ -0,0 +1,380 @@
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. Releases 0.94 and
higher support a command line option to switch off this warning
("-Wno-label-indent").
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.
There's a "*=" command inside an offset assembly block. There has
been some discussion on the ACME mailing list over whether this
should raise a warning. Future releases of ACME might not warn
anymore.
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 warning can be switched off using
modifier keywords when changing the program counter via "*=".
Future versions of ACME might throw an error instead of a warning
in this case.
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 warning can be switched
off using modifier keywords when changing the program counter via
"*=".
Future versions of ACME might throw an error instead of a warning
in this case.
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.
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.
Unknown "*=" segment modifier.
You used a modifier keyword ACME does not know.
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.

31
trunk/docs/Example.txt Normal file
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 -DSYSTEM=64 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 using "-DSYSTEM=128" instead of
"-DSYSTEM=64", you can also generate "ddrv128.prg", a C128 binary.
"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. :)

59
trunk/docs/Floats.txt Normal file
View File

@ -0,0 +1,59 @@
Hi!
This is a preliminary release of ACME. I added basic support for
floating point maths. Consider this a special version for beta
testers. Known bugs: Segment counting may be screwed up at the moment.
New:
The maths parser knows about floating point maths, so you can finally
build sin/cos tables directly in ACME. But the expression parser still
uses integer calculations per default. Here are the rules:
a) if a maths operation is useless when done with integers, it is done
with floats and returns a float. Applies to sin(), cos(), tan(),
arcsin(), arccos(), arctan() and float(): These are always computed in
float mode and always return floats.
b) if a maths operation is useles when done with floats, it is done
with integers and returns an integer. Applies to NOT, AND, OR, XOR,
MOD, DIV, LSR, lowbyteof, highbyteof, bankbyteof and int(). These are
always computed in integer mode and always return integers.
c) All other mathematical operations are done in float mode if and
only if at least one of the operands is a float. So "1/2*2" will give
zero because it is done in integer mode, but "1.0/2*2" will give 1
because it is done in float mode.
To force a numerical value to be flagged as being a float, just add
a decimal point and a zero. If a decimal value ends with a
dot character, ACME switches to using the C type "double" and keeps
reading digits. The value is then flagged internally as being float.
Examples:
!byte 1 / 2 * 2 ; gives 0 (integer maths)
!byte 1 / 2 * 2.0 ; gives 0 (1/2 => 0 in integer maths,
; float usage comes too late)
!byte 1 / 2.0 * 2 ; gives 1 (FP in effect)
!byte 1 / 2.0 * 2.0 ; gives 1 (FP in effect)
!byte 1.0 / 2 * 2 ; gives 1 (FP in effect)
!byte 1.0 / 2 * 2.0 ; gives 1 (FP in effect)
!byte 1.0 / 2.0 * 2 ; gives 1 (FP in effect)
!byte 1.0 / 2.0 * 2.0 ; gives 1 (FP in effect)
You can use the new float() and int() functions to ensure the type of
maths:
!byte a / b * c ; depends on a/b/c's internal flags
!byte float(a)/b*c ; calculation is done in FP
!byte int(a)/int(b)*int(c); calculation is done in integer
As you will have guessed, the trigonometric functions assume radians
for measuring angles (90 degrees equals PI/2).
Have a look at the example source code, it builds some sin/cos tables.
Have fun, and let me know what you think,
Marco Baye

177
trunk/docs/Help.txt Normal file
View File

@ -0,0 +1,177 @@
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.
ACME supports both integer and floating point maths 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

130
trunk/docs/Illegals.txt Normal file
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

21
trunk/docs/Lib.txt Normal file
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.

380
trunk/docs/QuickRef.txt Normal file
View File

@ -0,0 +1,380 @@
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 !ifndef !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.
-DLABEL=VALUE define global label
This option is useful if you build your projects using
Makefiles: "-DSYSTEM=64" could build the C64 version while
"-DSYSTEM=128" could build the C128 version of the software
(using conditional assembly in your source code file).
-W fine-tune amount and type of warnings
Currently only sub-option is supported: "-Wno-label-indent"
will switch off warnings about implicit label definitions not
being in the leftmost column.
--use-stdout fix for 'Relaunch64' IDE
With this option, errors are written to the standard output
stream instead of to the standard error stream.
-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.
Calculations are done using either signed 32-bit integer arithmetic or
floating point arithmetic using the C "double" data type. Label values
are stored the same way.
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 Divide
10 v DIV w Integer-Divide
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 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.
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. :)
This is a list of the value formats currently known by ACME:
Examples Notes
---------------------------------------------------------------------
128 a decimal value, integer
128.5 a decimal value, floating point
$d011 hexadecimal values are indicated by either
0xffd2 leading "$" or leading "0x"
&1701 an octal value, indicated by "&"
%010010 binary values are indicated by "%". In binary values,
%....#... you can substitute the characters "0" and "1" by
"." and "#" respectively. This way the values are
much more readable, especially when building
bitmapped objects (like C64 sprites or fonts) in
your source code.
"p" character values are indicated by double or single
'q' quotes. The actual numeric value depends on the
current conversion table (none/petscii/screen),
chosen using the "!ct" pseudo opcode.
poll_joy2 a global label
.fail a local label, indicated by leading dot
* the current program counter. 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.
----------------------------------------------------------------------
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.
In earlier releases of ACME, arithmetic operators like MOD, XOR, LSL
had to be written in UPPER CASE. This is no longer needed.
Label names are case sensitive, so "label" and "Label" are two
different things.

19
trunk/docs/Source.txt Normal file
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.

119
trunk/docs/Upgrade.txt Normal file
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...

621
trunk/examples/ddrv.a Normal file
View File

@ -0,0 +1,621 @@
;ACME 0.94
;!sl "ddrv.l"
; Name DuoDriver
; Purpose Input driver for mouse and joystick
; Author (c) Marco Baye, 1999
; Licence Free software
; Changes:
; 23 Apr 1999 Release 2.20. Internal info:
; DuoDriver v2.20 by Mac Bacon 23 Apr 1999. Freeware!
; Somewhen Added self-calibration, forming release 3.00. Internal info:
; Mac Bacon:DuoDrv3,PD
; 21 Jul 1999 Used reverse subtraction, forming release 3.01. Internal info:
; Mac Bacon:DuoDrv3,PD
; 1 Aug 1999 Release 4.00.
; Both 128 and 64 versions
; Now supports overlay-sprite mouse pointer
; Binary includes sprites
; Released in GO64 8/1999 (without release number).
; 3 Aug 1999 Same source file for both 128 and 64 versions. Release 4.01.
; Apart from that, virtually identical to release 4.00.
; 04 Feb 2003 Beautified
; 05 Feb 2003 Added "SpriteLine" macro and made sprites inline
; 26 May 2005 Release 4.02. All changes since release 4.00 are source-only!
; The resulting binaries are identical to those of release 4.00
; (which were included in GO64 magazine 8/1999)
; 26 Mar 2006 Release 4.03. Adjusted source to ACME 0.91 capabilities.
; 25 Nov 2007 Release 4.04. Adjusted source to ACME 0.94 capabilities.
; This source code file uses conditional assembly
; to decide which version to produce (C64 or C128).
; Select type of binary to assemble (64 => c64, anything else => c128)
!ifndef SYSTEM {
!warn "Label SYSTEM not defined. Use -DSYSTEM=64 to build C64 version, -DSYSTEM=128 to build C128 version. Will now default to C64 version."
SYSTEM = 64
}
!if SYSTEM != 64 & SYSTEM != 128 {
!serious "Please use either -DSYSTEM=64 or -DSYSTEM=128 when assembling this project."
}
; --- Configurable values
; Start address, output file name and VIC location
!if SYSTEM = 64 {
*=$c000
!to "ddrv64.prg", cbm
VIC_Base = $d000
}
!if SYSTEM = 128 {
*=$0c00
!to "ddrv128.prg", cbm
VIC_Base = $11d6; Location of mirror registers
}
; Pointer's maximum coordinates
MaximumCoordinateX = 319; VIC value
; MaximumCoordinateX = 639; VDC value
MaximumCoordinateY = 199
; Maximum pixel step size ("speed") for joystick acceleration routine.
MaxStep = $10; (max. $7f)
; Distance before acceleration starts, in pixels.
MaxTime = $04; (max. $7f)
; Sprites to use for overlay pointer
Sprite_A = 0
Sprite_B = 1
; Coordinates of "pointer pixel" within pointer sprites; adjust these
; if you use different sprites. (0,0) is sprite's upper left pixel.
Sprite_HotspotX = 1
Sprite_HotspotY = 1
; Locations to store button states, $ff = pressed, $00 = not pressed.
; Mouse uses both buttons, joystick only uses "LeftButton".
; Location to store pointer's current character coordinates.
!if SYSTEM = 64 {
LeftButton = $a4
RightButton = $a5
CharX = $b3
CharY = $b4
}
!if SYSTEM = 128 {
LeftButton = $fa
RightButton = $ff
CharX = $9b
CharY = $9c
}
; Location to store pointer's current pixel coordinates. The driver
; code relies on having *four consecutive* bytes:
; x low, x high, y low, y high
Coordinates = $fb; $fb-$fe
; --- System constants
; Interrupt vector
sys_iirq = $0314
; I/O registers
sid_pot = $d419
cia1_pra = $dc00
cia1_prb = $dc01
cia1_ddrb = $dc03
mmu_cr = $ff00; c128 only
; --- Label definitions
; New names for some precalculated values, only to improve
; readability. Don't change these.
PointerXnow = Coordinates
PointerYnow = Coordinates + 2
SpriteA_X = VIC_Base + 2*Sprite_A
SpriteA_Y = VIC_Base + 2*Sprite_A + 1
SpriteB_X = VIC_Base + 2*Sprite_B
SpriteB_Y = VIC_Base + 2*Sprite_B + 1
Sprites_OF = VIC_Base + 16; X Overflow
; The character "^" in the following calculation means "to the power
; of". It is ACME syntax - if your assembler cannot do this, you may
; want to use hardcoded values here instead of calculations.
Sprites_Bitmask = 2^Sprite_A + 2^Sprite_B
;alternative:
; Sprites_Bitmask = 1<<Sprite_A | 1<<Sprite_B
SpriteOffset_X = $18 - Sprite_HotspotX
SpriteOffset_Y = $32 - Sprite_HotspotY
; In the sprite coordinate system, the graphics pixel (0,0) has the
; coordinates ($18,$32), so these are needed for converting. Blame the
; VIC.
; --- Entry point
; Because this routine is the first, the file can be BOOTed on a c128.
; Initialisation code, installs driver on IRQ vector.
; Fetch IRQ vector and write to end
Init lda sys_iirq
ldx sys_iirq+1
sta mod16
stx mod16+1
; Let IRQ vector point to driver code
lda #<Entry
ldx #>Entry
php
sei
sta sys_iirq
stx sys_iirq+1
plp
!if SYSTEM=128 {
lda mmu_cr
tay
and #$fe; activate I/O chips
sta mmu_cr
}
; Init mouse buttons
lda #$11
sta cia1_prb
!if SYSTEM=128 {sty mmu_cr }
!if SYSTEM = 64 {
; Copy sprites to tape buffer
ldx #127
- lda Sprites,x
sta $0340,x
dex
bpl -
lda #Sprites_Bitmask
; Set sprite block pointers
ldx #$0d
stx 2040+Sprite_A
inx
stx 2040+Sprite_B
; Activate pointer sprites
ora VIC_Base+21
sta VIC_Base+21
}
rts
; --- Variables
; Pixel counter before accelerating
JoyWaittime !byte 0
; --- Main code
Entry
; The driver consists of several distinct parts. To minimise
; performance wastage, you should remove all parts you don't need for
; the specific application.
; --- Part 0, initialisations
; Make sure decimal mode is off
cld
; Set button states to "not pressed", so the other parts only have to
; deal with setting them to "pressed".
lda #$00
sta LeftButton
sta RightButton
; --- Part 1, handling mouse movements
; mouse x
ldx #$00; 0 means "x stuff"
jsr PotDelta
; Now signed x movement is in A/Y. Add to current x value.
clc
adc PointerXnow
sta PointerXnow
tya
adc PointerXnow+1
sta PointerXnow+1
; mouse y
ldx #$01; 1 means "y stuff"
jsr PotDelta
; Now signed y movement is in A/Y. Mouse and computer use different y
; directions, so don't add to, but subtract from current y value.
; This is a reverse subtraction - it might be harder to understand,
; but it is both faster and smaller than the usual way.
clc
sbc PointerYnow
eor #$ff
sta PointerYnow
tya
sbc PointerYnow+1
eor #$ff
sta PointerYnow+1
; --- Part 2, handling mouse buttons
; Prepare CIA by setting bits to input
ldy #$11
sty cia1_ddrb
ldx #$ff; $ff means "pressed"
lda #$10; check left button
bit cia1_prb
bne +
stx LeftButton; store state
+ lda #$01; check right button
bit cia1_prb
bne +
stx RightButton; store state
; Reset CIA to normal state
+ ldy #$00
sty cia1_ddrb
; --- Part 3, handling the joystick
; Fetch byte holding direction flags
lda cia1_pra
tax; ...and remember it
; Check 'up' direction
ror
bcs ++
; Subtract current step size from y value if needed.
tay
sec
lda PointerYnow
sbc JoyStepsize
sta PointerYnow
bcs +
dec PointerYnow+1
+ tya
; Check 'down' direction
++ ror
bcs ++
; Add current step size to y value if needed.
tay
;clc; C is always clear here
lda PointerYnow
adc JoyStepsize
sta PointerYnow
bcc +
inc PointerYnow+1
+ tya
; Check 'left' direction
++ ror
bcs ++
; Subtract current step size from x value if needed.
tay
sec
lda PointerXnow
sbc JoyStepsize
sta PointerXnow
bcs +
dec PointerXnow+1
+ tya
; Check 'right' direction
++ ror
bcs ++
; Add current step size to x value if needed.
tay
;clc; C is always clear here
lda PointerXnow
adc JoyStepsize
sta PointerXnow
bcc +
inc PointerXnow+1
+ tya
++
; --- Part 4, handling joystick button
ror
bcs +
lda #$ff; $ff means "pressed"
sta LeftButton
+
; --- Part 5, joystick acceleration
; Restore joystick direction bits and check whether to set speed to
; zero.
txa
and #$0f; Clear unneeded bits
cmp #$0f; Any direction bit ?
bne +
; No direction was used, so reset speed and wait counter to normal.
lda #$01
sta JoyStepsize
lda #MaxTime
sta JoyWaittime
jmp Part5End
+
; A direction bit was used, so check whether to accelerate: If speed
; is already maximum speed, don't accelerate.
JoyStepsize=*+1
lda #$00; (self-modifying)
; If the variable "JoyStepsize" would have been defined as a separate
; location (using "!byte"), it would have taken a byte of memory. By
; storing the value inside an LDA command's argument, we save that one
; byte. It might make a difference. :)
cmp #MaxStep; If speed is max.,
bcs Part5End; don't accelerate.
; Speed isn't maximum yet. Check whether
; we have to wait before accelerating.
dec JoyWaittime
bpl Part5End
; Counter has underrun, so accelerate.
inc JoyWaittime; reset counter
inc JoyStepsize; increase speed
Part5End
; --- Part 6, restrict coordinate range
; restrict x value
ldx #$00; 0 means "x stuff"
jsr Restrict
; restrict y value
ldx #$02; 2 means "y stuff"
jsr Restrict
; --- Part 7, positioning sprites
; Set sprites' x positions
lda PointerXnow
clc
adc #SpriteOffset_X
sta SpriteA_X; set both sprites
sta SpriteB_X
lda Sprites_OF; get x overflow
bcs SetOF
ldx PointerXnow+1
bne SetOF
and #Sprites_Bitmask XOR $ff
bcc StoreOF; C is clear here
SetOF ora #Sprites_Bitmask
StoreOF sta Sprites_OF; set x overflow
; Set sprites' y positions
lda PointerYnow
clc
adc #SpriteOffset_Y
sta SpriteA_Y
sta SpriteB_Y
; The y value's high byte is useless in this case.
; --- Part 8, making char coordinates
; Convert x coordinate. There are different "best" routines for
; different resolutions, so I've given the VIC and VDC routines.
lda PointerXnow
lsr
lsr
lsr
ldx PointerXnow+1
;ora OrTable,x; VDC only (see below for data table)
beq +; VIC only
ora #$20; VIC only
+ sta CharX
; Convert y coordinate.
lda PointerYnow
lsr
lsr
lsr
sta CharY
; --- Add further parts here
; Here you can add further routines, for example to use the button
; states to fake keypresses etc.
; --- The end
; The initialisation routine sets the argument to the address of the
; previous IRQ routine.
mod16=*+1: jmp $ffff; (self-modifying)
; This table is for part 8.
;OrTable !byte 0,32,64; VDC only
; --- "Restrict" subroutine
PointerXmax !word MaximumCoordinateX
PointerYmax !word MaximumCoordinateY
; "y" word must follow directly after "x" word in memory.
Restrict
; Restrict internal coordinates to configured range. Entry conditions:
; X is direction handle (0 = x, 2 = y)
lda PointerXnow+1,x
bmi SetTo0
cmp PointerXmax+1,x
bcc Eosr
bne +
lda PointerXmax,x
cmp PointerXnow,x
bcs Eosr
+ lda PointerXmax,x
ldy PointerXmax+1,x
jmp DefCo
SetTo0 lda #0
tay
DefCo sta PointerXnow,x
sty PointerXnow+1,x
Eosr rts
; --- "Pot" subroutine
; This routine computes the mouse movements and therefore contains the
; self-calibration stuff and the other improvements over the standard
; 1351 driver.
PotMax !word 0; max. POTs yet plus 1 !
PotMin !word $ffff; lowest POTs yet
PotOld !word 0; old values
PotWidth !word 0; interval width
HalfPotWidth !word 0; half width
; (buffered for speed increase)
; The above variables are not really words: The first byte is the x
; value, the second byte is the y value respectively.
; Compute the signed distance of mouse movement.
; Entry conditions: X is direction handle (0 = x, 1 = y)
; Exit conditions: A/Y are signed distance (low/high)
; First, get new value and clear "recalculate signal width" flag.
PotDelta lda sid_pot,x
ldy #$00
; Check whether new value is lower than lowest known.
cmp PotMin,x
bcs +
; Store new "lowest" und set "recalculate signal width" flag.
sta PotMin,x
ldy #$ff
+ ; Check whether new value is higher than highest known.
cmp PotMax,x
bcc +
; Set "recalculate signal width" flag and store new "highest".
ldy #$ff
pha; Remember current value
adc #$00; Add one (C is set)
sta PotMax,x
; Value $ff (0 after adding) means that there is no mouse connected,
; so reset min/max in that case.
beq ResetMM; Stack is untidy...
pla; Restore current value
+ ; If flag is set, recalculate signal width.
iny; Check flag
bne ++
tay; Buffer current value.
lda PotMax,x; Get highest+1
sec; Subtract lowest
sbc PotMin,x
bcc +
sta PotWidth,x; Store signal
lsr; width and half signal
sta HalfPotWidth,x; width
+ tya; Restore current value.
++ ; Calculate distance
tay; Buffer current value.
sec
sbc PotOld,x
pha
tya
sta PotOld,x
pla
beq zero; If not moved, exit.
bcc minus; Negative difference
; Positive difference:
; Check whether movement caused a value wrap-around.
cmp HalfPotWidth,x
bcc Decrease
beq Decrease
; It did, so calculate "real" distance and jump to exit
;sec; C is always set here
sbc PotWidth,x; Fix distance
; We now know that the (fixed) distance is really negative, so we
; finally wipe out that annoying bit 0 noise by incrementing the
; value.
Increase ;clc; C is always clear here
adc #$01
beq zero; If increasing gives zero, jump to zero handler.
ldy #$ff; Set up high byte for negative values.
rts
; Negative difference:
; Check whether movement caused a value wrap-around.
minus eor #$ff; Complement
; If we would do a real negation (by adding "1"), then we would need
; to branch using BCC *and* BEQ. So the above way might be harder to
; understand, but it is both shorter *and* faster - which I like. :)
cmp HalfPotWidth,x
eor #$ff; Restore value
bcc Increase
; Movement caused a value wrap-around, so calculate "real" distance and exit.
clc
adc PotWidth,x; Fix distance
; We now know that the (fixed) distance is really positive, so we
; finally wipe out that annoying bit 0 noise by decrementing the value.
Decrease sec
sbc #$01
; No difference or positive difference; both need zero as the high byte.
zero ldy #0
rts
; If there is no mouse, reset "lowest" ("highest" will have been reset
; already) and return zero.
ResetMM tay; Set Y to zero.
pla; Tidy stack
lda #$ff; Reset "lowest"
sta PotMin,x
tya; Return with A/Y = 0
rts
; --- Include sprites
; Because the c64 version copies the sprite data into the tape buffer
; on initialisation, the data is included right here.
; In the c128 version, we skip memory until we reach $0e00 - this is
; where the sprites are stored by default.
!if SYSTEM = 128 {
!align $ffff, $e00, $0
}
!macro SpriteLine .v {
!by .v>>16, (.v>>8)&255, .v&255
}
Sprites ; 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
; 765432107654321076543210
+SpriteLine %##......................
+SpriteLine %###.....................
+SpriteLine %####....................
+SpriteLine %#####...................
+SpriteLine %######..................
+SpriteLine %#######.................
+SpriteLine %########................
+SpriteLine %#########...............
+SpriteLine %##########..............
+SpriteLine %###########.............
+SpriteLine %###########.............
+SpriteLine %#########...............
+SpriteLine %########................
+SpriteLine %########................
+SpriteLine %###..####...............
+SpriteLine %##...####...............
+SpriteLine %......####..............
+SpriteLine %......####..............
+SpriteLine %.......####.............
+SpriteLine %.......####.............
+SpriteLine %........###.............

BIN
trunk/examples/ddrv128.exp Normal file

Binary file not shown.

BIN
trunk/examples/ddrv64.exp Normal file

Binary file not shown.

74
trunk/examples/macedit.a Normal file
View File

@ -0,0 +1,74 @@
;ACME 0.94
; ist der komplette Sourcecode von MacEdit
; (80-Zeichen-Version)
; Version 0.7
; Weitere Informationen am Ende der Datei
; Parameter:
!to "macedit.o", cbm
;!sl "macedit.l"
*= $1300
!ct pet
!source <6502/std.a>
!ifndef Lib_6502_std_a {
!serious "To assemble this program, you need to install the ACME library."
}
!source "me/macros.a"
!source "me/const.a"
; Code:
jmp init ; zum Programm
!text "TekFile", 0 ; DateiFormat + 'program'
!word progend - keyb ; length
; Gelinkt wird:
keyb
!binary "me/tables.bin", 826
keytabs = keyb + 12 ; 6 Tastaturtabs &
atst = keytabs + $22e ; ASCII-2-Screen-Tabelle
!source "me/vars.a"
!source "me/core.a"
!source "me/file.a"
!source "me/out.a"
!source "me/cursor.a"
linebuf
progend = linebuf+128
!byte 0 ; 128 Byte Zeilenpuffer
!eof
Änderungen von Version 0.6 zu Version 0.7:
Das DCS-Window wurde implementiert, dadurch wurde auch ein Unterschied zwischen "Beenden" und "Basic" eingebaut (Bei ersterem erfolgt die DCS-Abfrage).
Die Strings der Windows liegen jetzt nicht mehr als Screencodes vor, sondern als PetSCII-Werte; die Routine ".makewin" konvertiert dies also.
Die Bedeutung des Flags "unnamed" wurde invertiert.
Spätere Änderungen am Source:
19. 4.1997: Durch Weiterentwicklung von V0.6 erzeugt (kommentarlos)
24. 9.1998: Kommentare von V0.6 wieder hinzugefügt
25. 9.1998: Umformatierung auf ACME-Syntax
10.10.1998: Ersetzen von "{" und "}" in Labels durch "_" und "__"
12.10.1998: Unterschiede zu v0.6 dokumentiert.
30.10.1998: "+ =" wieder zu "+=" korrigiert.
1.11.1998: Alle Labels wieder globalisiert.
2.11.1998: Tabulatorlayout wieder korrigiert und "~" durch "___" ersetzt.
3.11.1998: Label "notmany!" durch "notmany" ersetzt. Wo kam das bloß her ?
4.11.1998: Zwei fehlerhafte Auskommentierungen entsorgt. Die Stellen wurden mit "**mark**" gekennzeichnet. Wo kam das bloß her ? Außerdem wurde "< = >" in einem Textstring wieder zu "<=>" korrigiert. Das ist wohl beim automatischen Layout passiert.
4.11.1998: Top-Bit-Set-Zeichen aus Textstrings enfernt und byteweise eingefügt, z.B. auch "Cursor up/down/left/right"-Werte. Außerdem alle Filenamen angepaßt.
5.11.1998: Auch die Umlaute nun zu Bytewerten gewandelt.
19.11.1998: "!cbm" eingefügt, da geänderte ACME-Funktion "!text".
24.11.1998: Filenamen bei "!to" und "!bin" auf UNIX-Stil gebracht.
27.11.1998: Aufeinanderfolgende "!tx" und "!by" gemerged, BIT-Trick benutzt, Hexzahlen auf lowercase gebracht, Binärzahlen auf Spezialformat gebracht, Einrückungen dezimiert, Zahlen durch Label ersetzt, "firsttry" in "repeatedtry" umbenannt (war vorher unlogisch).
28.11.1998: Auf Benutzung von Modulen und lokalen Labels umgestellt.
30.11.1998: Alle "!module" durch "!zone" ersetzt (wegen ACME-Änderung).
1.12.1998: Mehrere Labels pro Zeile entzerrt (wegen ACME-Änderung).
2.12.1998: Multifile-Version, Änderungstext ans Ende verschoben.
10.12.1998: Makros eingebaut.
8. 1.1999: Library benutzt und daher Branch-Makros gelöscht, außerdem BIT-Trick durch Makroaufruf ersetzt.
24. 8.1999: An die leicht geänderte Syntax von ACME 007 angepaßt.
04 Jun 2005:
Adjusted to ACME 0.86 syntax (added output file format indicator).
26 Mar 2006:
Adjusted to ACME 0.91 syntax (anonymous labels)
Now throws serious error if the library file could not be loaded.

BIN
trunk/examples/macedit.exp Normal file

Binary file not shown.

93
trunk/examples/me/const.a Normal file
View File

@ -0,0 +1,93 @@
;ACME 0.91
; Konstanten:
FALSE = 0 ; Das Programm verläßt sich an etlichen Stellen
TRUE = $ff ; darauf, daß genau diese Werte zugewiesen wurden.
MODIFIED8 = $ff ; Defaultwerte für
MODIFIED16 = $ffff ; Selbstmodifikationen
Char_NUL = $00
Char_STOP = $03
Char_RETURN = $0d
Char_CursorDown = $11
Char_HOME = $13
Char_DEL = $14
Char_ESCAPE = $1b
Char_CursorRight= $1d
Char_At = $40
CharOwn_Delete = $74
Char_ShiftRETURN= $8d
Char_CursorUp = $91 ; Diese Werte waren früher als Strings angegeben.
Char_CLEAR = $93
Char_INST = $94
Char_Grey2 = $98
Char_BlueL = $9a
Char_Grey3 = $9b
Char_CursorLeft = $9d
_ = 1 ; Dieser Code steht für das unsichtbare Space in den Windows.
ä = $bb ; Werte um Umlaute verwenden zu können.
ö = $bc
ü = $bd
ß = $be
Ä = $db
Ö = $dc
Ü = $dd
chrol = 104 ; Fensterrahmen
chroo = 102
chror = 106
chrll = 97
chrmm = 32
chrrr = 97
chrul = 98
chruu = 102
chrur = 100
lf = 8 ; Filenr. & Sek.-Addy
; Zeropage:
D8502 = $00 ; Direction
R8502 = $01 ; Register
vvek = $83 ; Vektor auf LineVektor
lvek = $85 ; LineVektor
tmp1 = $87
tmp2 = $89
vtemp = $8d ; crsr-address (3) ; zeropage (**mark**)
status = $90 ; System variable ST
fnlen = $b7 ; Dateiparameter
fnbank = $c7 ; Bank of file name
ndx = $d0 ; Tasten- &
kyndx = $d1 ; F- Buffer
keyidx = $d2 ; F-Zeichenzähler
mode = $d7 ; Bit 7 = Cursorscreen (40/80)
color = $f1 ; current attribute
locks = $f7 ; Verhindert CBM-Shift
beep = $f9 ; Tastenklick
lftb = $fa ; Maustasten
rgtb = $fb
line = $fc ; Zähler
col = $fd
zahl = $fe ; fürs Wrap
; System:
nmivek = $0318 ; NMI
keybuffer= $034a
pkydef = $100a ; Strings der F-Tasten
texttop = $1210 ; Basic-Ende+1
maxmem0 = $1212 ; Ende Bank 0
basic = $12fd ; Basic-IRQ
kernel_copyfont = $c027 ; Systemroutine, kopiert Font in VDC-RAM
kernel_cls = $c142 ; Systemroutine, löscht Screen
kernel_switchmode= $cd2e ; Systemroutine, switcht aktiven Monitor
takt = $d030 ; 2 MHz ; register (**mark**)
vdc = $d600 ; VDC
reg = $d601
conreg = $ff00 ; MMU-CR
nmiend = $ff33 ; NMI-Ende
primm = $ff7d ; Kernel
open = $ffc0
close = $ffc3
chkin = $ffc6
chkout = $ffc9
clrchn = $ffcc
basin = $ffcf
basout = $ffd2

798
trunk/examples/me/core.a Normal file
View File

@ -0,0 +1,798 @@
;ACME 0.91
!zone
; Programm:
mainloop
; Cursor setzen:
lda posy ; screeny = posy-spry
sec
sbc scry
tay ; y in Y
; ab hier X
lda posx ; screenx = posx-scrx
sec
sbc scrx
jsr crsrset ; set crsr
; hier eigentliche Hauptroutine
lda nwfrm ; new frame ?
beq +
jsr newframe ; yes = >
+ lda updatewbi ; update flags?
beq +
jsr showwbi ; yes = >
+ jsr getchar ; get CHARACTER
tax ; & buffer
and #%.##..... ; command ?
beq + ; yes = >
eor #%.##..... ; command ?
beq + ; yes = >
jsr chrout ; char out
jmp mainloop
+ jsr execom ; execute command
jmp mainloop
!zone
; Pseudo-Sub: (ESC uses jmp)
F_esc clc ; 'ESC' on!
lda clraktv
ldx #hFlag_Escape
jsr setflagdata
- jsr getkey ; get KEY
beq -
sta byte ; & buffer
clc ; 'ESC' off!
lda clrinak
ldx #hFlag_Escape
jsr setflagdata
ldx byte ; get byte
txa ; & buffer
eor #%.#...... ; a-z ?
and #%.##.....
bne + ; no = >
txa ; get byte
and #%...##### ; & short
asl ; *2 &
tax ; as index
lda etab+1,x ; get Hi
beq .no ; 0 = >
sta .m+1 ; set
lda etab,x ; get Lo
sta .m ; set
.m=*+1: jmp MODIFIED16 ; execute sequence
.no rts ; nothing...
+ txa ; get byte ( = FKey?)
bpl .no ; out = >
eor #%..#..... ; convert
and #%.##..... ; test
beq .no ; out = >
txa ; get byte
and #%...##### ; convert
cmp #$05 ; test bottom border
bcc .no ; too low = >
cmp #$0d ; test upper border
bcs .no ; too high = >
; here: define f-keys !
rts
!zone
; NMI
nmirtn lda #0 ; clear keybuffers
sta ndx
sta kyndx
jmp nmiend
!zone
; Subs:
execom txa ; get & convert Byte
bpl + ; (therefore strange
eor #%#.#..... ; vectorlist)
+ asl
tax
lda ctab+1,x ; get Hi
beq noroutine ; 0 = >
sta .m+1 ; and set
lda ctab,x ; get Lo
sta .m ; and set
.m=*+1: jmp MODIFIED16 ; use command
noroutine rts ; not defined (fixme - could save a byte here)
!zone
F_new jsr willblost
beq noroutine
jsr newtext
jsr needline
ldx #$0f ; use counter as "TRUE"
stx nwfrm
stx updatewbi
stx unnamed
- lda newname,x
sta txtname,x
dex
bpl -
inx
stx changes
rts
!zone
newtext ldx #1 ; '1'
stx scrx ; as X of screen,
stx anfx ; blockstart, -end &
stx endx ; crsr.
stx posx
stx scry ; ...as Y-Lo
stx anfy
stx endy
stx posy
dex ; '0'
stx scry+1 ; ...as Y-Hi
stx anfy+1
stx endy+1
stx posy+1
stx zzbe ; no lines
stx zzbe+1 ; used
rts
!zone
; 'key' ist der Kern, holt einen Code
; ausm Puffer. 'char' wuerde, wenns ein
; F-Key oder Accent ist, den Puffer
; aendern und dann das erste Byte
; abliefern. Hier nur das Standardprog:
getchar jmp getkey
.defrag ;{check fragjob}
getkey ;{check mousejob}
;{check clockjob}
ldx kyndx ; F-keys as standard
beq .std
ldy keyidx
lda pkydef,y
dec kyndx
inc keyidx
rts
.std ldx ndx ; chars in buffer ?
beq .defrag ; 0 = >
sei ; else
ldy keybuffer ; get first byte
ldx #255 - 8 ; loop to shift other 9 chars down
- lda keybuffer - 255 + 9,x
sta keybuffer - 255 + 8,x
inx ; (f7 to ff)
bne -
dec ndx ; dec number
tya ; byte = >A
stx keybuffer+9 ; clear lastbyte
cli
rts
!zone
getvvek lda scry,x ; get y-Lo
asl ; *2
tay ; buffer
lda scry+1,x ; get y-Hi
rol ; *2 ( = clc)
sta vvek+1 ; in Hi
tya ; get Lo
adc memin ; + BaseLo
sta vvek ; = VectorLo
lda vvek+1 ; get Hi
adc memin+1 ; + BaseHi
sta vvek+1 ; = VectorHi
rts ; (VekVek)
; stellt Vektor auf Cursor-Y
poslvek ldx #POS
; stellt Vektor auf Zeile
!zone
getlvek jsr getvvek ; get VekVek
ldy #0 ; Y-Init
lda (vvek),y ; get Lo-Byte
sta lvek ; store
iny ; inc vector
lda (vvek),y ; get Hi-Byte
sta lvek+1 ; store
rts
!zone
windowproof lda posx ;crsr-X
cmp scrx ; screen(home)-X
bcs + ; bigger = >
sta scrx ; else set screen-X
sta nwfrm ; and NewFrame
bcc .UpDown
+ sbc scrx ; difference
cmp #scrcols ; cmp screenwidth
bcc .UpDown ; ok = >
lda posx ; else NewFrame,
sta nwfrm
sbc #scrcols-1 ; set screen-X
sta scrx ; & store
.UpDown lda scry+1 ; HiByte screen-
cmp posy+1 ; Y and crsr-Y
bcc crsrweiter ; shorter = >
bne .set ; equal = >
lda posy ; else cmp Lo-bytes
cmp scry
bcs crsrweiter ; shorter = >
.set ldx posy ; get crsrpos as
lda posy+1 ; new screenstart
stx scry
sta scry+1
lda #TRUE ; NewFrame
sta nwfrm
rts
!zone
crsrweiter sec ; for sbc
lda posy ; calculate
sbc scry ; Lo-difference
tax ; in X
lda posy+1 ; calculate
sbc scry+1 ; Hi-difference
bne + ; if Hi = 0
cpx #scrlins ; & Lo ok,
bcc ++ ; ready = >
+ lda posy+1 ; else: copy Hibyte
sta scry+1
sec ; for sbc
lda posy ; calculate & save
sbc #scrlins-1 ; new Hibyte
sta scry
bcs + ; ggfs. = >
dec scry+1 ; correct Hibyte
+ lda #TRUE ; NewFrame
sta nwfrm
++ rts
; Scrollroutines missing !
!zone
; fuellt Speicher mit Zeilen
fillmem lda #0 ; Keine Zeilen da
sta zzan
sta zzan+1
ldx llen ; Zeilenlaenge
inx ; + Info-Byte
stx .m1 ; in SBC #$dummy
lda maxmem0 ; holt MAX-MEM-0
sta txts ; und nimmt es als
lda maxmem0+1 ; Obergrenze !
sta txts+1
lda mod_id ; Holt ID-Adresse (Lo)
tax ; sichern
lsr ; Bit 0 ins Carry
txa ; zurueck
adc #6 ; +ID-2+C
sta memin ; wird Vektorstart (Lo)
lda mod_id+1 ; Hi-Byte
adc #0 ; entsprechend
sta memin+1 ; anpassen (Auto-CLC)
; Carry wird addiert, damit Vektoren bei
; einer GERADEN Adresse starten!
lda memin ; Die VekVeks
adc #2 ; werden ab dem
sta vvek ; Vektorstart+2
lda memin+1 ; abgelegt, da es
adc #0 ; keine nullte Zeile
sta vvek+1 ; gibt
.Check lda txts ; TextstartLo
sec
.m1=*+1: sbc #MODIFIED8 ; -Zeilenlänge
sta tmp1 ; wird gepuffert
ldx txts+1
bcs +
dex
+ stx tmp1+1
cpx vvek+1 ; Vektorkollision ?
bcc .NoLine ; Ja = > keine Zeile !
bne .MakeLn ; Nein = > neue Zeile !
ldx vvek ; Gleich: Lo-Bytes
inx ; vergleichen
cpx tmp1 ; Wieder: Kollision ?
bcs .NoLine ; Ja = > keine Zeile !
.MakeLn lda tmp1 ; Nein: dann temp als
sta txts ; Textstart und in den
ldy #0 ; Linevektor
sta (vvek),y
lda tmp1+1 ; dito, Highbyte
sta txts+1
iny
sta (vvek),y
inc vvek ; VekVek 2 Byte weiter
+inc16 vvek
inc zzan ; angelegte Zeilen
bne .Check ; eins hoeher
inc zzan+1
jmp .Check
.NoLine rts
!zone
clearline lda #" " ; Space
ldy llen ; Y auf Zeilenende
- sta (lvek),y ; Space setzen
dey ; zurueck
bne - ; Infobyte ?
tya ; Dann auf
sta (lvek),y ; Null setzen
dey ; Y auf $ff fuer
sty nwfrm ; NewFrame
sty changes ; Veraendert !
; WordWrap sinnlos !
rts
!zone
; stellt Zeilen zur Verfuegung oder gibt Fehlermeldung
needline +cmp16bit ZZA, ZZB ; vergleichen
beq + ; Wenn gleich, wirds gesetzte Carry 'failure'
+inc16 zzbe ; sonst: Zahl der genutzten Zeilen hoeher
ldx #ZZB
stx changes ; Veraendert !
jsr getlvek ; Holt Vektor
jsr clearline ; und leert Zeile
clc ; 'success'
; EIGENTLICH ist das Carrybit hier schon
; durch die beiden Subs gelöscht...
+ rts
cmp16bit lda scry+1,x ; Hi-Bytes vergleichen
cmp scry+1,y
bne + ; wenn gleich,
lda scry,x ; Lo-Bytes vergleichen
cmp scry,y
+ rts
F_gcr inc posx
jmp proofpos
F_gcl dec posx
jmp proofpos
F_gcu ldx posy
bne +
dec posy+1
+ dec posy
jmp proofpos
F_gcd +inc16 posy
!zone
proofpos ldx posx ; CRSR-X
beq .jBack ; Null = >
dex ; verringern und mit
cpx llen ; Laenge vergl.
bcs jump ; zu weit rechts = >
lda posy+1 ; CRSR-Y (Hi)
bmi firstline ; >32K = > 1. Zeile = >
ora posy ; ODERt Low-Byte
beq firstline ; = 0 = > 1. Zeile = >
+cmp16bit ZZB, POS ; vergleichen
bcc F_geot ; CRSR zu weit = >
jmp windowproof ; okay
.jBack ldx llen ; Zeilenlaenge wird
stx posx ; neue Position & hoch
jsr F_gcu
jsr poslvek ; LineVek holen
jsr findend ; Ende suchen
iny ; dahintersetzen
sty posx
jmp proofpos
jump jsr newline ; naechste Zeile
bcs + ; CRSR zu weit,
jsr needline ; Zeile anfordern
bcc + ; Bei Fehlschlag
jsr memfull ; Warnung zeigen
+ jmp proofpos
!zone
firstline ldx #1 ; CRSR in erste Zeile
stx posy
dex
stx posy+1
jmp windowproof
F_geot +cp16 zzbe, posy; CRSR in letzte Zeile
jmp windowproof
!zone
newline lda #1 ; X-Pos : = 1 & Y += 1
sta posx
+inc16 posy
+cmp16bit ZZB, POS ; vergleichen
rts
!zone
F_cs lda #1 ; CRSR 2 next linestart
sta posx
+inc16 posy
jmp proofpos
!zone
chrout stx byte ; sichert Zeichen
jsr poslvek ; LineVek
ldy esca ; Autoinsert ?
beq + ; ggfs. kein
jsr insert1 ; insert
+ ldy posx ; Versatz
lda byte ; Akku holen
sta (lvek),y ; und setzen
jsr poswrap ; Wrap ?
lda #TRUE ; NewFrame fordern
sta nwfrm
sta changes ; Veraendert !
jmp F_gcr
!zone
F_insert jsr poslvek ; LineVek
jsr insert1 ; insert
jsr poswrap ; Wrap ?
rts ; fixme - could save a byte here
!zone
insert1 ldy scrx,x ; X-Wert holen & in
sty .mod ; Immediate
ldy lvek+1 ; Quell-Vektor =
ldx lvek ; aktueller Vektor-1
bne +
dey
+ dex
stx tmp1
sty tmp1+1
ldy llen ; Shiftstart LineEnd
-
.mod=*+1: cpy #MODIFIED8 ; X
beq + ; Ende = >
lda (tmp1),y ; Zeichen holen
sta (lvek),y ; und shiften
dey ; neue Pos
jmp -
+ lda #" " ; 'Space' an
sta (lvek),y ; Pos setzen
sta nwfrm ; NewFrame fordern
sta changes ; Veraendert !
rts
!zone
F_dcl jsr F_gcl
F_dcr jsr poslvek ; LineVek
jsr delchr1 ; Delete
jsr poswrap ; Wrap ?
jmp proofpos
!zone
delchr1 ldy scrx,x ; X-Wert in Immediate
sty .m
ldx lvek ; Zielvektor = aktueller
ldy lvek+1 ; Vektor+1
inx
bne +
iny
+ stx tmp1
sty tmp1+1
.m=*+1: ldy #MODIFIED8 ; X
- cpy llen ; Zeilenende ?
beq + ; Dann = >
lda (tmp1),y ; Zeichen holen
sta (lvek),y ; und shiften
iny ; neue Pos
jmp -
+ lda #" " ; Space an letzte
sta (lvek),y ; Pos setzen
lda #TRUE ; NewFrame fordern
sta nwfrm
sta changes ; Veraendert !
rts
!zone
; Einsprung: X = StartIndex, Y = EndIndex
; Bewegt Zeilenbloecke: X bis Y-1 werden nach X+1 bis Y geschoben. Danach
; liegt die Endzeile in der Startzeile
rollfwd jsr howmany ; Zeilenanzahl ?
beq ++ ; ggfs Abbruch !
tya ; Y in X
tax
jsr getlvek ; lvek in lvek puffern
sec ; Quellvektor = Vektor-2
lda vvek
sbc #2
sta tmp1
lda vvek+1
sbc #0
sta tmp1+1
ldy #2 ; Versatz 2
- tya ; Y pruefen
bne + ; ggfs
dec tmp1+1 ; Page sichern
dec vvek+1
+ dey ; Versatz runter
lda (tmp1),y ; High-Byte oben setzen
sta (vvek),y
dey ; Versatz runter
lda (tmp1),y ; Low-Byte oben setzen
sta (vvek),y
inc tmpy ; Anzahl der Shifts
bne - ; pruefen, ggfs loop
inc tmpy+1
bne -
lda lvek ; alten Vektor holen
sta (tmp1),y ; und in letzte
iny ; Position setzen
lda lvek+1
sta (tmp1),y
++ lda #TRUE ; NewFrame fordern
sta nwfrm
sta changes ; Veraendert !
rts
!zone
; Einsprung: X = StartIndex, Y = Endzeile
; Bewegt Zeilenbloecke: X+1 bis Y werden nach X bis Y-1 geschoben. Danach
; liegt die Startzeile in der Endzeile !
rollrwd jsr howmany ; Zeilenanzahl ?
beq ++ ; ggfs Abbruch !
jsr getlvek ; lvek in lvek puffern
clc ; Quellvektor = Vektor+2
lda vvek
adc #2
sta tmp1
lda vvek+1
adc #0
sta tmp1+1
ldy #0 ; Versatz 0
- lda (tmp1),y ; Hi-Byte unten setzen
sta (vvek),y
iny ; weiter
lda (tmp1),y ; Lo-Byte unten setzen
sta (vvek),y
iny ; weiter
bne + ; Page sichern
inc tmp1+1
inc vvek+1
+ inc tmpy ; Anzahl Shifts
bne - ; pruefen, ggfs loop
inc tmpy+1
bne -
lda lvek ; alten Vektor an die
sta (vvek),y ; letzte Pos setzen
iny
lda lvek+1
sta (vvek),y
++ lda #TRUE ; NewFrame fordern
sta nwfrm
sta changes ; Veraendert !
rts
!zone
howmany jsr cmp16bit ; Sicherheit
bcs + ; ggfs Abbruch = >
sec ; Negativ, um INC statt DEC nutzen zu können
lda scry,x
sbc scry,y
sta tmpy
lda scry+1,x
sbc scry+1,y
sta tmpy+1
rts
+ lda #0 ; Zeilen
rts
!zone
movx2y lda scrx,x ; Copy X-indexed Werte in Y-indexed Variablen
sta scrx,y
lda scry,x
sta scry,y
lda scry+1,x
sta scry+1,y
rts
ESC_at rts ; fixme - could save one byte here
ESC_a lda #TRUE ; Set AutoInsert
sta esca
sta updatewbi ; Update fordern
rts
ESC_b ldx #POS ; BlockEnd: = Cursorposition
ldy #END
jsr movx2y
!zone
; Block legal ? Vertauscht ggfs Zeiger
nblck +cmp16bit ANF, END ; Blockstart und -Ende vergleichen
bcc ++ ; anf<end: ok
bne + ; anf>end: not ok
lda scrx,y ; Bei Gleichheit noch
cmp scrx,x ; X pruefen
bcs ++ ; end> = anf: ok
+ ldy #TMP ; (Anf) in Temp
jsr movx2y
ldx #END ; Ende in Anf
ldy #ANF
jsr movx2y
ldx #TMP ; Temp in Ende
ldy #END
jsr movx2y
++ lda #TRUE ; NewFrame fordern
sta nwfrm ; (Blockanzeige)
sta blockflag ; Block ein
rts
!zone
ESC_c ldx #FALSE ; Clear AutoInsert
stx esca
dex ; Update fordern
stx updatewbi
rts
ESC_d ldx #POS ; Start: Cursorposition
jsr delline ; Zeile weg
jmp poswrap ; und wrap
!zone
delline ldy #ZZB ; Ende: LastLine
jsr rollrwd ; runterrollen
lda zzbe ; Anzahl der benutzten Zeilen runter
bne +
dec zzbe+1
+ dec zzbe
bne + ; Low = 0 ?
lda zzbe+1 ; Dann High pruefen und ggfs Zeile fordern
bne +
jsr needline
+ jmp proofpos
!zone
ESC_g ldx #FALSE ; Beep On
stx beep
dex ; Update fordern
stx updatewbi
rts
ESC_h lda #TRUE ; Beep Off
sta beep
sta updatewbi ; Update fordern
rts
!zone
ESC_i jsr needline ; Zeile fordern
+bcs memfull ; bei Fehlschlag Warnung = >
ldx #POS ; Start: Cursorposition
ldy #ZZB ; Ende: LastLine
jsr rollfwd ; raufrollen
rts ; fixme - could save a byte here
!zone
F_gsol
ESC_j lda #1 ; Cursor-X: = 1
sta posx
jmp windowproof
F_geol
ESC_k jsr poslvek ; LineVek
jsr findend ; sucht letztes Byte, dahinter steht dann Cursor
iny
sty posx
jmp proofpos
ESC_o lda blockflag ; toggle Flag
eor #$ff
sta blockflag
rts
ESC_p
ESC_q rts ; fixme - could save a byte here
ESC_t ldx #POS ; Blockstart = Cursorposition
ldy #ANF
jsr movx2y
jmp nblck ; legal ?
F_home ldx scrx ; Normal HOME only,
ldy scry ; if CRSR not there
lda scry+1
cpx posx ; Otherwise ScreenUp
bne scrnhome
cpy posy
bne scrnhome
cmp posy+1
bne scrnhome
!zone
F_scrnu lda posy ; Displaystart =
sec ; Displaystart
sbc #scrlins ; - Zeilenzahl
sta posy
bcs +
dec posy+1
+ lda #TRUE ; NewFrame fordern
sta nwfrm
jmp proofpos
!zone
scrnhome stx posx ; Cursor: = Display
sty posy
sta posy+1
jmp proofpos
F_ahome clc ; errechnet Werte
lda scry ; fuer antih1
adc #scrlins-1 ; in A, X, Y
tax
lda scry+1
adc #0
tay
lda scrx ; CRSR dort ?
cmp posx
bne antih1
cpx posy ; Nein = > dorthin
bne antih1
cpy posy+1 ; Ja = > ScreenDown !
bne antih1
!zone
F_scrnd lda posy ; One screen down
clc
adc #scrlins
sta posy
bcc +
inc posy+1
+ lda #TRUE ; NewFrame fordern
sta nwfrm
jmp proofpos
!zone
antih1 sta posx ; Cursor: = DisplayEnd
stx posy
sty posy+1
jmp proofpos
F_gsot ldx #1 ; X = Y = 1
stx posx
stx posy
stx nwfrm ; NewFrame fordern
dex ; Y-Hi: = 0
stx posy+1
jmp proofpos
!zone
handleid ldx #7 ; 8 Byte Kennung
-
mod_id=*+1: lda MODIFIED16,x; Schleife, um evtl. vorhandenen Text zu
cmp idtext,x
bne makeid ; erkennen & zu reaktivieren
dex
bpl -
clc ; 'OldText'
rts
makeid lda texttop ; Neue ID wird ans Basic-Ende gesetzt
sta mod_id
sta .m1
lda texttop+1
sta mod_id+1
sta .m1+1
ldx #7
- lda idtext,x
.m1=*+1: sta MODIFIED16,x
dex
bpl -
sec ; 'NewText'
rts
F_cr jsr F_lfeed
jmp jump
F_c :F_f :F_ffeed :F_dir
F_fbox :F_hlp :F_bell :F_tab
F_text :F_middle :F_graphic
F_fn :F_ff :F_un :F_uf :F_rn :F_rf
F_sf :F_sk :F_su :F_st :F_sl
F_gld :F_glu :F_gad :F_gau :F_gpd :F_gpu
F_gtr :F_gtl :F_gwr :F_gwl
F_bttnn :F_bttnf :F_find :F_print :F_mode :F_dword
F_cut :F_copy :F_paste :F_move
F_fmtl :F_fmtr :F_fmtm :F_fmtb
rts ; (yet) missing

111
trunk/examples/me/cursor.a Normal file
View File

@ -0,0 +1,111 @@
;ACME 0.91
; ab hier liegt die Cursorsteuerung
; A = screenx, Y = screeny
!zone
crsrset sta .m ; buffer x
iny ; adjust height
iny
iny
sty .n ; buffer y
jsr crsroff
lda #0 ; clear Hi
sta vtemp+1
.n=*+1: lda #MODIFIED8 ; y
asl ; *2
asl ; *4
rol vtemp+1
asl ; *8
rol vtemp+1
asl ; *16
rol vtemp+1
sta vtemp ; stash Lo
ldy vtemp+1 ; copy Hi
sty vtemp+2
asl ; *32
rol vtemp+2
asl ; *64
rol vtemp+2
adc vtemp ; + 16er-Lo
sta vtemp ; 80er-Lo in vtemp
bcc + ; page
inc vtemp+1
clc
+
.m=*+1: adc #MODIFIED8 ; x
sta vtemp ; store Lo
lda vtemp+1 ; get 16er-Hi
adc vtemp+2 ; add 64er-Hi
adc #attrhi ; add base
sta vtemp+1 ; store Hi
!zone
crsron lda conreg ; buffert CR
sta .m
+bank15
jsr vpntcrsr ; set address
- bit vdc ; get ready
bpl -
lda reg ; get attribute
sta tcolor ; buffer it
jsr vpntcrsr ; set address
lda clrcrsr ; get crsr
- bit vdc ; get ready
bpl -
sta reg ; set crsr
.m=*+1: lda #MODIFIED8 ; bank
sta conreg ; restore CR
rts
!zone
crsroff lda conreg ; buffer CR
sta .m
+bank15
jsr vpntcrsr ; set address
lda tcolor ; get attribute
- bit vdc ; get ready
bpl -
sta reg ; set attribute
.m=*+1: lda #MODIFIED8 ; bank
sta conreg ; restore CR
rts
; push data
!zone
crsrnew ldx crsrheap ; get stackpointer
lda vtemp ; get low
sta crsrheap,x ; push
lda vtemp+1 ; get high
sta crsrheap+1,x; push
inx ; inc stackpointer
inx
stx crsrheap ; set stackpointer
jsr crsroff
!zone
crsrhide ldx #$3f ; place cursor
stx vtemp+1 ; outside visible
ldx #$ff ; area
stx vtemp
rts
!zone
crsrold ldx crsrheap ; get stackpointer
dex ; previous entry !
dex
lda crsrheap,x ; get lo
sta vtemp ; set lo
lda crsrheap+1,x; get hi
sta vtemp+1 ; set hi
stx crsrheap ; set stackpointer
jmp crsron
!zone
crsrinit ldx #1 ; init cursorstack
stx crsrheap
jmp crsrhide ; and hide cursor
crsrheap !fill 33, 33
vpntcrsr +ldax vtemp
jmp ramaccess ; set vdc

286
trunk/examples/me/file.a Normal file
View File

@ -0,0 +1,286 @@
;ACME 0.91
; ChangesNotSaved.Save?
!zone
willblost ldx changes
bne +
inx
rts ; return with X=1 ("Changes safe, go on")
+ jsr crsrnew
ldx #hWindow_DCS
stx menunr
jsr makewin
ldy #$0b ; y-pos of cursor in window
lda #$32 ; x-pos
jsr crsrset
wblchoice jsr getchar
cmp #Char_DEL
beq wblchoiced
cmp #Char_STOP
beq wblchoicec
cmp #Char_RETURN
bne wblchoice
jsr pullscr
jsr crsrold
jsr F_saveas
jmp willblost
wblchoiced jsr pullscr
jsr crsrold
ldx #FALSE
stx changes
ldx #2
rts ; return with X=2 ("Changes discarded, go on")
wblchoicec jsr pullscr
jsr crsrold
ldx #0
rts ; return with X=1 ("Cancel operation !")
eotflag !byte 0 ; End-Flag
!zone
F_mergeas lda #$1f ; get Mergename
sta loadflag ; Mode MERGE
jmp +
noload rts ; fixme - could save a byte here
F_loadas jsr willblost ; Changes saved ?
beq noload
lda #0 ; Mode LOAD
sta loadflag
lda #$3f ; get LOADname
+ jsr rename
bne load ; ggfs Abbruch
rts
!zone
loadalien lda loadflag
bne loadfirst
jmp noheader
load lda conreg ; Bank sichern
pha
jsr crsrnew ; new copy (hidden)
ldx #hWindow_Load
stx menunr
jsr makewin
jsr copypara ; Parameter setzen
lda #"r" ; Lesemodus
sta dosmode
+bank15
jsr open ; Open File
ldx #lf ; File: = Input
jsr chkin
ldy #$0f ; Header pruefen
- jsr basin
cmp idfile,y
bne loadalien
dey
bpl -
ldy #$0f ; Namen holen
- jsr basin
sta dosname,y
dey
bpl -
lda loadflag ; Bei LOAD
bne loadfirst ; Name kopieren,
sta unnamed ; (clear Flag)
ldy #$0f
- lda dosname,y
sta txtname,y
sta lodname,y
dey
bpl -
sty updatewbi ; Update verlangen,
jsr newtext ; Defaultwerte
loadfirst ldy #FALSE ; Pufferstart
sty eotflag ; init Flag
!zone
loadline +xbank15
- iny ; Eins weiter
lda #" " ; get Space
ldx status
bne + ; ggfs
jsr basin ; get Byte
+ sta linebuf,y ; und setzen
cpy llen
bne -
ldy #1 ; Neustart
- lda linebuf,y
cmp #Char_RETURN
beq ++
cmp #"-"
bne +
sty linebuf ; Dann Pos merken
+ cmp #" "
bne +
sty linebuf ; Dann Pos merken
+ iny ; weiter
cpy llen
bne -
lda linebuf,y ; LineEnd = Space ?
cmp #" " ; Dann Grenze: = Y &
bne +
sty linebuf
lda status
beq + ; ggfs setflag
sta eotflag
+ ldy linebuf ; get Grenze
bne +
ldy llen
dey
++ sty linebuf
+ +xram0
jsr needline ; fordert Zeile
bcs nomemleft ; ggfs Abbruch
ldy linebuf ; copy buffer2line
- lda linebuf,y
sta (lvek),y
dey
bne -
lda eotflag ; Ende ?
bne endoffile
ldx linebuf ; shift buffer
- cpx llen ; fertig ?
beq loadline ; Dann lesen !
inx
iny
lda linebuf,x
sta linebuf,y
jmp -
nomemleft jsr memfull ; Warnung
endoffile +bank15
lda loadflag
sta changes
noheader jsr clrchn ; Standard
lda #lf ; Close File
jsr close
jsr pullscr ; Win weg
jsr crsrold ; restore cursor
pla ; alte Bank
sta conreg
rts
!zone
nosave rts ; Abbruch (fixme - could save a byte here)
F_saveas jsr F_rnmtxt ; get Textname
beq nosave ; ggfs Abbruch
lda #FALSE ; Name vorhanden
sta unnamed
F_save lda unnamed ; Name ?
bne F_saveas ; ggfs holen
ldy #$0f ; proof "?"
- lda txtname,y
cmp #"?"
beq F_saveas
cmp #"*"
beq F_saveas
cmp #","
beq F_saveas
cmp #":"
beq F_saveas
sta dosname,y
dey
bpl -
lda #"w" ; Schreibmodus
sta dosmode
lda conreg ; Bank sichern
pha
+bank15
jsr crsrnew ; new copy (hidden)
ldx #hWindow_Save; Save-Win
stx menunr
jsr makewin
jsr copykill ; Killparameter
jsr open ; Open CmdChannel
lda killpara+1 ; (Scratch)
jsr close ; Close CC
jsr copypara ; Dateiparameter
jsr open ; Open Outputfile
ldx #lf
jsr chkout
ldy #$0f ; Sendet Header
- lda idfile,y
jsr basout
dey
bpl -
ldy #$0f ; Sendet Name
- lda txtname,y
jsr basout
dey
bpl -
iny ; Y: = 0, tmpy wird fuers
sty tmpy+1 ; Speichern init.
tya ; A: = 0
iny ; Y: = 1
sty tmpy
sec ; errechnet negativen
sbc zzbe ; Zeilenzaehler (tmp2)
sta tmp2
lda #0
sbc zzbe+1
sta tmp2+1
-- +xram0 ; volles RAM
ldx #1 ; mind. 1 Byte/Zeile
stx linebuf
ldx #TMP
jsr getlvek ; LineVek
ldy #1 ; Versatz: = 1
- lda (lvek),y ; Byte in Puffer
cmp #" "
beq +
sty linebuf ; Pos sichern
+ sta linebuf,y
iny
cpy llen
bne -
ldx linebuf
lda linebuf,x ; letztes Byte
cmp #Char_RETURN
beq +
cmp #"-"
beq +
cmp #" "
beq +
inx ; Dann Space hinter
lda #" " ; die Zeile
sta linebuf,x
+ stx .m ; Ende speichern
+xbank15
- inx ; X = 1
lda linebuf,x ; Zeichen senden
jsr basout
.m=*+1: cpx #MODIFIED8 ; Länge
bne - ; alle ?
+inc16 tmpy ; tmpy += 1
inc tmp2 ; zaehler += 1
bne --
inc tmp2+1
bne --
jsr clrchn ; Standardkanaele
lda #lf
jsr close ; Close File
jsr pullscr ; Win weg
jsr crsrold ; restore cursor
pla ; alte Bank
sta conreg
lda #FALSE ; Changes saved !
sta changes
rts
!zone
copykill ldy #$0b ; Scratchparameter
+bit16 ; BIT-Trick !
copypara ldy #$05 ; Fileparameter
ldx #5 ; 6 Bytes
- lda filepara,y ; ins System
sta fnlen,x
dey
dex
bpl -
rts

View File

@ -0,0 +1,72 @@
;ACME 0.91
!macro cmp16bit .data1, .data2 {
ldx #.data1
ldy #.data2
jsr cmp16bit
}
a = 0
x = 1
y = 2
!macro bank .r, .v {
!if .r = a {
lda #.v
sta conreg
}
!if .r = x {
ldx #.v
stx conreg
}
!if .r = y {
ldy #.v
sty conreg
}
}
CR_BANK15 = 0
CR_RAM0_IO = $3e
CR_RAM0 = $3f
!macro bank15 {
+bank a, CR_BANK15
}
!macro xbank15 {
+bank x, CR_BANK15
}
!macro ybank15 {
+bank y, CR_BANK15
}
!macro ram0io {
+bank a, CR_RAM0_IO
}
!macro yram0io {
+bank y, CR_RAM0_IO
}
!macro xram0 {
+bank x, CR_RAM0
}
!macro inc16x .a {
inc .a,x
bne +
inc .a+1,x
+
}
!macro ldax .a {
lda .a+1
ldx .a
}
!macro cp16 .s, .t {
ldx .s
lda .s+1
stx .t
sta .t+1
}

1439
trunk/examples/me/out.a Normal file

File diff suppressed because it is too large Load Diff

Binary file not shown.

124
trunk/examples/me/vars.a Normal file
View File

@ -0,0 +1,124 @@
;ACME 0.91
; Vermerk:
!text "MacEdit was written by Mac Bacon in 1994-97."
!text " This is Freeware !"
; Variablen:
stck !byte 0 ; Stackbuffer
nmibuf !word 0 ; NMI-Buffer
idtext !text "MacEdV0" ; RAM-Kennung
scratch !byte "s" ; DOS-String
dospre !text "0:"
dosname !text "-Anleitung .txt,p," ; Default
dosmode !text "r"
filepara !byte 22, lf, lf, 8
!word dospre
killpara !byte 19, 15, 15, 8
!word scratch
idfile !text "FormatVersion1.0"
; Farben:
; 2rufRGBI-Format
clrcrsr !byte %##.##..# ; Cursor
clrback !byte %........ ; Screen (xxxxRGBI-Format)
clraktv !byte %#...###. ; Aktive Flags
clrinak !byte %#......# ; Inaktive
clrmenu !byte %##..##.# ; Menu
clrmenu1 !byte %#...#### ; aktives Menu
clraktl !byte %##..#### ; Menupunkt
clrboxs !byte %#....### ; Menuboxen
!byte %#....#.# ; Dialogboxen
!byte %#..##..# ; Warnungen
; Vars
bank !byte 0 ; Bankbuffer
memin !word 0 ; Startaddy Vektoren
txts !word 0 ; Startaddy Text
unnamed !byte TRUE ; ist Text benannt ?
changes !byte FALSE ; Sind Changes saved ?
nwfrm !byte FALSE ; neues Bild ?
blockflag !byte FALSE ; Block definiert ?
wrapflag !byte TRUE ; PARWing ?
esca !byte TRUE ; AutoInsert ?
updatewbi !byte FALSE ; Flag-Redraw nötig ?
repeatedtry !byte FALSE ; Schon früher gestartet ?
loadflag !byte 0 ; 0 = LOAD (/MERGE)
txtname !text "unbenannt .txt"
mrgname !text "merge .txt"
newname !text "unbenannt .txt"
lodname !text "unbenannt .txt"
xindex !byte 0 ; Index-Puffer
; Folgende Vars werden per x indiziert
SCR = 0 ; x-Wert
scrx !byte 0 ; Display
scry !word 0
ANF = 3 ; x-Wert
anfx !byte 0 ; Blockstart
anfy !word 0
END = 6 ; x-Wert
endx !byte 0 ; Ende
endy !word 0
POS = 9 ; x-Wert
posx !byte 0 ; Cursor
posy !word 0
TMP = 12 ; x-Wert
tmpx !byte 0 ; temp
tmpy !word 0
ZZA = 15 ; x-Wert
llen !byte preflen ; Zeilenlaenge
zzan !word 0 ; vorhandene Zeilen
ZZB = 18 ; x-Wert
byte !byte 0 ; akt. Zeichen
zzbe !word 0 ; benutzte Zeilen
WRP = 21 ; x-Wert
wrpx !byte 0 ; Wrap
wrpy !word 0
PRW = 24 ; x-Wert
prwx !byte 0 ; Parw
prwy !word 0
; Tabs:
etab ; ESC-Jumps
!word ESC_at, ESC_a, ESC_b, ESC_c
!word ESC_d, 0, 0, ESC_g
!word ESC_h, ESC_i, ESC_j, ESC_k
!word 0, 0, 0, ESC_o
!word ESC_p, ESC_q, 0, 0
!word ESC_t, 0, 0, 0
!word 0, 0, 0, 0
!word 0, 0, 0, 0
ctab ; Command-Jumps 1. Achtel
!word 0, 0, F_un, F_menu
!word 0, F_c, 0, F_bell
!word 0, F_tab, F_lfeed, 0
!word F_ffeed, F_cr, F_text, F_fn
!word 0, F_gcd, F_rn, F_home
!word F_dcl, F_sf, F_sk, F_su
!word F_st, F_sw, F_sl, F_esc
!word F_c, F_gcr, F_c, F_c
; 5. Achtel
!word F_dir, F_c, F_uf, F_fbox
!word F_hlp, F_f, F_f, F_f
!word F_f, F_f, F_f, F_f
!word F_f, F_cs, F_graphic, F_ff
!word F_c, F_gcu, F_rf, F_gsot
!word F_insert, F_c, F_c, F_c
!word F_c, F_c, F_c, F_c
!word F_c, F_gcl, F_c, F_c
; 8. Achtel
!word F_bttnf, F_gosys, 0, 0
!word F_info, F_f, F_f, F_f
!word F_f, F_f, F_f, F_f
!word F_f, 0, F_geol, F_print
!word F_glu, F_gau, F_scrnu, F_geot
!word F_dword, F_save, F_saveas, F_rnmtxt
!word F_gtl, F_fmtl, F_fmtr, F_fmtm
!word F_fmtb, F_gwl, F_gpu, 0
; 4. Achtel
!word F_bttnn, F_goout, 0, 0
!word F_mode, 0, 0, 0
!word 0, 0, 0, 0
!word 0, 0, F_gsol, F_new
!word F_gld, F_gad, F_scrnd, F_ahome
!word F_dcr, F_loadas, F_mergeas, F_find
!word F_gtr, F_cut, F_copy, F_paste
!word F_move, F_gwr, F_gpd, F_middle

37
trunk/examples/trigono.a Normal file
View File

@ -0,0 +1,37 @@
;ACME 0.93
!to "trigono.o", plain
*=$c000
PI = 3.14159265358979323846
!raw "cos[0,pi/2] scaled to 0-255 range"
!align $f, 0, 0 ; make output file look better in hex editor :)
!for x, 256 {
!byte cos(float(x-1) / 255 * PI/2) * 255 + 0.5
}
; "x-1" converts interval [1,256] to interval [0,255]
; "float()" makes sure this calculation is done in float mode now
; "/255*half_PI" converts interval [0,255] to interval [0,PI/2]
; "cos()" returns cosine. Wow.
; "*255" converts interval [0,1] to interval [0,255]
; "+0.5" ensures correct rounding to integer
!align $f, 0, 0
!raw "sin[-pi/2,pi/2] scaled to full range of 16b.16b fixed point"
!align $f, 0, 0
!for x, 1024 {
!32 sin(float(x-513) / 1024 * PI) * 65536 + 0.5
}
;undefined = 0.0 / 0.0 ; throws error when active
;range = arcsin(-10) ; throws error when active
!by 1 / 2 * 2 ; should give 0
!by 1 / 2 * 2.0 ; should give 0
!by 1 / 2.0 * 2 ; should give 1
!by 1 / 2.0 * 2.0 ; should give 1
!by 1.0 / 2 * 2 ; should give 1
!by 1.0 / 2 * 2.0 ; should give 1
!by 1.0 / 2.0 * 2 ; should give 1
!by 1.0 / 2.0 * 2.0 ; should give 1

BIN
trunk/examples/trigono.exp Normal file

Binary file not shown.

64
trunk/src/Makefile Normal file
View File

@ -0,0 +1,64 @@
CFLAGS = -O3 -Wall
LIBS = -lm
CC = gcc
RM = rm
#SRC =
PROGS = acme
BINDIR = /usr/local/bin
USERBIN = $(HOME)/bin
all: $(PROGS)
acme: acme.o alu.o basics.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 basics.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
acme.o: config.h platform.h acme.h alu.h basics.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h section.h acme.h acme.c
alu.o: config.h platform.h cpu.h dynabuf.h encoding.h global.h input.h label.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 platform.h acme.h cpu.h input.h label.h macro.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
install: all
install -d $(BINDIR)
install $(PROGS) $(BINDIR)
userinstall: all
install -d $(USERBIN)
install $(PROGS) $(USERBIN)
# DO NOT DELETE

64
trunk/src/Makefile.dos Normal file
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 platform.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 section.h acme.h acme.c
alu.o: config.h platform.h cpu.h dynabuf.h encoding.h global.h input.h label.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 platform.h acme.h cpu.h input.h label.h macro.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

66
trunk/src/Makefile.riscos Normal file
View File

@ -0,0 +1,66 @@
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 basics.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 basics.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 platform.h acme.h alu.h basics.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h section.h acme.h acme.c
alu.o: config.h platform.h cpu.h dynabuf.h encoding.h global.h input.h label.h section.h tree.h alu.h alu.c
basics.o: config.h alu.h cpu.h dynabuf.h input.h global.h output.h tree.h basics.h basics.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 platform.h acme.h cpu.h input.h label.h macro.h output.h section.h tree.h global.h global.c
input.o: config.h alu.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 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

53
trunk/src/_amiga.h Normal file
View File

@ -0,0 +1,53 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
#define PLATFORM_UINT2CHAR(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

48
trunk/src/_dos.c Normal file
View File

@ -0,0 +1,48 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
// used as PLATFORM_INIT: reads "ACME" environment variable
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 character to DOS-style pathname character
char DOS_convert_path_char(char byte)
{
if (byte == '/')
return '\\';
if (byte == '\\')
return '/';
return byte;
}
#endif

60
trunk/src/_dos.h Normal file
View File

@ -0,0 +1,60 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
#define PLATFORM_UINT2CHAR(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
// used as PLATFORM_INIT: reads "ACME" environment variable
extern void DOS_entry(void);
// Convert UNIX-style pathname character to DOS-style pathname character
extern char DOS_convert_path_char(char);
#endif

104
trunk/src/_riscos.c Normal file
View File

@ -0,0 +1,104 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
// 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;
}
}
// used as PLATFORM_INIT: registers exit handler
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

72
trunk/src/_riscos.h Normal file
View File

@ -0,0 +1,72 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
#define PLATFORM_UINT2CHAR(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
// used as PLATFORM_INIT: registers exit handler
extern void RISCOS_entry(void);
// convert UNIX-style pathname to RISC OS-style pathname
extern char RISCOS_convert_path_char(char);
// setting the created files' types
extern void RISCOS_set_filetype(const char *, int);
// use DDEUtils module's "Throwback" protocol
extern void RISCOS_throwback(const char *, int);
#endif

37
trunk/src/_std.c Normal file
View File

@ -0,0 +1,37 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
// used as PLATFORM_INIT: reads "ACME" environment variable
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

55
trunk/src/_std.h Normal file
View File

@ -0,0 +1,55 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
#define PLATFORM_UINT2CHAR(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
// used as PLATFORM_INIT: reads "ACME" environment variable
extern void AnyOS_entry(void);
#endif

477
trunk/src/acme.c Normal file
View File

@ -0,0 +1,477 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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.94.2" // update before release (FIXME)
#define CODENAME "Zarquon" // update before release
#define CHANGE_DATE "28 Sep" // update before release
#define CHANGE_YEAR "2011" // update before release
#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/" // FIXME
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "acme.h"
#include "alu.h"
#include "basics.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_USE_STDOUT "use-stdout"
#define OPTION_VERSION "version"
// options for "-W"
#define OPTIONWNO_LABEL_INDENT "no-label-indent"
// variables
static const char **toplevel_sources;
static int toplevel_src_count = 0;
static signed long start_addr = -1; // <0 is illegal
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;
// show release and platform info (and exit, if wanted)
static void show_version(int 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(
"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:\n"
"acme [OPTION...] [FILE]...\n"
"\n"
"Options:\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"
" -DLABEL=VALUE define global label\n"
// as long as there is only one -W option:
#define OPTIONWNO_LABEL_INDENT "no-label-indent"
" -W" OPTIONWNO_LABEL_INDENT " suppress warnings about indented labels\n"
// when there are more, use next line and add a separate function:
//" -W show warning level options\n"
" --" OPTION_USE_STDOUT " fix for 'Relaunch64' IDE (see docs)\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).\n", 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);
fclose(fd);
}
// perform a single pass. Returns number of "NeedValue" type errors.
static int perform_pass(void)
{
FILE *fd;
int i;
// call modules' "pass init" functions
CPU_passinit(default_cpu); // set default cpu values (PC undefined)
Output_passinit(start_addr); // call after CPU_passinit(), to define PC
Encoding_passinit(); // set default encoding
Section_passinit(); // set initial zone (untitled)
// init variables
pass_undefined_count = 0; // no "NeedValue" errors yet
pass_real_errors = 0; // no real errors yet
// 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]);
// FIXME - if "filename" starts with "-", tell user to give options FIRST, files SECOND!
pass_real_errors++;
}
}
if (pass_real_errors)
exit(ACME_finalize(EXIT_FAILURE));
else
Output_end_segment();
return pass_undefined_count;
}
// do passes until done (or errors occured). Return whether output is ready.
static int do_actual_work(void)
{
int undefined_prev, // "NeedValue" errors of previous pass
undefined_curr; // "NeedValue" errors of current pass
if (Process_verbosity > 1)
puts("First pass.");
pass_count = 0;
undefined_curr = perform_pass(); // 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)) {
pass_count++;
undefined_prev = undefined_curr;
if (Process_verbosity > 1)
puts("Further pass.");
undefined_curr = perform_pass();
}
// If still errors (unsolvable by doing further passes),
// perform additional pass to find and show them
if (undefined_curr == 0)
return 1;
if (Process_verbosity > 1)
puts("Further pass needed to find error.");
ALU_throw_errors(); // activate error output (CAUTION - one-way!)
pass_count++;
perform_pass(); // perform pass, but now show "value undefined"
return 0;
}
// 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_safe_get_next("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_safe_get_next("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);
}
}
static void could_not_parse(const char strange[])
{
fprintf(stderr, "%sCould not parse '%s'.\n", cliargs_error, strange);
exit(EXIT_FAILURE);
}
// return signed long representation of string.
// copes with hexadecimal if prefixed with "$", "0x" or "0X".
// copes with octal if prefixed with "&".
// copes with binary if prefixed with "%".
// assumes decimal otherwise.
static signed long string_to_number(const char *string)
{
signed long result;
char *end;
int base = 10;
if (*string == '%') {
base = 2;
string++;
} else if (*string == '&') {
base = 8;
string++;
} else if (*string == '$') {
base = 16;
string++;
} else if ((*string == '0') && ((string[1] == 'x') || (string[1] == 'X'))) {
base = 16;
string += 2;
}
result = strtol(string, &end, base);
if (*end)
could_not_parse(end);
return result;
}
// set program counter
static void set_starting_pc(void)
{
start_addr = string_to_number(cliargs_safe_get_next("program counter"));
if ((start_addr > -1) && (start_addr < 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 = string_to_number(cliargs_safe_get_next("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);
}
// define label
static void define_label(const char definition[])
{
const char *walk = definition;
signed long value;
// copy definition to GlobalDynaBuf until '=' reached
DYNABUF_CLEAR(GlobalDynaBuf);
while ((*walk != '=') && (*walk != '\0'))
DynaBuf_append(GlobalDynaBuf, *walk++);
if ((*walk == '\0') || (walk[1] == '\0'))
could_not_parse(definition);
value = string_to_number(walk + 1);
DynaBuf_append(GlobalDynaBuf, '\0');
Label_define(value);
}
// 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_safe_get_next(name_outfile);
else if (strcmp(string, OPTION_LABELDUMP) == 0)
labeldump_filename = cliargs_safe_get_next(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 = string_to_number(cliargs_safe_get_next("maximum error count"));
else if (strcmp(string, OPTION_MAXDEPTH) == 0)
macro_recursions_left = (source_recursions_left =
string_to_number(cliargs_safe_get_next("recursion depth")));
// else if (strcmp(string, "strictsyntax") == 0)
// strict_syntax = TRUE;
else if (strcmp(string, OPTION_USE_STDOUT) == 0)
msg_stream = stdout;
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 'D': // "-D" define constants
define_label(argument + 1);
goto done;
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_safe_get_next(name_outfile);
break;
case 'l': // "-l" selects label dump filename
labeldump_filename = cliargs_safe_get_next(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;
case 'W': // "-W" tunes warning level
if (strcmp(argument + 1, OPTIONWNO_LABEL_INDENT) == 0) {
warn_on_indented_labels = FALSE;
goto done;
} else {
fprintf(stderr, "%sUnknown warning level.\n", cliargs_error);
exit(EXIT_FAILURE);
}
break;
default: // unknown ones: program termination
return *argument;
}
argument++;
}
done:
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();
msg_stream = stderr;
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();
Label_clear_init(); // needed so
Outputfile_init();
// prepare a buffer large enough to hold pointers to "-D" switch values
// cli_defines = safe_malloc(argc * sizeof(*cli_defines));
// 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();
Basics_init();
CPU_init();
Encoding_init();
Flow_init();
Input_init();
Label_register_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
}

27
trunk/src/acme.h Normal file
View File

@ -0,0 +1,27 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
// Tidy up before exiting by saving label dump
extern int ACME_finalize(int exit_code);
#endif

1495
trunk/src/alu.c Normal file

File diff suppressed because it is too large Load Diff

60
trunk/src/alu.h Normal file
View File

@ -0,0 +1,60 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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"
// constants
// meaning of bits in "flags" of result_t and result_int_t structures:
#define MVALUE_IS_FP (1u << 8)
// floating point value (never set in result_int_t)
#define MVALUE_INDIRECT (1u << 7)
// needless parentheses indicate use of indirect addressing modes
#define MVALUE_EXISTS (1u << 6)
// 0: expression was empty. 1: there was *something* to parse.
#define MVALUE_UNSURE (1u << 5)
// value once was related to undefined expression. Needed for producing
// the same addresses in all passes; because in the first pass there
// will almost for sure be labels that are undefined, you can't simply
// get the addressing mode from looking at the parameter's value.
#define MVALUE_DEFINED (1u << 4)
// 0: undefined expression (value will be zero). 1: known result
#define MVALUE_ISBYTE (1u << 3)
// value is guaranteed to fit in one byte
#define MVALUE_FORCE24 (1u << 2)
// value usage forces 24-bit usage
#define MVALUE_FORCE16 (1u << 1)
// value usage forces 16-bit usage
#define MVALUE_FORCE08 (1u << 0)
// value usage forces 8-bit usage
#define MVALUE_FORCEBITS (MVALUE_FORCE08|MVALUE_FORCE16|MVALUE_FORCE24)
#define MVALUE_GIVEN (MVALUE_DEFINED | MVALUE_EXISTS)
// bit mask for fixed values (defined and existing)
// create dynamic buffer, operator/function trees and operator/operand stacks
extern void ALU_init(void);
// activate error output for "value undefined"
extern void ALU_throw_errors(void);
// returns int value (0 if result was undefined)
extern intval_t ALU_any_int(void);
// returns int value (if result was undefined, serious error is thrown)
extern intval_t ALU_defined_int(void);
// stores int value if given. Returns whether stored. Throws error if undefined.
extern int ALU_optional_defined_int(intval_t *);
// stores int value and flags (floats are transformed to int)
extern void ALU_int_result(struct result_int_t *);
// stores int value and flags, allowing for one '(' too many (x-indirect addr)
extern int ALU_liberal_int(struct result_int_t *);
// stores value and flags (result may be either int or float)
extern void ALU_any_result(struct result_t *);
#endif

250
trunk/src/basics.c Normal file
View File

@ -0,0 +1,250 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// basic assembly stuff
#include <stdlib.h>
#include <stdio.h>
#include "config.h"
#include "cpu.h"
#include "basics.h"
#include "alu.h"
#include "dynabuf.h"
#include "input.h"
#include "global.h"
#include "output.h"
#include "tree.h"
// constants
#define USERMSG_DYNABUF_INITIALSIZE 80
static const char s_08[] = "08";
#define s_8 (s_08 + 1) // Yes, I know I'm sick
#define s_16 (s_65816 + 3) // Yes, I know I'm sick
// variables
static struct dynabuf_t *user_message; // dynamic buffer (!warn/error/serious)
// helper function for !8, !16, !24 and !32 pseudo opcodes
static enum eos_t output_objects(void (*fn)(intval_t))
{
do
fn(ALU_any_int());
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);
}
// Include binary file
static enum eos_t PO_binary(void)
{
FILE *fd;
int byte;
intval_t size = -1, // means "not given" => "until EOF"
skip = 0;
// if file name is missing, don't bother continuing
if (Input_read_filename(TRUE))
return SKIP_REMAINDER;
// try to open file
fd = fopen(GLOBALDYNABUF_CURRENT, FILE_READBINARY);
if (fd == NULL) {
Throw_error(exception_cannot_open_input_file);
return SKIP_REMAINDER;
}
// read optional arguments
if (Input_accept_comma()) {
if (ALU_optional_defined_int(&size)
&& (size < 0))
Throw_serious_error("Negative size argument.");
if (Input_accept_comma())
ALU_optional_defined_int(&skip); // read skip
}
// check whether including is a waste of time
if ((size >= 0) && (pass_undefined_count || pass_real_errors)) {
Output_fake(size); // really including is useless anyway
} else {
// really insert file
fseek(fd, skip, SEEK_SET); // set read pointer
// if "size" non-negative, read "size" bytes.
// otherwise, read until EOF.
while (size != 0) {
byte = getc(fd);
if (byte == EOF)
break;
Output_byte(byte);
size--;
}
// if more should have been read, warn and add padding
if (size > 0) {
Throw_warning("Padding with zeroes.");
do
Output_byte(0);
while (--size);
}
}
fclose(fd);
// if verbose, produce some output
if ((pass_count == 0) && (Process_verbosity > 1))
printf("Loaded %d (0x%04x) bytes from file offset %ld (0x%04lx).\n",
CPU_2add, CPU_2add, skip, skip);
return ENSURE_EOS;
}
// Reserve space by sending bytes of given value ("!fi" / "!fill" pseudo opcode)
static enum eos_t PO_fill(void)
{
intval_t fill = FILLVALUE_FILL,
size = ALU_defined_int();
if (Input_accept_comma())
fill = ALU_any_int();
while (size--)
Output_8b(fill);
return ENSURE_EOS;
}
// show user-defined message
static enum eos_t throw_string(const char prefix[], void (*fn)(const char *))
{
struct 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_any_result(&result);
if (result.flags & MVALUE_IS_FP) {
// floating point
if (result.flags & MVALUE_DEFINED)
DynaBuf_add_double(
user_message,
result.val.fpval);
else
DynaBuf_add_string(
user_message,
"<UNDEFINED FLOAT>");
} else {
// integer
if (result.flags & MVALUE_DEFINED)
DynaBuf_add_signed_long(
user_message,
result.val.intval);
else
DynaBuf_add_string(
user_message,
"<UNDEFINED INT>");
}
}
} while (Input_accept_comma());
DynaBuf_append(user_message, '\0');
fn(user_message->buffer);
return ENSURE_EOS;
}
////
//static enum eos_t PO_info(void)
//static enum eos_t PO_print(void)
//{
// return throw_string();
//}
// 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);
}
// pseudo ocpode table
static struct node_t pseudo_opcodes[] = {
PREDEFNODE(s_08, PO_08),
PREDEFNODE(s_8, PO_08),
PREDEFNODE("by", PO_08),
PREDEFNODE("byte", PO_08),
PREDEFNODE(s_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("info", PO_info),
// PREDEFNODE("print", PO_print),
PREDEFNODE("warn", PO_warn),
PREDEFNODE(s_error, PO_error),
PREDEFLAST("serious", PO_serious),
// ^^^^ this marks the last element
};
// register pseudo opcodes and create dynamic buffer
void Basics_init(void)
{
user_message = DynaBuf_create(USERMSG_DYNABUF_INITIALSIZE);
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}

17
trunk/src/basics.h Normal file
View File

@ -0,0 +1,17 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// basic assembly stuff
#ifndef basics_H
#define basics_H
#include "config.h"
// register pseudo opcodes and create dynamic buffer
extern void Basics_init(void);
#endif

105
trunk/src/cliargs.c Normal file
View File

@ -0,0 +1,105 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
// 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;
for (;;) {
// 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
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);
}
}
}
}
// return next arg. If there is none, complain and exit
const char *cliargs_safe_get_next(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);
}
// 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);
}
}

26
trunk/src/cliargs.h Normal file
View File

@ -0,0 +1,26 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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[];
// 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_safe_get_next(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

52
trunk/src/config.h Normal file
View File

@ -0,0 +1,52 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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 intval_t; // at least 32 bits
#define INTVAL_MAXCHARACTERS 11 // -2^32 takes 11 characters
typedef unsigned long uintval_t; // just for logical shift right
// result structure type definition with support for floating point
struct result_t { // either int or float
int flags; // expression flags
union {
intval_t intval; // integer value
double fpval; // floating point value
} val; // Expression value
};
// result structure type definition for int
struct result_int_t {
int flags; // expression flags
intval_t intval; // expression value
};
// 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
// Nullpointer definition
#ifndef NULL
#define NULL ((void *) 0)
#endif
// Boolean values
#ifndef FALSE
#define FALSE 0
#define TRUE 1
#endif
#endif

292
trunk/src/cpu.c Normal file
View File

@ -0,0 +1,292 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// CPU stuff
#include "config.h"
#include "alu.h"
#include "cpu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "mnemo.h"
#include "output.h"
#include "tree.h"
// constants
static struct cpu_t CPU_6502 = {
keyword_is_6502mnemo,
CPUFLAG_INDIRECTJMPBUGGY, // JMP ($xxFF) is buggy
234, // !align fills with "NOP"
FALSE, // short accu
FALSE // short xy
};
static struct cpu_t CPU_6510 = {
keyword_is_6510mnemo,
CPUFLAG_INDIRECTJMPBUGGY, // JMP ($xxFF) is buggy
234, // !align fills with "NOP"
FALSE, // short accu
FALSE // short xy
};
static struct cpu_t CPU_65c02= {
keyword_is_65c02mnemo,
0, // no flags
234, // !align fills with "NOP"
FALSE, // short accu
FALSE // short xy
};
/*
static struct cpu_t CPU_Rockwell65c02 = {
keyword_is_Rockwell65c02mnemo,
0, // no flags
234, // !align fills with "NOP"
FALSE, // short accu
FALSE // short xy
};
static struct cpu_t CPU_WDC65c02 = {
keyword_is_WDC65c02mnemo,
0, // no flags
234, // !align fills with "NOP"
FALSE, // short accu
FALSE // short xy
};
*/
static struct cpu_t CPU_65816 = {
keyword_is_65816mnemo,
CPUFLAG_SUPPORTSLONGREGS, // allows A and XY to be 16bits wide
234, // !align fills with "NOP"
FALSE, // short accu
FALSE // short xy
};
#define s_rl (s_brl + 1) // Yes, I know I'm sick
// variables
struct cpu_t *CPU_now; // struct of current CPU type (default 6502)
struct result_int_t CPU_pc; // (pseudo) program counter at start of statement
int CPU_2add; // increase PC by this after statement
static intval_t current_offset; // PseudoPC - MemIndex
static int uses_pseudo_pc; // offset assembly active?
// predefined stuff
static struct node_t *CPU_tree = NULL; // tree to hold CPU types
static struct 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(s_65816, &CPU_65816),
// ^^^^ this marks the last element
};
// insert byte until PC fits condition
static enum eos_t PO_align(void) {
intval_t and,
equal,
fill,
test = CPU_pc.intval;
// make sure PC is defined.
if ((CPU_pc.flags & MVALUE_DEFINED) == 0) {
Throw_error(exception_pc_undefined);
CPU_pc.flags |= MVALUE_DEFINED; // do not complain again
return SKIP_REMAINDER;
}
and = ALU_defined_int();
if (!Input_accept_comma())
Throw_error(exception_syntax);
equal = ALU_defined_int();
if (Input_accept_comma())
fill = ALU_any_int();
else
fill = CPU_now->default_align_value;
while ((test++ & and) != equal)
Output_8b(fill);
return ENSURE_EOS;
}
// try to find CPU type held in DynaBuf. Returns whether succeeded.
int CPU_find_cpu_struct(struct cpu_t **target)
{
void *node_body;
if (!Tree_easy_scan(CPU_tree, &node_body, GlobalDynaBuf))
return 0;
*target = node_body;
return 1;
}
// select CPU ("!cpu" pseudo opcode)
static enum eos_t PO_cpu(void)
{
struct 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)
{
int outer_state = uses_pseudo_pc;
intval_t new_pc,
outer_offset = current_offset;
int outer_flags = CPU_pc.flags;
// set new
new_pc = ALU_defined_int();
current_offset = (current_offset + new_pc - CPU_pc.intval) & 0xffff;
CPU_pc.intval = new_pc;
CPU_pc.flags |= MVALUE_DEFINED;
uses_pseudo_pc = TRUE;
// if there's a block, parse that and then restore old value!
if (Parse_optional_block()) {
// restore old
uses_pseudo_pc = outer_state;
CPU_pc.flags = outer_flags;
CPU_pc.intval = (outer_offset + CPU_pc.intval - current_offset) & 0xffff;
current_offset = outer_offset;
} else {
Throw_first_pass_warning(Warning_old_offset_assembly);
}
return ENSURE_EOS;
}
// end offset assembly
static enum eos_t PO_realpc(void)
{
Throw_first_pass_warning(Warning_old_offset_assembly);
// deactivate offset assembly
CPU_pc.intval = (CPU_pc.intval - current_offset) & 0xffff;
current_offset = 0;
uses_pseudo_pc = FALSE;
return ENSURE_EOS;
}
// return whether offset assembly is active
int CPU_uses_pseudo_pc(void)
{
return uses_pseudo_pc;
}
// 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, int make_long)
{
if (((CPU_now->flags & CPUFLAG_SUPPORTSLONGREGS) == 0) && make_long)
Throw_error("Chosen CPU does not support long registers.");
else
*var = make_long;
}
// set register length, block-wise if needed.
static enum eos_t set_register_length(int *var, int make_long)
{
int old_size = *var;
// set new register length (or complain - whichever is more fitting)
check_and_set_reg_length(var, make_long);
// if there's a block, parse that and then restore old value!
if (Parse_optional_block())
check_and_set_reg_length(var, old_size); // restore old length
return ENSURE_EOS;
}
// switch to long accu ("!al" pseudo opcode)
static enum eos_t PO_al(void)
{
return set_register_length(&CPU_now->a_is_long, TRUE);
}
// switch to short accu ("!as" pseudo opcode)
static enum eos_t PO_as(void)
{
return set_register_length(&CPU_now->a_is_long, FALSE);
}
// switch to long index registers ("!rl" pseudo opcode)
static enum eos_t PO_rl(void)
{
return set_register_length(&CPU_now->xy_are_long, TRUE);
}
// switch to short index registers ("!rs" pseudo opcode)
static enum eos_t PO_rs(void)
{
return set_register_length(&CPU_now->xy_are_long, FALSE);
}
// pseudo opcode table
static struct 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(s_rl, PO_rl),
PREDEFLAST("rs", PO_rs),
// ^^^^ this marks the last element
};
// set default values for pass
void CPU_passinit(struct cpu_t *cpu_type)
{
// handle cpu type (default is 6502)
CPU_now = cpu_type ? cpu_type : &CPU_6502;
CPU_pc.flags = 0; // not defined yet
CPU_pc.intval = 512; // actually, there should be no need to init
CPU_2add = 0; // increase PC by this at end of statement
CPU_65816.a_is_long = FALSE; // short accu
CPU_65816.xy_are_long = FALSE; // short index regs
uses_pseudo_pc = FALSE; // offset assembly is not active,
current_offset = 0; // so offset is 0
}
// create cpu type tree (is done early)
void CPUtype_init(void)
{
Tree_add_table(&CPU_tree, CPUs);
}
// register pseudo opcodes (done later)
void CPU_init(void)
{
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}
// set program counter to defined value
void CPU_set_pc(intval_t new_pc)
{
CPU_pc.flags |= MVALUE_DEFINED;
CPU_pc.intval = new_pc;
}

47
trunk/src/cpu.h Normal file
View File

@ -0,0 +1,47 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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 function is not allowed to change GlobalDynaBuf
// because that's where the mnemonic is stored!
int (*keyword_is_mnemonic)(int);
int flags;
char default_align_value;
int a_is_long;
int xy_are_long;
};
#define CPUFLAG_INDIRECTJMPBUGGY (1u << 0)
#define CPUFLAG_SUPPORTSLONGREGS (1u << 1)
// variables
extern struct cpu_t *CPU_now; // struct of current CPU type (default 6502)
extern struct result_int_t CPU_pc; // current program counter (pseudo value)
extern int CPU_2add; // add to PC after statement
// create cpu type tree (is done early)
extern void CPUtype_init(void);
// register pseudo opcodes (done later)
extern void CPU_init(void);
// set default values for pass
extern void CPU_passinit(struct cpu_t *cpu_type);
// set program counter to defined value
extern void CPU_set_pc(intval_t new_pc);
// try to find CPU type held in DynaBuf. Returns whether succeeded.
extern int CPU_find_cpu_struct(struct cpu_t **target);
// return whether offset assembly is active
extern int CPU_uses_pseudo_pc(void);
#endif

151
trunk/src/dynabuf.c Normal file
View File

@ -0,0 +1,151 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
struct dynabuf_t *GlobalDynaBuf; // global dynamic buffer
// Functions
// get new buffer of given size
static void resize(struct 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
struct dynabuf_t *DynaBuf_create(int initial_size)
{
struct dynabuf_t *db;
if (initial_size < DYNABUF_MINIMUM_INITIALSIZE)
initial_size = DYNABUF_MINIMUM_INITIALSIZE;
if ((db = malloc(sizeof(*db)))) {
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(struct 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(struct 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(struct dynabuf_t *db, char byte)
{
DYNABUF_APPEND(db, byte);
}
// Append string to buffer (without terminator)
void DynaBuf_add_string(struct dynabuf_t *db, const char *string)
{
char byte;
while ((byte = *string++))
DYNABUF_APPEND(db, byte);
}
// make sure DynaBuf is large enough to take "size" more bytes
// return pointer to end of current contents
static char *ensure_free_space(struct dynabuf_t *db, int size)
{
while ((db->reserved - db->size) < size)
resize(db, MAKE_LARGER_THAN(db->reserved));
return db->buffer + db->size;
}
// add string version of int to buffer (without terminator)
void DynaBuf_add_signed_long(struct dynabuf_t *db, signed long value)
{
char *write = ensure_free_space(db, INTVAL_MAXCHARACTERS + 1);
db->size += sprintf(write, "%ld", value);
}
// add string version of float to buffer (without terminator)
void DynaBuf_add_double(struct dynabuf_t *db, double value)
{
char *write = ensure_free_space(db, 40); // reserve 40 chars
// write up to 30 significant characters. remaining 10 should suffice
// for sign, decimal point, exponent, terminator etc.
db->size += sprintf(write, "%.30g", value);
}
// Convert buffer contents to lower case (target and source may be identical)
void DynaBuf_to_lower(struct dynabuf_t *target, struct 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);
}

59
trunk/src/dynabuf.h Normal file
View File

@ -0,0 +1,59 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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) do {db->size = 0;} while (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
};
// variables
extern struct dynabuf_t *GlobalDynaBuf; // global dynamic buffer
// create global DynaBuf (call once on program startup)
extern void DynaBuf_init(void);
// create (private) DynaBuf
extern struct dynabuf_t *DynaBuf_create(int initial_size);
// call whenever buffer is too small
extern void DynaBuf_enlarge(struct dynabuf_t *db);
// return malloc'd copy of buffer contents
extern char *DynaBuf_get_copy(struct dynabuf_t *db);
// copy string to buffer (without terminator)
extern void DynaBuf_add_string(struct dynabuf_t *db, const char *);
// add string version of int to buffer (without terminator)
extern void DynaBuf_add_signed_long(struct dynabuf_t *db, signed long value);
// add string version of float to buffer (without terminator)
extern void DynaBuf_add_double(struct dynabuf_t *db, double value);
// converts buffer contents to lower case
extern void DynaBuf_to_lower(struct dynabuf_t *target, struct dynabuf_t *source);
// add char to buffer
extern void DynaBuf_append(struct dynabuf_t *db, char);
#endif

256
trunk/src/encoding.c Normal file
View File

@ -0,0 +1,256 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
static char outermost_table[256]; // space for encoding table...
static char *loaded_table = outermost_table; // ...loaded from file
// predefined stuff
static struct node_t *encoder_tree = NULL; // tree to hold encoders
// Functions
// convert character using current encoding
// Conversion function pointer. No init needed: gets set before each pass.
char (*Encoding_encode_char)(char);
// Insert string(s)
static enum eos_t encode_string(encoder_t inner_encoder, char xor)
{
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(xor ^ 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.
Output_8b(ALU_any_int());
}
} 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, XOR'd
static enum eos_t PO_scrxor(void)
{
intval_t num = ALU_any_int();
if (Input_accept_comma())
return encode_string(encoder_scr, num);
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
Throw_first_pass_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 local_table[256],
*buffered_table = loaded_table;
encoder_t buffered_encoder = Encoding_encode_char;
// if file name is missing, don't bother continuing
if (Input_read_filename(TRUE))
return SKIP_REMAINDER;
fd = fopen(GLOBALDYNABUF_CURRENT, 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);
}
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 struct 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(s_scrxor, PO_scrxor),
PREDEFNODE("text", PO_text),
PREDEFLAST("tx", PO_text),
// ^^^^ this marks the last element
};
// keywords for "!convtab" pseudo opcode
static struct 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
// register pseudo opcodes and build keyword tree for encoders
void Encoding_init(void)
{
Tree_add_table(&encoder_tree, encoders);
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}
// Set "raw" as default encoding
void Encoding_passinit(void)
{
Encoding_encode_char = encoder_raw;
}

20
trunk/src/encoding.h Normal file
View File

@ -0,0 +1,20 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Character encoding stuff
#ifndef encoding_H
#define encoding_H
// Prototypes
// register pseudo opcodes and build keyword tree for encoders
extern void Encoding_init(void);
// convert character using current encoding
extern char (*Encoding_encode_char)(char);
// Set "raw" as default encoding
extern void Encoding_passinit(void);
#endif

440
trunk/src/flow.c Normal file
View File

@ -0,0 +1,440 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
// function uses the same technique to parse the top level file.
//
// 24 Nov 2007 Added "!ifndef"
#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
};
struct loop_condition {
enum cond_key_t type; // either ID_UNTIL or ID_WHILE
int line; // original line number
char *body; // pointer to actual expression
};
// variables
// predefined stuff
static struct node_t *condkey_tree = NULL; // tree to hold UNTIL and WHILE
static struct 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 (could also be used for macro body)
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(struct loop_condition *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);
}
}
// check a condition expression
static int check_condition(struct loop_condition *condition)
{
intval_t expression;
// First, check whether there actually *is* a condition
if (condition->body == NULL)
return 1; // 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
expression = ALU_defined_int();
if (GotByte)
Throw_serious_error(exception_syntax);
return (condition->type == ID_UNTIL) ? !expression : !!expression;
}
// looping assembly ("!do"). Has to be re-entrant.
static enum eos_t PO_do(void) // Now GotByte = illegal char
{
struct loop_condition condition1,
condition2;
struct input_t loop_input,
*outer_input;
char *loop_body;
int go_on,
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
{
struct input_t loop_input,
*outer_input;
struct result_t loop_counter;
intval_t maximum;
char *loop_body; // pointer to loop's body block
struct 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 = Input_get_force_bit(); // skips spaces after
label = Label_find(zone, force_bit);
if (Input_accept_comma() == 0) {
Throw_error(exception_syntax);
return SKIP_REMAINDER;
}
maximum = ALU_defined_int();
if (maximum < 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;
// init counter
loop_counter.flags = MVALUE_DEFINED | MVALUE_EXISTS;
loop_counter.val.intval = 0;
// if count == 0, skip loop
if (maximum) {
do {
loop_counter.val.intval++; // increment counter
Label_set_value(label, &loop_counter, TRUE);
parse_ram_block(loop_start, loop_body);
} while (loop_counter.val.intval < maximum);
} else {
Label_set_value(label, &loop_counter, TRUE);
}
// 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", "!ifdef" and "!ifndef"
// parse or skip a block. Returns whether block's '}' terminator was missing.
// afterwards: GotByte = '}'
static int skip_or_parse_block(int parse)
{
if (!parse) {
Input_skip_or_store_block(FALSE);
return 0;
}
// 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 0;
}
// parse {block} [else {block}]
static void parse_block_else_block(int 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
{
intval_t cond;
cond = ALU_defined_int();
if (GotByte != CHAR_SOB)
Throw_serious_error(exception_no_left_brace);
parse_block_else_block(!!cond);
return ENSURE_EOS;
}
// conditional assembly ("!ifdef" and "!ifndef"). Has to be re-entrant.
static enum eos_t ifdef_ifndef(int invert) // Now GotByte = illegal char
{
struct node_ra_t *node;
struct label_t *label;
zone_t zone;
int 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 = (struct label_t *) node->body;
// in first pass, count usage
if (pass_count == 0)
label->usage++;
if (label->result.flags & MVALUE_DEFINED)
defined = TRUE;
}
SKIPSPACE();
// if "ifndef", invert condition
if (invert)
defined = !defined;
if (GotByte == CHAR_SOB)
parse_block_else_block(defined);
else
return defined ? PARSE_REMAINDER : SKIP_REMAINDER;
return ENSURE_EOS;
}
// conditional assembly ("!ifdef"). Has to be re-entrant.
static enum eos_t PO_ifdef(void) // Now GotByte = illegal char
{
return ifdef_ifndef(FALSE);
}
// conditional assembly ("!ifndef"). Has to be re-entrant.
static enum eos_t PO_ifndef(void) // Now GotByte = illegal char
{
return ifdef_ifndef(TRUE);
}
// 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_count == 0) {
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;
struct 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 (Input_read_filename(TRUE))
return SKIP_REMAINDER;
// If file could be opened, parse it. Otherwise, complain.
if ((fd = fopen(GLOBALDYNABUF_CURRENT, FILE_READBINARY))) {
char filename[GlobalDynaBuf->size];
strcpy(filename, GLOBALDYNABUF_CURRENT);
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);
}
// Leave nesting level
source_recursions_left++;
return ENSURE_EOS;
}
// pseudo opcode table
static struct node_t pseudo_opcodes[] = {
PREDEFNODE("do", PO_do),
PREDEFNODE("for", PO_for),
PREDEFNODE("if", PO_if),
PREDEFNODE("ifdef", PO_ifdef),
PREDEFNODE("ifndef", PO_ifndef),
PREDEFNODE("macro", PO_macro),
PREDEFNODE("source", PO_source),
PREDEFLAST("src", PO_source),
// ^^^^ this marks the last element
};
// register pseudo opcodes and build keyword tree for until/while
void Flow_init(void)
{
Tree_add_table(&condkey_tree, condkeys);
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}

22
trunk/src/flow.h Normal file
View File

@ -0,0 +1,22 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
// register pseudo opcodes and build keyword tree for until/while
extern void Flow_init(void);
// Parse a whole source code file
extern void Parse_and_close_file(FILE *fd, const char *filename);
#endif

391
trunk/src/global.c Normal file
View File

@ -0,0 +1,391 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Global stuff - things that are needed by several modules
// 4 Oct 2006 Fixed a typo in a comment
// 22 Nov 2007 Added warn_on_indented_labels
#include <stdio.h>
#include "platform.h"
#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_65816[] = "65816";
const char s_and[] = "and";
const char s_asl[] = "asl";
const char s_asr[] = "asr";
const char s_brl[] = "brl";
const char s_cbm[] = "cbm";
const char s_eor[] = "eor";
const char s_error[] = "error";
const char s_lsr[] = "lsr";
const char s_scrxor[] = "scrxor";
// 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_number_out_of_range[] = "Number out of range.";
const char exception_pc_undefined[] = "Program counter undefined.";
const char exception_syntax[] = "Syntax error.";
// 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,// "xyz{|}~" 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
struct node_t *pseudo_opcode_tree = NULL; // tree to hold pseudo opcodes
int pass_count; // number of current pass (starts 0)
char GotByte; // Last byte read (processed)
int Process_verbosity = 0; // Level of additional output
int warn_on_indented_labels = TRUE; // warn if indented label is encountered
// global counters
int pass_undefined_count; // "NeedValue" type errors
int pass_real_errors; // Errors yet
signed long max_errors = MAXERRORS; // errors before giving up
FILE *msg_stream = NULL; // set to stdout by --use-stdout
// 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;
}
// Parser stuff
// Parse (re-)definitions of program counter
static void parse_pc_def(void) // Now GotByte = "*"
{
NEXTANDSKIPSPACE(); // proceed with next char
// re-definitions of program counter change segment
if (GotByte == '=') {
GetByte(); // proceed with next char
Output_start_segment();
Input_ensure_EOS();
} else {
Throw_error(exception_syntax);
Input_skip_remainder();
}
}
// Parse pseudo opcodes. Has to be re-entrant.
static void parse_pseudo_opcode(void) // Now GotByte = "!"
{
void *node_body;
enum eos_t (*fn)(void),
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 int 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 == ' ')
Throw_first_pass_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)
{
struct 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->result.val.intval++;
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 '*':
parse_pc_def();
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
// adjust program counter
CPU_pc.intval = (CPU_pc.intval + CPU_2add) & 0xffff;
CPU_2add = 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.
int 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 function 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(msg_stream, "%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 a warning if in first pass. See above.
void Throw_first_pass_warning(const char *message)
{
if (pass_count == 0)
Throw_warning(message);
}
// 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);
}

118
trunk/src/global.h Normal file
View File

@ -0,0 +1,118 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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 << 0) // statement had space or tab
#define SF_IMPLIED_LABEL (1u << 1) // statement had implied label def
extern const char s_65816[];
extern const char s_and[];
extern const char s_asl[];
extern const char s_asr[];
extern const char s_brl[];
extern const char s_cbm[];
extern const char s_eor[];
extern const char s_error[];
extern const char s_lsr[];
extern const char s_scrxor[];
// 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_number_out_of_range[];
extern const char exception_pc_undefined[];
extern const char exception_syntax[];
// 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 struct 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_count;
extern int Process_verbosity; // Level of additional output
extern int warn_on_indented_labels; // warn if indented label is encountered
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 signed long max_errors; // errors before giving up
extern FILE *msg_stream; // set to stdout by --errors_to_stdout
// Macros for skipping a single space character
#define SKIPSPACE() \
do { \
if (GotByte == ' ') \
GetByte(); \
} while (0)
#define NEXTANDSKIPSPACE() \
do { \
if (GetByte() == ' ') \
GetByte(); \
} while (0)
// Prototypes
// Allocate memory and die if not available
extern void *safe_malloc(size_t);
// 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.
extern void Parse_until_eob_or_eof(void);
// 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.
extern int Parse_optional_block(void);
// 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.
extern void Throw_warning(const char *);
// Output a warning if in first pass. See above.
extern void Throw_first_pass_warning(const char *);
// 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.
extern void Throw_error(const char *);
// 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.
extern void Throw_serious_error(const char *);
// Handle bugs
extern void Bug_found(const char *, int);
#endif

524
trunk/src/input.c Normal file
View File

@ -0,0 +1,524 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Input stuff
#include "config.h"
#include "alu.h"
#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 struct 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
struct 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 struct node_t pseudo_opcodes[] = {
PREDEFNODE("eof", PO_eof),
PREDEFLAST("endoffile", PO_eof),
// ^^^^ this marks the last element
};
// Functions
// register pseudo opcodes
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;
for (;;) {
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);
}
}
}
// This function delivers the next byte from the currently active byte source
// in shortened high-level format. FIXME - use fn ptr?
// When inside quotes, use GetQuotedByte() instead!
char GetByte(void)
{
// for (;;) {
// 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++;
// }
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(int 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;
for (;;) {
// 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();
}
}
// 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 whether error occurred (TRUE on error). Filename in GlobalDynaBuf.
// Errors are handled and reported, but caller should call
// Input_skip_remainder() then.
int Input_read_filename(int 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 TRUE;
}
// 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 TRUE;
}
#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 TRUE;
}
}
// read first character, complain if closing quote
if (GetQuotedByte() == end_quote) {
Throw_error("No file name given.");
return TRUE;
}
// 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
if (GotByte == CHAR_EOS)
return TRUE;
GetByte(); // fetch next to forget closing quote
// terminate string
DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator
return FALSE; // no error
}
// Try to read a comma, skipping spaces before and after. Return TRUE if comma
// found, otherwise FALSE.
int Input_accept_comma(void)
{
SKIPSPACE();
if (GotByte != ',')
return FALSE;
NEXTANDSKIPSPACE();
return TRUE;
}
// read optional info about parameter length
int Input_get_force_bit(void)
{
char byte;
int force_bit = 0;
if (GotByte == '+') {
byte = GetByte();
if (byte == '1')
force_bit = MVALUE_FORCE08;
else if (byte == '2')
force_bit = MVALUE_FORCE16;
else if (byte == '3')
force_bit = MVALUE_FORCE24;
if (force_bit)
GetByte();
else
Throw_error("Illegal postfix.");
}
SKIPSPACE();
return force_bit;
}

116
trunk/src/input.h Normal file
View File

@ -0,0 +1,116 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
};
struct input_t {
const char *original_filename; // during RAM reads, too
int line_number, // in file (on RAM reads, too)
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;
};
// 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 struct input_t *Input_now; // current input structure
// Prototypes
// register pseudo opcodes
extern void Input_init(void);
// Let current input point to start of file
extern void Input_new_file(const char *filename, FILE *fd);
// get next byte from currently active byte source in shortened high-level
// format. When inside quotes, use GetQuotedByte() instead!
extern char GetByte(void);
// get next byte from currently active byte source in un-shortened high-level
// format. Complains if CHAR_EOS (end of statement) is read.
extern char GetQuotedByte(void);
// Skip remainder of statement, for example on error
extern void Input_skip_remainder(void);
// Ensure that the remainder of the current statement is empty, for example
// after mnemonics using implied addressing.
extern void Input_ensure_EOS(void);
// 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.
extern char *Input_skip_or_store_block(int store);
// 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).
extern void Input_until_terminator(char terminator);
// Append to GlobalDynaBuf while characters are legal for keywords.
// Throws "missing string" error if none. Returns number of characters added.
extern int Input_append_keyword_to_global_dynabuf(void);
// 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.
extern int Input_read_zone_and_keyword(zone_t *);
// 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.
extern int Input_read_keyword(void);
// 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.
extern int Input_read_and_lower_keyword(void);
// 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 whether error occurred (TRUE on error). Filename in GlobalDynaBuf.
// Errors are handled and reported, but caller should call
// Input_skip_remainder() then.
extern int Input_read_filename(int library_allowed);
// Try to read a comma, skipping spaces before and after. Return TRUE if comma
// found, otherwise FALSE.
extern int Input_accept_comma(void);
// read optional info about parameter length
extern int Input_get_force_bit(void);
#endif

313
trunk/src/label.c Normal file
View File

@ -0,0 +1,313 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Label stuff
//
// 22 Nov 2007 "warn on indented labels" is now a CLI switch
// 25 Sep 2011 Fixed bug in !sl (colons in filename could be interpreted as EOS)
#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 "platform.h"
#include "section.h"
#include "tree.h"
// constants
#define s_sl (s_asl + 1) // Yes, I know I'm sick
// variables
struct node_ra_t *Label_forest[256]; // ... (because of 8-bit hash)
// Dump label value and flags to dump file
static void dump_one_label(struct node_ra_t *node, FILE *fd)
{
struct label_t *label = node->body;
// output name
fprintf(fd, "%s", node->id_string);
switch (label->result.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->result.flags & MVALUE_DEFINED) {
if (label->result.flags & MVALUE_IS_FP)
fprintf(fd, "%.30f", label->result.val.fpval); //FIXME %g
else
fprintf(fd, "$%x", (unsigned) label->result.val.intval);
} else {
fprintf(fd, " ?");
}
if (label->result.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.
struct label_t *Label_find(zone_t zone, int flags)
{
struct node_ra_t *node;
struct label_t *label;
int node_created,
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));
// Finish empty label item
label->result.flags = flags;
if (flags & MVALUE_IS_FP)
label->result.val.fpval = 0;
else
label->result.val.intval = 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->result.flags & MVALUE_FORCEBITS) != force_bits)
Throw_error("Too late for postfix.");
return label;
}
// Assign value to label. The function acts upon the label's flag bits and
// produces an error if needed.
void Label_set_value(struct label_t *label, struct result_t *newvalue, int change_allowed)
{
int oldflags = label->result.flags;
// value stuff
if ((oldflags & MVALUE_DEFINED) && (change_allowed == FALSE)) {
// Label is already defined, so compare new and old values
// if different type OR same type but different value, complain
if (((oldflags ^ newvalue->flags) & MVALUE_IS_FP)
|| ((oldflags & MVALUE_IS_FP)
? (label->result.val.fpval != newvalue->val.fpval)
: (label->result.val.intval != newvalue->val.intval)))
Throw_error("Label already defined.");
} else {
// Label is not defined yet OR redefinitions are allowed
label->result = *newvalue;
}
// flags stuff
// Ensure that "unsure" labels without "isByte" state don't get that
if ((oldflags & (MVALUE_UNSURE | MVALUE_ISBYTE)) == MVALUE_UNSURE)
newvalue->flags &= ~MVALUE_ISBYTE;
if (change_allowed) {
oldflags = (oldflags & MVALUE_UNSURE) | newvalue->flags;
} else {
if ((oldflags & MVALUE_FORCEBITS) == 0)
if ((oldflags & (MVALUE_UNSURE | MVALUE_DEFINED)) == 0)
oldflags |= newvalue->flags & MVALUE_FORCEBITS;
oldflags |= newvalue->flags & ~MVALUE_FORCEBITS;
}
label->result.flags = oldflags;
}
// (Re)set label
static enum eos_t PO_set(void) // Now GotByte = illegal char
{
struct result_t result;
int force_bit;
struct 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 = Input_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_any_result(&result);
// clear label's force bits and set new ones
label->result.flags &= ~(MVALUE_FORCEBITS | MVALUE_ISBYTE);
if (force_bit) {
label->result.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)
{
// bugfix: first read filename, *then* check for first pass.
// if skipping right away, quoted colons might be misinterpreted as EOS
// FIXME - why not just fix the skipping code to handle quotes? :)
// "!to" has been fixed as well
// read filename to global dynamic buffer
// if no file name given, exit (complaining will have been done)
if (Input_read_filename(FALSE))
return SKIP_REMAINDER;
// only process this pseudo opcode in first pass
if (pass_count)
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;
}
// get malloc'd copy of filename
labeldump_filename = DynaBuf_get_copy(GlobalDynaBuf);
// ensure there's no garbage at end of line
return ENSURE_EOS;
}
// predefined stuff
static struct node_t pseudo_opcodes[] = {
PREDEFNODE("set", PO_set),
PREDEFLAST(s_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, int change)
{
struct result_t result;
struct label_t *label;
label = Label_find(zone, force_bit);
// implicit label definition (label)
if ((stat_flags & SF_FOUND_BLANK) && warn_on_indented_labels)
Throw_first_pass_warning("Implicit label definition not in leftmost column.");
result.flags = CPU_pc.flags & MVALUE_DEFINED;
result.val.intval = CPU_pc.intval;
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)
{
struct result_t result;
struct label_t *label;
int force_bit = Input_get_force_bit(); // skips spaces after
// FIXME - force bit is allowed for implicit label defs?!
if (GotByte == '=') {
// explicit label definition (label = <something>)
label = Label_find(zone, force_bit);
// label = parsed value
GetByte(); // skip '='
ALU_any_result(&result);
Label_set_value(label, &result, FALSE);
Input_ensure_EOS();
} else {
Label_implicit_definition(zone, stat_flags, force_bit, FALSE);
}
}
// set global label to value, no questions asked (for "-D" switch)
// Name must be held in GlobalDynaBuf.
void Label_define(intval_t value)
{
struct result_t result;
struct label_t *label;
result.flags = MVALUE_GIVEN;
result.val.intval = value;
label = Label_find(ZONE_GLOBAL, 0);
Label_set_value(label, &result, TRUE);
}
// dump global labels to file
void Label_dump_all(FILE *fd)
{
Tree_dump_forest(Label_forest, ZONE_GLOBAL, dump_one_label, fd);
PLATFORM_SETFILETYPE_TEXT(labeldump_filename);
}
// clear label forest (is done early)
void Label_clear_init(void)
{
struct node_ra_t **ptr;
int i;
// cut down all the trees (clear pointer table)
ptr = Label_forest;
for (i = 255; i >= 0; i--)
*ptr++ = NULL;
}
// register pseudo opcodes (done later)
void Label_register_init(void)
{
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}
// 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.
struct label_t *Label_fix_forward_name(void)
{
struct 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->result.val.intval = 0;
}
number = (unsigned long) counter_label->result.val.intval;
// now append to the name to make it unique
GlobalDynaBuf->size--; // forget terminator, we want to append
do {
DYNABUF_APPEND(GlobalDynaBuf, 'a' + (number & 15));
number >>= 4;
} while (number);
DynaBuf_append(GlobalDynaBuf, '\0');
return counter_label;
}

51
trunk/src/label.h Normal file
View File

@ -0,0 +1,51 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Label stuff
#ifndef label_H
#define label_H
#include <stdio.h>
#include "config.h"
// "label" structure type definition
struct label_t {
struct result_t result; // Expression flags and value
int usage; // usage count
int pass; // pass of creation (for anon counters)
};
// variables
extern struct node_ra_t *Label_forest[]; // trees (because of 8-bit hash)
// clear label forest (is done early)
extern void Label_clear_init(void);
// register pseudo opcodes (done later)
extern void Label_register_init(void);
// function acts upon the label's flag bits and produces an error if needed.
extern void Label_set_value(struct label_t *, struct result_t *, int change_allowed);
// Parse implicit label definition (can be either global or local).
// Name must be held in GlobalDynaBuf.
extern void Label_implicit_definition(zone_t zone, int stat_flags, int force_bit, int change);
// Parse label definition (can be either global or local).
// Name must be held in GlobalDynaBuf.
extern void Label_parse_definition(zone_t zone, int stat_flags);
// Search for label. Create if nonexistant. If created, assign flags.
// Name must be held in GlobalDynaBuf.
extern struct label_t *Label_find(zone_t, int flags);
// set global label to value, no questions asked (for "-D" switch)
// Name must be held in GlobalDynaBuf.
extern void Label_define(intval_t value);
// Dump global labels to file
extern void Label_dump_all(FILE *fd);
// Fix name of anonymous forward label (held in GlobalDynaBuf, NOT TERMINATED!)
// so it references the *next* anonymous forward label definition.
extern struct label_t *Label_fix_forward_name(void);
#endif

350
trunk/src/macro.c Normal file
View File

@ -0,0 +1,350 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Macro stuff
#include <string.h> // needs strlen() + memcpy()
#include "config.h"
#include "platform.h"
#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
*original_name, // user-supplied name for error msgs
*parameter_list, // parameters (whole line)
*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 {
struct result_t result; // value and flags (call by value)
struct label_t *label; // pointer to label struct (call by reference)
};
// Variables
static struct dynabuf_t *user_macro_name; // original macro title
static struct dynabuf_t *internal_name; // plus param type chars
static struct 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(*arg_table));
if (arg_table == NULL)
Throw_serious_error(exception_no_memory_left);
}
// create dynamic buffers and 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 int pipe_comma(void)
{
int 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 int search_for_macro(struct node_ra_t **result, zone_t zone, int 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(struct 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;
struct 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(*new_macro));
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;
struct label_t *label;
struct section_t new_section,
*outer_section;
struct input_t new_input,
*outer_input;
struct macro_t *actual_macro;
struct 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_any_result(&(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_count == 0))
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->result = arg_table[arg_count].result;
}
arg_count++;
} while (Input_accept_comma());
}
// and now, finally, parse the actual macro body
Input_now->state = INPUTSTATE_NORMAL; // FIXME - fix others!
// maybe call parse_ram_block(actual_macro->def_line_number, actual_macro->body)
Input_now->src.ram_ptr = actual_macro->body;
Parse_until_eob_or_eof();
if (GotByte != CHAR_EOB)
Bug_found("IllegalBlockTerminator", GotByte);
// 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
}

23
trunk/src/macro.h Normal file
View File

@ -0,0 +1,23 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Macro stuff
#ifndef macro_H
#define macro_H
#include "config.h"
// Prototypes
// create dynamic buffers and arg table
extern void Macro_init(void); // create private dynabuf
// only call once (during first pass)
extern void Macro_parse_definition(void);
// Parse macro call ("+MACROTITLE"). Has to be re-entrant.
extern void Macro_parse_call(void);
#endif

946
trunk/src/mnemo.c Normal file
View File

@ -0,0 +1,946 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Mnemonics stuff
#include "config.h"
#include "alu.h"
#include "cpu.h"
#include "dynabuf.h"
#include "global.h"
#include "input.h"
#include "output.h"
#include "tree.h"
// Constants
#define s_ror (s_error + 2) // Yes, I know I'm sick
#define MNEMO_DYNABUF_INITIALSIZE 8 // 4 + terminator should suffice
// These values are needed to recognize addressing modes.
// Bits:
// 7....... "Implied" no value given
// .6...... "Immediate" "#" at start
// ..5..... "IndirectLong" "[" at start and "]" after value
// ...4.... "Indirect" Value has at least one unnecessary pair of "()"
// ....32.. "Indexed-Int" Index given inside of "()"
// ......10 "Indexed-Ext" Index given outside of (or without any) "()"
//
// Index bits:
// 00 = no index
// 01 = ",s" (Stack-indexed)
// 10 = ",x" (X-indexed)
// 11 = ",y" (Y-indexed)
// Components (Values for indices)
#define HAM__ (0u << 0) // No index
#define HAM_S (1u << 0) // Stack-indexed
#define HAM_X (2u << 0) // X-indexed
#define HAM_Y (3u << 0) // Y-indexed
// End values base value internal index external index
#define HAM_IMP (1u << 7)
#define HAM_IMM (1u << 6)
#define HAM_ABS 0
#define HAM_ABSS (1u << 0)
#define HAM_ABSX (2u << 0)
#define HAM_ABSY (3u << 0)
#define HAM_IND (1u << 4)
#define HAM_XIND ((1u << 4) | (2u << 2))
#define HAM_INDY ((1u << 4) | (3u << 0))
#define HAM_SINDY ((1u << 4) | (1u << 2) | (3u << 0))
#define HAM_LIND (1u << 5)
#define HAM_LINDY ((1u << 5) | (3u << 0))
// Values of internal indices equal values of external indices, shifted left
// by two bits. The program relies on this !
// Constant values, used to mark the possible parameter lengths of commands.
// Not all of the eight values are actually used, however (because of the
// supported CPUs).
#define MAYBE______ (0)
#define MAYBE_1____ (MVALUE_FORCE08)
#define MAYBE___2__ (MVALUE_FORCE16)
#define MAYBE_1_2__ (MVALUE_FORCE08 | MVALUE_FORCE16)
#define MAYBE_____3 (MVALUE_FORCE24)
#define MAYBE_1___3 (MVALUE_FORCE08 | MVALUE_FORCE24)
#define MAYBE___2_3 (MVALUE_FORCE16 | MVALUE_FORCE24)
#define MAYBE_1_2_3 (MVALUE_FORCE08 | MVALUE_FORCE16 | MVALUE_FORCE24)
// The mnemonics are split up into groups, each group has its own function to be dealt with:
enum mnemogroup_t {
GROUP_ACCU, // main accumulator stuff, plus PEI Byte value = table index
GROUP_MISC, // read-modify-write and others Byte value = table index
GROUP_ALLJUMPS, // the jump instructions Byte value = table index
GROUP_IMPLIEDONLY, // mnemonics using only implied addressing Byte value = opcode
GROUP_RELATIVE8, // short branch instructions Byte value = opcode
GROUP_RELATIVE16, // mnemonics with 16bit relative addressing Byte value = opcode
GROUP_BOTHMOVES // the "move" commands MVP and MVN Byte value = opcode
};
// save some space
#define SCB static const unsigned char
#define SCS static const unsigned short
#define SCL static const unsigned long
// Code tables for group GROUP_ACCU:
// These tables are used for the main accumulator-related mnemonics. By reading
// the mnemonic's byte value (from the mnemotable), the assembler finds out the
// column to use here. The row depends on the used addressing mode. A zero
// entry in these tables means that the combination of mnemonic and addressing
// mode is illegal.
// | 6502 | 65c02 | 65816 | 6510 illegals |
enum { IDX_ORA,IDX_AND,IDX_EOR,IDX_ADC,IDX_STA,IDX_LDA,IDX_CMP,IDX_SBC,IDXcORA,IDXcAND,IDXcEOR,IDXcADC,IDXcSTA,IDXcLDA,IDXcCMP,IDXcSBC,IDX816ORA,IDX816AND,IDX816EOR,IDX816ADC,IDX816STA,IDX816LDA,IDX816CMP,IDX816SBC,IDX816PEI,IDX_SLO,IDX_RLA,IDX_SRE,IDX_RRA,IDX_SAX,IDX_LAX,IDX_DCP,IDX_ISC};
SCL accu_abs[] = { 0x0d05, 0x2d25, 0x4d45, 0x6d65, 0x8d85, 0xada5, 0xcdc5, 0xede5, 0x0d05, 0x2d25, 0x4d45, 0x6d65, 0x8d85, 0xada5, 0xcdc5, 0xede5, 0x0f0d05, 0x2f2d25, 0x4f4d45, 0x6f6d65, 0x8f8d85, 0xafada5, 0xcfcdc5, 0xefede5, 0, 0x0f07, 0x2f27, 0x4f47, 0x6f67, 0x8f87, 0xafa7, 0xcfc7, 0xefe7}; // $ff $ffff $ffffff
SCL accu_xabs[] = { 0x1d15, 0x3d35, 0x5d55, 0x7d75, 0x9d95, 0xbdb5, 0xddd5, 0xfdf5, 0x1d15, 0x3d35, 0x5d55, 0x7d75, 0x9d95, 0xbdb5, 0xddd5, 0xfdf5, 0x1f1d15, 0x3f3d35, 0x5f5d55, 0x7f7d75, 0x9f9d95, 0xbfbdb5, 0xdfddd5, 0xfffdf5, 0, 0x1f17, 0x3f37, 0x5f57, 0x7f77, 0, 0, 0xdfd7, 0xfff7}; // $ff,x $ffff,x $ffffff,x
SCS accu_yabs[] = { 0x1900, 0x3900, 0x5900, 0x7900, 0x9900, 0xb900, 0xd900, 0xf900, 0x1900, 0x3900, 0x5900, 0x7900, 0x9900, 0xb900, 0xd900, 0xf900, 0x1900, 0x3900, 0x5900, 0x7900, 0x9900, 0xb900, 0xd900, 0xf900, 0, 0x1b00, 0x3b00, 0x5b00, 0x7b00, 0x97, 0xbfb7, 0xdb00, 0xfb00}; // $ff,y $ffff,y
SCB accu_xind8[] = { 0x01, 0x21, 0x41, 0x61, 0x81, 0xa1, 0xc1, 0xe1, 0x01, 0x21, 0x41, 0x61, 0x81, 0xa1, 0xc1, 0xe1, 0x01, 0x21, 0x41, 0x61, 0x81, 0xa1, 0xc1, 0xe1, 0, 0x03, 0x23, 0x43, 0x63, 0x83, 0xa3, 0xc3, 0xe3}; // ($ff,x)
SCB accu_indy8[] = { 0x11, 0x31, 0x51, 0x71, 0x91, 0xb1, 0xd1, 0xf1, 0x11, 0x31, 0x51, 0x71, 0x91, 0xb1, 0xd1, 0xf1, 0x11, 0x31, 0x51, 0x71, 0x91, 0xb1, 0xd1, 0xf1, 0, 0x13, 0x33, 0x53, 0x73, 0, 0xb3, 0xd3, 0xf3}; // ($ff),y
SCB accu_imm[] = { 0x09, 0x29, 0x49, 0x69, 0, 0xa9, 0xc9, 0xe9, 0x09, 0x29, 0x49, 0x69, 0, 0xa9, 0xc9, 0xe9, 0x09, 0x29, 0x49, 0x69, 0, 0xa9, 0xc9, 0xe9, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // #$ff #$ffff
SCB accu_ind8[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0x12, 0x32, 0x52, 0x72, 0x92, 0xb2, 0xd2, 0xf2, 0x12, 0x32, 0x52, 0x72, 0x92, 0xb2, 0xd2, 0xf2, 0xd4, 0, 0, 0, 0, 0, 0, 0, 0}; // ($ff)
SCB accu_sabs8[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x03, 0x23, 0x43, 0x63, 0x83, 0xa3, 0xc3, 0xe3, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // $ff,s
SCB accu_sindy8[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x13, 0x33, 0x53, 0x73, 0x93, 0xb3, 0xd3, 0xf3, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // ($ff,s),y
SCB accu_lind8[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x07, 0x27, 0x47, 0x67, 0x87, 0xa7, 0xc7, 0xe7, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // [$ff]
SCB accu_lindy8[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x17, 0x37, 0x57, 0x77, 0x97, 0xb7, 0xd7, 0xf7, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // [$ff],y
// Code tables for group GROUP_MISC:
// These tables are needed for finding out the correct code in cases when
// there are no general rules. By reading the mnemonic's byte value (from the
// mnemotable), the assembler finds out the column to use here. The row
// depends on the used addressing mode. A zero entry in these tables means
// that the combination of mnemonic and addressing mode is illegal.
// | 6502 | 65c02 | 65816 | 6510 illegals |
enum { IDX_BIT,IDX_ASL,IDX_ROL,IDX_LSR,IDX_ROR,IDX_STY,IDX_STX,IDX_LDY,IDX_LDX,IDX_CPY,IDX_CPX,IDX_DEC,IDX_INC,IDXcTSB,IDXcTRB,IDXcBIT,IDXcDEC,IDXcINC,IDXcSTZ,IDX816COP,IDX816REP,IDX816SEP,IDX816PEA,IDX_ANC,IDX_ASR,IDX_ARR,IDX_SBX,IDX_DOP,IDX_TOP,IDX_JAM};
SCS misc_abs[] = { 0x2c24, 0x0e06, 0x2e26, 0x4e46, 0x6e66, 0x8c84, 0x8e86, 0xaca4, 0xaea6, 0xccc4, 0xece4, 0xcec6, 0xeee6, 0x0c04, 0x1c14, 0x2c24, 0xcec6, 0xeee6, 0x9c64, 0x02, 0, 0, 0xf400, 0, 0, 0, 0, 0x04, 0x0c00, 0}; // $ff $ffff
SCS misc_xabs[] = { 0, 0x1e16, 0x3e36, 0x5e56, 0x7e76, 0x94, 0, 0xbcb4, 0, 0, 0, 0xded6, 0xfef6, 0, 0, 0x3c34, 0xded6, 0xfef6, 0x9e74, 0, 0, 0, 0, 0, 0, 0, 0, 0x14, 0x1c00, 0}; // $ff,x $ffff,x
SCS misc_yabs[] = { 0, 0, 0, 0, 0, 0, 0x96, 0, 0xbeb6, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0}; // $ff,y $ffff,y
SCB misc_imm[] = { 0, 0, 0, 0, 0, 0, 0, 0xa0, 0xa2, 0xc0, 0xe0, 0, 0, 0, 0, 0x89, 0, 0, 0, 0, 0xc2, 0xe2, 0, 0x2b, 0x4b, 0x6b, 0xcb, 0x80, 0, 0}; // #$ff
SCB misc_impl[] = { 0, 0x0a, 0x2a, 0x4a, 0x6a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x3a, 0x1a, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x80, 0x0c, 0x02}; // implied/accu
// Code tables for group GROUP_ALLJUMPS:
// These tables are needed for finding out the correct code when the mnemonic
// is "jmp" or "jsr" (or the long versions "jml" and "jsl").
// By reading the mnemonic's byte value (from the mnemotable), the assembler
// finds out the column to use here. The row depends on the used addressing
// mode. A zero entry in these tables means that the combination of mnemonic
// and addressing mode is illegal.
// | 6502 | 65c02 | 65816 |
enum { IDX_JMP,IDX_JSR,IDXcJMP,IDX816JMP,IDX816JML,IDX816JSR,IDX816JSL};
SCL jump_abs[] = { 0x4c00, 0x2000, 0x4c00, 0x5c4c00, 0x5c0000, 0x222000, 0x220000}; // $ffff $ffffff
SCS jump_ind[] = { 0x6c00, 0, 0x6c00, 0x6c00, 0, 0, 0}; // ($ffff)
SCS jump_xind[] = { 0, 0, 0x7c00, 0x7c00, 0, 0xfc00, 0}; // ($ffff,x)
SCS jump_lind[] = { 0, 0, 0, 0xdc00, 0xdc00, 0, 0}; // [$ffff]
#undef SCB
#undef SCS
#undef SCL
// error message strings
static const char exception_illegal_combination[] = "Illegal combination of command and addressing mode.";
static const char exception_highbyte_zero[]= "Using oversized addressing mode.";
static const char exception_too_far[] = "Target out of range.";
// Variables
static struct dynabuf_t *mnemo_dyna_buf; // dynamic buffer for mnemonics
// predefined stuff
static struct node_t *mnemo_6502_tree = NULL; // holds 6502 mnemonics
static struct node_t *mnemo_6510_tree = NULL; // holds 6510 extensions
static struct node_t *mnemo_65c02_tree = NULL; // holds 65c02 extensions
//static node_t *mnemo_Rockwell65c02_tree = NULL; // Rockwell
static struct node_t *mnemo_WDC65c02_tree = NULL; // WDC's "stp"/"wai"
static struct node_t *mnemo_65816_tree = NULL; // holds 65816 extensions
// Command's code and group values are stored together in a single integer.
// To extract the code, use "& CODEMASK".
// To extract the group, use ">> GROUPSHIFT"
#define CODEMASK 0xff // opcode or table index
#define FLAGSMASK 0x300 // flags concerning immediate addressing:
#define IMM_ACCU 0x100 // ...depends on accumulator length
#define IMM_IDX 0x200 // ...depends on index register length
#define GROUPSHIFT 10 // shift right by this to extract group
#define MERGE(g, v) ((g << GROUPSHIFT) | v)
static struct node_t mnemos_6502[] = {
PREDEFNODE("ora", MERGE(GROUP_ACCU, IDX_ORA | IMM_ACCU)),
PREDEFNODE(s_and, MERGE(GROUP_ACCU, IDX_AND | IMM_ACCU)),
PREDEFNODE(s_eor, MERGE(GROUP_ACCU, IDX_EOR | IMM_ACCU)),
PREDEFNODE("adc", MERGE(GROUP_ACCU, IDX_ADC | IMM_ACCU)),
PREDEFNODE("sta", MERGE(GROUP_ACCU, IDX_STA)),
PREDEFNODE("lda", MERGE(GROUP_ACCU, IDX_LDA | IMM_ACCU)),
PREDEFNODE("cmp", MERGE(GROUP_ACCU, IDX_CMP | IMM_ACCU)),
PREDEFNODE("sbc", MERGE(GROUP_ACCU, IDX_SBC | IMM_ACCU)),
PREDEFNODE("bit", MERGE(GROUP_MISC, IDX_BIT | IMM_ACCU)),
PREDEFNODE(s_asl, MERGE(GROUP_MISC, IDX_ASL)),
PREDEFNODE("rol", MERGE(GROUP_MISC, IDX_ROL)),
PREDEFNODE(s_lsr, MERGE(GROUP_MISC, IDX_LSR)),
PREDEFNODE(s_ror, MERGE(GROUP_MISC, IDX_ROR)),
PREDEFNODE("sty", MERGE(GROUP_MISC, IDX_STY)),
PREDEFNODE("stx", MERGE(GROUP_MISC, IDX_STX)),
PREDEFNODE("ldy", MERGE(GROUP_MISC, IDX_LDY | IMM_IDX)),
PREDEFNODE("ldx", MERGE(GROUP_MISC, IDX_LDX | IMM_IDX)),
PREDEFNODE("cpy", MERGE(GROUP_MISC, IDX_CPY | IMM_IDX)),
PREDEFNODE("cpx", MERGE(GROUP_MISC, IDX_CPX | IMM_IDX)),
PREDEFNODE("dec", MERGE(GROUP_MISC, IDX_DEC)),
PREDEFNODE("inc", MERGE(GROUP_MISC, IDX_INC)),
PREDEFNODE("bpl", MERGE(GROUP_RELATIVE8, 16)),
PREDEFNODE("bmi", MERGE(GROUP_RELATIVE8, 48)),
PREDEFNODE("bvc", MERGE(GROUP_RELATIVE8, 80)),
PREDEFNODE("bvs", MERGE(GROUP_RELATIVE8, 112)),
PREDEFNODE("bcc", MERGE(GROUP_RELATIVE8, 144)),
PREDEFNODE("bcs", MERGE(GROUP_RELATIVE8, 176)),
PREDEFNODE("bne", MERGE(GROUP_RELATIVE8, 208)),
PREDEFNODE("beq", MERGE(GROUP_RELATIVE8, 240)),
PREDEFNODE("jmp", MERGE(GROUP_ALLJUMPS, IDX_JMP)),
PREDEFNODE("jsr", MERGE(GROUP_ALLJUMPS, IDX_JSR)),
PREDEFNODE("brk", MERGE(GROUP_IMPLIEDONLY, 0)),
PREDEFNODE("php", MERGE(GROUP_IMPLIEDONLY, 8)),
PREDEFNODE("clc", MERGE(GROUP_IMPLIEDONLY, 24)),
PREDEFNODE("plp", MERGE(GROUP_IMPLIEDONLY, 40)),
PREDEFNODE("sec", MERGE(GROUP_IMPLIEDONLY, 56)),
PREDEFNODE("rti", MERGE(GROUP_IMPLIEDONLY, 64)),
PREDEFNODE("pha", MERGE(GROUP_IMPLIEDONLY, 72)),
PREDEFNODE("cli", MERGE(GROUP_IMPLIEDONLY, 88)),
PREDEFNODE("rts", MERGE(GROUP_IMPLIEDONLY, 96)),
PREDEFNODE("pla", MERGE(GROUP_IMPLIEDONLY, 104)),
PREDEFNODE("sei", MERGE(GROUP_IMPLIEDONLY, 120)),
PREDEFNODE("dey", MERGE(GROUP_IMPLIEDONLY, 136)),
PREDEFNODE("txa", MERGE(GROUP_IMPLIEDONLY, 138)),
PREDEFNODE("tya", MERGE(GROUP_IMPLIEDONLY, 152)),
PREDEFNODE("txs", MERGE(GROUP_IMPLIEDONLY, 154)),
PREDEFNODE("tay", MERGE(GROUP_IMPLIEDONLY, 168)),
PREDEFNODE("tax", MERGE(GROUP_IMPLIEDONLY, 170)),
PREDEFNODE("clv", MERGE(GROUP_IMPLIEDONLY, 184)),
PREDEFNODE("tsx", MERGE(GROUP_IMPLIEDONLY, 186)),
PREDEFNODE("iny", MERGE(GROUP_IMPLIEDONLY, 200)),
PREDEFNODE("dex", MERGE(GROUP_IMPLIEDONLY, 202)),
PREDEFNODE("cld", MERGE(GROUP_IMPLIEDONLY, 216)),
PREDEFNODE("inx", MERGE(GROUP_IMPLIEDONLY, 232)),
PREDEFNODE("nop", MERGE(GROUP_IMPLIEDONLY, 234)),
PREDEFLAST("sed", MERGE(GROUP_IMPLIEDONLY, 248)),
// ^^^^ this marks the last element
};
static struct node_t mnemos_6510[] = {
PREDEFNODE("slo", MERGE(GROUP_ACCU, IDX_SLO)), // ASL + ORA (aka ASO)
PREDEFNODE("rla", MERGE(GROUP_ACCU, IDX_RLA)), // ROL + AND
PREDEFNODE("sre", MERGE(GROUP_ACCU, IDX_SRE)), // LSR + EOR (aka LSE)
PREDEFNODE("rra", MERGE(GROUP_ACCU, IDX_RRA)), // ROR + ADC
PREDEFNODE("sax", MERGE(GROUP_ACCU, IDX_SAX)), // STX + STA (aka AAX aka AXS)
PREDEFNODE("lax", MERGE(GROUP_ACCU, IDX_LAX)), // LDX + LDA
PREDEFNODE("dcp", MERGE(GROUP_ACCU, IDX_DCP)), // DEC + CMP (aka DCM)
PREDEFNODE("isc", MERGE(GROUP_ACCU, IDX_ISC)), // INC + SBC (aka ISB aka INS)
PREDEFNODE("anc", MERGE(GROUP_MISC, IDX_ANC)), // ROL + AND, ASL + ORA (aka AAC)
PREDEFNODE(s_asr, MERGE(GROUP_MISC, IDX_ASR)), // LSR + EOR (aka ALR)
PREDEFNODE("arr", MERGE(GROUP_MISC, IDX_ARR)), // ROR + ADC
PREDEFNODE("sbx", MERGE(GROUP_MISC, IDX_SBX)), // DEX + CMP (aka AXS aka SAX)
PREDEFNODE("dop", MERGE(GROUP_MISC, IDX_DOP)), // skip next byte
PREDEFNODE("top", MERGE(GROUP_MISC, IDX_TOP)), // skip next two bytes
PREDEFLAST("jam", MERGE(GROUP_MISC, IDX_JAM)), // jam/crash/kill/halt-and-catch-fire
// ^^^^ this marks the last element
};
static struct node_t mnemos_65c02[] = {
PREDEFNODE("ora", MERGE(GROUP_ACCU, IDXcORA | IMM_ACCU)),
PREDEFNODE(s_and, MERGE(GROUP_ACCU, IDXcAND | IMM_ACCU)),
PREDEFNODE(s_eor, MERGE(GROUP_ACCU, IDXcEOR | IMM_ACCU)),
PREDEFNODE("adc", MERGE(GROUP_ACCU, IDXcADC | IMM_ACCU)),
PREDEFNODE("sta", MERGE(GROUP_ACCU, IDXcSTA)),
PREDEFNODE("lda", MERGE(GROUP_ACCU, IDXcLDA | IMM_ACCU)),
PREDEFNODE("cmp", MERGE(GROUP_ACCU, IDXcCMP | IMM_ACCU)),
PREDEFNODE("sbc", MERGE(GROUP_ACCU, IDXcSBC | IMM_ACCU)),
PREDEFNODE("jmp", MERGE(GROUP_ALLJUMPS, IDXcJMP)),
PREDEFNODE("bit", MERGE(GROUP_MISC, IDXcBIT | IMM_ACCU)),
PREDEFNODE("dec", MERGE(GROUP_MISC, IDXcDEC)),
PREDEFNODE("inc", MERGE(GROUP_MISC, IDXcINC)),
PREDEFNODE("bra", MERGE(GROUP_RELATIVE8, 128)),
PREDEFNODE("phy", MERGE(GROUP_IMPLIEDONLY, 90)),
PREDEFNODE("ply", MERGE(GROUP_IMPLIEDONLY, 122)),
PREDEFNODE("phx", MERGE(GROUP_IMPLIEDONLY, 218)),
PREDEFNODE("plx", MERGE(GROUP_IMPLIEDONLY, 250)),
PREDEFNODE("tsb", MERGE(GROUP_MISC, IDXcTSB)),
PREDEFNODE("trb", MERGE(GROUP_MISC, IDXcTRB)),
PREDEFLAST("stz", MERGE(GROUP_MISC, IDXcSTZ)),
// ^^^^ this marks the last element
};
//static struct node_t mnemos_Rockwell65c02[] = {
// PREDEFNODE("rmb0", MERGE(G_ , 0x07)),
// PREDEFNODE("rmb1", MERGE(G_ , 0x17)),
// PREDEFNODE("rmb2", MERGE(G_ , 0x27)),
// PREDEFNODE("rmb3", MERGE(G_ , 0x37)),
// PREDEFNODE("rmb4", MERGE(G_ , 0x47)),
// PREDEFNODE("rmb5", MERGE(G_ , 0x57)),
// PREDEFNODE("rmb6", MERGE(G_ , 0x67)),
// PREDEFNODE("rmb7", MERGE(G_ , 0x77)),
// PREDEFNODE("smb0", MERGE(G_ , 0x87)),
// PREDEFNODE("smb1", MERGE(G_ , 0x97)),
// PREDEFNODE("smb2", MERGE(G_ , 0xa7)),
// PREDEFNODE("smb3", MERGE(G_ , 0xb7)),
// PREDEFNODE("smb4", MERGE(G_ , 0xc7)),
// PREDEFNODE("smb5", MERGE(G_ , 0xd7)),
// PREDEFNODE("smb6", MERGE(G_ , 0xe7)),
// PREDEFNODE("smb7", MERGE(G_ , 0xf7)),
// PREDEFNODE("bbr0", MERGE(G_ , 0x0f)),
// PREDEFNODE("bbr1", MERGE(G_ , 0x1f)),
// PREDEFNODE("bbr2", MERGE(G_ , 0x2f)),
// PREDEFNODE("bbr3", MERGE(G_ , 0x3f)),
// PREDEFNODE("bbr4", MERGE(G_ , 0x4f)),
// PREDEFNODE("bbr5", MERGE(G_ , 0x5f)),
// PREDEFNODE("bbr6", MERGE(G_ , 0x6f)),
// PREDEFNODE("bbr7", MERGE(G_ , 0x7f)),
// PREDEFNODE("bbs0", MERGE(G_ , 0x8f)),
// PREDEFNODE("bbs1", MERGE(G_ , 0x9f)),
// PREDEFNODE("bbs2", MERGE(G_ , 0xaf)),
// PREDEFNODE("bbs3", MERGE(G_ , 0xbf)),
// PREDEFNODE("bbs4", MERGE(G_ , 0xcf)),
// PREDEFNODE("bbs5", MERGE(G_ , 0xdf)),
// PREDEFNODE("bbs6", MERGE(G_ , 0xef)),
// PREDEFLAST("bbs7", MERGE(G_ , 0xff)),
// // ^^^^ this marks the last element
//};
static struct node_t mnemos_WDC65c02[] = {
PREDEFNODE("stp", MERGE(GROUP_IMPLIEDONLY, 219)),
PREDEFLAST("wai", MERGE(GROUP_IMPLIEDONLY, 203)),
// ^^^^ this marks the last element
};
static struct node_t mnemos_65816[] = {
PREDEFNODE("ora", MERGE(GROUP_ACCU, IDX816ORA | IMM_ACCU)),
PREDEFNODE(s_and, MERGE(GROUP_ACCU, IDX816AND | IMM_ACCU)),
PREDEFNODE(s_eor, MERGE(GROUP_ACCU, IDX816EOR | IMM_ACCU)),
PREDEFNODE("adc", MERGE(GROUP_ACCU, IDX816ADC | IMM_ACCU)),
PREDEFNODE("sta", MERGE(GROUP_ACCU, IDX816STA)),
PREDEFNODE("lda", MERGE(GROUP_ACCU, IDX816LDA | IMM_ACCU)),
PREDEFNODE("cmp", MERGE(GROUP_ACCU, IDX816CMP | IMM_ACCU)),
PREDEFNODE("sbc", MERGE(GROUP_ACCU, IDX816SBC | IMM_ACCU)),
PREDEFNODE("pei", MERGE(GROUP_ACCU, IDX816PEI)),
PREDEFNODE("jmp", MERGE(GROUP_ALLJUMPS, IDX816JMP)),
PREDEFNODE("jsr", MERGE(GROUP_ALLJUMPS, IDX816JSR)),
PREDEFNODE("jml", MERGE(GROUP_ALLJUMPS, IDX816JML)),
PREDEFNODE("jsl", MERGE(GROUP_ALLJUMPS, IDX816JSL)),
PREDEFNODE("mvp", MERGE(GROUP_BOTHMOVES, 0x44)),
PREDEFNODE("mvn", MERGE(GROUP_BOTHMOVES, 0x54)),
PREDEFNODE("per", MERGE(GROUP_RELATIVE16, 98)),
PREDEFNODE(s_brl, MERGE(GROUP_RELATIVE16, 130)),
PREDEFNODE("cop", MERGE(GROUP_MISC, IDX816COP)),
PREDEFNODE("rep", MERGE(GROUP_MISC, IDX816REP)),
PREDEFNODE("sep", MERGE(GROUP_MISC, IDX816SEP)),
PREDEFNODE("pea", MERGE(GROUP_MISC, IDX816PEA)),
PREDEFNODE("phd", MERGE(GROUP_IMPLIEDONLY, 11)),
PREDEFNODE("tcs", MERGE(GROUP_IMPLIEDONLY, 27)),
PREDEFNODE("pld", MERGE(GROUP_IMPLIEDONLY, 43)),
PREDEFNODE("tsc", MERGE(GROUP_IMPLIEDONLY, 59)),
PREDEFNODE("wdm", MERGE(GROUP_IMPLIEDONLY, 66)),
PREDEFNODE("phk", MERGE(GROUP_IMPLIEDONLY, 75)),
PREDEFNODE("tcd", MERGE(GROUP_IMPLIEDONLY, 91)),
PREDEFNODE("rtl", MERGE(GROUP_IMPLIEDONLY, 107)),
PREDEFNODE("tdc", MERGE(GROUP_IMPLIEDONLY, 123)),
PREDEFNODE("phb", MERGE(GROUP_IMPLIEDONLY, 139)),
PREDEFNODE("txy", MERGE(GROUP_IMPLIEDONLY, 155)),
PREDEFNODE("plb", MERGE(GROUP_IMPLIEDONLY, 171)),
PREDEFNODE("tyx", MERGE(GROUP_IMPLIEDONLY, 187)),
PREDEFNODE("xba", MERGE(GROUP_IMPLIEDONLY, 235)),
PREDEFLAST("xce", MERGE(GROUP_IMPLIEDONLY, 251)),
// ^^^^ this marks the last element
};
// Functions
// create dynamic buffer, build keyword trees
void Mnemo_init(void)
{
mnemo_dyna_buf = DynaBuf_create(MNEMO_DYNABUF_INITIALSIZE);
Tree_add_table(&mnemo_6502_tree, mnemos_6502);
Tree_add_table(&mnemo_6510_tree, mnemos_6510);
Tree_add_table(&mnemo_65c02_tree, mnemos_65c02);
// Tree_add_table(&mnemo_Rockwell65c02_tree, mnemos_Rockwell65c02);
Tree_add_table(&mnemo_WDC65c02_tree, mnemos_WDC65c02);
Tree_add_table(&mnemo_65816_tree, mnemos_65816);
}
// Address mode parsing
// Utility function for parsing indices.
static int get_index(int next)
{
int addressing_mode = HAM__;
if (next)
GetByte();
if (!Input_accept_comma())
return addressing_mode;
switch (GotByte) {
case 's':
case 'S':
addressing_mode = HAM_S;
break;
case 'x':
case 'X':
addressing_mode = HAM_X;
break;
case 'y':
case 'Y':
addressing_mode = HAM_Y;
break;
default:
Throw_error(exception_syntax);
}
if (addressing_mode != HAM__)
NEXTANDSKIPSPACE();
return addressing_mode;
}
// This function stores the command's argument in the given result_int_t
// structure (using the valueparser). The addressing mode is returned.
static int get_argument(struct result_int_t *result)
{
int open_paren,
addressing_mode = HAM_ABS;
SKIPSPACE();
switch (GotByte) {
case '[':
GetByte(); // proceed with next char
ALU_int_result(result);
if (GotByte == ']')
addressing_mode |= HAM_LIND | get_index(TRUE);
else
Throw_error(exception_syntax);
break;
case '#':
GetByte(); // proceed with next char
addressing_mode |= HAM_IMM;
ALU_int_result(result);
break;
default:
// liberal, to allow for "(...,"
open_paren = ALU_liberal_int(result);
// check for implied addressing
if ((result->flags & MVALUE_EXISTS) == 0)
addressing_mode |= HAM_IMP;
// check for indirect addressing
if (result->flags & MVALUE_INDIRECT)
addressing_mode |= HAM_IND;
// check for internal index (before closing parenthesis)
if (open_paren) {
// in case there are still open parentheses,
// read internal index
addressing_mode |= (get_index(FALSE) << 2);
if (GotByte == ')')
GetByte(); // go on with next char
else
Throw_error(exception_syntax);
}
// check for external index (after closing parenthesis)
addressing_mode |= get_index(FALSE);
}
// ensure end of line
Input_ensure_EOS();
//printf("AM: %x\n", addressing_mode);
return addressing_mode;
}
// Helper function for calc_arg_size()
// Only call with "size_bit = MVALUE_FORCE16" or "size_bit = MVALUE_FORCE24"
static int check_oversize(int size_bit, struct result_int_t *argument)
{
// only check if value is *defined*
if ((argument->flags & MVALUE_DEFINED) == 0)
return size_bit; // pass on result
// value is defined, so check
if (size_bit == MVALUE_FORCE16) {
// check 16-bit argument for high byte zero
if ((argument->intval <= 255) && (argument->intval >= -128))
Throw_warning(exception_highbyte_zero);
} else {
// check 24-bit argument for bank byte zero
if ((argument->intval <= 65535) && (argument->intval >= -32768))
Throw_warning(exception_highbyte_zero);
}
return size_bit; // pass on result
}
// Utility function for comparing force bits, argument value, argument size,
// "unsure"-flag and possible addressing modes. Returns force bit matching
// number of parameter bytes to send. If it returns zero, an error occurred
// (and has already been delivered).
// force_bit none, 8b, 16b, 24b
// argument value and flags of parameter
// addressing_modes adressing modes (8b, 16b, 24b or any combination)
// Return value = force bit for number of parameter bytes to send (0 = error)
static int calc_arg_size(int force_bit, struct result_int_t *argument, int addressing_modes)
{
// if there are no possible addressing modes, complain
if (addressing_modes == MAYBE______) {
Throw_error(exception_illegal_combination);
return 0;
}
// if command has force bit, act upon it
if (force_bit) {
// if command allows this force bit, return it
if (addressing_modes & force_bit)
return force_bit;
// if not, complain
Throw_error("Illegal combination of command and postfix.");
return 0;
}
// Command has no force bit. Check whether value has one
// if value has force bit, act upon it
if (argument->flags & MVALUE_FORCEBITS) {
// Value has force bit set, so return this or bigger size
if (MVALUE_FORCE08 & addressing_modes & argument->flags)
return MVALUE_FORCE08;
if (MVALUE_FORCE16 & addressing_modes & argument->flags)
return MVALUE_FORCE16;
if (MVALUE_FORCE24 & addressing_modes)
return MVALUE_FORCE24;
Throw_error(exception_number_out_of_range); // else error
return 0;
}
// Value has no force bit. Check whether there's only one addr mode
// if only one addressing mode, use that
if ((addressing_modes == MVALUE_FORCE08)
|| (addressing_modes == MVALUE_FORCE16)
|| (addressing_modes == MVALUE_FORCE24))
return addressing_modes; // There's only one, so use it
// There's more than one addressing mode. Check whether value is sure
// if value is unsure, use default size
if (argument->flags & MVALUE_UNSURE) {
// if there is an 8-bit addressing mode *and* the value
// is sure to fit into 8 bits, use the 8-bit mode
if ((addressing_modes & MVALUE_FORCE08) && (argument->flags & MVALUE_ISBYTE))
return MVALUE_FORCE08;
// if there is a 16-bit addressing, use that
// call helper function for "oversized addr mode" warning
if (MVALUE_FORCE16 & addressing_modes)
return check_oversize(MVALUE_FORCE16, argument);
// if there is a 24-bit addressing, use that
// call helper function for "oversized addr mode" warning
if (MVALUE_FORCE24 & addressing_modes)
return check_oversize(MVALUE_FORCE24, argument);
// otherwise, use 8-bit-addressing, which will raise an
// error later on if the value won't fit
return MVALUE_FORCE08;
}
// Value is sure, so use its own size
// if value is negative, size cannot be chosen. Complain!
if (argument->intval < 0) {
Throw_error("Negative value - cannot choose addressing mode.");
return 0;
}
// Value is positive or zero. Check size ranges
// if there is an 8-bit addressing mode and value fits, use 8 bits
if ((addressing_modes & MVALUE_FORCE08) && (argument->intval < 256))
return MVALUE_FORCE08;
// if there is a 16-bit addressing mode and value fits, use 16 bits
if ((addressing_modes & MVALUE_FORCE16) && (argument->intval < 65536))
return MVALUE_FORCE16;
// if there is a 24-bit addressing mode, use that. In case the
// value doesn't fit, the output function will do the complaining.
if (addressing_modes & MVALUE_FORCE24)
return MVALUE_FORCE24;
// Value is too big for all possible addressing modes
Throw_error(exception_number_out_of_range);
return 0;
}
// Mnemonics using only implied addressing.
static void group_only_implied_addressing(int opcode)
{
Output_byte(opcode);
Input_ensure_EOS();
}
// Mnemonics using only 8bit relative addressing (short branch instructions).
static void group_only_relative8_addressing(int opcode)
{
struct result_int_t result;
ALU_int_result(&result);
if (result.flags & MVALUE_DEFINED) {
result.intval -= (CPU_pc.intval + 2);
if ((result.intval > 127) || (result.intval < -128))
Throw_error(exception_too_far);
}
Output_byte(opcode);
// this fn has its own range check (see above).
// No reason to irritate the user with another error message,
// so use Output_byte() instead of Output_8b()
//Output_8b(result.value);
Output_byte(result.intval);
Input_ensure_EOS();
}
// Mnemonics using only 16bit relative addressing (BRL and PER).
static void group_only_relative16_addressing(int opcode)
{
struct result_int_t result;
ALU_int_result(&result);
if (result.flags & MVALUE_DEFINED) {
result.intval -= (CPU_pc.intval + 3);
if ((result.intval > 32767) || (result.intval < -32768))
Throw_error(exception_too_far);
}
Output_byte(opcode);
// this fn has its own range check (see above).
// No reason to irritate the user with another error message,
// so use Output_byte() instead of Output_16b()
//Output_16b(result.value);
Output_byte(result.intval);
Output_byte(result.intval >> 8);
Input_ensure_EOS();
}
// set addressing mode bits depending on which opcodes exist, then calculate
// argument size and output both opcode and argument
static void make_command(int force_bit, struct result_int_t *result, unsigned long opcodes)
{
int addressing_modes = MAYBE______;
if (opcodes & 0x0000ff)
addressing_modes |= MAYBE_1____;
if (opcodes & 0x00ff00)
addressing_modes |= MAYBE___2__;
if (opcodes & 0xff0000)
addressing_modes |= MAYBE_____3;
switch (calc_arg_size(force_bit, result, addressing_modes)) {
case MVALUE_FORCE08:
Output_byte(opcodes & 255);
Output_8b(result->intval);
break;
case MVALUE_FORCE16:
Output_byte((opcodes >> 8) & 255);
Output_16b(result->intval);
break;
case MVALUE_FORCE24:
Output_byte((opcodes >> 16) & 255);
Output_24b(result->intval);
}
}
// check whether 16bit immediate addressing is allowed. If not, return given
// opcode. If it is allowed, set force bits according to CPU register length
// and return given opcode for both 8- and 16-bit mode.
static unsigned int imm_ops(int *force_bit, unsigned char opcode, int imm_flag)
{
// if the CPU does not allow 16bit immediate addressing (or if the
// opcode does not allow it), return immediately.
if (((CPU_now->flags & CPUFLAG_SUPPORTSLONGREGS) == 0) || (imm_flag == 0))
return opcode;
// check force bits (if no force bits given, use relevant flag)
if (*force_bit == 0)
*force_bit = ((imm_flag & IMM_ACCU) ?
CPU_now->a_is_long :
CPU_now->xy_are_long) ?
MVALUE_FORCE16 :
MVALUE_FORCE08;
// return identical opcodes for 8bit and 16bit args!
return (((unsigned int) opcode) << 8) | opcode;
}
// The main accumulator stuff (ADC, AND, CMP, EOR, LDA, ORA, SBC, STA)
// plus PEI.
static void group_main(int index, int imm_flag)
{
unsigned long imm_opcodes;
struct result_int_t result;
int force_bit = Input_get_force_bit(); // skips spaces after
switch (get_argument(&result)) {
case HAM_IMM: // #$ff or #$ffff (depending on accu length)
imm_opcodes = imm_ops(&force_bit, accu_imm[index], imm_flag);
// CAUTION - do not incorporate the line above into the line
// below - "force_bit" might be undefined (depends on compiler).
make_command(force_bit, &result, imm_opcodes);
break;
case HAM_ABS: // $ff, $ffff, $ffffff
make_command(force_bit, &result, accu_abs[index]);
break;
case HAM_ABSX: // $ff,x, $ffff,x, $ffffff,x
make_command(force_bit, &result, accu_xabs[index]);
break;
case HAM_ABSY: // $ffff,y (in theory, "$ff,y" as well)
make_command(force_bit, &result, accu_yabs[index]);
break;
case HAM_ABSS: // $ff,s
make_command(force_bit, &result, accu_sabs8[index]);
break;
case HAM_XIND: // ($ff,x)
make_command(force_bit, &result, accu_xind8[index]);
break;
case HAM_IND: // ($ff)
make_command(force_bit, &result, accu_ind8[index]);
break;
case HAM_INDY: // ($ff),y
make_command(force_bit, &result, accu_indy8[index]);
break;
case HAM_LIND: // [$ff]
make_command(force_bit, &result, accu_lind8[index]);
break;
case HAM_LINDY: // [$ff],y
make_command(force_bit, &result, accu_lindy8[index]);
break;
case HAM_SINDY: // ($ff,s),y
make_command(force_bit, &result, accu_sindy8[index]);
break;
default: // other combinations are illegal
Throw_error(exception_illegal_combination);
}
}
// Various mnemonics with different addressing modes.
static void group_misc(int index, int imm_flag)
{
unsigned long imm_opcodes;
struct result_int_t result;
int force_bit = Input_get_force_bit(); // skips spaces after
switch (get_argument(&result)) {
case HAM_IMP: // implied addressing
if (misc_impl[index])
Output_byte(misc_impl[index]);
else
Throw_error(exception_illegal_combination);
break;
case HAM_IMM: // #$ff or #$ffff (depending on index register length)
imm_opcodes = imm_ops(&force_bit, misc_imm[index], imm_flag);
// CAUTION - do not incorporate the line above into the line
// below - "force_bit" might be undefined (depends on compiler).
make_command(force_bit, &result, imm_opcodes);
break;
case HAM_ABS: // $ff or $ffff
make_command(force_bit, &result, misc_abs[index]);
break;
case HAM_ABSX: // $ff,x or $ffff,x
make_command(force_bit, &result, misc_xabs[index]);
break;
case HAM_ABSY: // $ff,y or $ffff,y
make_command(force_bit, &result, misc_yabs[index]);
break;
default: // other combinations are illegal
Throw_error(exception_illegal_combination);
}
}
// Mnemonics using "8bit, 8bit" addressing. Only applies to "MVN" and "MVP".
static void group_move(int opcode)
{
intval_t source,
target;
// assembler syntax: "opcode source, target"
source = ALU_any_int();
if (Input_accept_comma()) {
target = ALU_any_int(); // machine language:
Output_byte(opcode); // opcode
Output_8b(target); // target
Output_8b(source); // source
Input_ensure_EOS();
} else {
Throw_error(exception_syntax);
}
}
// The jump instructions.
static void group_jump(int index)
{
struct result_int_t result;
int force_bit = Input_get_force_bit(); // skips spaces after
switch (get_argument(&result)) {
case HAM_ABS: // absolute16 or absolute24
make_command(force_bit, &result, jump_abs[index]);
break;
case HAM_IND: // ($ffff)
make_command(force_bit, &result, jump_ind[index]);
// check whether to warn about 6502's JMP() bug
if (((result.intval & 0xff) == 0xff)
&& (result.flags & MVALUE_DEFINED)
&& (CPU_now->flags & CPUFLAG_INDIRECTJMPBUGGY))
Throw_warning("Assembling buggy JMP($xxff) instruction");
break;
case HAM_XIND: // ($ffff,x)
make_command(force_bit, &result, jump_xind[index]);
break;
case HAM_LIND: // [$ffff]
make_command(force_bit, &result, jump_lind[index]);
break;
default: // other combinations are illegal
Throw_error(exception_illegal_combination);
}
}
// Work function
static int check_mnemo_tree(struct node_t *tree, struct dynabuf_t *dyna_buf)
{
void *node_body;
int code,
imm_flag; // flag for immediate addressing
// search for tree item
if (!Tree_easy_scan(tree, &node_body, dyna_buf))
return FALSE;
code = ((int) node_body) & CODEMASK; // get opcode or table index
imm_flag = ((int) node_body) & FLAGSMASK; // get flag
switch (((long) node_body) >> GROUPSHIFT) {
case GROUP_ACCU: // main accumulator stuff
group_main(code, imm_flag);
break;
case GROUP_MISC: // misc
group_misc(code, imm_flag);
break;
case GROUP_ALLJUMPS: // the jump instructions
group_jump(code);
break;
case GROUP_IMPLIEDONLY: // mnemonics with only implied addressing
group_only_implied_addressing(code);
break;
case GROUP_RELATIVE8: // short relative
group_only_relative8_addressing(code);
break;
case GROUP_RELATIVE16: // long relative
group_only_relative16_addressing(code);
break;
case GROUP_BOTHMOVES: // "mvp" and "mvn"
group_move(code);
break;
default: // others indicate bugs
Bug_found("IllegalGroupIndex", code);
}
return TRUE;
}
// Check whether mnemonic in GlobalDynaBuf is supported by 6502 cpu.
int keyword_is_6502mnemo(int length)
{
if (length != 3)
return FALSE;
// make lower case version of mnemonic in local dynamic buffer
DynaBuf_to_lower(mnemo_dyna_buf, GlobalDynaBuf);
return check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf) ? TRUE : FALSE;
}
// Check whether mnemonic in GlobalDynaBuf is supported by 6510 cpu.
int keyword_is_6510mnemo(int length)
{
if (length != 3)
return FALSE;
// make lower case version of mnemonic in local dynamic buffer
DynaBuf_to_lower(mnemo_dyna_buf, GlobalDynaBuf);
// first check extensions...
if (check_mnemo_tree(mnemo_6510_tree, mnemo_dyna_buf))
return TRUE;
// ...then check original opcodes
return check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf) ? TRUE : FALSE;
}
// Check whether mnemonic in GlobalDynaBuf is supported by 65c02 cpu.
int keyword_is_65c02mnemo(int length)
{
if (length != 3)
return FALSE;
// make lower case version of mnemonic in local dynamic buffer
DynaBuf_to_lower(mnemo_dyna_buf, GlobalDynaBuf);
// first check extensions...
if (check_mnemo_tree(mnemo_65c02_tree, mnemo_dyna_buf))
return TRUE;
// ...then check original opcodes
return check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf) ? TRUE : FALSE;
}
//// Check whether mnemonic in GlobalDynaBuf is supported by Rockwell 65c02 cpu.
//int keyword_is_Rockwell65c02mnemo(int length)
//{
// if ((length != 3) && (length != 4))
// return FALSE;
//
// // make lower case version of mnemonic in local dynamic buffer
// DynaBuf_to_lower(mnemo_dyna_buf, GlobalDynaBuf);
// // first check 65c02 extensions...
// if (check_mnemo_tree(mnemo_65c02_tree, mnemo_dyna_buf))
// return TRUE;
//
// // ...then check original opcodes...
// if (check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf))
// return TRUE;
//
// // ...then check Rockwell/WDC extensions (rmb, smb, bbr, bbs)
// return check_mnemo_tree(mnemo_Rockwell65c02_tree, mnemo_dyna_buf) ? TRUE : FALSE;
//}
//// Check whether mnemonic in GlobalDynaBuf is supported by WDC 65c02 cpu.
//int keyword_is_WDC65c02mnemo(int length)
//{
// if ((length != 3) && (length != 4))
// return FALSE;
//
// // make lower case version of mnemonic in local dynamic buffer
// DynaBuf_to_lower(mnemo_dyna_buf, GlobalDynaBuf);
// // first check 65c02 extensions...
// if (check_mnemo_tree(mnemo_65c02_tree, mnemo_dyna_buf))
// return TRUE;
//
// // ...then check original opcodes...
// if (check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf))
// return TRUE;
//
// // ...then check Rockwell/WDC extensions (rmb, smb, bbr, bbs)...
// if (check_mnemo_tree(mnemo_Rockwell65c02_tree, mnemo_dyna_buf))
// return TRUE;
//
// // ...then check WDC extensions (only two mnemonics, so do last)
// return check_mnemo_tree(mnemo_WDC65c02_tree, mnemo_dyna_buf) ? TRUE : FALSE;
//}
// Check whether mnemonic in GlobalDynaBuf is supported by 65816 cpu.
int keyword_is_65816mnemo(int length)
{
if (length != 3)
return FALSE;
// make lower case version of mnemonic in local dynamic buffer
DynaBuf_to_lower(mnemo_dyna_buf, GlobalDynaBuf);
// first check "new" extensions...
if (check_mnemo_tree(mnemo_65816_tree, mnemo_dyna_buf))
return TRUE;
// ...then "old" extensions...
if (check_mnemo_tree(mnemo_65c02_tree, mnemo_dyna_buf))
return TRUE;
// ...then check original opcodes...
if (check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf))
return TRUE;
// ...then check WDC extensions "stp" and "wai"
return check_mnemo_tree(mnemo_WDC65c02_tree, mnemo_dyna_buf) ? TRUE : FALSE;
}

28
trunk/src/mnemo.h Normal file
View File

@ -0,0 +1,28 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Mnemonic definitions
#ifndef mnemo_H
#define mnemo_H
// Prototypes
// create dynamic buffer, build keyword trees
extern void Mnemo_init(void);
// Check whether mnemonic in GlobalDynaBuf is supported by 6502 cpu.
extern int keyword_is_6502mnemo(int length);
// Check whether mnemonic in GlobalDynaBuf is supported by 6510 cpu.
extern int keyword_is_6510mnemo(int length);
// Check whether mnemonic in GlobalDynaBuf is supported by 65c02 cpu.
extern int keyword_is_65c02mnemo(int length);
// Check whether mnemonic in GlobalDynaBuf is supported by Rockwell 65c02 cpu.
//extern int keyword_is_Rockwell65c02mnemo(int length);
// Check whether mnemonic in GlobalDynaBuf is supported by WDC 65c02 cpu.
//extern int keyword_is_WDC65c02mnemo(int length);
// Check whether mnemonic in GlobalDynaBuf is supported by 65816 cpu.
extern int keyword_is_65816mnemo(int length);
#endif

525
trunk/src/output.c Normal file
View File

@ -0,0 +1,525 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Output stuff
// 24 Nov 2007 Added possibility to suppress segment overlap warnings
// 25 Sep 2011 Fixed bug in !to (colons in filename could be interpreted as EOS)
#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"
// Structure for linked list of segment data
struct segment_t {
struct segment_t *next,
*prev;
intval_t start,
length;
};
// constants
#define OUTBUFFERSIZE 65536
// variables
// segment stuff
static struct segment_t segments_head; // head element of segment list
static intval_t segment_start; // start of current segment
static intval_t segment_max; // highest address segment may use
static int segment_flags; // "overlay" and "invisible" flags
// misc
static intval_t lowest_idx; // smallest address program uses
static intval_t highest_idx; // end address of program plus one
// output buffer stuff
static char *output_buffer = NULL; // to hold assembled code
static char *write_ptr = NULL; // points into output_buffer
intval_t write_idx; // index in output buffer
static int memory_initialised = FALSE;
// predefined stuff
static struct node_t *file_format_tree = NULL; // tree to hold output formats
// possible file formats
enum out_format_t {
OUTPUT_FORMAT_UNSPECIFIED, // default (uses "plain" actually)
OUTPUT_FORMAT_PLAIN,
OUTPUT_FORMAT_CBM, // default for "!to" pseudo opcode
};
static struct node_t file_formats[] = {
PREDEFNODE(s_cbm, OUTPUT_FORMAT_CBM),
// PREDEFNODE("o65", OUTPUT_FORMAT_O65),
PREDEFLAST("plain", OUTPUT_FORMAT_PLAIN),
// ^^^^ this marks the last element
};
// chosen file format
static enum out_format_t output_format = OUTPUT_FORMAT_UNSPECIFIED;
// predefined stuff
static struct node_t *segment_modifier_tree = NULL; // tree to hold segment modifiers
// segment modifiers
#define SEGMENT_FLAG_OVERLAY (1u << 0)
#define SEGMENT_FLAG_INVISIBLE (1u << 1)
static struct node_t segment_modifiers[] = {
PREDEFNODE("overlay", SEGMENT_FLAG_OVERLAY),
PREDEFLAST("invisible", SEGMENT_FLAG_INVISIBLE),
// ^^^^ this marks the last element
};
// fill output buffer with given byte value
static void fill_completely(char value)
{
memset(output_buffer, value, OUTBUFFERSIZE);
}
// set up new segment_max value according to the given address.
// just find the next segment start and subtract 1.
static void find_segment_max(intval_t new_pc)
{
struct segment_t *test_segment = segments_head.next;
// search for smallest segment start address that
// is larger than given address
// use list head as sentinel
segments_head.start = OUTBUFFERSIZE;
while (test_segment->start <= new_pc)
test_segment = test_segment->next;
segment_max = test_segment->start;
segment_max--; // last free address available
}
//
static void border_crossed(int current_offset)
{
if (current_offset >= OUTBUFFERSIZE)
Throw_serious_error("Produced too much code.");
if (pass_count == 0) {
Throw_warning("Segment reached another one, overwriting it.");
find_segment_max(current_offset + 1);
}
}
// send low byte to output buffer
void (*Output_byte)(intval_t byte);
static void real_output(intval_t byte); // fn for actual output
static void no_output(intval_t byte); // fn when output impossible
// set output pointer (negative values deactivate output)
static void set_mem_ptr(signed long index)
{
if (index < 0) {
Output_byte = no_output;
write_ptr = output_buffer;
write_idx = 0;
} else {
Output_byte = real_output;
write_ptr = output_buffer + index;
write_idx = index;
}
}
// send low byte to output buffer, automatically increasing program counter
static void real_output(intval_t byte)
{
if (write_idx > segment_max)
border_crossed(write_idx);
*write_ptr++ = byte & 0xff;
write_idx++;
CPU_2add++;
}
// fail to write to output buffer
static void no_output(intval_t byte)
{
Throw_error(exception_pc_undefined);
// set ptr to not complain again. as we have thrown an error, assembly
// fails, so don't care about actual value.
set_mem_ptr(512); // 512 to not garble zero page and stack. ;)
CPU_2add++;
}
// call this if really calling Output_byte would be a waste of time
void Output_fake(int size)
{
// check whether ptr undefined
if (Output_byte == no_output) {
no_output(0);
size--;
}
if (write_idx + size - 1 > segment_max)
border_crossed(write_idx + size - 1);
write_ptr += size;
write_idx += size;
CPU_2add += size;
}
// output 8-bit value with range check
void Output_8b(intval_t value)
{
if ((value <= 0xff) && (value >= -0x80))
Output_byte(value);
else
Throw_error(exception_number_out_of_range);
}
// output 16-bit value with range check
void Output_16b(intval_t value)
{
if ((value <= 0xffff) && (value >= -0x8000)) {
Output_byte(value);
Output_byte(value >> 8);
} else {
Throw_error(exception_number_out_of_range);
}
}
// output 24-bit value with range check
void Output_24b(intval_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 value (without range check)
void Output_32b(intval_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);
// }
}
// define default value for empty memory ("!initmem" pseudo opcode)
static enum eos_t PO_initmem(void)
{
intval_t content;
// ignore in all passes but in first
if (pass_count)
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
content = ALU_defined_int();
if ((content > 0xff) || (content < -0x80))
Throw_error(exception_number_out_of_range);
// init memory
fill_completely(content);
// enforce another pass
if (pass_undefined_count == 0)
pass_undefined_count = 1;
// FIXME - enforcing another pass is not needed if there hasn't been any
// output yet. But that's tricky to detect without too much overhead.
// The old solution was to add &&(lowest_idx < highest_idx) to "if" above
return ENSURE_EOS;
}
// try to set output format held in DynaBuf. Returns whether succeeded.
int 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)
{
// bugfix: first read filename, *then* check for first pass.
// if skipping right away, quoted colons might be misinterpreted as EOS
// FIXME - why not just fix the skipping code to handle quotes? :)
// "!sl" has been fixed as well
// read filename to global dynamic buffer
// if no file name given, exit (complaining will have been done)
if (Input_read_filename(FALSE))
return SKIP_REMAINDER;
// only act upon this pseudo opcode in first pass
if (pass_count)
return SKIP_REMAINDER;
// if output file already chosen, complain and exit
if (output_filename) {
Throw_warning("Output file already chosen.");
return SKIP_REMAINDER;
}
// get malloc'd copy of filename
output_filename = DynaBuf_get_copy(GlobalDynaBuf);
// 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
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 struct node_t pseudo_opcodes[] = {
PREDEFNODE("initmem", PO_initmem),
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);
}
// alloc and init mem buffer, register pseudo opcodes (done later)
void Output_init(signed long fill_value)
{
output_buffer = safe_malloc(OUTBUFFERSIZE);
write_ptr = output_buffer;
if (fill_value == MEMINIT_USE_DEFAULT)
fill_value = FILLVALUE_INITIAL;
else
memory_initialised = TRUE;
// init output buffer (fill memory with initial value)
fill_completely(fill_value & 0xff);
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
Tree_add_table(&segment_modifier_tree, segment_modifiers);
// init ring list of segments
segments_head.next = &segments_head;
segments_head.prev = &segments_head;
}
// dump memory buffer into output file
void Output_save_file(FILE *fd)
{
intval_t amount = highest_idx - lowest_idx;
if (Process_verbosity)
printf("Saving %ld ($%lx) bytes ($%lx - $%lx exclusive).\n",
amount, amount, lowest_idx, highest_idx);
// 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(lowest_idx & 255, fd);
putc(lowest_idx >> 8, fd);
}
// dump output buffer to file
fwrite(output_buffer + lowest_idx, sizeof(char), amount, fd);
}
// link segment data into segment ring
static void link_segment(intval_t start, intval_t length)
{
struct segment_t *new_segment,
*test_segment = segments_head.next;
// init new segment
new_segment = safe_malloc(sizeof(*new_segment));
new_segment->start = start;
new_segment->length = length;
// use ring head as sentinel
segments_head.start = start;
segments_head.length = length + 1;
// walk ring to find correct spot
while ((test_segment->start < new_segment->start) ||
((test_segment->start == new_segment->start) &&
(test_segment->length < new_segment->length)))
test_segment = test_segment->next;
// link into ring
new_segment->next = test_segment;
new_segment->prev = test_segment->prev;
new_segment->next->prev = new_segment;
new_segment->prev->next = new_segment;
}
// show start and end of current segment
// called whenever a new segment begins, and at end of pass.
void Output_end_segment(void)
{
intval_t amount;
if (CPU_uses_pseudo_pc())
Throw_first_pass_warning("Offset assembly still active at end of segment."); // FIXME - should be error!
if ((pass_count == 0) && !(segment_flags & SEGMENT_FLAG_INVISIBLE)) {
amount = write_idx - segment_start;
link_segment(segment_start, amount);
if (Process_verbosity > 1)
printf(
"Segment size is %ld ($%lx) bytes ($%lx - $%lx exclusive).\n",
amount, amount, segment_start, write_idx);
}
// FIXME - this was in real_output():
// check for new max address (FIXME - move to close_segment?)
if (write_idx > highest_idx)
highest_idx = write_idx;
}
// check whether given PC is inside segment.
// only call in first pass, otherwise too many warnings might be thrown
static void check_segment(intval_t new_pc)
{
struct segment_t *test_segment = segments_head.next;
// use list head as sentinel
segments_head.start = new_pc + 1;
segments_head.length = 1;
// search ring for matching entry
while (test_segment->start <= new_pc) {
if ((test_segment->start + test_segment->length) > new_pc) {
Throw_warning("Segment starts inside another one, overwriting it.");
return;
}
test_segment = test_segment->next;
}
}
// init lowest and highest address
static void init_borders(intval_t address)
{
lowest_idx = address;
highest_idx = address;
}
// clear segment list
void Output_passinit(signed long start_addr)
{
// struct segment_t *temp;
//FIXME - why clear ring list in every pass?
// delete segment list (and free blocks)
// while ((temp = segment_list)) {
// segment_list = segment_list->next;
// free(temp);
// }
set_mem_ptr(start_addr); // negative values deactivate output
// if start address given, set program counter
if (start_addr >= 0) {
CPU_set_pc(start_addr);
// FIXME - integrate next two?
init_borders(start_addr);
segment_start = start_addr;
} else {
init_borders(0); // set to _something_ (for !initmem)
segment_start = 0;
}
// other stuff
segment_max = OUTBUFFERSIZE-1;
segment_flags = 0;
}
// called when "*=EXPRESSION" is parsed
void Output_start_segment(void)
{
void *node_body;
int new_flags = 0;
intval_t new_addr = ALU_defined_int();
// check for modifiers
while (Input_accept_comma()) {
// parse modifier
// if no keyword given, give up
if (Input_read_and_lower_keyword() == 0)
return;
if (!Tree_easy_scan(segment_modifier_tree, &node_body, GlobalDynaBuf)) {
Throw_error("Unknown \"*=\" segment modifier.");
return;
}
new_flags |= (int) node_body;
}
if (CPU_pc.flags & MVALUE_DEFINED) {
// it's a redefinition. Check some things:
// check whether new low
if (new_addr < lowest_idx)
lowest_idx = new_addr;
// show status of previous segment
Output_end_segment();
// in first pass, maybe issue warning
if (pass_count == 0) {
if (!(new_flags & SEGMENT_FLAG_OVERLAY))
check_segment(new_addr);
find_segment_max(new_addr);
}
} else {
init_borders(new_addr); // it's the first pc definition
}
CPU_set_pc(new_addr);
segment_start = new_addr;
segment_flags = new_flags;
set_mem_ptr(new_addr);
}

48
trunk/src/output.h Normal file
View File

@ -0,0 +1,48 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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 MEMINIT_USE_DEFAULT 256
// Prototypes
// Init file format tree (is done early)
extern void Outputfile_init(void);
// alloc and init mem buffer, register pseudo opcodes (done later)
extern void Output_init(signed long fill_value);
// clear segment list
extern void Output_passinit(signed long start_addr);
// call this if really calling Output_byte would be a waste of time
extern void Output_fake(int size);
// Send low byte of arg to output buffer and advance pointer
extern void (*Output_byte)(intval_t);
// Output 8-bit value with range check
extern void Output_8b(intval_t);
// Output 16-bit value with range check
extern void Output_16b(intval_t);
// Output 24-bit value with range check
extern void Output_24b(intval_t);
// Output 32-bit value (without range check)
extern void Output_32b(intval_t);
// Try to set output format held in DynaBuf. Returns whether succeeded.
extern int Output_set_output_format(void);
// write smallest-possible part of memory buffer to file
extern void Output_save_file(FILE *fd);
// Call when "*=EXPRESSION" is parsed
extern void Output_start_segment(void);
// Show start and end of current segment
extern void Output_end_segment(void);
#endif

31
trunk/src/platform.c Normal file
View File

@ -0,0 +1,31 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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, OS/2 and Windows
#if defined(__DJGPP__) || defined(__OS2__) || defined(__Windows__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
#include "_dos.c"
#endif
// RISC OS
#ifdef __riscos__
#include "_riscos.c"
#endif
// add further platform files here
// Unix/Linux/others (surprisingly also works on at least some versions of Windows)
#include "_std.c"

32
trunk/src/platform.h Normal file
View File

@ -0,0 +1,32 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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, OS/2 and Windows
#if defined(__DJGPP__) || defined(__OS2__) || defined(__Windows__) || defined(WIN32) || defined(_WIN32) || defined(__WIN32__)
#define PLATFORM_VERSION "DOS/OS2/Win32 version."
#include "_dos.h"
#endif
// RISC OS
#ifdef __riscos__
#define PLATFORM_VERSION "RISC OS version."
#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."
#endif
#include "_std.h"

120
trunk/src/section.c Normal file
View File

@ -0,0 +1,120 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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 struct 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
struct section_t *Section_now = &initial_section; // current section
static struct 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(struct section_t *section, const char *type, char *title, int 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(struct 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)
{
struct section_t entry_values; // buffer for outer zone
char *new_title;
int 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
Throw_first_pass_warning("\"!subzone {}\" is deprecated; use \"!zone {}\" instead.");
// call "!zone" instead
return PO_zone();
}
// predefined stuff
static struct 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
};
// register pseudo opcodes
void Section_init(void)
{
Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes);
}
// Setup outermost section
void Section_passinit(void)
{
zone_max = ZONE_GLOBAL; // will be incremented by next line
Section_new_zone(&outer_section, type_zone, untitled, FALSE);
}

44
trunk/src/section.h Normal file
View File

@ -0,0 +1,44 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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
struct section_t {
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
};
// Constants
#define ZONE_GLOBAL 0 // Number of "global zone"
// Variables
// current section structure
extern struct section_t *Section_now;
// Prototypes
// Write given info into given zone structure and activate it
extern void Section_new_zone(struct section_t *, const char *type, char *title, int allocated);
// register pseudo opcodes
extern void Section_init(void);
// Setup outermost section
extern void Section_passinit(void);
// Tidy up: If necessary, release section title.
extern void Section_finalize(struct section_t *section);
#endif

202
trunk/src/tree.c Normal file
View File

@ -0,0 +1,202 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 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 function is not allowed to change GlobalDynaBuf!
hash_t make_hash(struct 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(struct node_t **tree, struct 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(struct node_t **tree, struct 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.
int Tree_easy_scan(struct node_t *tree, void **node_body, struct dynabuf_t *dyna_buf)
{
struct 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.
int Tree_hard_scan(struct node_ra_t **result, struct node_ra_t **forest, int id_number, int create)
{
struct node_t wanted; // temporary storage
struct node_ra_t **current_node;
struct 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_UINT2CHAR(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(*new_leaf_node));
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(struct node_ra_t *node, int id_number, void (*fn)(struct 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(struct node_ra_t **forest, int id_number, void (*fn)(struct node_ra_t *, FILE *), FILE *env)
{
int i;
for (i = 255; i >= 0; i--) {
if (*forest)
dump_tree(*forest, id_number, fn, env);
forest++;
}
}

60
trunk/src/tree.h Normal file
View File

@ -0,0 +1,60 @@
// ACME - a crossassembler for producing 6502/65c02/65816 code.
// Copyright (C) 1998-2009 Marco Baye
// Have a look at "acme.c" for further info
//
// Tree stuff
#ifndef tree_H
#define tree_H
#include <stdio.h>
#include "config.h"
// Macros for pre-defining tree node tables
#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 {
struct node_t *greater_than; // pointer to sub-tree
struct 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 {
struct node_ra_t *greater_than; // pointer to sub-tree
struct 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
// Add predefined tree items to given tree.
extern void Tree_add_table(struct node_t **tree, struct node_t *table_to_add);
// Search for a given ID string in a given tree. Store "Body" component in
// NodeBody and return TRUE. Return FALSE if no matching item found.
extern int Tree_easy_scan(struct node_t *tree, void **node_body, struct dynabuf_t *dyna_buf);
// Search for a "RAM tree" item. Save pointer to found tree item in given
// location. If no matching item is found, check the "Create" flag: If set,
// create new tree item, link to tree, fill with data and store its pointer.
// If "Create" is clear, store NULL. Returns whether item was created.
extern int Tree_hard_scan(struct node_ra_t **, struct node_ra_t **, int, int);
// Calls given function for each node of each tree of given forest.
extern void Tree_dump_forest(struct node_ra_t **, int, void (*)(struct node_ra_t *, FILE *), FILE *);
#endif