diff --git a/trunk/ACME_Lib/6502/std.a b/trunk/ACME_Lib/6502/std.a new file mode 100644 index 0000000..513b494 --- /dev/null +++ b/trunk/ACME_Lib/6502/std.a @@ -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 +} diff --git a/trunk/ACME_Lib/65816/std.a b/trunk/ACME_Lib/65816/std.a new file mode 100644 index 0000000..f7c22ea --- /dev/null +++ b/trunk/ACME_Lib/65816/std.a @@ -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 +} + diff --git a/trunk/docs/65816.txt b/trunk/docs/65816.txt new file mode 100644 index 0000000..0d23bb5 --- /dev/null +++ b/trunk/docs/65816.txt @@ -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. diff --git a/trunk/docs/AddrModes.txt b/trunk/docs/AddrModes.txt new file mode 100644 index 0000000..40e8fc7 --- /dev/null +++ b/trunk/docs/AddrModes.txt @@ -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 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 ; 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... + } diff --git a/trunk/docs/COPYING b/trunk/docs/COPYING new file mode 100644 index 0000000..60549be --- /dev/null +++ b/trunk/docs/COPYING @@ -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. + + + Copyright (C) 19yy + + 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. + + , 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. diff --git a/trunk/docs/Changes.txt b/trunk/docs/Changes.txt new file mode 100644 index 0000000..751759b --- /dev/null +++ b/trunk/docs/Changes.txt @@ -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. :-) diff --git a/trunk/docs/Errors.txt b/trunk/docs/Errors.txt new file mode 100644 index 0000000..f4fadc1 --- /dev/null +++ b/trunk/docs/Errors.txt @@ -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. diff --git a/trunk/docs/Example.txt b/trunk/docs/Example.txt new file mode 100644 index 0000000..58c5a28 --- /dev/null +++ b/trunk/docs/Example.txt @@ -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. :) + diff --git a/trunk/docs/Floats.txt b/trunk/docs/Floats.txt new file mode 100644 index 0000000..6b8350a --- /dev/null +++ b/trunk/docs/Floats.txt @@ -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 diff --git a/trunk/docs/Help.txt b/trunk/docs/Help.txt new file mode 100644 index 0000000..e7eec6a --- /dev/null +++ b/trunk/docs/Help.txt @@ -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 diff --git a/trunk/docs/Illegals.txt b/trunk/docs/Illegals.txt new file mode 100644 index 0000000..20081cd --- /dev/null +++ b/trunk/docs/Illegals.txt @@ -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 diff --git a/trunk/docs/Lib.txt b/trunk/docs/Lib.txt new file mode 100644 index 0000000..fcf8b07 --- /dev/null +++ b/trunk/docs/Lib.txt @@ -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. diff --git a/trunk/docs/QuickRef.txt b/trunk/docs/QuickRef.txt new file mode 100644 index 0000000..62d9d94 --- /dev/null +++ b/trunk/docs/QuickRef.txt @@ -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. diff --git a/trunk/docs/Source.txt b/trunk/docs/Source.txt new file mode 100644 index 0000000..d248999 --- /dev/null +++ b/trunk/docs/Source.txt @@ -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. diff --git a/trunk/docs/Upgrade.txt b/trunk/docs/Upgrade.txt new file mode 100644 index 0000000..33580b1 --- /dev/null +++ b/trunk/docs/Upgrade.txt @@ -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... diff --git a/trunk/examples/ddrv.a b/trunk/examples/ddrv.a new file mode 100644 index 0000000..fb36fb5 --- /dev/null +++ b/trunk/examples/ddrv.a @@ -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<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 %........###............. diff --git a/trunk/examples/ddrv128.exp b/trunk/examples/ddrv128.exp new file mode 100644 index 0000000..34ae534 Binary files /dev/null and b/trunk/examples/ddrv128.exp differ diff --git a/trunk/examples/ddrv64.exp b/trunk/examples/ddrv64.exp new file mode 100644 index 0000000..d422a4b Binary files /dev/null and b/trunk/examples/ddrv64.exp differ diff --git a/trunk/examples/macedit.a b/trunk/examples/macedit.a new file mode 100644 index 0000000..c4d1755 --- /dev/null +++ b/trunk/examples/macedit.a @@ -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. diff --git a/trunk/examples/macedit.exp b/trunk/examples/macedit.exp new file mode 100644 index 0000000..613bbaa Binary files /dev/null and b/trunk/examples/macedit.exp differ diff --git a/trunk/examples/me/const.a b/trunk/examples/me/const.a new file mode 100644 index 0000000..aa79de3 --- /dev/null +++ b/trunk/examples/me/const.a @@ -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 diff --git a/trunk/examples/me/core.a b/trunk/examples/me/core.a new file mode 100644 index 0000000..0c0962c --- /dev/null +++ b/trunk/examples/me/core.a @@ -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 ++ ; anfend: 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 diff --git a/trunk/examples/me/cursor.a b/trunk/examples/me/cursor.a new file mode 100644 index 0000000..e608802 --- /dev/null +++ b/trunk/examples/me/cursor.a @@ -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 diff --git a/trunk/examples/me/file.a b/trunk/examples/me/file.a new file mode 100644 index 0000000..7e8536c --- /dev/null +++ b/trunk/examples/me/file.a @@ -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 diff --git a/trunk/examples/me/macros.a b/trunk/examples/me/macros.a new file mode 100644 index 0000000..d64cfc1 --- /dev/null +++ b/trunk/examples/me/macros.a @@ -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 +} diff --git a/trunk/examples/me/out.a b/trunk/examples/me/out.a new file mode 100644 index 0000000..30fd6d3 --- /dev/null +++ b/trunk/examples/me/out.a @@ -0,0 +1,1439 @@ +;ACME 0.91 + +!zone +F_info ldx #hWindow_InfoBox + jmp xwindow + +memfull ldx #hWindow_NoMemLeft + +xwindow lda conreg ; Bank sichern + pha + +yram0io + stx menunr + jsr crsroff ; Cursor aus + jsr makewin ; Win bauen + jsr getchar ; Auf Taste warten + jsr pullscr ; Win weg + jsr crsron ; Cursor an + pla ; alte Bank + sta conreg + rts + +F_sw lda wrapflag ; Toggle Flag + eor #$ff + sta wrapflag + lda #TRUE ; Update fordern + sta updatewbi + rts + +F_lfeed jsr poslvek ; LineVek + jsr insert1 ; immer insert + lda #Char_RETURN; 'CR' an Cursorpos + sta (lvek),y + sta nwfrm ; NewFrame fordern + sta changes ; Veraendert ! + jsr posparw ; PARW ? + ldy posx ; Cursor am Rand ? + cpy llen + beq poswrap ; Nein: Wrap + lda posy ; Zeile in wrap + sta wrpy + lda posy+1 + sta wrpy+1 + jsr found ; und runterwrap + rts + +; splittet VOLLE Wrap-Zeilen +linefull ldy llen ; Zeilenlaenge-1 + dey + jmp found ; wrap + +cancelwrp jmp memfull ; Zeilen alle + +;multiwrap: + ;jsr getlvek ; LineVek + ;!by 44 ; Wrap (bit-Trick) +!zone +poswrap ldx #POS + stx xindex ; sichern + lda scry,x ; Zahl umkopieren + sta wrpy + lda scry+1,x + sta wrpy+1 + jsr posparw ; PARW ? + bcs proofparw ; Dann kein WRAP + ldy llen ; LineEnd = Space ? + lda (lvek),y + cmp #" " + beq proofparw ; Ja = > PARW +- dey ; sonst zurueck bis zu + beq linefull ; Space, "-", CR oder + lda (lvek),y ; LineStart (bekommt + cmp #" " ; Sonderbehandlung) + beq found + cmp #"-" + beq found + cmp #Char_RETURN + bne - + +!zone +found sty wrpx ; puffert Y und addiert + tya ; es zum Vektor, damit + clc ; gut geshiftet wird + adc lvek + sta tmp2 + lda lvek+1 + adc #0 + sta tmp2+1 + lda llen ; errechnet Anzahl zu + sec ; shiftender Bytes + sbc wrpx ; als 'zahl' + sta zahl + jsr needline ; fordert Zeile + bcs cancelwrp ; ggfs Abbruch + jsr incwrp ; naechste Zeile holen + ldx #WRP ; neue Zeile in die + ldy #ZZB ; naechste + jsr rollfwd + ldy zahl ; Anzahl Bytes + ldx #" " ; Space +- lda (tmp2),y ; holt Byte und setzt + sta (lvek),y + txa ; loescht Quelle + sta (tmp2),y + dey + bne - + ldx xindex ; errechnet neue + lda scrx,x ; Variablenwerte + sec ; Sind nur relevant, + sbc wrpx ; wenn Location + beq proofparw ; mitgewrapped wurde. + bmi proofparw + sta scrx,x + +inc16x scry + +!zone +proofparw ldx wrapflag ; Wrap erlaubt ? + beq + ; Nein = > Ende +.pl jsr incwrp ; naechste Zeile + ldx #WRP + jsr getlvek ; LineVek + jsr parw ; PARWen + bcs .pl ; ggfs Schleife ++ ldx xindex ; Restore LineVek + jsr getlvek + rts + +!zone +incwrp +inc16 wrpy ; Y hoeher + rts + +!zone +posparw clc + ldx #POS ; Cursor-Index + lda wrapflag ; Wrap erlaubt ? + beq + ; ggfs Ende + jsr parw ; PARWed + bcc + ; ggfs Variablen + jsr moveloc ; angleichen ++ php ; C sichern + ldx xindex ; Restore LineVek + jsr getlvek + plp ; C zurueck + rts + +!zone +.no clc ; C = 0 < = > nix PARWed + rts +parw stx xindex ; Sichert Index + ldx #ZZB ; mit PARW-Zeile vergleichen + ldy xindex + jsr cmp16bit + bcc .no ; kleiner = > Abbruch + ldx xindex ; X zurueck + lda scry+1,x ; holt Vektor und + ldy scry,x ; zaehlt runter + bne + + sec + sbc #1 ++ sta prwy+1 ; Hi speichern + dey ; Lo runter & speichern + sty prwy + ora prwy ; Vorige Zeile = 0 ? + beq .no ; Dann Ende ! + lda lvek ; lvek in tmp2 + sta tmp2 + lda lvek+1 + sta tmp2+1 + ldx #PRW ; lvek auf vorige Zeile + jsr getlvek + jsr findend ; sucht Zeilenende + bne + ; Zeile leer ? + jsr delline ; loescht Zeile und + lda llen ; stellt Werte ein, + sta zahl ; die die Location + lda #0 ; entsprechend + sta prwx ; verschieben + sec ; C = 1 < = > gePARWt + rts ++ cmp #Char_RETURN; letztes Zeichen CR ? + beq .no ; Dann Ende ! + cmp #"-" + beq + ; Dann kein Space + iny ; reservieren ++ sty prwx ; Y auf LastByte + lda llen ; errechnet Versatz, + sec ; bei dem Suche + sbc prwx ; gestartet wird + beq .no ; nix frei = > Ende + tay + lda prwx ; LineVek += Versatz, um + clc ; besser zu shiften + adc lvek + sta lvek + lda lvek+1 + adc #0 + sta lvek+1 + lda (tmp2),y ; 1. Quellbyte holen +- cmp #" " + beq + + dey + beq .no ; LineStart = > Abbruch + lda (tmp2),y ; next Quellbyte + cmp #"-" + beq + + cmp #Char_RETURN + bne - ++ sty zahl ; Anzahl sichern +- sta (lvek),y ; Zielbyte setzen + dey + beq + + lda (tmp2),y ; Quellbyte holen + jmp - ; Schleife ++ lda llen ; Zeilenlaenge minus + sec ; 'zahl' Bytes muessen + sbc zahl ; noch shifted werden + sta .m + lda tmp2 ; Muss zum Schieben + clc ; wieder Vektor + adc zahl ; aendern + sta lvek + lda tmp2+1 + adc #0 + sta lvek+1 +; Y noch 0 ! +- iny ; Y weiter + lda (lvek),y ; holt & setzt Byte + sta (tmp2),y +.m=*+1: cpy #MODIFIED8 ; Anzahl Bytes + bne - + lda #" " +- iny ; new line damit + sta (tmp2),y ; auffuellen + cpy llen ; bis Ende + bne - + sec ; C = 1 < = > PARWed + rts + +moveloc ldx xindex + lda zahl ; X-Pos vor PARWgrenze? + cmp scrx,x + bcc inline ; Dann Pos += Anzahl + clc + lda scrx,x + adc prwx + sta scrx,x + lda prwy ; verringerten Y-Wert + sta scry,x ; einkopieren + lda prwy+1 + sta scry+1,x + sec ; C = 1 + rts + +inline lda scrx,x ; (C = 1) + sbc zahl ; setzt Cursor zurueck + adc #0 ; +Carry + sta scrx,x + rts ; (C = 0) + +!zone +findend ldy llen ; Versatz auf Ende +- lda (lvek),y ; holt Zeichen bis + cmp #" " ; Zeilenstart oder + bne endfound ; nicht-Space + dey + bne - + +endfound rts ; Z = 1 = > Zeile leer (fixme - save a byte) + +; Menuevariablen +manywins !byte 0 ; Anzahl Wins +menunr !byte 0 ; Menunr +menupoint !word 0 ; Menupunkt+Puffer + +changewin beq lp19 ; kein Nachbar = > Ende + pha ; neue Nr sichern + jsr pullscr ; altes Win weg + pla ; neue Nr holen + tax ; prueft ob Menupunkt + stx menunr ; beibehalten werden + lda menupoint ; kann. Ggfs auf 1 + cmp winhe,x ; setzen + bcc menuloop + lda #1 + sta menupoint + +!zone +menuloop jsr makewin ; Window bauen +pointloop jsr calcpoint ; berechnet Adresse + lda clraktl ; holt Farbe + jsr changeflg ; aendert Flag +lp19 jsr getchar ; Auf Zeichen warten + ldx menunr ; Verkuerzt folgende + ldy menupoint ; Abfragen + cmp #Char_RETURN + beq menucom ; Ausfuehren + cmp #Char_ShiftRETURN + beq menucom ; Ausfuehren + ;cmp #$80 ; Linke Maustaste ? + ;beq mouselft ; => + cmp #Char_CursorUp + bne + + dey ; Punkt hoch & wech + jmp prfpoint ++ cmp #Char_CursorDown + bne + + iny ; Punkt runter & wech + jmp prfpoint ++ cmp #Char_CursorLeft + bne + + lda lftwin,x ; Nachbar holen & wech + jmp changewin ++ cmp #Char_CursorRight + bne + + lda rgtwin,x ; Nachbar holen & wech + jmp changewin ++ cmp #Char_STOP + bne lp19 ; Nein = > Schleife +menuend jsr pullscr ; sonst restaurieren + rts ; und Ende (fixme - could save a byte here) + +!zone +menucom pha ; CR/CS sichern, dazu + txa ; die aktuelle Menunr + pha ; und den Menupunkt + tya + pha + lda winadlo,x ; stellt Zeiger auf + sta tmp1 ; Menutexte + lda winadhi,x + sta tmp1+1 + ldy #0 ; Versatz Null + jmp + ; in die Schleife +- iny ; naechstes Byte holen + lda (tmp1),y + bne - ; sucht Ende-Null + iny ; Versatz auf Command ++ lda (tmp1),y ; Command holen + dec menupoint ; richtiger Punkt ? + bne - ; Nein = > erneut + tax ; Ja = > Command ! + jsr execom + jsr crsroff ; Sicherheitshalber... + pla ; Werte zurueck + sta menupoint + pla + sta menunr + jsr calcpoint ; Vektor stellen + pla ; Ausloesecode zurueck + cmp #Char_RETURN + beq menuend ; Dann Ende + jmp lp19 ; Sonst Schleife (ShiftRETURN) +;----- +;mouselft: +; jmp lp19 ; Linke Maustaste +;----- + +!zone +prfpoint tya ; Wenn Punkt = 0, + bne ++ ; dann Punkt: = Letzter + ldy winhe,x + dey + jmp + + +++ cmp winhe,x ; Wenn Punkt zu gross, + bcc + ; dann Punkt: = 1 + ldy #1 ++ sty menupoint+1 ; neuen Punkt sichern + jsr calcpoint ; alten berechnen und + lda clrboxs ; mit WinFarbe faerben + jsr changeflg + ldy menupoint+1 ; neuen Punkt holen, + sty menupoint ; wird aktueller + jmp pointloop + +; errechnet Adresse des Menupunkts +!zone +calcpoint ldx menunr ; holt Nummer & damit + lda winhi,x ; Screen-Addy + sta tmp1+1 ; Hi setzen + lda winlo,x + ldx menupoint ; holt Punkt + sec ; +1 (Rand weg) +- adc #scrcols ; addiert Zeile + bcc + ; Page sichern + inc tmp1+1 + clc ++ dex ; ggfs + bne - ; Schleife + sta tmp1 ; Lo setzen + sec ; Erledigt Arbeit fuer + ldx menunr ; aufrufende Routinen, + ldy winwi,x ; da X, Y, C richtig + dey ; belegt werden + rts + +; Windowdaten: +hWindow_InfoBox = 0 +; 1 bis 6 sind Menuleiste +hWindow_FirstAfterMenu = 7 +hWindow_NoMemLeft = 7 +hWindow_Filename = 8 +hWindow_DCS = 9 +hWindow_Load = 10 +hWindow_Save = 11 + +winwi !byte 31, 16, 7, 13, 10, 14, 8, 25, 25, 32, 10, 15 ; Breite - 1 +winhe !byte 8, 8, 5, 3, 5, 8, 2, 5, 5, 8, 2, 2 ; Hoehe - 1 + +; Vektoren auf Texte: +winadlo !byte info, >datei, >edit, >block, >format,>switch + !byte >hilfe, >nomem, >namewin, >dcswin,>lwin, >swin + +winclr !byte 1, 0, 0, 0, 0, 0, 0, 2, 1, 0, 1, 1 ; FarbIndex +lftwin !byte 0, 6, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0 ; Linker Nachbar +rgtwin !byte 0, 2, 3, 4, 5, 6, 1, 0, 0, 0, 0, 0 ; Rechter Nachbar + +; Menutexte +info !text 0,_,_,_,_,_,_,_,_, "---MacEdit---", 0 + !text 0, "Version",_, "0.7",_,_,_,_,_,_,_,_, "vom",_, "19.4.97",0 + !text 0, "geschrieben",_, "von:", 0 + !text 0,_,_,_,_,_,_,_,_,_,_,_,_, "Mac",_, "Bacon/TekLords",0 + !text 0,_,_,_,_,_, "Dies",_, "ist",_, "Freeware",_, "!",0 + !byte 0, 0 + !text 0,_,_,_, "Bitte",_, "eine",_, "Taste",_, "dr",ü, "cken", 0 +datei !text $6f, "Neu", 0 + !text $75, "Laden", 0 + !text $76, "Nachladen", 0 + !text $f5, "Speichern", 0 + !text $f6, "Speichern",_, "unter", 0 + !text $e1, "Basic", 0 + !text $61, "Beenden", 0 +edit !text $79, "Cut", 0 + !text $7a, "Copy", 0 + !text $7b, "Paste", 0 + !text $14, "Delete", 0 +block !text 0, "Laden", 0 + !text 0, "Speichern", 0 +format !text $f9, "Links", 0 + !text $fa,_,_,_, "Rechts", 0 + !text $fb, "Zentriert", 0 + !text $fc, "Blocksatz", 0 +switch !text $19, "WordWrap", 0 + !text $07, "Beep", 0 + !text 0, "AutoInsert", 0 + !text $15, "Fett", 0 + !text $16, "Kursiv", 0 + !text $17, "Unterstrichen", 0 + !text 0, "Revers", 0 +hilfe !text $e4, "NochNix" +nomem !byte 0 ; Auch 'hilfe'-Ende + !text "---",_,_,_,_, "Achtung",_, "!",_,_,_, "---", 0 + !text 0, "Der",_, "Speicher",_, "ist",_, "voll",_, "!", 0 + !byte 0, 0 + !text 0, "Bitte",_, "eine",_, "Taste",_, "dr", ü, "cken" +namewin !byte 0 ; Auch 'nomem'-Ende + !text "Bitte",_, "Filenamen",_, "eingeben", 0 + !byte 0, 0 + !text 0,_,_,_, ":",_,_,_,_,_,_,_,_,_,_,_,_,_,_,_,_, ":", 0 + !byte 0 ; Rest unten +dcswin !byte 0, 0 ; Auch fuer 'namewin' + !byte 0 ; Auch fuer 'namewin' + !text _,_, "Ungespeicherte",_, Ä, "nderungen",_, "!",_,_, 0 + !byte 0, 0 + !byte 0, 0 + !text 0,_, "[DEL]",_,_,_,_, "Verwerfen...", 0 + !text 0,_, "[RETURN]",_, "Speichern...",_,_,_,_, "*", 0 + !text 0,_, "[STOP]",_,_,_, "Aktion",_, "abbrechen...", 0 +lwin !byte 0 ; Auch fuer 'dcswin' + !text _, "Lade..." +swin !byte 0 ; Auch fuer 'lwin' + !text _, "Speichere...", 0 + +;-----Screen-Routines (VDC-Version)----- +; V1 R1 rev4 vom 4.5.96 +; labels + + scrlo = $f0 ; screentextstart + scrhi = 0 + attrhi = 8 ; colorRAM Hi + scrcols = 80 ; columns & lines + scrlins = 22 ; (screen) + preflen = 80 ; pref-linelength + bckgrnd = 26 ; screencolor-register + flnmpos = 87 ; filenameposition + wsstart = $0fd0 ; windowstack-start + tcolor = $f2 ; attribute-buffer + +; Subs: +!zone +init ldx repeatedtry ; first start ? + bne + + sta repeatedtry ++ tsx ; buffer stack, bank & + stx stck ; NMI for end + ldx conreg + stx bank + ldy nmivek + ldx nmivek+1 + sty nmibuf + stx nmibuf+1 + +xbank15 + inx ; X = 1 => 2 MHz + stx takt + dex + stx fnbank ; filename-CR + dex + stx locks ; 'CBM-shift' off + stx $0ac5 ; 'ASC/DIN' off + stx basic ; 'Basic-IRQ' off + jsr crsrinit ; init crsr + ldx #$0b ; VIC off + stx $11e7 + lda mode ; which mode ? + bmi + ; if 40 then + jsr kernel_switchmode; switch mode ++ lda #111 ; DIN on + sta D8502 + lda #%..##..## + sta R8502 + jsr kernel_copyfont + lda #47 ; DIN off + sta D8502 + lda #addfont1_start + sta tmp1+1 + lda #>addfont1_target; Hi + ldx #addfont2_start + sta tmp1+1 + lda #>addfont2_target; Hi + ldx #", Char_BlueL + !fill 80, $a6 ; separatorline (TABs) + !byte Char_Grey3, Char_ESCAPE, Char_At, Char_NUL +!zone + lda #26 ; Backgroundcolor + sta vdc + lda clrback +- bit vdc ; get ready + bpl - + sta reg + lda #nmirtn + sta nmivek + stx nmivek+1 + sta conreg+1 ; full RAM + ldx #$0b ; loop for new +- lda keyb,x ; keyboard-tabs + sta $033e,x + dex + bpl - + jsr handleid ; old ID ? + bcc + ; then ok ! + jsr fillmem ; else fill memory + jsr newtext ; defaults + jsr needline ; wanting line + ldx #FALSE + stx changes ++ jsr F_info ; credits + lda #TRUE ; update screen + sta nwfrm + sta updatewbi ; update flags + ldx repeatedtry ; first start ? + bne + ; then load defaulttext + dec repeatedtry + jsr load ++ jmp mainloop + +addfont1_target=$35f1 +addfont1_start + !byte %.####### + !byte %.#####.# + !byte %.##.##.# + !byte %.#.....# + !byte %.##.#### + !byte %.####### + !fill 15, 0 + !byte %.#.#.#.# + !fill 9, 0 + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#### + !fill 11, 0 + + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %######## + !fill 11, 0 + + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %#####... + !fill 11, 0 + + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#### + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + + !fill 4, 0 + !byte %######## + !fill 11, 0 + + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %#####... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + + !fill 4, 0 + !byte %....#### + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + + !fill 4, 0 + !byte %######## + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + + !fill 4, 0 + !byte %#####... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %######## + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... + !byte %....#... +addfont1_end +addfont2_target=$3ff0 +addfont2_start + !byte %........ + !byte %........ + !byte %........ + !byte %....#... + !byte %........ + !byte %........ + !byte %........ + !byte %........ +addfont2_end + +gonot rts ; fixme - could save a byte here +F_goout jsr willblost ; changes saved ? + beq gonot +F_gosys +bank15 + jsr kernel_cls ; CLS + lda #0 ; '0' for + sta locks ; 'CBM-Shift' on + sta $0ac5 ; 'ASC/DIN' on + sta basic ; 'Basic-IRQ' on + lda nmibuf ; restore NMI + ldx nmibuf+1 + sta nmivek + stx nmivek+1 + ldx bank ; restore bank & stack + stx conreg + ldx stck + txs + rts + +!zone +bigtext lda #0 ; no empty lines, + ldy #scrlins ; full text + jmp m61 + +newframe +ybank15 + lda #scrhi ; textstart on screen + ldx #scrlo + jsr ramaccess ; set vdc + sty conreg+1 ; full RAM + ldx #SCR + jsr getvvek ; set VekVek + stx nwfrm ; clear jobflag + lda zzbe ; Test: less text than + sec ; screen ? + sbc scry ; calculates both + tay ; numbers + lda zzbe+1 + sbc scry+1 + bne bigtext + cpy #scrlins-1 + bcs bigtext + iny ; Base 1 + sty .m1 + lda #scrlins + sec +.m1=*+1: sbc #MODIFIED8 ; errechnet Leerzeilen +; Parameter: A = Leerzeilen, Y = Textzeilen + +!zone +m61 sta tmp2 ; Leerzeilen + sty line ; zu zeigende Zeilen +--- ldy #0 ; Low-Byte + lda (vvek),y ; holen + sta lvek ; & speichern + iny ; High-Byte + lda (vvek),y ; holen + sta lvek+1 ; & speichern + lda #scrcols ; darzustellende + sta col ; Spalten + ldy scrx ; x-Versatz holen +-- lda (lvek),y ; Zeichen holen + +xbank15 + tax ; & in Screencode + lda atst,x ; konvertieren +- bit vdc ; get ready + bpl - + sta reg ; in VDC + sta conreg+1 ; full RAM + iny ; Versatz erhoehen + dec col ; naechste Spalte + bne -- ; schon alle = > + inc vvek ; naechster Vektor (2 Byte !) + +inc16 vvek + dec line ; naechste Zeile + bne --- ; schon alle ? = > + +bank15 + lda #" " +-- dec tmp2 ; Anzahl runter + bmi + ; ggfs Ende + ldy #scrcols ; Spaltenzahl +- bit vdc ; get ready + bpl - + sta reg ; Space setzen + dey ; alle Spalten ? + bne - + jmp -- ++ sta conreg+1 ; full RAM + rts + +; Folgende Routine aendert ScreenFlag, +; z.B. esc, Menupunkte. Parameter: +;-A = Attribut-Code +; Einsprung setflagdata: +;-X = Flagnr (Routine holt Daten) +; Einsprung changeflg: +;-Y = Laenge +;-tmp1 = Vektor auf Position +setflagdata ldy flgadr,x ; Setzt Adresse (Lo) + sty tmp1 + ldy #scrhi + sty tmp1+1 + ldy flglen,x ; Holt Laenge + +!zone +changeflg sta .m3 ; buffer color + sty .m2 ; buffer length + lda #attrhi-scrhi + clc ; bend vector to + adc tmp1+1 ; attributes + sta tmp1+1 + ldy conreg ; buffer bank + sty .m1 + +ybank15 + jsr tmpaccess ; set vdc +.m3=*+1: lda #MODIFIED8 ; attribute +.m2=*+1: ldy #MODIFIED8 ; length +- bit vdc ; get ready + bpl - + sta reg ; set attributes + dey ; count + bne - +.m1=*+1: ldy #MODIFIED8 ; bank + sty conreg + rts + +hFlag_Escape = 0 +; 1-6 = datei, edit, block, format, switch, hilfe +hFlag_Wrap = 7 +hFlag_Beep = 8 +hFlag_Insert = 9 + +flgadr !byte 135, 0, 12, 24, 36, 48, 60, 144, 145, 146 + !byte 150, 151, 152, 153, 157, 158, 159 ; f k u r < = > +flglen !byte 6, 10, 10, 10, 10, 10, 10, 1, 1, 1 + !byte 1, 1, 1, 1, 1, 1, 1 + +!zone +showwbi lda clraktv ; Aktiv-Farbe + ldy wrapflag ; Flag pruefen + bne + ; ggfs andere Farbe + lda clrinak ++ ldx #hFlag_Wrap + jsr setflagdata ; Farbe setzen + lda clraktv ; Ab hier Wiederholung + ldy beep + beq + + lda clrinak ++ ldx #hFlag_Beep + jsr setflagdata + lda clraktv + ldy esca + bne + + lda clrinak ++ ldx #hFlag_Insert + jsr setflagdata + lda #>txtname ; set vector + sta tmp1+1 + lda #flnmpos ; show filename + ldx #dosname + sty tmp1+1 + ldy #$0f ; set length + sty vimm6 + +!zone +showname jsr copystr1 ; show name +-- ldy #9 ; Y- und X-Wert + lda xpos ; errechnen + clc + adc #$20 + jsr crsrset ; Cursor setzen +- jsr getchar ; Byte holen und + tax ; sichern + and #%.##..... ; Command ? + beq + ; ggfs => + eor #%.##..... + beq + + lda esca ; AutoInsert ? + beq ++ ; ggfs + jsr einf1 ; insert +++ txa ; Zeichen an Position + ldy xpos ; setzen + sta dosname,y + jsr rechts1 ; eins rechts + jmp showname ++ txa ; Byte holen + cmp #Char_CursorRight + beq rechts + cmp #Char_CursorLeft + beq links + cmp #Char_DEL + beq back + cmp #CharOwn_Delete + beq dele + cmp #Char_INST + beq einf + cmp #Char_RETURN + beq + + cmp #Char_STOP + bne - ; Nein = > + lda #0 ; Stop- ++ sta xpos ; Flag sichern + jsr pullscr ; Window weg + jsr crsrold ; old copy + pla ; alte Bank + sta conreg + pla ; Y: = Namepointer + tay + lda xpos ; STOP ? + beq .noname ; Nein: Namen kopieren + ldx #$0f +- lda dosname,x + sta txtname,y + dey + dex + bpl - + lda #TRUE ; und Update fordern + sta updatewbi +.noname rts ; Z = Stopflag +rechts jsr rechts1 ; Eins weiter + jmp -- +links jsr links1 ; Eins zurück + jmp -- +back jsr links1 ; Erst zurück, dann +dele jsr dele1 ; löschen + jmp showname +einf jsr einf1 ; Eins freimachen + jmp showname + +!zone +rechts1 inc xpos ; Pos += 1 & pruefen + jmp + + +links1 dec xpos ; Pos- = 1 & pruefen ++ lda xpos ; Negativ ? + bpl + + lda #0 ; Dann Pos 0 ++ cmp #$0c ; Ende ? + bcc + + lda #$0b ; Dann zum Rand ++ sta xpos ; Pos setzen + rts + +!zone +dele1 ldy xpos ; Start bei Cursor +- cpy #$0b ; schon am Rand ? + beq + ; Dann Ende ! + lda dosname+1,y ; Sonst shiften + sta dosname,y + iny + jmp - + ++ lda #" " ; Space setzen + sta dosname,y + rts + +!zone +einf1 ldy #$0b ; Start am Rand +- cpy xpos ; schon fertig ? + beq + ; Ja = > + lda dosname-1,y ; Zeichen shiften + sta dosname,y + dey + jmp - + ++ lda #" " ; Space an Pos + sta dosname,y + rts + +!zone +; access for new window +wsnew +ldax wspntr + jmp + + +; access for old window +wsold +ldax wsback ++ ldy #24 ; VDC (Vscroll) + sty vdc + ldy #$a0 ; 'block-copy' +- bit vdc ; get ready + bpl - + sty reg ; set + jmp ramaccess + +; sichert Screen & erzeugt Rahmen und BG fuer Window +; Windownr in menunr ! +!zone +makewin inc manywins ; eins mehr ! + lda conreg ; Bank sichern + pha + +ram0io + ldx menunr ; Holt Nummer + beq + ; Ist das Window ein + cpx #hWindow_FirstAfterMenu ; Menu-Window, wird + bcs + ; das Flag aktiviert + lda clrmenu1 + jsr setflagdata ++ jsr wsnew ; macht VDC zum Stack + ldy wsback+1 ; alter end-pointer + sta wsback+1 ; wird zu neuem + lda wsback ; last-pointer + stx wsback + jsr stashbt ; buffer last pointer + tya + jsr stashbt ; (Hi) + ldx menunr ; get Win-Nr + txa + jsr stashbt ; wird gestacked + lda winlo,x ; Holt mit X die + sta tmp1 ; anderen Parameter + sta tmp2 + lda winhi,x + sta tmp1+1 + clc + adc #attrhi-scrhi + sta tmp2+1 + ldy winwi,x ; get width + sty .m1 ; for colorate + iny ; convert + sty mWidth ; for push + lda winclr,x ; get colorcode + tay ; convert + lda clrboxs,y ; get color + sta .m2 ; in Immediate + jsr scrn2stck ; push chars + lda tmp2 ; attrib-vector + sta tmp1 + lda tmp2+1 + sta tmp1+1 + jsr scrn2stck ; push attributes + ldy #18 ; VDC (access Hi) + sty vdc + jsr fetchbt ; get pointerHi + sta wspntr+1 ; set pointerHi + iny ; VDC (access Lo) + sty vdc + jsr fetchbt ; get pointerLo + sta wspntr ; set pointerLo +; ab hier wird gefaerbt + ldy #24 ; VDC (Vscroll) + sty vdc + lda #$20 ; 'block-write' + jsr stashbt + lda winhe,x ; get height + sta line +- +ldax tmp2 ; get target + jsr ramaccess ; set vdc +.m2=*+1: lda #MODIFIED8 ; attribute + jsr stashbt ; set attribute + dey ; VDC (counter) + sty vdc +.m1=*+1: lda #MODIFIED8 ; width + jsr stashbt ; start write + lda #scrcols ; address for next row + clc + adc tmp2 + sta tmp2 + bcc + + inc tmp2+1 ++ dec line ; alle Zeilen ? + bpl - ; Nein = > +; bisher Backup und Faerben, jetzt Win + ldx menunr ; get Win-Nr + lda winlo,x ; get location, width, + sta tmp1 ; heigth & textvector + lda winhi,x + sta tmp1+1 + lda winwi,x + sta col + lda winhe,x + sta line + lda winadlo,x + sta tmp2 + lda winadhi,x + sta tmp2+1 +; hier oberer Rand + jsr tmpaccess ; set vdc + ldx col ; get width & + dex ; convert + lda #chrol ; 'Oben Links' + jsr stashbt ; set + lda #chroo ; 'Oben' +- jsr stashbt ; set + dex + bne - + lda #chror ; 'Oben Rechts' + jsr stashbt ; set + dec line ; naechste Zeile +; jetzt die Zeilen +.field lda #scrcols ; rechnet den + clc ; Vektor auf die + adc tmp1 ; naechste Zeile + sta tmp1 ; um + bcc + + inc tmp1+1 ++ jsr tmpaccess ; set vdc + ldy #1 ; textindex 1 + lda #chrll ; 'Links' + jsr stashbt ; set +- lda (tmp2),y ; get char + beq + ; end ? if not then + tax ; convert to screencode + lda atst,x + jsr stashbt ; output + iny ; next column + jmp - ++ tya ; if string has ended then + tax ; remember memory offset in x +- cpy col ; finished ? + beq + ; if not then + lda #chrmm ; output inner space + jsr stashbt + iny ; next column + jmp - ++ lda #chrrr ; when finished, output right border + jsr stashbt + txa ; calculate new memory pointer + sec + adc tmp2 ; line + sta tmp2 + bcc + + inc tmp2+1 ++ dec line ; schon alle Zeilen ? + bne .field ; ggfs Schleife +; jetzt der Unterrand + lda #scrcols ; calculate next line + clc + adc tmp1 + sta tmp1 + bcc + + inc tmp1+1 ++ jsr tmpaccess ; set vdc + ldx col ; get width & + dex ; convert + lda #chrul ; 'Unten Links' + jsr stashbt ; set + lda #chruu ; 'Unten' +- jsr stashbt ; set + dex + bne - + lda #chrur ; 'Unten Rechts' + jsr stashbt ; set + pla ; alte Bank + sta conreg + rts + +; Windowdaten: +; 0 = StartBox +; 1 bis 6 sind Menuleiste +; 7 = MemFull +; 8 = FileName +; 9 = SaveChanges? +; 10 = Load +; 11 = Save +; Addys (Ecke links oben): +winlo !byte 152, 80, 92, 104, 116, 128, 140, 235, 235, 151, 146, 144 +winhi !byte 2, 0, 0, 0, 0, 0, 0, 2, 2, 2, 3, 3 + +!zone +scrn2stck lda winhe,x ; get height + sta line +- ldy #32 ; VDC (source Hi) + sty vdc + lda tmp1+1 + jsr stashbt ; set sourceHi + iny ; VDC (source Lo) + sty vdc + lda tmp1 + jsr stashbt ; set sourceLo + ldy #30 ; VDC (counter) + sty vdc +mWidth=*+1: lda #MODIFIED8 ; width + jsr stashbt ; start copy + lda #scrcols ; address for next row + clc + adc tmp1 + sta tmp1 + bcc + + inc tmp1+1 ++ dec line ; alle Zeilen ? + bpl - ; Nein = > + rts + +!zone +pullscr lda conreg ; Bank sichern + pha + +ram0io + jsr wsold ; init stack for read + sta wspntr+1 ; new write-start + stx wspntr ; new write-start + jsr fetchbt ; get old laststart + sta wsback ; and use + jsr fetchbt + sta wsback+1 + jsr fetchbt ; get handle from stack + tax + lda winlo,x ; Holt mit X die + sta tmp1 ; anderen Parameter + sta tmp2 + lda winhi,x + sta tmp1+1 + clc + adc #attrhi-scrhi + sta tmp2+1 + ldy winwi,x ; get width + iny ; convert + sty .m1 + ldy #18 ; VDC (access Hi) + sty vdc + jsr fetchbt ; get Hi + ldy #32 ; VDC (source Hi) + sty vdc + jsr stashbt ; set Hi + ldy #19 ; VDC (access Lo) + sty vdc + jsr fetchbt ; get Lo + ldy #33 ; VDC (source Lo) + sty vdc + jsr stashbt ; set Lo + jsr stck2scrn ; pull chars + lda tmp2 ; attrib-vector + sta tmp1 + lda tmp2+1 + sta tmp1+1 + jsr stck2scrn ; pull attributes + txa ; if menu-window, + beq + ; then deactivate flag + cmp #7 + bcs + + lda clrmenu + jsr setflagdata ++ dec manywins ; minus one win + pla ; restore bank + sta conreg + rts +stck2scrn lda winhe,x ; get height + sta line +- ldy #18 ; VDC (access Hi) + sty vdc + lda tmp1+1 + jsr stashbt ; set targetHi + iny ; VDC (access Lo) + sty vdc + lda tmp1 + jsr stashbt ; set targetLo + ldy #30 ; VDC (counter) + sty vdc +.m1=*+1: lda #MODIFIED8 ; width + jsr stashbt ; start copy + lda #scrcols ; address for next row + clc + adc tmp1 + sta tmp1 + bcc + + inc tmp1+1 ++ dec line ; alle Zeilen ? + bpl - ; Nein = > + rts + +wspntr !word wsstart ; win-stackpointer +wsback !word wsstart ; last pointer + +F_menu lda #wsstart + sta wspntr+1 + jsr crsrnew ; new copy + ldx #0 ; yet no windows + stx manywins + inx ; menu 1 & point 1 + stx menunr + stx menupoint + jsr menuloop ; menu-routine + jsr crsrold ; old copy + rts ; fixme - could save one byte here + +tmpaccess +ldax tmp1 + +; A = Highbyte, X = Lowbyte +!zone +ramaccess ldy #18 ; VDC (access Hi) + sty vdc +- bit vdc ; get ready + bpl - + sta reg ; set Hi + iny ; VDC (access Lo) + sty vdc +- bit vdc ; get ready + bpl - + stx reg ; set Lo + ldy #31 ; VDC (ram data) + sty vdc + rts + +fetchbt bit vdc ; get ready + bpl fetchbt + lda reg ; fetch byte + rts + +stashbt bit vdc ; get ready + bpl stashbt + sta reg ; stash byte + rts diff --git a/trunk/examples/me/tables.bin b/trunk/examples/me/tables.bin new file mode 100644 index 0000000..4e72161 Binary files /dev/null and b/trunk/examples/me/tables.bin differ diff --git a/trunk/examples/me/vars.a b/trunk/examples/me/vars.a new file mode 100644 index 0000000..eec4089 --- /dev/null +++ b/trunk/examples/me/vars.a @@ -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 diff --git a/trunk/examples/trigono.a b/trunk/examples/trigono.a new file mode 100644 index 0000000..c832199 --- /dev/null +++ b/trunk/examples/trigono.a @@ -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 diff --git a/trunk/examples/trigono.exp b/trunk/examples/trigono.exp new file mode 100644 index 0000000..2f658f8 Binary files /dev/null and b/trunk/examples/trigono.exp differ diff --git a/trunk/src/Makefile b/trunk/src/Makefile new file mode 100644 index 0000000..cedff16 --- /dev/null +++ b/trunk/src/Makefile @@ -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 diff --git a/trunk/src/Makefile.dos b/trunk/src/Makefile.dos new file mode 100644 index 0000000..e4d1b01 --- /dev/null +++ b/trunk/src/Makefile.dos @@ -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 diff --git a/trunk/src/Makefile.riscos b/trunk/src/Makefile.riscos new file mode 100644 index 0000000..08403ce --- /dev/null +++ b/trunk/src/Makefile.riscos @@ -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 diff --git a/trunk/src/_amiga.h b/trunk/src/_amiga.h new file mode 100644 index 0000000..dde9b36 --- /dev/null +++ b/trunk/src/_amiga.h @@ -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 diff --git a/trunk/src/_dos.c b/trunk/src/_dos.c new file mode 100644 index 0000000..3e83098 --- /dev/null +++ b/trunk/src/_dos.c @@ -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 +#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 diff --git a/trunk/src/_dos.h b/trunk/src/_dos.h new file mode 100644 index 0000000..7a95bfb --- /dev/null +++ b/trunk/src/_dos.h @@ -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 diff --git a/trunk/src/_riscos.c b/trunk/src/_riscos.c new file mode 100644 index 0000000..03dd3ff --- /dev/null +++ b/trunk/src/_riscos.c @@ -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 +#include +#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 diff --git a/trunk/src/_riscos.h b/trunk/src/_riscos.h new file mode 100644 index 0000000..b802404 --- /dev/null +++ b/trunk/src/_riscos.h @@ -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 diff --git a/trunk/src/_std.c b/trunk/src/_std.c new file mode 100644 index 0000000..25f1509 --- /dev/null +++ b/trunk/src/_std.c @@ -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 +#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 diff --git a/trunk/src/_std.h b/trunk/src/_std.h new file mode 100644 index 0000000..53720b5 --- /dev/null +++ b/trunk/src/_std.h @@ -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 diff --git a/trunk/src/acme.c b/trunk/src/acme.c new file mode 100644 index 0000000..dcdcb4e --- /dev/null +++ b/trunk/src/acme.c @@ -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 +#include +#include +#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 +} diff --git a/trunk/src/acme.h b/trunk/src/acme.h new file mode 100644 index 0000000..094932b --- /dev/null +++ b/trunk/src/acme.h @@ -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 diff --git a/trunk/src/alu.c b/trunk/src/alu.c new file mode 100644 index 0000000..187275a --- /dev/null +++ b/trunk/src/alu.c @@ -0,0 +1,1495 @@ +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2009 Marco Baye +// Have a look at "acme.c" for further info +// +// Arithmetic/logic unit +// 11 Oct 2006 Improved float reading in parse_decimal_value() +// 24 Nov 2007 Now accepts floats starting with decimal point +// 31 Jul 2009 Changed ASR again, just to be on the safe side. +#include +#include // only for fp support +#include "platform.h" +#include "alu.h" +#include "cpu.h" +#include "dynabuf.h" +#include "encoding.h" +#include "global.h" +#include "input.h" +#include "label.h" +#include "section.h" +#include "tree.h" + + +// constants + +#define FUNCTION_DYNABUF_INITIALSIZE 8 // enough for "arctan" +#define HALF_INITIAL_STACK_SIZE 8 +static const char exception_div_by_zero[] = "Division by zero."; +static const char exception_no_value[] = "No value given."; +static const char exception_paren_open[] = "Too many '('."; +static const char exception_undefined[] = "Value not defined."; +#define s_or (s_eor + 1) // Yes, I know I'm sick +#define s_xor (s_scrxor + 3) // Yes, I know I'm sick +static const char s_arcsin[] = "arcsin"; +#define s_sin (s_arcsin + 3) // Yes, I know I'm sick +static const char s_arccos[] = "arccos"; +#define s_cos (s_arccos + 3) // Yes, I know I'm sick +static const char s_arctan[] = "arctan"; +#define s_tan (s_arctan + 3) // Yes, I know I'm sick + +// operator handles (FIXME - use function pointers instead? or too slow?) +enum operator_handle { +// special values (pseudo operators) + OPHANDLE_END, // "reached end of expression" + OPHANDLE_RETURN, // "return value to caller" +// functions + OPHANDLE_INT, // int(v) + OPHANDLE_FLOAT, // float(v) + OPHANDLE_SIN, // sin(v) + OPHANDLE_COS, // cos(v) + OPHANDLE_TAN, // tan(v) + OPHANDLE_ARCSIN, // arcsin(v) + OPHANDLE_ARCCOS, // arccos(v) + OPHANDLE_ARCTAN, // arctan(v) +// monadic operators + OPHANDLE_OPENING, // (v '(', starts subexpression + OPHANDLE_NOT, // !v NOT v bit-wise NOT + OPHANDLE_NEGATE, // -v Negate + OPHANDLE_LOWBYTEOF, // v Highbyte of + OPHANDLE_BANKBYTEOF, // ^v Bankbyte of +// dyadic operators + OPHANDLE_CLOSING, // v) ')', ends subexpression + OPHANDLE_POWEROF, // v^w + OPHANDLE_MULTIPLY, // v*w + OPHANDLE_DIVIDE, // v/w (Integer) Division + OPHANDLE_INTDIV, // v/w v DIV w Integer Division + OPHANDLE_MODULO, // v%w v MOD w Remainder + OPHANDLE_SL, // v<>w v ASR w Arithmetic shift right + OPHANDLE_LSR, // v>>>w v LSR w Logical shift right + OPHANDLE_ADD, // v+w + OPHANDLE_SUBTRACT, // v-w + OPHANDLE_EQUALS, // v=w + OPHANDLE_LE, // v<=w + OPHANDLE_LESSTHAN, // v< w + OPHANDLE_GE, // v>=w + OPHANDLE_GREATERTHAN, // v> w + OPHANDLE_NOTEQUAL, // v!=w v<>w v>>= 1; + } + return result; +} + + +// arithmetic shift right (works even if C compiler does not support it) +static intval_t my_asr(intval_t left, intval_t right) +{ + // if first operand is positive or zero, ASR and LSR are equivalent, + // so just do it and return the result: + if (left >= 0) + return left >> right; + + // However, if the first operand is negative, the result is + // implementation-defined: While most compilers will do ASR, some others + // might do LSR instead, and *theoretically*, it is even possible for a + // compiler to define silly stuff like "shifting a negative value to the + // right will always return -1". + // Therefore, in case of a negative operand, we'll use this quick and + // simple workaround: + return ~((~left) >> right); +} + + +// Lookup (and create, if necessary) label tree item and return its value. +// DynaBuf holds the label's name and "zone" its zone. +// This function is not allowed to change DynaBuf because that's where the +// label name is stored! +static void get_label_value(zone_t zone) +{ + struct label_t *label; + + // if the label gets created now, mark it as unsure + label = Label_find(zone, MVALUE_UNSURE); + // in first pass, count usage + if (pass_count == 0) + label->usage++; + // push operand, regardless of whether int or float + operand_stack[operand_sp] = label->result; + operand_stack[operand_sp++].flags |= MVALUE_EXISTS; +} + + +// Parse quoted character. +// The character will be converted using the current encoding. +static void parse_quoted_character(char closing_quote) +{ + intval_t value; + + // read character to parse - make sure not at end of statement + if (GetQuotedByte() == CHAR_EOS) + return; + + // on empty string, complain + if (GotByte == closing_quote) { + Throw_error(exception_missing_string); + Input_skip_remainder(); + return; + } + + // parse character + value = (intval_t) Encoding_encode_char(GotByte); + // Read closing quote (hopefully) + if (GetQuotedByte() == closing_quote) + GetByte(); // if length == 1, proceed with next byte + else + if (GotByte) { + // if longer than one character + Throw_error("There's more than one character."); + Input_skip_remainder(); + } + PUSH_INTOPERAND(value, MVALUE_GIVEN | MVALUE_ISBYTE); + // Now GotByte = char following closing quote (or CHAR_EOS on error) +} + + +// Parse hexadecimal value. It accepts "0" to "9", "a" to "f" and "A" to "F". +// Capital letters will be converted to lowercase letters using the flagtable. +// The current value is stored as soon as a character is read that is none of +// those given above. +static void parse_hexadecimal_value(void) // Now GotByte = "$" or "x" +{ + char byte; + int go_on, // continue loop flag + digits = -1, // digit counter + flags = MVALUE_GIVEN; + intval_t value = 0; + + do { + digits++; + go_on = 0; + byte = GetByte(); + // first, convert "A-F" to "a-f" + byte |= (BYTEFLAGS(byte) & BYTEIS_UPCASE); + // if digit, add digit value + if ((byte >= '0') && (byte <= '9')) { + value = (value << 4) + (byte - '0'); + go_on = 1; // keep going + } + // if legal ("a-f") character, add character value + if ((byte >= 'a') && (byte <= 'f')) { + value = (value << 4) + (byte - 'a') + 10; + go_on = 1; // keep going + } + } while (go_on); + // set force bits + if (digits > 2) { + if (digits > 4) { + if (value < 65536) + flags |= MVALUE_FORCE24; + } else { + if (value < 256) + flags |= MVALUE_FORCE16; + } + } + PUSH_INTOPERAND(value, flags); + // Now GotByte = non-hexadecimal char +} + + +// parse fractional part of a floating-point value +static void parse_frac_part(int integer_part) // Now GotByte = first digit after decimal point +{ + double denominator = 1, + fpval = integer_part; + + // parse digits until no more + while ((GotByte >= '0') && (GotByte <= '9')) { + fpval = 10 * fpval + (GotByte & 15); // this works. it's ASCII. + denominator *= 10; + GetByte(); + } + // FIXME - add possibility to read 'e' and exponent! + PUSH_FPOPERAND(fpval / denominator, MVALUE_GIVEN); +} + + +// Parse a decimal value. As decimal values don't use any prefixes, this +// function expects the first digit to be read already. If the first two +// digits are "0x", this function branches to the one for parsing hexadecimal +// values. If a decimal point is read, this function branches to the one for +// parsing floating-point values. +// This function accepts '0' through '9' and one dot ('.') as the decimal +// point. The current value is stored as soon as a character is read that is +// none of those given above. Float usage is only activated when a decimal +// point has been found, so don't expect "100000000000000000000" to work. +// CAUTION: "100000000000000000000.0" won't work either, because when the +// decimal point gets parsed, the integer value will have overflown already. +static void parse_decimal_value(void) // Now GotByte = first digit +{ + intval_t intval = (GotByte & 15); // this works. it's ASCII. + + GetByte(); + if ((intval == 0) && (GotByte == 'x')) { + parse_hexadecimal_value(); + return; + } + // parse digits until no more + while ((GotByte >= '0') && (GotByte <= '9')) { + intval = 10 * intval + (GotByte & 15); // ASCII, see above + GetByte(); + } + // check whether it's a float + if (GotByte == '.') { + // read fractional part + GetByte(); + parse_frac_part(intval); + } else { + PUSH_INTOPERAND(intval, MVALUE_GIVEN); + } + // Now GotByte = non-decimal char +} + + +// Parse octal value. It accepts "0" to "7". The current value is stored as +// soon as a character is read that is none of those given above. +static void parse_octal_value(void) // Now GotByte = "&" +{ + intval_t value = 0; + int flags = MVALUE_GIVEN, + digits = 0; // digit counter + + GetByte(); + while ((GotByte >= '0') && (GotByte <= '7')) { + value = (value << 3) + (GotByte & 7); // this works. it's ASCII. + digits++; + GetByte(); + } + // set force bits + if (digits > 3) { + if (digits > 6) { + if (value < 65536) + flags |= MVALUE_FORCE24; + } else { + if (value < 256) + flags |= MVALUE_FORCE16; + } + } + PUSH_INTOPERAND(value, flags); + // Now GotByte = non-octal char +} + + +// Parse binary value. Apart from '0' and '1', it also accepts the characters +// '.' and '#', this is much more readable. The current value is stored as soon +// as a character is read that is none of those given above. +static void parse_binary_value(void) // Now GotByte = "%" +{ + intval_t value = 0; + int go_on = TRUE, // continue loop flag + flags = MVALUE_GIVEN, + digits = -1; // digit counter + + do { + digits++; + switch (GetByte()) { + case '0': + case '.': + value <<= 1; + break; + case '1': + case '#': + value = (value << 1) | 1; + break; + default: + go_on = 0; + } + } while (go_on); + // set force bits + if (digits > 8) { + if (digits > 16) { + if (value < 65536) + flags |= MVALUE_FORCE24; + } else { + if (value < 256) + flags |= MVALUE_FORCE16; + } + } + PUSH_INTOPERAND(value, flags); + // Now GotByte = non-binary char +} + + +// Parse function call (sin(), cos(), arctan(), ...) +static void parse_function_call(void) +{ + void *node_body; + + // make lower case version of name in local dynamic buffer + DynaBuf_to_lower(function_dyna_buf, GlobalDynaBuf); + // search for tree item + if (Tree_easy_scan(function_tree, &node_body, function_dyna_buf)) + PUSH_OPERATOR((struct operator_t*) node_body); + else + Throw_error("Unknown function."); +} + + +// Expect operand or monadic operator (hopefully inlined) +static void expect_operand_or_monadic_operator(void) +{ + struct operator_t *operator; + int perform_negation; + + SKIPSPACE(); + switch (GotByte) { + case '+': // anonymous forward label + // count plus signs to build name of anonymous label + DYNABUF_CLEAR(GlobalDynaBuf); + do + DYNABUF_APPEND(GlobalDynaBuf, '+'); + while (GetByte() == '+'); + Label_fix_forward_name(); + get_label_value(Section_now->zone); + goto now_expect_dyadic; + + case '-': // NEGATION operator or anonymous backward label + // count minus signs in case it's an anonymous backward label + perform_negation = FALSE; + DYNABUF_CLEAR(GlobalDynaBuf); + do { + DYNABUF_APPEND(GlobalDynaBuf, '-'); + perform_negation = !perform_negation; + } while (GetByte() == '-'); + SKIPSPACE(); + if (BYTEFLAGS(GotByte) & FOLLOWS_ANON) { + DynaBuf_append(GlobalDynaBuf, '\0'); + get_label_value(Section_now->zone); + goto now_expect_dyadic; + } + + if (perform_negation) + PUSH_OPERATOR(&ops_negate); + // State doesn't change + break; +// Real monadic operators (state doesn't change, still ExpectMonadic) + case '!': // NOT operator + operator = &ops_not; + goto get_byte_and_push_monadic; + + case '<': // LOWBYTE operator + operator = &ops_lowbyteof; + goto get_byte_and_push_monadic; + + case '>': // HIGHBYTE operator + operator = &ops_highbyteof; + goto get_byte_and_push_monadic; + + case '^': // BANKBYTE operator + operator = &ops_bankbyteof; + goto get_byte_and_push_monadic; + +// Faked monadic operators + case '(': // left parenthesis + operator = &ops_opening; + goto get_byte_and_push_monadic; + + case ')': // right parenthesis + // this makes "()" also throw a syntax error + Throw_error(exception_syntax); + alu_state = STATE_ERROR; + break; +// Operands (values, state changes to ExpectDyadic) + case '"': // Quoted character + case '\'': // Quoted character + // Character will be converted using current encoding + parse_quoted_character(GotByte); + // Now GotByte = char following closing quote + goto now_expect_dyadic; + + case '%': // Binary value + parse_binary_value(); // Now GotByte = non-binary char + goto now_expect_dyadic; + + case '&': // Octal value + parse_octal_value(); // Now GotByte = non-octal char + goto now_expect_dyadic; + + case '$': // Hexadecimal value + parse_hexadecimal_value(); + // Now GotByte = non-hexadecimal char + goto now_expect_dyadic; + + case '*': // Program counter + GetByte(); // proceed with next char + PUSH_INTOPERAND(CPU_pc.intval, CPU_pc.flags | MVALUE_EXISTS); + // Now GotByte = char after closing quote + goto now_expect_dyadic; + + case '.': // Local label or fractional part of float value + GetByte(); // start after '.' + // check for fractional part of float value + if ((GotByte >= '0') && (GotByte <= '9')) { + parse_frac_part(0); + // Now GotByte = non-decimal char + goto now_expect_dyadic; + } + + if (Input_read_keyword()) { + // Now GotByte = illegal char + get_label_value(Section_now->zone); + goto now_expect_dyadic; + } + + alu_state = STATE_ERROR; + break; + // Decimal values and global labels + default: // all other characters + if ((GotByte >= '0') && (GotByte <= '9')) { + parse_decimal_value(); + // Now GotByte = non-decimal char + goto now_expect_dyadic; + } + + if (BYTEFLAGS(GotByte) & STARTS_KEYWORD) { + register int length; + // Read global label (or "NOT") + length = Input_read_keyword(); + // Now GotByte = illegal char + // Check for NOT. Okay, it's hardcoded, + // but so what? Sue me... + if ((length == 3) + && ((GlobalDynaBuf->buffer[0] | 32) == 'n') + && ((GlobalDynaBuf->buffer[1] | 32) == 'o') + && ((GlobalDynaBuf->buffer[2] | 32) == 't')) { + PUSH_OPERATOR(&ops_not); + // state doesn't change + } else { + if (GotByte == '(') { + parse_function_call(); + } else { + get_label_value(ZONE_GLOBAL); + goto now_expect_dyadic; + } + + } + } else { + // illegal character read - so don't go on + PUSH_INTOPERAND(0, 0); + // push pseudo value, EXISTS flag is clear + if (operator_stack[operator_sp-1] == &ops_return) { + PUSH_OPERATOR(&ops_end); + alu_state = STATE_TRY_TO_REDUCE_STACKS; + } else { + Throw_error(exception_syntax); + alu_state = STATE_ERROR; + } + } + break; + +// no other possibilities, so here are the shared endings + +get_byte_and_push_monadic: + GetByte(); + PUSH_OPERATOR(operator); + // State doesn't change + break; + +now_expect_dyadic: + alu_state = STATE_EXPECT_DYADIC_OPERATOR; + break; + } +} + + +// Expect dyadic operator (hopefully inlined) +static void expect_dyadic_operator(void) +{ + void *node_body; + struct operator_t *op; + + SKIPSPACE(); + switch (GotByte) { +// Single-character dyadic operators + case '^': // "to the power of" + op = &ops_powerof; + goto get_byte_and_push_dyadic; + + case '+': // add + op = &ops_add; + goto get_byte_and_push_dyadic; + + case '-': // subtract + op = &ops_subtract; + goto get_byte_and_push_dyadic; + + case '*': // multiply + op = &ops_multiply; + goto get_byte_and_push_dyadic; + + case '/': // divide + op = &ops_divide; + goto get_byte_and_push_dyadic; + + case '%': // modulo + op = &ops_modulo; + goto get_byte_and_push_dyadic; + + case '&': // bitwise AND + op = &ops_and; + goto get_byte_and_push_dyadic; + + case '|': // bitwise OR + op = &ops_or; + goto get_byte_and_push_dyadic; + +// This part is commented out because there is no XOR character defined +// case ???: // bitwise exclusive OR +// op = &ops_xor; +// goto get_byte_and_push_dyadic; + + case '=': // is equal + op = &ops_equals; + goto get_byte_and_push_dyadic; + + case ')': // closing parenthesis + op = &ops_closing; + goto get_byte_and_push_dyadic; + +// Multi-character dyadic operators + case '!': // "!=" + if (GetByte() == '=') { + op = &ops_notequal; + goto get_byte_and_push_dyadic; + } + + Throw_error(exception_syntax); + alu_state = STATE_ERROR; + break; + case '<': // "<", "<=", "<<" and "<>" + switch (GetByte()) { + case '=': // "<=", less or equal + op = &ops_le; + goto get_byte_and_push_dyadic; + + case '<': // "<<", shift left + op = &ops_sl; + goto get_byte_and_push_dyadic; + + case '>': // "<>", not equal + op = &ops_notequal; + goto get_byte_and_push_dyadic; + + default: // "<", less than + op = &ops_lessthan; + goto push_dyadic; + + } + //break; unreachable + case '>': // ">", ">=", ">>", ">>>" and "><" + switch (GetByte()) { + case '=': // ">=", greater or equal + op = &ops_ge; + goto get_byte_and_push_dyadic; + + case '<': // "><", not equal + op = &ops_notequal; + goto get_byte_and_push_dyadic; + + case '>': // ">>" or ">>>", shift right + op = &ops_asr; // arithmetic shift right + if (GetByte() != '>') + goto push_dyadic; + + op = &ops_lsr; // logical shift right + goto get_byte_and_push_dyadic; + + default: // ">", greater than + op = &ops_greaterthan; + goto push_dyadic; + + } + //break; unreachable +// end of expression or text version of dyadic operator + default: + // check string version of operators + if (BYTEFLAGS(GotByte) & STARTS_KEYWORD) { + Input_read_and_lower_keyword(); + // Now GotByte = illegal char + // search for tree item + if (Tree_easy_scan(operator_tree, &node_body, GlobalDynaBuf)) { + op = node_body; + goto push_dyadic; + } + + // goto means we don't need an "else {" here + Throw_error("Unknown operator."); + alu_state = STATE_ERROR; + } else { + op = &ops_end; + goto push_dyadic; + } + + } + return; + +// shared endings +get_byte_and_push_dyadic: + GetByte(); +push_dyadic: + PUSH_OPERATOR(op); + alu_state = STATE_TRY_TO_REDUCE_STACKS; +} + + +// call C's sin/cos/tan function +static void perform_fp(double (*fn)(double)) +{ + if ((RIGHT_FLAGS & MVALUE_IS_FP) == 0) { + RIGHT_FPVAL = RIGHT_INTVAL; + RIGHT_FLAGS |= MVALUE_IS_FP; + } + RIGHT_FPVAL = fn(RIGHT_FPVAL); +} + + +// make sure arg is in [-1, 1] range before calling function +static void perform_ranged_fp(double (*fn)(double)) +{ + if ((RIGHT_FLAGS & MVALUE_IS_FP) == 0) { + RIGHT_FPVAL = RIGHT_INTVAL; + RIGHT_FLAGS |= MVALUE_IS_FP; + } + if ((RIGHT_FPVAL >= -1) && (RIGHT_FPVAL <= 1)) { + RIGHT_FPVAL = fn(RIGHT_FPVAL); + } else { + if (RIGHT_FLAGS & MVALUE_DEFINED) + Throw_error("Argument out of range."); + RIGHT_FPVAL = 0; + } +} + + +// convert right-hand value from fp to int +static void right_fp_to_int() +{ + RIGHT_INTVAL = RIGHT_FPVAL; + RIGHT_FLAGS &= ~MVALUE_IS_FP; +} + + +// check both left-hand and right-hand values. if float, convert to int. +// in first pass, throw warning +static void both_ensure_int(int warn) +{ + if (LEFT_FLAGS & MVALUE_IS_FP) { + LEFT_INTVAL = LEFT_FPVAL; + LEFT_FLAGS &= ~MVALUE_IS_FP; + } + if (RIGHT_FLAGS & MVALUE_IS_FP) { + RIGHT_INTVAL = RIGHT_FPVAL; + RIGHT_FLAGS &= ~MVALUE_IS_FP; + } + // FIXME - "warn" is not used + Throw_first_pass_warning("Converted to integer for binary logic operator."); +} + + +// check both left-hand and right-hand values. if int, convert to float. +static void both_ensure_fp(void) +{ + if ((LEFT_FLAGS & MVALUE_IS_FP) == 0) { + LEFT_FPVAL = LEFT_INTVAL; + LEFT_FLAGS |= MVALUE_IS_FP; + } + if ((RIGHT_FLAGS & MVALUE_IS_FP) == 0) { + RIGHT_FPVAL = RIGHT_INTVAL; + RIGHT_FLAGS |= MVALUE_IS_FP; + } +} + + +// make sure both values are float, but mark left one as int (will become one) +static void ensure_int_from_fp(void) +{ + both_ensure_fp(); + LEFT_FLAGS &= ~MVALUE_IS_FP; +} + + +// Try to reduce stacks by performing high-priority operations +static void try_to_reduce_stacks(int *open_parentheses) +{ + if (operator_sp < 2) { + alu_state = STATE_EXPECT_OPERAND_OR_MONADIC_OPERATOR; + return; + } + + if (operator_stack[operator_sp - 2]->priority < operator_stack[operator_sp - 1]->priority) { + alu_state = STATE_EXPECT_OPERAND_OR_MONADIC_OPERATOR; + return; + } + + switch (operator_stack[operator_sp - 2]->handle) { +// special (pseudo) operators + case OPHANDLE_RETURN: + // don't touch indirect_flag; needed for INDIRECT flag + operator_sp--; // decrement operator stack pointer + alu_state = STATE_END; + break; + case OPHANDLE_OPENING: + indirect_flag = MVALUE_INDIRECT; // parentheses found + switch (operator_stack[operator_sp - 1]->handle) { + case OPHANDLE_CLOSING: // matching parentheses + operator_sp -= 2; // remove both of them + alu_state = STATE_EXPECT_DYADIC_OPERATOR; + break; + case OPHANDLE_END: // unmatched parenthesis + (*open_parentheses)++; // count + goto RNTLObutDontTouchIndirectFlag; + + default: + Bug_found("StrangeParenthesis", operator_stack[operator_sp - 1]->handle); + } + break; + case OPHANDLE_CLOSING: + Throw_error("Too many ')'."); + goto remove_next_to_last_operator; + +// functions + case OPHANDLE_INT: + if (RIGHT_FLAGS & MVALUE_IS_FP) + right_fp_to_int(); + goto remove_next_to_last_operator; + + case OPHANDLE_FLOAT: + // convert right-hand value from int to fp + if ((RIGHT_FLAGS & MVALUE_IS_FP) == 0) { + RIGHT_FPVAL = RIGHT_INTVAL; + RIGHT_FLAGS |= MVALUE_IS_FP; + } + goto remove_next_to_last_operator; + + case OPHANDLE_SIN: + perform_fp(sin); + goto remove_next_to_last_operator; + + case OPHANDLE_COS: + perform_fp(cos); + goto remove_next_to_last_operator; + + case OPHANDLE_TAN: + perform_fp(tan); + goto remove_next_to_last_operator; + + case OPHANDLE_ARCSIN: + perform_ranged_fp(asin); + goto remove_next_to_last_operator; + + case OPHANDLE_ARCCOS: + perform_ranged_fp(acos); + goto remove_next_to_last_operator; + + case OPHANDLE_ARCTAN: + perform_fp(atan); + goto remove_next_to_last_operator; + +// monadic operators + case OPHANDLE_NOT: + // different operations for fp and int + if (RIGHT_FLAGS & MVALUE_IS_FP) + right_fp_to_int(); + RIGHT_INTVAL = ~(RIGHT_INTVAL); + RIGHT_FLAGS &= ~MVALUE_ISBYTE; + goto remove_next_to_last_operator; + + case OPHANDLE_NEGATE: + // different operations for fp and int + if (RIGHT_FLAGS & MVALUE_IS_FP) + RIGHT_FPVAL = -(RIGHT_FPVAL); + else + RIGHT_INTVAL = -(RIGHT_INTVAL); + RIGHT_FLAGS &= ~MVALUE_ISBYTE; + goto remove_next_to_last_operator; + + case OPHANDLE_LOWBYTEOF: + // fp becomes int + if (RIGHT_FLAGS & MVALUE_IS_FP) + right_fp_to_int(); + RIGHT_INTVAL = (RIGHT_INTVAL) & 255; + RIGHT_FLAGS |= MVALUE_ISBYTE; + RIGHT_FLAGS &= ~MVALUE_FORCEBITS; + goto remove_next_to_last_operator; + + case OPHANDLE_HIGHBYTEOF: + // fp becomes int + if (RIGHT_FLAGS & MVALUE_IS_FP) + right_fp_to_int(); + RIGHT_INTVAL = ((RIGHT_INTVAL) >> 8) & 255; + RIGHT_FLAGS |= MVALUE_ISBYTE; + RIGHT_FLAGS &= ~MVALUE_FORCEBITS; + goto remove_next_to_last_operator; + + case OPHANDLE_BANKBYTEOF: + // fp becomes int + if (RIGHT_FLAGS & MVALUE_IS_FP) + right_fp_to_int(); + RIGHT_INTVAL = ((RIGHT_INTVAL) >> 16) & 255; + RIGHT_FLAGS |= MVALUE_ISBYTE; + RIGHT_FLAGS &= ~MVALUE_FORCEBITS; + goto remove_next_to_last_operator; + +// dyadic operators + case OPHANDLE_POWEROF: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + both_ensure_fp(); + LEFT_FPVAL = pow(LEFT_FPVAL, RIGHT_FPVAL); + goto handle_flags_and_dec_stacks; + } + + if (RIGHT_INTVAL >= 0) { + LEFT_INTVAL = my_pow(LEFT_INTVAL, RIGHT_INTVAL); + } else { + if (RIGHT_FLAGS & MVALUE_DEFINED) + Throw_error("Exponent is negative."); + LEFT_INTVAL = 0; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_MULTIPLY: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + both_ensure_fp(); + LEFT_FPVAL *= RIGHT_FPVAL; + } else { + LEFT_INTVAL *= RIGHT_INTVAL; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_DIVIDE: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + both_ensure_fp(); + if (RIGHT_FPVAL) { + LEFT_FPVAL /= RIGHT_FPVAL; + } else { + if (RIGHT_FLAGS & MVALUE_DEFINED) + Throw_error(exception_div_by_zero); + LEFT_FPVAL = 0; + } + } else { + if (RIGHT_INTVAL) { + LEFT_INTVAL /= RIGHT_INTVAL; + } else { + if (RIGHT_FLAGS & MVALUE_DEFINED) + Throw_error(exception_div_by_zero); + LEFT_INTVAL = 0; + } + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_INTDIV: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + both_ensure_fp(); + if (RIGHT_FPVAL) { + LEFT_INTVAL = LEFT_FPVAL / RIGHT_FPVAL; + } else { + if (RIGHT_FLAGS & MVALUE_DEFINED) + Throw_error(exception_div_by_zero); + LEFT_INTVAL = 0; + } + LEFT_FLAGS &= ~MVALUE_IS_FP; + } else { + if (RIGHT_INTVAL) { + LEFT_INTVAL /= RIGHT_INTVAL; + } else { + if (RIGHT_FLAGS & MVALUE_DEFINED) + Throw_error(exception_div_by_zero); + LEFT_INTVAL = 0; + } + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_MODULO: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) + both_ensure_int(FALSE); + if (RIGHT_INTVAL) { + LEFT_INTVAL %= RIGHT_INTVAL; + } else { + if (RIGHT_FLAGS & MVALUE_DEFINED) + Throw_error(exception_div_by_zero); + LEFT_INTVAL = 0; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_ADD: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + both_ensure_fp(); + LEFT_FPVAL += RIGHT_FPVAL; + } else { + LEFT_INTVAL += RIGHT_INTVAL; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_SUBTRACT: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + both_ensure_fp(); + LEFT_FPVAL -= RIGHT_FPVAL; + } else { + LEFT_INTVAL -= RIGHT_INTVAL; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_SL: + if (RIGHT_FLAGS & MVALUE_IS_FP) + right_fp_to_int(); + if (LEFT_FLAGS & MVALUE_IS_FP) + LEFT_FPVAL *= (1 << RIGHT_INTVAL); + else + LEFT_INTVAL <<= RIGHT_INTVAL; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_ASR: + if (RIGHT_FLAGS & MVALUE_IS_FP) + right_fp_to_int(); + if (LEFT_FLAGS & MVALUE_IS_FP) + LEFT_FPVAL /= (1 << RIGHT_INTVAL); + else + LEFT_INTVAL = my_asr(LEFT_INTVAL, RIGHT_INTVAL); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_LSR: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) + both_ensure_int(TRUE); + LEFT_INTVAL = ((uintval_t) LEFT_INTVAL) >> RIGHT_INTVAL; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_LE: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + ensure_int_from_fp(); + LEFT_INTVAL = (LEFT_FPVAL <= RIGHT_FPVAL); + } else { + LEFT_INTVAL = (LEFT_INTVAL <= RIGHT_INTVAL); + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_LESSTHAN: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + ensure_int_from_fp(); + LEFT_INTVAL = (LEFT_FPVAL < RIGHT_FPVAL); + } else { + LEFT_INTVAL = (LEFT_INTVAL < RIGHT_INTVAL); + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_GE: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + ensure_int_from_fp(); + LEFT_INTVAL = (LEFT_FPVAL >= RIGHT_FPVAL); + } else { + LEFT_INTVAL = (LEFT_INTVAL >= RIGHT_INTVAL); + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_GREATERTHAN: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + ensure_int_from_fp(); + LEFT_INTVAL = (LEFT_FPVAL > RIGHT_FPVAL); + } else { + LEFT_INTVAL = (LEFT_INTVAL > RIGHT_INTVAL); + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_NOTEQUAL: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + ensure_int_from_fp(); + LEFT_INTVAL = (LEFT_FPVAL != RIGHT_FPVAL); + } else { + LEFT_INTVAL = (LEFT_INTVAL != RIGHT_INTVAL); + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_EQUALS: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) { + ensure_int_from_fp(); + LEFT_INTVAL = (LEFT_FPVAL == RIGHT_FPVAL); + } else { + LEFT_INTVAL = (LEFT_INTVAL == RIGHT_INTVAL); + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_AND: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) + both_ensure_int(TRUE); + LEFT_INTVAL &= RIGHT_INTVAL; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_EOR: + Throw_first_pass_warning("\"EOR\" is deprecated; use \"XOR\" instead."); + /*FALLTHROUGH*/ + case OPHANDLE_XOR: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) + both_ensure_int(TRUE); + LEFT_INTVAL ^= RIGHT_INTVAL; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_OR: + if ((RIGHT_FLAGS | LEFT_FLAGS) & MVALUE_IS_FP) + both_ensure_int(TRUE); + LEFT_INTVAL |= RIGHT_INTVAL; + goto handle_flags_and_dec_stacks; + + default: + Bug_found("IllegalOperatorHandle", operator_stack[operator_sp - 2]->handle); + } + return; + +// shared endings: + +// entry point for dyadic operators +handle_flags_and_dec_stacks: + // Handle flags and decrement value stack pointer + // "OR" EXISTS, UNSURE and FORCEBIT flags + LEFT_FLAGS |= RIGHT_FLAGS & + (MVALUE_EXISTS|MVALUE_UNSURE|MVALUE_FORCEBITS); + // "AND" DEFINED flag + LEFT_FLAGS &= (RIGHT_FLAGS | ~MVALUE_DEFINED); + LEFT_FLAGS &= ~MVALUE_ISBYTE; // clear ISBYTE flag + operand_sp--; +// entry point for monadic operators +remove_next_to_last_operator: + // toplevel operation was something other than parentheses + indirect_flag = 0; +// entry point for '(' operator (has set indirect_flag, so don't clear now) +RNTLObutDontTouchIndirectFlag: + // Remove operator and shift down next one + operator_stack[operator_sp-2] = operator_stack[operator_sp-1]; + operator_sp--; // decrement operator stack pointer +} + + +// The core of it. Returns number of parentheses left open. +// FIXME - make state machine using function pointers? or too slow? +static int parse_expression(struct result_t *result) +{ + int open_parentheses = 0; + + operator_sp = 0; // operator stack pointer + operand_sp = 0; // value stack pointer + // begin by reading value (or monadic operator) + alu_state = STATE_EXPECT_OPERAND_OR_MONADIC_OPERATOR; + indirect_flag = 0; // Contains either 0 or MVALUE_INDIRECT + PUSH_OPERATOR(&ops_return); + do { + // check stack sizes. enlarge if needed + if (operator_sp >= operator_stk_size) + enlarge_operator_stack(); + if (operand_sp >= operand_stk_size) + enlarge_operand_stack(); + switch (alu_state) { + case STATE_EXPECT_OPERAND_OR_MONADIC_OPERATOR: + expect_operand_or_monadic_operator(); + break; + case STATE_EXPECT_DYADIC_OPERATOR: + expect_dyadic_operator(); + break; // no fallthrough; state might + // have been changed to END or ERROR + case STATE_TRY_TO_REDUCE_STACKS: + try_to_reduce_stacks(&open_parentheses); + break; + case STATE_MAX_GO_ON: // suppress + case STATE_ERROR: // compiler + case STATE_END: // warnings + break; + } + } while (alu_state < STATE_MAX_GO_ON); + // done. check state. + if (alu_state == STATE_END) { + // check for bugs + if (operand_sp != 1) + Bug_found("OperandStackNotEmpty", operand_sp); + if (operator_sp != 1) + Bug_found("OperatorStackNotEmpty", operator_sp); + // copy result + *result = operand_stack[0]; + result->flags |= indirect_flag; // OR indirect flag + // only allow *one* force bit + if (result->flags & MVALUE_FORCE24) + result->flags &= ~(MVALUE_FORCE16 | MVALUE_FORCE08); + else if (result->flags & MVALUE_FORCE16) + result->flags &= ~MVALUE_FORCE08; + // if there was nothing to parse, mark as undefined + // (so ALU_defined_int() can react) + if ((result->flags & MVALUE_EXISTS) == 0) + result->flags &= ~MVALUE_DEFINED; + // do some checks depending on int/float + if (result->flags & MVALUE_IS_FP) { +/*float*/ // if undefined, return zero + if ((result->flags & MVALUE_DEFINED) == 0) + result->val.fpval = 0; + // if value is sure, check to set ISBYTE + else if (((result->flags & MVALUE_UNSURE) == 0) + && (result->val.fpval <= 255.0) + && (result->val.fpval >= -128.0)) + result->flags |= MVALUE_ISBYTE; + } else { +/*int*/ // if undefined, return zero + if ((result->flags & MVALUE_DEFINED) == 0) + result->val.intval = 0; + // if value is sure, check to set ISBYTE + else if (((result->flags & MVALUE_UNSURE) == 0) + && (result->val.intval <= 255) + && (result->val.intval >= -128)) + result->flags |= MVALUE_ISBYTE; + } + } else { + // State is STATE_ERROR. But actually, nobody cares. + // ...errors have already been reported anyway. :) + } + // return number of open (unmatched) parentheses + return open_parentheses; +} + + +// These functions handle numerical expressions. There are operators for +// arithmetic, logic, shift and comparison operations. +// There are several different ways to call the core function: +// intval_t ALU_any_int(void); +// returns int value (0 if result was undefined) +// intval_t ALU_defined_int(void); +// returns int value +// if result was undefined, serious error is thrown +// void ALU_int_result(result_int_t*); +// stores int value and flags (floats are transformed to int) +// void ALU_any_result(result_t*); +// stores value and flags (result may be either int or float) +// int ALU_liberal_int(result_int_t*); +// stores int value and flags. allows one '(' too many (for x- +// indirect addressing). returns number of additional '(' (1 or 0). +// int ALU_optional_defined_int(intval_t*); +// stores int value if given. Returns whether stored. +// Throws error if undefined. + +// return int value (if result is undefined, returns zero) +// If the result's "exists" flag is clear (=empty expression), it throws an +// error. +// If the result's "defined" flag is clear, result_is_undefined() is called. +intval_t ALU_any_int(void) +{ + struct result_t result; + + if (parse_expression(&result)) + Throw_error(exception_paren_open); + if ((result.flags & MVALUE_EXISTS) == 0) + Throw_error(exception_no_value); + else if ((result.flags & MVALUE_DEFINED) == 0) + result_is_undefined(); + if (result.flags & MVALUE_IS_FP) + return result.val.fpval; + else + return result.val.intval; +} + + +// return int value (if result is undefined, serious error is thrown) +intval_t ALU_defined_int(void) +{ + struct result_t result; + + if (parse_expression(&result)) + Throw_error(exception_paren_open); + if ((result.flags & MVALUE_DEFINED) == 0) + Throw_serious_error(exception_undefined); + if (result.flags & MVALUE_IS_FP) + return result.val.fpval; + else + return result.val.intval; +} + + +// Store int value if given. Returns whether stored. Throws error if undefined. +// This function needs either a defined value or no expression at all. So +// empty expressions are accepted, but undefined ones are not. +// If the result's "defined" flag is clear and the "exists" flag is set, it +// throws a serious error and therefore stops assembly. +int ALU_optional_defined_int(intval_t *target) +{ + struct result_t result; + + if (parse_expression(&result)) + Throw_error(exception_paren_open); + if ((result.flags & MVALUE_GIVEN) == MVALUE_EXISTS) + Throw_serious_error(exception_undefined); + if ((result.flags & MVALUE_EXISTS) == 0) + return 0; + // something was given, so store + if (result.flags & MVALUE_IS_FP) + *target = result.val.fpval; + else + *target = result.val.intval; + return 1; +} + + +// Store int value and flags (floats are transformed to int) +// It the result's "exists" flag is clear (=empty expression), it throws an +// error. +// If the result's "defined" flag is clear, result_is_undefined() is called. +void ALU_int_result(struct result_int_t *intresult) +{ + struct result_t result; + + if (parse_expression(&result)) + Throw_error(exception_paren_open); + if ((result.flags & MVALUE_EXISTS) == 0) + Throw_error(exception_no_value); + else if ((result.flags & MVALUE_DEFINED) == 0) + result_is_undefined(); + if (result.flags & MVALUE_IS_FP) { + intresult->intval = result.val.fpval; + intresult->flags = result.flags & ~MVALUE_IS_FP; + } else { + intresult->intval = result.val.intval; + intresult->flags = result.flags; + } +} + + +// Store int value and flags. +// This function allows for one '(' too many. Needed when parsing indirect +// addressing modes where internal indices have to be possible. Returns number +// of parentheses still open (either 0 or 1). +int ALU_liberal_int(struct result_int_t *intresult) +{ + struct result_t result; + int parentheses_still_open; + + parentheses_still_open = parse_expression(&result); + if (parentheses_still_open > 1) { + parentheses_still_open = 0; + Throw_error(exception_paren_open); + } + if ((result.flags & MVALUE_EXISTS) + && ((result.flags & MVALUE_DEFINED) == 0)) + result_is_undefined(); + if (result.flags & MVALUE_IS_FP) { + intresult->intval = result.val.fpval; + intresult->flags = result.flags & ~MVALUE_IS_FP; + } else { + intresult->intval = result.val.intval; + intresult->flags = result.flags; + } + return parentheses_still_open; +} + + +// Store value and flags (result may be either int or float) +// It the result's "exists" flag is clear (=empty expression), it throws an +// error. +// If the result's "defined" flag is clear, result_is_undefined() is called. +void ALU_any_result(struct result_t *result) +{ + if (parse_expression(result)) + Throw_error(exception_paren_open); + if ((result->flags & MVALUE_EXISTS) == 0) + Throw_error(exception_no_value); + else if ((result->flags & MVALUE_DEFINED) == 0) + result_is_undefined(); +} diff --git a/trunk/src/alu.h b/trunk/src/alu.h new file mode 100644 index 0000000..60e74c2 --- /dev/null +++ b/trunk/src/alu.h @@ -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 diff --git a/trunk/src/basics.c b/trunk/src/basics.c new file mode 100644 index 0000000..7862a94 --- /dev/null +++ b/trunk/src/basics.c @@ -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 +#include +#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, + ""); + } else { + // integer + if (result.flags & MVALUE_DEFINED) + DynaBuf_add_signed_long( + user_message, + result.val.intval); + else + DynaBuf_add_string( + user_message, + ""); + } + } + } 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); +} diff --git a/trunk/src/basics.h b/trunk/src/basics.h new file mode 100644 index 0000000..e658da6 --- /dev/null +++ b/trunk/src/basics.h @@ -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 diff --git a/trunk/src/cliargs.c b/trunk/src/cliargs.c new file mode 100644 index 0000000..c311d3a --- /dev/null +++ b/trunk/src/cliargs.c @@ -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 +#include +#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); + } +} diff --git a/trunk/src/cliargs.h b/trunk/src/cliargs.h new file mode 100644 index 0000000..aa1efad --- /dev/null +++ b/trunk/src/cliargs.h @@ -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 diff --git a/trunk/src/config.h b/trunk/src/config.h new file mode 100644 index 0000000..4fe1f89 --- /dev/null +++ b/trunk/src/config.h @@ -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 diff --git a/trunk/src/cpu.c b/trunk/src/cpu.c new file mode 100644 index 0000000..83ec5a9 --- /dev/null +++ b/trunk/src/cpu.c @@ -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; +} diff --git a/trunk/src/cpu.h b/trunk/src/cpu.h new file mode 100644 index 0000000..9440188 --- /dev/null +++ b/trunk/src/cpu.h @@ -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 diff --git a/trunk/src/dynabuf.c b/trunk/src/dynabuf.c new file mode 100644 index 0000000..9043b58 --- /dev/null +++ b/trunk/src/dynabuf.c @@ -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 +#include +#include +#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); +} diff --git a/trunk/src/dynabuf.h b/trunk/src/dynabuf.h new file mode 100644 index 0000000..ae4b2c3 --- /dev/null +++ b/trunk/src/dynabuf.h @@ -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 diff --git a/trunk/src/encoding.c b/trunk/src/encoding.c new file mode 100644 index 0000000..5280e4c --- /dev/null +++ b/trunk/src/encoding.c @@ -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 +#include +#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; +} diff --git a/trunk/src/encoding.h b/trunk/src/encoding.h new file mode 100644 index 0000000..edead14 --- /dev/null +++ b/trunk/src/encoding.h @@ -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 diff --git a/trunk/src/flow.c b/trunk/src/flow.c new file mode 100644 index 0000000..0c2ddf8 --- /dev/null +++ b/trunk/src/flow.c @@ -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 +#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); +} diff --git a/trunk/src/flow.h b/trunk/src/flow.h new file mode 100644 index 0000000..94f638e --- /dev/null +++ b/trunk/src/flow.h @@ -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 +#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 diff --git a/trunk/src/global.c b/trunk/src/global.c new file mode 100644 index 0000000..0884f4e --- /dev/null +++ b/trunk/src/global.c @@ -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 +#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); +} diff --git a/trunk/src/global.h b/trunk/src/global.h new file mode 100644 index 0000000..c4affe8 --- /dev/null +++ b/trunk/src/global.h @@ -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 +#include +#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 diff --git a/trunk/src/input.c b/trunk/src/input.c new file mode 100644 index 0000000..a350d72 --- /dev/null +++ b/trunk/src/input.c @@ -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 = { + "", // 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; +} diff --git a/trunk/src/input.h b/trunk/src/input.h new file mode 100644 index 0000000..3edbce7 --- /dev/null +++ b/trunk/src/input.h @@ -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 + + +// 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 diff --git a/trunk/src/label.c b/trunk/src/label.c new file mode 100644 index 0000000..e88c33d --- /dev/null +++ b/trunk/src/label.c @@ -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 +#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 = ) + 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; +} diff --git a/trunk/src/label.h b/trunk/src/label.h new file mode 100644 index 0000000..81a6d1d --- /dev/null +++ b/trunk/src/label.h @@ -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 +#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 diff --git a/trunk/src/macro.c b/trunk/src/macro.c new file mode 100644 index 0000000..8c2f040 --- /dev/null +++ b/trunk/src/macro.c @@ -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 // 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 +} diff --git a/trunk/src/macro.h b/trunk/src/macro.h new file mode 100644 index 0000000..74ee5b9 --- /dev/null +++ b/trunk/src/macro.h @@ -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 diff --git a/trunk/src/mnemo.c b/trunk/src/mnemo.c new file mode 100644 index 0000000..d2b6ec2 --- /dev/null +++ b/trunk/src/mnemo.c @@ -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; +} diff --git a/trunk/src/mnemo.h b/trunk/src/mnemo.h new file mode 100644 index 0000000..182516f --- /dev/null +++ b/trunk/src/mnemo.h @@ -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 diff --git a/trunk/src/output.c b/trunk/src/output.c new file mode 100644 index 0000000..6201e34 --- /dev/null +++ b/trunk/src/output.c @@ -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 +//#include +#include // 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); +} diff --git a/trunk/src/output.h b/trunk/src/output.h new file mode 100644 index 0000000..8ac9980 --- /dev/null +++ b/trunk/src/output.h @@ -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 +#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 diff --git a/trunk/src/platform.c b/trunk/src/platform.c new file mode 100644 index 0000000..435958a --- /dev/null +++ b/trunk/src/platform.c @@ -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" diff --git a/trunk/src/platform.h b/trunk/src/platform.h new file mode 100644 index 0000000..4494dbe --- /dev/null +++ b/trunk/src/platform.h @@ -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" diff --git a/trunk/src/section.c b/trunk/src/section.c new file mode 100644 index 0000000..8022fdf --- /dev/null +++ b/trunk/src/section.c @@ -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[] = ""; +// ...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); +} diff --git a/trunk/src/section.h b/trunk/src/section.h new file mode 100644 index 0000000..6547ae7 --- /dev/null +++ b/trunk/src/section.h @@ -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 diff --git a/trunk/src/tree.c b/trunk/src/tree.c new file mode 100644 index 0000000..1960a1f --- /dev/null +++ b/trunk/src/tree.c @@ -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++; + } +} diff --git a/trunk/src/tree.h b/trunk/src/tree.h new file mode 100644 index 0000000..cc306c6 --- /dev/null +++ b/trunk/src/tree.h @@ -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 +#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