mirror of
https://github.com/uffejakobsen/acme.git
synced 2025-01-10 21:30:30 +00:00
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:
parent
d1045a9474
commit
db3c69a77b
73
trunk/ACME_Lib/6502/std.a
Normal file
73
trunk/ACME_Lib/6502/std.a
Normal 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
|
||||
}
|
80
trunk/ACME_Lib/65816/std.a
Normal file
80
trunk/ACME_Lib/65816/std.a
Normal 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
87
trunk/docs/65816.txt
Normal 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
172
trunk/docs/AddrModes.txt
Normal 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
818
trunk/docs/AllPOs.txt
Normal 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
340
trunk/docs/COPYING
Normal 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
348
trunk/docs/Changes.txt
Normal 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
380
trunk/docs/Errors.txt
Normal 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
31
trunk/docs/Example.txt
Normal 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
59
trunk/docs/Floats.txt
Normal 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
177
trunk/docs/Help.txt
Normal 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
130
trunk/docs/Illegals.txt
Normal 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
21
trunk/docs/Lib.txt
Normal 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
380
trunk/docs/QuickRef.txt
Normal 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
19
trunk/docs/Source.txt
Normal 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
119
trunk/docs/Upgrade.txt
Normal 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
621
trunk/examples/ddrv.a
Normal 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
BIN
trunk/examples/ddrv128.exp
Normal file
Binary file not shown.
BIN
trunk/examples/ddrv64.exp
Normal file
BIN
trunk/examples/ddrv64.exp
Normal file
Binary file not shown.
74
trunk/examples/macedit.a
Normal file
74
trunk/examples/macedit.a
Normal 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
BIN
trunk/examples/macedit.exp
Normal file
Binary file not shown.
93
trunk/examples/me/const.a
Normal file
93
trunk/examples/me/const.a
Normal 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
798
trunk/examples/me/core.a
Normal 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
111
trunk/examples/me/cursor.a
Normal 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
286
trunk/examples/me/file.a
Normal 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
|
72
trunk/examples/me/macros.a
Normal file
72
trunk/examples/me/macros.a
Normal 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
1439
trunk/examples/me/out.a
Normal file
File diff suppressed because it is too large
Load Diff
BIN
trunk/examples/me/tables.bin
Normal file
BIN
trunk/examples/me/tables.bin
Normal file
Binary file not shown.
124
trunk/examples/me/vars.a
Normal file
124
trunk/examples/me/vars.a
Normal 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
37
trunk/examples/trigono.a
Normal 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
BIN
trunk/examples/trigono.exp
Normal file
Binary file not shown.
64
trunk/src/Makefile
Normal file
64
trunk/src/Makefile
Normal 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
64
trunk/src/Makefile.dos
Normal 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
66
trunk/src/Makefile.riscos
Normal 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
53
trunk/src/_amiga.h
Normal 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
48
trunk/src/_dos.c
Normal 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
60
trunk/src/_dos.h
Normal 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
104
trunk/src/_riscos.c
Normal 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, ®s, ®s);
|
||||
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, ®s, ®s);
|
||||
}
|
||||
|
||||
|
||||
// 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, ®s, ®s);
|
||||
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, ®s, ®s);
|
||||
}
|
||||
// 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, ®s, ®s);
|
||||
}
|
||||
|
||||
|
||||
#endif
|
72
trunk/src/_riscos.h
Normal file
72
trunk/src/_riscos.h
Normal 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
37
trunk/src/_std.c
Normal 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
55
trunk/src/_std.h
Normal 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
477
trunk/src/acme.c
Normal 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
27
trunk/src/acme.h
Normal 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
1495
trunk/src/alu.c
Normal file
File diff suppressed because it is too large
Load Diff
60
trunk/src/alu.h
Normal file
60
trunk/src/alu.h
Normal 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
250
trunk/src/basics.c
Normal 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
17
trunk/src/basics.h
Normal 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
105
trunk/src/cliargs.c
Normal 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
26
trunk/src/cliargs.h
Normal 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
52
trunk/src/config.h
Normal 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
292
trunk/src/cpu.c
Normal 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
47
trunk/src/cpu.h
Normal 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
151
trunk/src/dynabuf.c
Normal 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
59
trunk/src/dynabuf.h
Normal 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
256
trunk/src/encoding.c
Normal 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
20
trunk/src/encoding.h
Normal 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
440
trunk/src/flow.c
Normal 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
22
trunk/src/flow.h
Normal 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
391
trunk/src/global.c
Normal 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
118
trunk/src/global.h
Normal 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
524
trunk/src/input.c
Normal 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
116
trunk/src/input.h
Normal 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
313
trunk/src/label.c
Normal 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
51
trunk/src/label.h
Normal 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
350
trunk/src/macro.c
Normal 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(¯o_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(¯o_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(¯o_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
23
trunk/src/macro.h
Normal 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
946
trunk/src/mnemo.c
Normal 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
28
trunk/src/mnemo.h
Normal 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
525
trunk/src/output.c
Normal 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
48
trunk/src/output.h
Normal 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
31
trunk/src/platform.c
Normal 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
32
trunk/src/platform.h
Normal 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
120
trunk/src/section.c
Normal 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
44
trunk/src/section.h
Normal 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
202
trunk/src/tree.c
Normal 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
60
trunk/src/tree.h
Normal 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
|
Loading…
x
Reference in New Issue
Block a user