diff --git a/.gitignore b/.gitignore index babd1d58..e5206375 100644 --- a/.gitignore +++ b/.gitignore @@ -16,10 +16,14 @@ mg /Platform/Apple/tools/*/nbproject/private/ /Platform/Apple/tools/*/dist/ +# Skip error files +/Platform/Apple/virtual/pack_error.txt + # Don't check in data specific to any particular game /Platform/Apple/virtual/data/images/*.bin /Platform/Apple/virtual/data/fonts/*.bin /Platform/Apple/virtual/data/world/*.xml +/Platform/Apple/virtual/data/world/*.cache /Platform/Apple/virtual/data/world/*.tsv # Only check in sample.build.props; each person's build.props will be different. diff --git a/Platform/Apple/tools/ACME/ACME_Lib/6502/Help.txt b/Platform/Apple/tools/ACME/ACME_Lib/6502/Help.txt new file mode 100644 index 00000000..cf44e4be --- /dev/null +++ b/Platform/Apple/tools/ACME/ACME_Lib/6502/Help.txt @@ -0,0 +1,3 @@ +!eof + +This directory contains files that are useful when writing programs for the 6502 processor, but which are independent of the computer used. diff --git a/Platform/Apple/tools/ACME/ACME_Lib/65816/Help.txt b/Platform/Apple/tools/ACME/ACME_Lib/65816/Help.txt new file mode 100644 index 00000000..5ddbce4f --- /dev/null +++ b/Platform/Apple/tools/ACME/ACME_Lib/65816/Help.txt @@ -0,0 +1,3 @@ +!eof + +This directory contains files that are useful when writing programs for the 65816 processor, but which are independent of the computer used. diff --git a/Platform/Apple/tools/ACME/ACME_Lib/Own/Help.txt b/Platform/Apple/tools/ACME/ACME_Lib/Own/Help.txt new file mode 100644 index 00000000..4362283e --- /dev/null +++ b/Platform/Apple/tools/ACME/ACME_Lib/Own/Help.txt @@ -0,0 +1,26 @@ +!eof + +If you have written files that you often need to include in your sources, you can store them in this directory. + +This way they can be accessed in ACME sources using + + !source + +or + + !binary + + +Please bear in mind that you cannot assemble such source codes on a machine that does *not* have your own include files. If you want to freely give away your programs though, there are two ways to do this: + + a) Change those references to "normal" quotes and store your own library files in the same directory as the file that includes them. This means editing by hand, therefore the "Own" directory would lose its meaning. + + b) Store your library files into a uniquely-named subdirectory inside the library tree and give away that part of the library as well. The position of such a personal directory inside the library tree should be chosen intelligently - it wouldn't be helpful if every single user of ACME would place his/her directory at the top level of the library. If your files are only of use for one specific computer, then you should create a new directory called "3rdParty" in that computer's directory and put your own directory inside it. The name of your own directory may be your name or IRC nick, for example. + +Demo groups, software houses and the like should use this method as well, forming paths like this: + + !binary + !source + +If you have a file that you think should be added to the "normal" library tree, just send me an e-mail and I'll include it in the next release. Well, at least I might consider it. :) + diff --git a/Platform/Apple/tools/ACME/File_ID.Diz b/Platform/Apple/tools/ACME/File_ID.Diz new file mode 100644 index 00000000..d7e0f6bc --- /dev/null +++ b/Platform/Apple/tools/ACME/File_ID.Diz @@ -0,0 +1,5 @@ + + ACME - Release 0.91 (Linux/Unix/Platform independent version) + + MultiPlatform 6502/65c02/65816 X-Assembler + diff --git a/Platform/Apple/tools/ACME/ReadMe.txt b/Platform/Apple/tools/ACME/ReadMe.txt new file mode 100644 index 00000000..ed53b2fc --- /dev/null +++ b/Platform/Apple/tools/ACME/ReadMe.txt @@ -0,0 +1,73 @@ + + + ACME + + ...the ACME Crossassembler for Multiple Environments + + --- Linux/Unix/platform independent version --- + + +This file only describes stuff that is specific to the Linux / Unix +/ platform independent version of ACME. So if you are looking for more +general things, you should take a look at "docs/Help.txt". + + + + +*** Compiling and installing the executable: + +Change into the directory "acme091/src" that was created when +unpacking the archive and simply type "make". This will compile the +sources and produce an executable file. + +If you have root access: +Change into superuser mode using "su" and type "make install" to move +the executable to the appropriate directory (system-wide install). + +If you don't have root access: +Type "make userinstall" to move the executable to your "~/bin" +directory (user-specific install). + +Feel free to adjust the Makefile to your specific needs. + + + + +*** Installing the library: + +The directory "ACME_Lib" contains a bunch of files that may be useful. +Okay, there's hardly anything in it at the moment, but it will +hopefully grow over time. +Copy the ACME_Lib directory to a place you think is appropriate. You +will have to set up an environment variable called "ACME" to allow the +main program to find this directory tree. + +In bash: + + set ACME="/my/path/to/ACME_Lib" ; export ACME + +You don't *have* to install the library just to use ACME, but it +doesn't hurt to install it - one of the supplied example programs uses +the library as well. + + + + +*** Miscellaneous: + +ACME was tested and prepared on Redhat 5.2 but it should work on any +Linux distribution and on other Unices aswell (if not, then let me +know and i'll fix it ASAP). + +Krzysztof Dabrowski, current Linux port maintainer +mailto:brush@pol.pl + + + + +*** Changes: + +Release 0.91: Merged Linux/Unix version and source-only version. + +Release 0.04 beta: First Linux version + diff --git a/Platform/Apple/tools/ACME/docs/65816.txt b/Platform/Apple/tools/ACME/docs/65816.txt new file mode 100644 index 00000000..0d23bb5c --- /dev/null +++ b/Platform/Apple/tools/ACME/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/Platform/Apple/tools/ACME/docs/AddrModes.txt b/Platform/Apple/tools/ACME/docs/AddrModes.txt new file mode 100644 index 00000000..40e8fc72 --- /dev/null +++ b/Platform/Apple/tools/ACME/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 "äöüßÄÖÜ" ; whatever... :) + } + !tx "Test" ; outputs $d4 $45 $53 $54 again +Hint: If you don't want to fiddle with a hex editor to create a +conversion table file, try using ACME: + !to "asc2pet.ct", plain ; no load address + *=0 ; pc = table index + ; first create "as-is" table + !for i, 256 {!byte i-1} + ; now exchange upper and lower case characters + *=65 + !for i, 91-65 {!byte *+128} + *=97 + !for i, 123-97 {!byte *-32} +The resulting file can be used as a conversion table to convert to +PetSCII (which is useless, because ACME can do so anyway. But you get +the idea). + + +Call: !text STRING_VALUE [, STRING_VALUE]* +Purpose: Output the given string(s) using the current + conversion table. +Parameters: STRING_VALUE: Can be either a string given in double + quotes or any formula the value parser accepts. + Please note that formula results won't be converted, + but single characters involved in calculations will. +Aliases: "!tx" +Examples: !text "Loading...", Char_NewLine, "Filename:", 0 + !tx "Offset character is ", offset-1+'a', 0 + + +Call: !pet STRING_VALUE [, STRING_VALUE]* +Purpose: Output the given string(s) using the PetSCII + conversion table (This means to exchange the upper- + and lowercase characters; useful for C64 programs). +Parameters: STRING_VALUE: Can be either a string given in double + quotes or any formula the value parser accepts. + Please note that formula results won't be converted, + but single characters involved in calculations will. +Examples: !pet "Loading...", Char_NewLine, "Filename:", 0 + !pet "Offset character is ", offset-1+'a', 0 + + +Call: !raw STRING_VALUE [, STRING_VALUE]* +Purpose: Output the given string(s) without any conversion at + all. +Parameters: STRING_VALUE: Can be either a string given in double + quotes or any formula the value parser accepts. +Examples: !raw "Loading...", Char_NewLine, "Filename:", 0 + !raw "Offset character is ", offset-1+'a', 0 + + +Call: !scr STRING_VALUE [, STRING_VALUE]* +Purpose: Output the given string(s) using the C64 screen code + conversion table (useful for C64 programs, as you will + have guessed). +Parameters: STRING_VALUE: Can be either a string given in double + quotes or any formula the value parser accepts. + Please note that formula results won't be converted, + but single characters involved in calculations will. +Examples: !scr "Loading...", Char_NewLine, "Filename:", 0 + !scr "Offset character is ", offset-1+'a', 0 + + +Call: !scrxor XOR_VALUE, STRING_VALUE [, STRING_VALUE]* +Purpose: Output the given string(s) using the C64 screen code + conversion table and exclusive-OR-ing the results with + the given value (useful for C64 programs when inverse + video is needed, or EBC mode, etc.). +Parameters: XOR_VALUE: Any formula the value parser accepts. + STRING_VALUE: Can be either a string given in double + quotes or any formula the value parser accepts. + Please note that formula results will be neither + converted nor exclusive-OR-d. + Single characters involved in calculations will be + converted, but not exclusive-OR-d. +Examples: !scrxor $80, "Loading..." + !scrxor $a0, "Offset char is ", (offset-1+'a') EOR $a0 + + +---------------------------------------------------------------------- +Section: File stuff +---------------------------------------------------------------------- + +Call: !to FILENAME, FILEFORMAT +Purpose: Define the output file name and file type. If this + opcode isn't used, ACME still fully processes the + source code - as the resulting binary isn't stored, + this only serves to check for errors. Instead of using + this pseudo opcode, you can also use the command line + options "--outfile" and "--format". +Parameters: FILENAME: A file name given in "..." quoting. + FILEFORMAT: Name of file format. Valid names are: + cbm with load address (Commodore format) + plain without load address + If FILEFORMAT is omitted, ACME gives a warning and + then defaults to "cbm" (this can be changed using the + command line option "--format"). +Examples: !to "eprom.p", plain ; don't add a load address + !to "demo.o", cbm ; add c64-style load address + + +Call: !source FILENAME +Purpose: Assemble another source code file. After having + processed the new file, ACME continues processing the + old one. +Parameters: FILENAME: A file name given in "..." quoting (load + from current directory) or in <...> quoting (load from + library). +Aliases: "!src" +Examples: !source <6502/std.a> ; Read library file + !src "Macros.a" ; Read file from current dir + + +Call: !binary FILENAME [, [SIZE] [, [SKIP]]] +Purpose: Insert binary file directly into output file. +Parameters: FILENAME: A file name given in "..." quoting (load + from current directory) or in <...> quoting (load from + library). + SIZE: Any formula the value parser accepts, but it + must be solvable even in the first pass. If SIZE is + given, it is used: If the file is longer, only SIZE + bytes are read; if it is shorter, ACME will use + padding until SIZE is reached. If SIZE is omitted, + ACME will include the file until EOF. + SKIP: Any formula the value parser accepts. If SKIP is + omitted, it defaults to zero. ACME will start loading + the file from file offset SKIP. So C64 coders wanting + to include C64 files without their load addresses + should use a SKIP value of 2. +Aliases: "!bin" +Examples: !binary ; insert library file + !bin "asc2pet.b", 256, 2 ; insert 256 bytes + ; from file offset 2. + !bin "table", 2, 9 ; insert 2 bytes from offset 9 + !bin "list",, 9 ; insert from offset 9 to EOF + + +---------------------------------------------------------------------- +Section: Labels +---------------------------------------------------------------------- + +Call: !zone [TITLE] [ { BLOCK } ] +Purpose: Switch to new zone of local labels. Zones can either + be nested or used sequentially. +Parameters: TITLE: May consist of letters and digits. Its only + purpose is to be displayed in error messages, so it'll + be omitted in most cases. + BLOCK: A block of assembler statements + If no block is given, the previous zone is terminated + and the new zone is started. + If a block is given, the old zone continues after the + block. +Aliases: "!zn" +Examples: .backgroundcolor = 0 ; some local label + !zone File_IO ; new zone begins here + .backgroundcolor = 1 ; so this is a different label + !zn LinkedList_Init + .backgroundcolor = 2 + !zone LinkedList { ; start of nested zone + ; imagine some code here... + !zone LinkedList_Init + ; imagine some more code here... + !zone LinkedList_Body { + ; imagine yet some more code here... + !zone LinkedList_SecondPart + ; imagine still some more code here... + } + !zone LinkedList_End + ; you know what to imagine here... + } + .backgroundcolor = 3 ; => "Label already defined." + + +Call: !sl FILENAME +Purpose: Save all the global labels to the given file after the + assembly is finished. This table could be loaded + during another assembly session using the "!source" + pseudo opcode. +Parameters: FILENAME: A file name given in "..." quoting. +Examples: !sl "Labels.a" ; produce label dump after assembly + !sl "global" ; produce label dump after assembly + + +---------------------------------------------------------------------- +Section: Flow control +---------------------------------------------------------------------- + +Call: !if CONDITION { BLOCK } [ else { BLOCK } ] +Purpose: Conditional assembly. If the given condition is true, + the first block of statements will be parsed; + if it isn't, the second block will be parsed instead + (if present). +Parameters: CONDITION: Any formula the value parser accepts, but + it must be solvable even in the first pass. + BLOCK: A block of assembler statements. +Examples: !text "Black", 0 ; Choose wording according to + !if country = uk { ; content of "country" label. + !text "Grey" + } else { + !text "Gray" + } + !byte 0 + !text "White", 0 + + ; Insert debug commands if label "debug" is not zero: + !if debug { lda #"z":jsr char_output } + + +Call: !ifdef LABEL { BLOCK } [ else { BLOCK } ] +or: !ifdef LABEL STATEMENT +Purpose: Conditional assembly, depending on whether a label is + already defined or not. If it is defined, the first + block of statements will be parsed; if it isn't, the + second block will be parsed instead (if present). This + opcode was only added to speed up parsing of library + files. + Only use it in your own files if you're sure you + *really* know what you are doing - using it in the + wrong place will result in loads of error messages. +Parameters: LABEL: Any valid label name. + BLOCK: A block of assembler statements. + STATEMENT: Any assembler statement. +Example: ; this was taken straight from <6502/std.a>: + !ifdef Lib_6502_std_a !eof ; parse this file once + Lib_6502_std_a = 1 + + +Call: !for LABEL, TIMES { BLOCK } +Purpose: Looping assembly. The block of statements will be + parsed TIMES times. For a more flexible possibility, + have a look at "!do" below. +Parameters: LABEL: Any valid label name. The label's value will + show the number of the current loop cycle: + In the first cycle it will have the value 1, in the + last cycle it will have the value TIMES. + TIMES: Any formula the value parser accepts, but it + must be solvable even in the first pass. Negative + values are forbidden, zero causes the block to be + skipped. + BLOCK: A block of assembler statements. + Please note that it is impossible to change the number + of loop cycles "inside" the loop by fiddling with the + counter (using the "!set" pseudo opcode): The "!for" + routine keeps its own copy of the counter value and + only sets the label value, it never reads it back. + This was done to eliminate a possibility to hang ACME. +Examples: ; conversion table: integer to BCD + int2BCD !for Outer, 10 { + !for Inner, 10 { + !byte ((Outer-1) << 4) OR (Inner-1) + } + } + !fill 156, $ff ; values above 99 give 255 (invalid) + + ; conversion table: BCD to integer + BCD2int !for Outer, 10 { + !for Inner, 10 { + !byte 10 * (Outer-1) + (Inner-1) + } + !fill 6, $ff ; invalid BCD values give 255 + } + !fill 96, $ff ; invalid BCD values give 255 + + +Call: !set LABEL = VALUE +Purpose: Assign given value to label even if the label already + has a different value. Needed for loop counters when + using "!do", for example. Only use this opcode for + something else if you're sure you *really* know what + you are doing... :) +Parameters: LABEL: Any valid label name. + VALUE: Any formula the value parser accepts. +Example: see "!do" below + + +Call: !do [KEYWORD CONDITION] { BLOCK } [KEYWORD CONDITION] +Purpose: Looping assembly. The block of statements can be + parsed several times, depending on the given + condition(s). + Conditions may be placed before or after the block (or + even at both places), they are then parsed in every + repetition before or after the block respectively. If + there is a condition before the block and it isn't + met when first checked, the block will be skipped. +Parameters: KEYWORD: Either "until" or "while" (without quotes). + CONDITION: Any formula the value parser accepts, but + it must be solvable even in the first pass. + BLOCK: A block of assembler statements. +Examples: ; a loop with conditions at both start and end + !set a = 0 ; init loop counter + !do while loop_flag = TRUE { + lda #a + sta label+a + !set a = a + 1 + } until a > 6 + + ; a loop with a condition at the start + !do while * < $c000 { nop } + + ; a loop with a condition at the end + !do { !wo * + base } while * < base + 345 + + ; a never ending loop - this will cause an error + !do while 3 < 4 { nop } until 3 = 4 + + ; an empty loop - this will hang ACME + !do until 3 = 4 { } while 3 < 4 + + +Call: !endoffile +Purpose: Stop processing the current source file. Using this + pseudo opcode you can add explanatory text inside your + source file without having to comment out every single + line of it. +Aliases: "!eof" +Example: rts ; some assembler mnemonic + !eof + Though this text isn't preceded by a semicolon, it is + treated as if it were a comment. In fact, ACME doesn't + even parse this anymore - the file gets closed when + "!eof" is reached. + + +Call: !warn STRING_VALUE +Purpose: Show a warning during assembly. +Parameters: STRING_VALUE: A string given in double quotes. +Example: !if * > $a000 { + !warn "Program reached ROM area." + } + + +Call: !error STRING_VALUE +Purpose: Generate an error during assembly (therefore, no + output file will be generated). +Parameters: STRING_VALUE: A string given in double quotes. +Example: rts ; end of some function + start !source "colors.a" + end !if end-start > 256 { + !error "Color strings exceed 256 chars!" + } + + +Call: !serious STRING_VALUE +Purpose: Generate a serious error, immediately stopping + assembly. +Parameters: STRING_VALUE: A string given in double quotes. +Example: !source "part1.a" ; sets part1_version + !source "part2.a" ; sets part2_version + !if part1_version != part2_version { + !serious "part1.a and part2.a don't match!" + } + + +---------------------------------------------------------------------- +Section: Macro usage +---------------------------------------------------------------------- + +Call: !macro TITLE [[~]LABEL [, [~]LABEL]*] { BLOCK } +Purpose: Define a macro. +Parameters: TITLE: The macro's desired name (same rules as for + label names). If the title's first character is a dot + ("."), the macro will be local (though why anyone + could want this is beyond me). + LABEL: The desired name for the parameter value at + call time. Normally, these parameter labels should be + local (first character a dot), as different macro + calls will almost for sure have different parameter + values. + If you prefix LABEL with a '~' character, it will be + called by reference, not by value: Changing the value + inside the macro will result in the "outer" label to + be changed as well. + BLOCK: A block of assembler statements. +Examples: ; far branch, as defined in <6502/std.a> + !macro bne .target { + beq * + 5 + jmp .target + } + + ; increase 16-bit counters + !macro dinc .target { + inc .target + bne + ; "bne * + 5" would not work in zp + inc .target + 1 + + + } + ; Yes, anonymous label references can be used with + ; macros (unlike several other assemblers). That's + ; because ACME's macros are implemented more like + ; real functions. + + ; load A and X + !macro ldax .target { + lda .target + ldx .target + 1 + } + + ; store A and X + !macro stax .target { + sta .target + stx .target + 1 + } + + ; use call-by-reference for return value + !macro reserve ~.address, .amount { + .address = external_pc + !set external_pc = external_pc + .amount + } + + ; define a pixel row of a C64 hardware sprite + !macro SpriteLine .v { + !by .v>>16, (.v>>8)&255, .v&255 + } + + +Call: +TITLE [ARGUMENT [, ARGUMENT]*] +Purpose: Call a macro, using the given parameter values. +Parameters: TITLE: The macro's name as given in its definition. + ARGUMENT: This is either any formula the value parser + accepts, or (new in release 0.86) a '~' character + followed by a label name. The '~'-prefix indicates + call-by-reference semantics, which means that when the + macro changes the label's value, the "outer" label + value will change as well. +Examples: inc label + bne mark ; "near" branch + inc label2 + +bne mark2 ; "far" branch + + inc $fa ; increase 8-bit counter + +dinc $fb ; increase 16-bit counter + + ldy label ; get byte + +ldax label2 ; get two bytes + + ; using macro calls in a macro definition + !macro cp16 .source, .target { + +ldax .source + +stax .target + } + + ; use call-by-reference for return value + !set external_pc = $0400 + +reserve ~.line_buffer, 80 + +reserve ~.in_buffer, 256 + +reserve ~.out_buffer, 256 + +reserve ~.byte_var, 1 + + ; define a C64 hardware sprite + ; 765432107654321076543210 + +SpriteLine %........................ + +SpriteLine %.#...................... + +SpriteLine %.##..................... + +SpriteLine %.###.................... + +SpriteLine %.####................... + +SpriteLine %.#####.................. + +SpriteLine %.######................. + +SpriteLine %.#######................ + +SpriteLine %.########............... + +SpriteLine %.#########.............. + +SpriteLine %.########............... + +SpriteLine %.######................. + +SpriteLine %.######................. + +SpriteLine %.##..##................. + +SpriteLine %.#....##................ + +SpriteLine %......##................ + +SpriteLine %.......##............... + +SpriteLine %.......##............... + +SpriteLine %........##.............. + +SpriteLine %........##.............. + +SpriteLine %........................ + !byte 0 ; pad to 64-byte block + +Since release 0.86, different macros are allowed to have the same name +as long as their parameter lists differ in size (number of arguments) +or type (call-by-value vs. call-by-reference). So + !macro process_bytes b1,b2 {...whatever...} + !macro process_bytes b1,b2,b3 {...whatever...} + !macro process_bytes b1,b2,~b3 {...whatever...} +can *all* be used at the same time without any name clash. + + +---------------------------------------------------------------------- +Section: Segment assembly +---------------------------------------------------------------------- + +Call: *= EXPRESSION +Purpose: Set program counter to given value and start new + segment. This opcode must be given at least once + (or the command line option "--setpc" must be used). + If segments overlap each other, warnings will be + issued (not errors, as some people do this overlapping + on purpose). +Parameters: EXPRESSION: Any formula the value parser accepts, but + it must be solvable even in the first pass. +Examples: !to "TinyDemo", cbm ; define output file + format + *= $0801 ; Start at C64 BASIC start + +basic_header ; Call program header macro + !src "main.a" ; include main program + *= $1000 ; jump to new segment + !bin "music.b" ; load music to $1000 + *= $8000 ; jump to new segment + !bin "pic.b" ; load graphics to $8000 + ; After assembly, ACME will save everything from $0801 + ; up to the highest address written to. The resulting + ; file will contain some big unused areas (zero'd), + ; but demos will get compressed anyway... :) + + +Call: !initmem EXPRESSION +Purpose: Define "unchanged" memory. ACME will fill its output + buffer completely with the given value before storing + the assembled code. So gaps between segments will + contain the desired byte when writing the output file. + Instead of using this pseudo opcode, you can also use + the "--initmem" command line option. If neither is + used, the buffer is cleared. +Parameters: EXPRESSION: Any formula the value parser accepts, but + it must be solvable even in the first pass (because + this opcode will be ignored in all later passes). +Examples: !to "TinyDemo", cbm ; define output file + format + !initmem $ea ; Default memory content $ea. + *= $0801 ; Start at C64 BASIC start + +basic_header ; Call macro to create program header + !src "main.a" ; include main program + *= $1000 ; jump to new segment + !bin "music.b" ; load music to $1000 + *= $8000 ; jump to new segment + !bin "pic.b" ; load graphics to $8000 + ; This is the same example as before, but now the big + ; unused areas will contain the value $ea instead of + ; zero. + + !initmem $ff ; Default memory content is now $ff. + ; Useful if you want to store your code in an EPROM. + + +---------------------------------------------------------------------- +Section: Offset assembly +---------------------------------------------------------------------- + +Call: !pseudopc EXPRESSION [ { BLOCK } ] +Purpose: Assemble code as if the program counter had the given + value, effectively producing a program that has to be + copied to a different address space before being run. + After having processed the block of statements with + the new program counter, the updated (!) old program + counter is used again. + Thanks to the block syntax, offset assembly can now be + nested. Then the old program counter would not + necessarily be the *real* program counter, but could + be a pseudopc as well. ;) +Parameters: EXPRESSION: Any formula the value parser accepts, but + it must be solvable even in the first pass. + BLOCK: A block of assembler statements. +Examples: ldx #.shifted_end-.shifted_start + - lda .shifted_start-1,x + sta .target-1,x + dex + bne - + jmp .target + .shifted_start + !pseudopc $0400 { + .target ; imagine some code here... + ; it should be copied to $0400 and executed *there* + } + .shifted_end + + +---------------------------------------------------------------------- +Section: CPU support pseudo opcodes (especially 65816 support) +---------------------------------------------------------------------- + +Call: !cpu KEYWORD [ { BLOCK } ] +Purpose: Select the processor to produce code for. If this PO + isn't used, ACME defaults to the 6502 CPU (or to the + one selected by the "--cpu" command line option). + ACME will give errors if you try to assemble commands + the chosen CPU does not have. You can change the + chosen CPU at any time. When used with block syntax, + the previously chosen CPU value is restored + afterwards. +Parameters: KEYWORD: Currently valid keywords are: + 6502 allows official mnemonics and addressing modes + 6510 adds mnemonics for some undocumented opcodes + (but includes all the official 6502 stuff) + 65c02 allows official 65c02 stuff (includes 6502) + 65816 allows official 65816 stuff (includes 65c02) + BLOCK: A block of assembler statements. +Examples: !if cputype = $65c02 { + !cpu 65c02 { ; temporarily allow 65c02 stuff + stz .todelete + } + } else { + pha + lda #0 + sta .todelete + pla + } + rts + !cpu 65816 ; allow 65816 commands from here on + + +Call: !al [ { BLOCK } ] +Purpose: Assume long (16 bits) accumulator. Only allowed when + producing code for the 65816 CPU. When used with block + syntax, the previous configuration is restored + afterwards. + + +Call: !as [ { BLOCK } ] +Purpose: Assume short (8 bits) accumulator. Only needed when + producing code for the 65816 CPU. When used with block + syntax, the previous configuration is restored + afterwards. Short accumulator is the default in every + pass. + + +Call: !rl [ { BLOCK } ] +Purpose: Assume long (16 bits) index registers. Only allowed + when producing code for the 65816 CPU. When used with + block syntax, the previous configuration is restored + afterwards. + + +Call: !rs [ { BLOCK } ] +Purpose: Assume short (8 bits) index registers. Only needed + when producing code for the 65816 CPU. When used with + block syntax, the previous configuration is restored + afterwards. Short registers are the default in every + pass. + + +---------------------------------------------------------------------- +Section: Deprecated pseudo opcodes (they still work at the moment) +---------------------------------------------------------------------- + +Call: !cbm +Purpose: Use PetSCII as the text conversion table. Now + superseded by the "!convtab" pseudo opcode. +Old usage: !cbm ; gives "use !ct pet instead" warning +Now use: !convtab pet ; does the same without warning + + +Call: !subzone [TITLE] { BLOCK } +Purpose: Allows nesting of zones. Now superseded by "!zone" + because that allows nesting as well. +Parameters: TITLE: May consist of letters and digits. Its only + purpose is to be displayed in error messages, so it'll + be omitted in most cases. + BLOCK: A block of assembler statements. +Aliases: "!sz" +Old usage: !subzone graphics { + !source "graphics.a" + } +Now use: !zone graphics { + !source "graphics.a" + } + + +Call: !realpc +Purpose: Restore the program counter to its real value, + therefore finishing offset assembly. Because + "!pseudopc" now knows block syntax and can be nested, + there's no reason to use "!realpc" any more. +Old usage: !pseudopc $0400 + ; imagine some code here... + !realpc +Now use: !pseudopc $0400 { + ; imagine some code here... + } diff --git a/Platform/Apple/tools/ACME/docs/COPYING b/Platform/Apple/tools/ACME/docs/COPYING new file mode 100644 index 00000000..60549be5 --- /dev/null +++ b/Platform/Apple/tools/ACME/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/Platform/Apple/tools/ACME/docs/Changes.txt b/Platform/Apple/tools/ACME/docs/Changes.txt new file mode 100644 index 00000000..eb2a1a9f --- /dev/null +++ b/Platform/Apple/tools/ACME/docs/Changes.txt @@ -0,0 +1,296 @@ + + + ACME + + ...the ACME Crossassembler for Multiple Environments + + --- change log --- + + +This text only contains descriptions of changes independent of the +platform used. There should be another help file in this archive +outlining the platform specific changes. + + +---------------------------------------------------------------------- +Section: New in release 0.91 +---------------------------------------------------------------------- + +Added anonymous labels (- + -- ++ --- +++ etc.). Every other assembler + seems to support them, so I added them to ACME as well... :) +New POs: "!warn MESSAGE", "!error MESSAGE", "!serious MESSAGE" +New CLI option: "--maxdepth NUMBER" sets maximum recursion depth for + macro calls and the "!source" pseudo opcode. +ACME now gives a warning when assembling JMP($xxff) on 6502/6510 + because that instruction is broken on those CPUs. +After giving the error "Target out of range", the error "Number out of + range" is now suppressed. +Corrected code example in QuickRef.txt (why didn't anyone tell me? :)) +Added additional example source code. + + +---------------------------------------------------------------------- +Section: New in release 0.90 +---------------------------------------------------------------------- + +Arithmetic shift right now has some watchdog code and should work + regardless of compiler. +Corrected some typos in error messages and docs. +New CLI option: "--cpu CPU_TYPE" +The output file format chosen with "--format FORMAT" is now used as + default when "!to" is used without format keyword. +Again: Tidier code. + + +---------------------------------------------------------------------- +Section: New in release 0.89 +---------------------------------------------------------------------- + +Support for more undocumented ("illegal") opcodes: anc, arr, asr, sbx, + dop, top, jam. See Illegals.txt for more info. +Change in shift operators: Logical shift right (">>" or "LSR") has on + most platforms actually been an arithmetic shift right all the + time! Therefore, ">>" now *officially* performs an arithmetic + shift right (can also be written as "ASR"), while ">>>" has been + added to perform a logical shift right (can also be written as + "LSR"). Note: This is about ACME's maths parser and has nothing to + do with the 6502 mnemonics "asl" and "lsr". +Finally added a "-o" command line option to set the output file! See + QuickRef.txt for info on the other new CLI options (--format, + --labeldump, --maxerrors, --setpc, --initmem, --version). +Fixed bug: "!align" could be used while program counter undefined. +Fixed bug: Numbers before mnemonics are no longer skipped (or rather, + implicit label definitions are no longer accepted if the label + name starts with a digit). +Change: Much better algorithm to compute to-the-power-of (read: it's + no longer braindead). +Some more internal tidying. + + +---------------------------------------------------------------------- +Section: New in release 0.88 +---------------------------------------------------------------------- + +Fixed architecture-dependent bug introduced in release 0.87. +Fixed bug: Unknown !cpu keywords could cause crashes. +Fixed bug in !ct "filename" nesting. + + +---------------------------------------------------------------------- +Section: New in release 0.87 +---------------------------------------------------------------------- + +Support for some undocumented ("illegal") opcodes: slo, rla, sre, rra, + sax, lax, dcp, isc. To use these, choose the 6510 cpu. +Two error messages gone: "Sorry, feature not yet implemented." and + "Chosen CPU does not support this command and/or addressing mode." +Explanation of new error message ("There's more than one character.") + added to docs. + + +---------------------------------------------------------------------- +Section: New in release 0.86 +---------------------------------------------------------------------- + +The "!convtab" pseudo opcode can now be given the file name of a + conversion table. The file must hold exactly 256 bytes. +Improved docs a bit (more and better examples, more info on verbosity + CLI switch). +If no "!to" pseudo opcode has been found, ACME will tell you so. + + +---------------------------------------------------------------------- +Section: New in release 0.86 beta +---------------------------------------------------------------------- + +Macros can now be used with call-by-reference semantics, therefore + allowing some kind of return value. Call-by-reference is + indicated by prefixing the relevant parameter(s) with a '~' + character. This has to be done at both the macro definition and + the macro call. +Different macros are allowed to have the same name as long as their + parameter lists differ in size (number of arguments) or type + (call-by-value vs. call-by-reference) +Macros do not have a limit on parameter count anymore. +Macro size is unlimited now. +The expression parser does not have a limit on recursion depth + anymore, so you can use as many parentheses as you like. +Loop block size is unlimited now. +Label name and string lengths are unlimited now. +The recursion depth of "!source" and macro calls is set to 64. The + only reason there still *is* a limit is to be able to spot + infinite recursions. +Offset assembly now has block support and can be nested. Using the old + syntax still works, but gives a warning. +Pseudo opcodes "!convtab", "!cpu", "!al", "!as", "!rl" and "!rs" now + have block support and can be nested. +Using "!to" without file format indicator now gives a warning (but + still works). +Fixed bug: The statement + !to "outfile" ANY_SPECIAL_CHARACTER_BUT_COMMA GARBAGE + wasn't flagged as an error. +Fixed bug: The statement + !source "a file that cannot be opened" + did not give an error, but was just ignored. +If a global label starts with a shift-space character, a warning is + issued (because it is highly likely that it is a typing error). +*Much* cleaner internals. *Very* *much* cleaner internals actually. +More bug checking at runtime. +Tree lookups should be a bit faster. +Initialising the memory should be a bit faster. +Writing the output file should be a bit faster. +The expression parser now uses repeated multiplication instead of the + math library's pow() call, so it is no longer necessary to include + the C math library when compiling. +The number of errors displayed before assembly stops was reduced from + 20 to 10. I really should make this configurable via a CLI switch. + + +---------------------------------------------------------------------- +Section: New in release 0.85 alpha +---------------------------------------------------------------------- + +Fixed bug: Handling of parentheses in new expression parser was badly + screwed up. Thanks go to Nathan Smith for reporting that bug. +Verbosity messages for segments and output file now contain size info. + + +---------------------------------------------------------------------- +Section: New in release 0.84 alpha +---------------------------------------------------------------------- + +Some changes in documentation (mainly corrected typos) +Usage count for labels (Unused ones are marked in label dump file) +New PO: "!8" (for 8-bit values, as "!byte" / "!by" / "!08") +Finally removed the dreaded only-two-input-files restriction +Improved PO: "!to" has parameter for choosing output file format +Fixed bug: Blanks after "!for"'s "}" character stopped assembly +Rewritten expression parser and label tree handler (should be faster) +Generally tidied up the source. +Skipped some version numbers to get a "less frightening" one. :) + + +---------------------------------------------------------------------- +Section: New in release 0.08 beta +---------------------------------------------------------------------- + +Fixed really serious bug: The 65816's indirect DP addressing caused + wrong opcodes to be generated. Thanks to Doc Bacardi/The Dreams + for reporting it. + + +---------------------------------------------------------------------- +Section: New in release 0.07 beta +---------------------------------------------------------------------- + +Fixed really serious bug: Indirect JMP / JSR were assembled without + target addresses. Thanks to YTM/Alliance for reporting that one. +Fixed bug in value parser's handling of parentheses: Expressions like + "a*(b-c)+d" gave "a*((b-c)+d)", obviously not the same. +Fixed bug: "!set LABEL = VALUE" now *really* works correctly. +Fixed bug: ACME gave "too late for postfix" error when reading a + predefined label of known size. Only occurred when using macros. +Fixed bug: Error messages given from within macro definitions used + truncated file names. +Fixed bug: Calling of local macros didn't work at all. +Fixed bug: "}" chars directly after macro calls were not found. +Fixed bug: Spaces after ":" and "{" gave syntax errors. +Fixed bug: Line counting inside loops was screwed up. +Fixed bug: Changed argument order of MVP and MVN (now it's "opcode, + source, target") +New PO: "!08" (for 8-bit values, as "!byte" / "!by") +New PO: "!16" (for 16-bit values, as "!word" / "!wo") +New PO: "!24" (for 24-bit values) +New PO: "!32" (for *signed* 32-bit values) +New PO: "!pseudopc" (starts offset assembly) +New PO: "!realpc" (ends offset assembly) +New PO: "!for LABEL, TIMES { LINES }" for easier loops. +New PO: "!initmem BYTE" to define empty memory. +New PO: "!endoffile" (short "!eof") replaces "!end". +New PO: "!ifdef" (only use this if you *really* know what you are + doing. Otherwise, just don't use it) +New PO: "!convtab CONVERSION" (short "!ct") selects the default + character conversion, making "!cbm" obsolete. +Improved PO: "!binary" now has "skip" parameter. +Change: "!cbm" outputs a warning when used - use "!ct pet" instead. +Change: "!end" no longer works - use "!eof" instead. +Change: "*=VALUE" is now segment change instead of offset assembly. +Change: Argument order of MVN/MVP is now as is standard. +The typecast system has been rewritten - now it works as intended. +BIT without any parameters no longer works - use a macro instead. +Leading zeros are stored in label structures and acted upon. +The documentation is in several files now. +Negative numbers are now handled much more sensibly. +'ACME' environment variable only needed when *really* needed. + + +---------------------------------------------------------------------- +Section: New in release 0.05 beta +---------------------------------------------------------------------- + +Fixed bug: No more multiple error messages. +Fixed bug: Zone names now work correctly (First char wasn't stored). +Fixed bug: "!set label = label" now works correctly (I hope). +Fixed bug: "stz ...,y" gave "number too big" instead of "illegal + combination of command and addressing mode" +New PO: "!subzone" (short "!sz") for nested zones. +Added support for library tree when using "!source" or "!binary". +Single-character strings can now be given in single quotes as well. +Real icons. +Startup errors now exit correctly with EXIT_FAILURE code. +Example program now includes "Expected_Output" file. +Further tidied up the sources. +Tidied up the general help file: + -Changed "Freeware" to "free software" + -Corrected the information given on "!align". + -Added examples for most of the pseudo opcodes. + + +---------------------------------------------------------------------- +Section: New in release 0.04 beta +---------------------------------------------------------------------- + +Corrected some small bugs. +New PO: "!zone" (short "!zn") replaces "!module" (short "!mod") +Tidied up the sources a lot. +Changed bad style C code reported by lint. +Added GNU GPL hint in every source file. +Added startup message in verbose mode. +Added "Error: " to startup error messages. +Added Amiga, Linux and OS/2 versions + + +---------------------------------------------------------------------- +Section: New in release 0.03 beta +---------------------------------------------------------------------- + +Generally tidied up the source. +Moved RISC OS-specific CLI options to platform file. +Added pathname conversion from UNIX style to current platform style. +Added context variables (enabling "!source"s and macros). +Translated all documentation to english. +Changed string pseudo opcodes to allow numeric values. +Added verbose mode (CLI option "v"). +Added output buffer, removing the need for additional output pass (and + now the "!to" pseudo opcode can be placed anywhere). +More than one "label = pc" definition per statement now illegal. +Instead added possibility to have several statements on a single line + by using ":" as a separator character. +Added new keywords: "!set", "!if", "else", "!do", "until", "while" and + "!macro" +Added support for "!source". +Added basic support for blocks. +Added support for "!if {...} else {...}". +Added support for zone titles. +Added support for loops (endless loops are only detected if producing + code). +Added support for macros (even nested definitions are possible now). +Added DOS version. + + +---------------------------------------------------------------------- +Section: New in release 0.02 alpha +---------------------------------------------------------------------- + +Er, I don't know anymore. It was a bad ugly hack and it only ran on + RISC OS. :-) diff --git a/Platform/Apple/tools/ACME/docs/Errors.txt b/Platform/Apple/tools/ACME/docs/Errors.txt new file mode 100644 index 00000000..c76bd20b --- /dev/null +++ b/Platform/Apple/tools/ACME/docs/Errors.txt @@ -0,0 +1,372 @@ + + + ACME + + ...the ACME Crossassembler for Multiple Environments + + --- error messages --- + + +Here's a sorted list of all error messages ACME can give, possible +reasons and what you can do to sort it out. + + +---------------------------------------------------------------------- +Section: Errors on startup +---------------------------------------------------------------------- + +Cannot open toplevel file "FILENAME". + Maybe you mistyped its name? + +Error in CLI arguments: ... + There are several of these errors, but they should be quite self- + explanatory. + + +---------------------------------------------------------------------- +Section: Warnings during assembly +---------------------------------------------------------------------- + +"!cbm" is deprecated; use "!ct pet" instead. + This is given when "!cbm" is encountered. It still works though. + +"!pseudopc/!realpc" is deprecated; use "!pseudopc {}" instead. + "!pseudopc" can now be used with a block, so it can be nested. + So "!realpc" is no longer needed. It still works though. + +"!subzone {}" is deprecated; use "!zone {}" instead. + "!zone" can now be used stand-alone (which just changes the + current zone) as well as with a block (which creates a subzone). + So "!subzone" is no longer needed. It still works though. + +!warn: ... + This is given when the pseudo opcode "!warn" is executed. The + actual message varies according to the pseudo opcode's arguments. + +Assembling buggy JMP($xxff) instruction + The original 6502 processor has a bug: When executing an indirect + JMP instruction where the low byte of the argument equals $ff, it + fetches the high byte of the jump target address not from memory + location ARGUMENT+1, but from ARGUMENT-255. Therefore ACME issues + this warning if you are about to generate such an instruction. + Note that this warning is only given for CPU types 6502 and 6510, + because 65c02 and 65816 have been fixed in this respect. + +Bug in ACME, code follows + A situation has been encountered implying there is a bug in ACME. + See the last section in this file. + +Implicit label definition not in leftmost column. + An implicit label definition has blanks before the label name. + Imagine this source code: + lda #00 + imx + rts + Obviously, there's a typo in the middle line (imx instead of inx), + but ACME does not recognize this: It looks just like an implicit + label definition! Therefore releases 0.89 and higher warn you when + an implicit label does not start in column 1. Future versions may + support a command line option to switch this off again, though. + +Label dump file already chosen. + The "!sl" command was given more than once (or in addition to the + "--labeldump" command line option). Only use it once. + +Label name starts with a shift-space character. + The name of a global label starts with a shift-space character. It + is highly likely that this is a typing error, therefore this + warning is issued. + +Memory already initialised. + The "!initmem" command was given more than once (or in addition to + the "--initmem" command line option). Only use it once. + +Offset assembly still active at end of segment. Switched it off. + There's a "*=" command inside an offset assembly block. If you + know of a situation where it makes sense to do this, please tell + me. + +Output file already chosen. + The "!to" command was given more than once (or in addition to the + "--outfile" command line option). Only use it once. + +Segment reached another one, overwriting it. + The program counter has just reached the start of another segment. + Because some people might want to assemble "onto" a binary file + that was loaded before, this is given as a warning instead of as + an error. + +Segment starts inside another one, overwriting it. + The given value in a "*=" command is located inside another + segment. Because some people might want to assemble "onto" a + binary file that was loaded before, this is given as a warning + instead of as an error. + +Used "!to" without file format indicator. Defaulting to "cbm". + Now that "!to" can be given a file format keyword (either "plain" + or "cbm"), using "cbm" as default seems inappropriate. It still + works though. + +Using oversized addressing mode. + ACME just assembled a command using an addressing mode that was + larger than needed. This only happens if ACME could not work out + the argument's value in the first pass, therefore assuming a 16- + bit addressing mode. If, in a later pass, ACME finds out that the + argument is small enough to fit in 8 bits, then this warning is + shown. If you define all your zeropage labels *before* they are + first used, this shouldn't happen. If you know that a specific + argument fits in 8 bits, you can force ACME to use 8 bits + addressing by postfixing the command with "+1". Example: + lda+1 label + ACME will then use an 8-bit addressing mode, regardless of whether + the label is known or not. If the label value happens to be too + large to fit in 8 bits, ACME will show an error of course (To + always truncate a value to 8 bits, use the '<' operator). + More about the postfixing method can be found in "AddrModes.txt". + + +---------------------------------------------------------------------- +Section: Errors during assembly +---------------------------------------------------------------------- + +"ACME" environment variable not found. + This will be shown if the source code references any files from + the library, but the library location variable wasn't set. This + can only be given on systems using the said variable. + +!error: ... + This is given when the pseudo opcode "!error" is executed. The + actual message varies according to the pseudo opcode's arguments. + +Cannot open input file. + ACME had problems opening an input file ("!bin", "!convtab" or + "!src"). Maybe you mistyped its name. + +Conversion table incomplete. + The conversion table file is too small. It needs to be exactly 256 + bytes in size. + +Division by zero. + Guess what - you attempted to divide by zero. + +Exponent is negative. + Using negative exponents would only give sensible results when + using floating point maths. + +File name quotes not found ("" or <>). + File names have to be given in quotes. Either "" quoting for files + located in the current directory or <> quoting for library files. + +Found '}' instead of end-of-file. + ACME encountered a '}' character when it expected the file to end + instead (because no blocks were open). + +Garbage data at end of statement. + There are still arguments when there should not be any more. + +Illegal combination of command and addressing mode. + The given command cannot be used with the given addressing mode on + the CPU you have chosen. + +Illegal combination of command and postfix. + The given command cannot be used with the addressing mode + indicated by the given postfix. + +Illegal postfix. + You used a postfix other than "+1", "+2" or "+3". + +Label already defined. + You defined a label that already had a different value. To change + a label's value, use the "!set" pseudo opcode. + +Macro already defined. + Macros can only be defined once. If you define a macro twice, ACME + will help you find the definitions by giving a warning for the + first definition and a serious error (stopping assembly) for the + second definition. + +Macro not defined (or wrong signature). + You tried to call a macro that either wasn't defined yet (always + define macros before using them) or was called with an illegal + argument list. There must be a 1:1 match between the definition's + formal parameters and the call's actual arguments. + +Macro parameter twice. + The same label name is used two (or more) times in the same macro + parameter list. + +Negative value - cannot choose addressing mode. + Because the argument is a negative value, ACME does not know what + addressing mode (8 bits, 16 bits, on a 65816 even 24 bits) to use. + You can overcome this problem using the postfix method. Or correct + your program to use positive addresses instead. + +No string given. + ACME expects a string but doesn't find it. + +Number out of range. + A value is too high or too low. + +Program counter is unset. + You didn't set the program counter, so ACME didn't know where to + start. + +Quotes still open at end of line. + You forgot the closing quotes. + +Source file contains illegal character. + Your source code file contained a null byte. + +Syntax error. + Guess what - there's a syntax error. + +Target out of range. + A relative addressing (branch commands or PER) only has a limited + range. You exceeded it. + +There's more than one character. + You used a text string in an arithmetic expression, but the string + contained more than a single character. + +Too late for postfix. + You can only postfix labels at the start, before they are used for + the first time. + +Too many '('. + A formula ends before all parentheses were closed. + +Too many ')'. + There are more closing than opening parentheses in a formula. + +Unknown encoding. + You used the "!convtab" command with a keyword ACME does not know. + +Unknown operator. + You used an arithmetic/logical operator ACME does not know. + Remember that text versions of operators have to be given in upper + case. + +Unknown output format. + You used the "!to" command with a keyword ACME does not know. + +Unknown processor. + You used the "!cpu" command with a keyword ACME does not know. + +Unknown pseudo opcode. + You have mistyped a "!" command. + +Value not yet defined. + A value could not be worked out. Maybe you mistyped a label name. + Whether this is given as a "normal" or as a serious error depends + on the currently parsed pseudo opcode. + + +---------------------------------------------------------------------- +Section: Serious errors (stopping assembly) +---------------------------------------------------------------------- + +!serious: ... + This is given when the pseudo opcode "!serious" is executed. The + actual message varies according to the pseudo opcode's arguments. + +Found end-of-file instead of '}'. + The file ended when ACME expected the block to be closed instead + (because there was at least one block left open). + +Loop count is negative. + You used the "!for" command with a negative loop count. + +Macro already defined. + Macros can only be defined once. If you define a macro twice, ACME + will help you find both definitions by giving a warning for the + first definition and a serious error (stopping assembly) for the + second definition. + +Missing '{'. + ACME didn't find the expected '{' character. Remember that '{' + characters must be given on the same line as the command they + belong to. + +Out of memory. + When ACME runs out of memory, it stops assembly, giving this + error. Free some memory and try again. It's highly unlikely anyone + will ever see this error, though. ;) + +Produced too much code. + The program counter reached address $10000, leaving the output + buffer. At the moment, ACME can only produce a maximum of 64 KB. + +Syntax error. + This is only given as a _serious_ error if it's in a "!do" loop + condition. + +Too deeply nested. Recursive macro calls? + The only reason for ACME to have a limit on macro call nesting + at all is to find infinite recursions. Current limit is 64. + +Too deeply nested. Recursive "!source"? + The only reason for ACME to still have a limit on "!source" + nesting at all is to find infinite recursions. Current limit is + 64. + +Value not yet defined. + A value could not be worked out. Maybe you mistyped a label name. + Whether this is given as a "normal" or as a serious error depends + on the currently parsed pseudo opcode. + + +---------------------------------------------------------------------- +Section: Errors on closedown +---------------------------------------------------------------------- + +Cannot open label dump file "FILENAME". +Cannot open output file "FILENAME". + Make sure the name doesn't contain wildcard characters and you + have write access to the directory. + +No output file specified (use the "-o" option or the "!to" pseudo opcode). + You didn't specify the output file, so ACME did not create one. + + +---------------------------------------------------------------------- +Section: Bugs in ACME +---------------------------------------------------------------------- + + The warning "Bug in ACME, code follows" is always followed by a + serious error message, stopping assembly. The second message + actually gives a hint about the bug's location in the source code. + If you ever get this combination of warning and serious error, + please send me an e-mail and tell me about it. If possible, + include a piece of source code that triggers it. + + Please don't get this wrong - there are no known bugs. I just left + some debugging code in place in case there is a bug I failed to + notice during testing. In practice, this warning is not expected + to be given at all. That's the reason why I want to be notified if + it *does* decide to show up. + + The hint messages are of no real interest to the end user, but here + they are for completeness' sake. + +IllegalGroupIndex + The mnemonic tree contains a group that I didn't add. + +IllegalBlockTerminator + A RAM block (macro or loop) was terminated incorrectly. + +IllegalOperatorHandle + The expression parser found an operator that does not exist. + +OperandStackNotEmpty + The expression parser has finished though there are still operands + left to parse. + +OperatorStackNotEmpty + The expression parser has finished though there are still + operators left to parse. + +StrangeInputMode + The input state machine has reached a state that does not exist. + +StrangeParenthesis + The expression parser found a non-existing operator. diff --git a/Platform/Apple/tools/ACME/docs/Example.txt b/Platform/Apple/tools/ACME/docs/Example.txt new file mode 100644 index 00000000..7d83ffc7 --- /dev/null +++ b/Platform/Apple/tools/ACME/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 ddrv.a + acme macedit.a + +ACME will parse the source code files and will then produce files +called "ddrv64.prg" and "macedit.o". You may compare them to the files +called "ddrv64.exp" and "macedit.exp", to make sure ACME works as it +should do. + +Just in case you wonder: + + "ddrv64.prg" is a joystick/mouse driver for the C64. The source + code is fairly well documented. Have a look at it if you need more + examples on how ACME works. By changing one line in the source, + you can also generate "ddrv128.prg", a binary for the C128. + + "macedit" is an unusably bad text editor for the C128. The source + code is not meant to be a good example of ACME's capabilities. + Please *don't* look at it. :) + diff --git a/Platform/Apple/tools/ACME/docs/Help.txt b/Platform/Apple/tools/ACME/docs/Help.txt new file mode 100644 index 00000000..211527fc --- /dev/null +++ b/Platform/Apple/tools/ACME/docs/Help.txt @@ -0,0 +1,176 @@ + + + ACME + + ...the ACME Crossassembler for Multiple Environments + + Release 0.91 + + - free software - + + (C) 1998-2006 Marco Baye + + +---------------------------------------------------------------------- +Section: Copyright +---------------------------------------------------------------------- + +ACME - a crossassembler for producing 6502/6510/65c02/65816 code. +Copyright (C) 1998-2006 Marco Baye +The ACME icon was designed by Wanja "Brix" Gayk + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation; either version 2 of the License, or (at +your option) any later version. + +This program is distributed in the hope that it will be useful, but +WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU +General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the +Free Software Foundation, Inc., 59 Temple Place, Suite 330, +Boston, MA 02111-1307 USA + + +---------------------------------------------------------------------- +Section: Introduction +---------------------------------------------------------------------- + +ACME is a crossassembler for the 65xx range of processors. It knows +about the standard 6502, the 65c02 and the 65816. It also supports +the undocumented ("illegal") opcodes of the 6510 processor (a 6502- +variant that is used in the Commodore C=64). + +This text and the other files in the same directory only describe the +basic functions independent of the platform used. There should be +another help file in this archive that outlines the features specific +to your platform. + +The files in the docs directory and what they contain: + + 65816.txt Stuff specific to the 65816 processor + AddrModes.txt How to choose non-standard addressing modes + AllPOs.txt Lists ACME's pseudo opcodes. Use as a reference. + Changes.txt The change log. + COPYING Version 2 of the GNU General Public License + Errors.txt Lists ACME's error messages and what they mean. + Example.txt Information on how to assemble the example sources. + Help.txt ...is this text. + Illegals.txt Support for undocumented opcodes. + Lib.txt Information about the library. + QuickRef.txt All the basic stuff about ACME. + Source.txt How to compile ACME. + Upgrade.txt Incompatibilities to earlier versions. + +IMPORTANT: If you upgrade from ACME 0.05 or earlier, don't forget to +read the file "Upgrade.txt" - release 0.07 and all later ones are +slightly incompatible to 0.05 and earlier. + +If you want to start using ACME right away, read the file +"QuickRef.txt", it contains the main help text. + + +---------------------------------------------------------------------- +Section: What it can and does +---------------------------------------------------------------------- + +ACME is a crossassembler. +ACME can produce code for the 6502, 6510, 65c02 and 65816 processors. +It does this *fast*. +It can produce at most 64 KBytes of code. +You can use global labels, local labels and anonymous labels. +It is fast. +You can use global and local macros. +You can use conditional assembly. +You can use looping assembly (There are two ways to do this; a very + simple and a very flexible one). +You can include other source files. +You can include binary files (either whole or parts) directly into the + output. +You can use offset assembly (code that is designed to run at a + different address). +It is fast. +ACME's maths parser uses operator priorities, so 1+2*3 will correctly + give 7 (unlike some other free assemblers that give 9 instead). +ACME's maths parser has no problems concerning parentheses and + indirect addressing modes. +ACME's maths parser knows a shit load of different operations. +You can dump the global labels into a file. +ACME supports a library of commonly used macros and labels. +It always takes as many passes as are needed. +ACME exists on several platforms, meaning you can easily exchange your + sources with other people (preferring other OSes). +ACME can convert its strings to PetSCII and screen code (Okay, this is + C64-specific). +Did I mention that it is fast? + + +---------------------------------------------------------------------- +Section: What it can't and doesn't +---------------------------------------------------------------------- + +ACME cannot transfer data to a C64 or another computer. +ACME does not produce ".o65"-format linkable object files. +ACME cannot produce more than 64 KB (would be useful for the 65816). +ACME cannot disassemble or relocate given code files. + + +---------------------------------------------------------------------- +Section: Platform independence +---------------------------------------------------------------------- + +ACME was initially developed under RISC OS. Currently there are +platform-specific versions available for AmigaOS, DOS, Linux, Windows +and RISC OS. The Linux sources should be ready to compile on most +other UNIX-like systems as well. In the future there will hopefully +also be a version that runs on the C64/128. +Though the source code does not exactly look like it *g*, ACME was +written with portability in mind: Some of its limitations were +included on purpose, just to allow a C64/128 version. To successfully +assemble multi-file source codes from other platforms, the file names +have to be altered as little as possible. Please name all your files +that may be distributed in a sensible way, for example by limiting +their file names to 8+3 format. I really hate this stupid will-it- +ever-die DOS convention, but using it is the only way to ensure +portability of files. + +Please use ".a" as the file name extension of ACME source code files. + +All file names used inside source code files have to be given in UNIX +style, ACME will convert them to the current host platform style if +needed. + +There should be no problems concerning newline characters, ACME was +designed to cope with CR, LF and CRLF. + +A minor problem is the different character tables used on different +systems. As all predefined ACME keywords only use 7-bit ASCII, the +assembler will work on any system that uses a superset of this +character table: UTF-8, ANSI, ISO 8859, etc. +Label names can contain top-bit-set characters - these may look +strange if the sources are edited on a different platform, but ACME +will still work. + +If you want to port ACME to another platform, please inform me so that +I can add your version to the ones already present on the ACME +homepage. As the sources are released under the GNU General Public +License, you are not forced to do this; but it would help to make ACME +available to other users. +The same goes for any changes or enhancements to the sources: Please +send me a copy so that the changes can be incorporated into the next +"official" release on the ACME home page. + + +---------------------------------------------------------------------- +Section: Contacting the author +---------------------------------------------------------------------- + +The newest version of ACME can be found at the ACME homepage: +http://home.pages.de/~mac_bacon/smorbrod/acme/ + +If you want to report a bug or make a suggestion, then simply send +me an email: +mailto:marco@baye.de diff --git a/Platform/Apple/tools/ACME/docs/Illegals.txt b/Platform/Apple/tools/ACME/docs/Illegals.txt new file mode 100644 index 00000000..0828f8ae --- /dev/null +++ b/Platform/Apple/tools/ACME/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/Platform/Apple/tools/ACME/docs/Lib.txt b/Platform/Apple/tools/ACME/docs/Lib.txt new file mode 100644 index 00000000..fcf8b079 --- /dev/null +++ b/Platform/Apple/tools/ACME/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/Platform/Apple/tools/ACME/docs/QuickRef.txt b/Platform/Apple/tools/ACME/docs/QuickRef.txt new file mode 100644 index 00000000..a115910a --- /dev/null +++ b/Platform/Apple/tools/ACME/docs/QuickRef.txt @@ -0,0 +1,358 @@ + + + ACME + + ...the ACME Crossassembler for Multiple Environments + + --- Quick reference --- + + +This file should give you a basic overview. More specialized stuff +like forcing a specific addressing mode is discussed in extra files +("AddrModes.txt" in this case). + + +---------------------------------------------------------------------- +Section: Example of what an ACME source code file looks like +---------------------------------------------------------------------- + +;--- Example code fragment, start --- + + !to "tiny.o", cbm ; set output file and format + *= $c000 ; set program counter + + basout = $ffd2 ; explicit global label def. + ; a string output loop: + ldx #0 + beq + ; enter loop + +- jsr basout ; output character + inx ; advance pointer ++ lda .string,x ; get character + bne - ; check whether last + rts +.string !pet "Dumb example", 13, 0 + +;--- Example code fragment, end --- + + +Here's the same fragment again, now with some additional info: + +;--- Example code fragment, start --- + + !to "tiny.o", cbm ; set output file and format +; This is a pseudo opcode to select the output filename and format. +; This can also be done using the command line options "-o" and "-f", +; respectively. + *= $c000 ; set program counter +; This can also be done using the command line option "--setpc". + basout = $ffd2 ; explicit global label def. +; Now "basout" is defined as a global label having the value $ffd2. + ; a string output loop: + ldx #0 + beq + ; enter loop +; "+" is an anonymous forward label. Other ones are "++", "+++", etc. +; They can be used like any other label, but they always reference +; their *NEXT* definition. This saves having to think of names for +; unimportant labels. As the label's value is not defined yet, ACME +; will need to perform a second pass. +- jsr basout ; output character +; "-" is an anonymous backward label. Other ones are "--", "---", etc. +; They can be used like any other label, but they always reference +; their *PREVIOUS* definition. This saves having to think of names for +; unimportant labels. In the line above, the value of "-" is set to +; the current program counter. + inx ; advance pointer ++ lda .string,x ; get character +; Here the value of "+" is set to the current program counter. +; ".string" is a local label (because its name starts with a '.' +; character), but as its value is not defined yet, ACME will need to +; perform a second pass. + bne - ; check whether last +; Here the last definition of the anonymous "-" label is referenced. + rts +.string !pet "Dumb example", 13, 0 +; Now the value of the local label ".string" is set to the current +; program counter. All label values are defined now, so after having +; done the second pass, the binary will be saved. The "!pet" pseudo +; opcode stores its string argument in PetSCII encoding to memory, +; followed by the given byte values. + +;--- Example code fragment, end --- + +As you can see, pseudo opcodes are prefixed with an exclamation mark. +That's non-standard, but: Backwards compatibility is the root of all +evil. :) + +Summary about labels: + +There are global labels (their names starting with a letter or an +underscore character). These can be accessed throughout the whole +assembly. +Then there are local labels (their names starting with a '.' +character). These can only be accessed from inside the macro or zone +they were defined in (for more about macros and zones, see the file +"AllPOs.txt"). +And then there are anonymous labels (their names being sequences of +either '-' or '+' characters). They are also local (bound to their +macro/zone), but in addition to that, the "-" labels can only be used +for backward references, while the "+" labels can only be used for +forward references. +In contrast to global and local labels, anonymous labels can not be +defined explicitly (as in LABEL=VALUE). + +Save the given example source code to a file called "tiny.a" and start +acme by typing + + acme tiny.a + +ACME will then parse the file and report any errors. An output file +will only be generated if there were no errors and if an output +filename has been given. + +After assembly, the example program can be run on a C64 using + + LOAD "tiny.o",8,1 + SYS 49152 + +Note that ACME does not include any routines for transferring data to +a C64. Such tools exist on almost every platform, and I didn't want +ACME to become bloatware. + + +---------------------------------------------------------------------- +Section: The pseudo opcodes +---------------------------------------------------------------------- + +A list with information on how to use all the Pseudo Opcodes can be +found in the file "AllPOs.txt". Here's just a short overview: + +!byte / !word / !24 / !32 / !fill / !align +...for directly placing values into the output file. + +!zone / !sl +...for defining the scope of local labels and saving global labels. + +!convtab / !pet / !raw / !scr / !scrxor / !text +...for converting and outputting strings. + +!do / !endoffile / !for / !if / !ifdef / !set +...for flow control; looping assembly and conditional assembly. + +!binary / !source / !to +...for handling input and output files. + +!pseudopc +...for offset assembly. + +!initmem *= +...for segment assembly. + +!macro + +...for defining and calling macros. + +!cpu !al !as !rl !rs +...for CPU support, especially the 65816 processor. + +!warn !error !serious +...for generating warnings, errors and serious errors. + + +---------------------------------------------------------------------- +Section: Command line arguments +---------------------------------------------------------------------- + +The command line syntax for calling acme is quite simple: + + acme [options] [files] + +Available options are: + -h, --help show this help and exit. + This is more or less useless, because the help is also shown + if ACME is run without any arguments at all. + + -f, --format FORMAT select output format ("plain" or "cbm") + -o, --outfile FILE select output file. + Output filename and format can also be given using the "!to" + pseudo opcode. If the format is not specified, "!to" defaults + to "cbm", while the command line option defaults to "plain". + + -l, --labeldump FILE select label dump file. + This can also be given using the "!sl" pseudo opcode. + + --cpu CPU_TYPE set processor type. + This can be changed in the source code using the "!cpu" pseudo + opcode. Defaults to 6502. + + --setpc NUMBER set program counter. + This can also be given in the source code using "*=NUMBER". + + --initmem NUMBER define 'empty' memory. + This can also be given using the "!initmem" pseudo opcode. + Defaults to zero. + + --maxerrors NUMBER set number of errors before exiting. + If not given, defaults to 10. + + --maxdepth NUMBER set recursion depth for macro calls and the + "!source" pseudo opcode. If not given, defaults to 64. + + -vDIGIT set verbosity level. + Sets how much additional informational output is generated. + Higher values mean more output: + + acme -v0 source.a + This is the default: No additional output is generated, + ACME will only display warnings and errors. + + acme -v1 source.a + Now the start and end addresses of the generated output + file are displayed, along with its size (a CBM-style + "load address" is *not* counted). + + acme -v2 source.a + In addition to the "-v1" output, ACME will announce each + pass, will show amount and offset of "!binary" loads, and + show start and end addresses and size of each segment. + + acme -v3 source.a + In addition to the "-v2" output, ACME will now announce + each source file. + + -V, --version show version and exit. + +Platform-specific versions of ACME might offer more options. +Since version 0.89, ACME accepts more than one top-level-filename +given on the command line. + + +---------------------------------------------------------------------- +Section: The maths parser +---------------------------------------------------------------------- + +ACME has a relatively powerful maths parser. This parser is used +whenever ACME expects to read an integer value. Supported operations +include addition, subtraction, multiplication, divisions, comparisons, +shifts, negation, boolean operations and some assembler-specific stuff +like extracting the "low byte", the "high byte" or the "bank byte" +of a value. +All calculations are done using signed 32-bit integer arithmetic and +all label values are internally handled using 32 bits. + + +This is a list of the operators currently known by ACME: + + Priority Example Meaning Alias +------------------------------------------------------------ + 13 ! v Complement of NOT + 12 v ^ w To the power of + 11 - v Negate + 10 v * w Multiply + 10 v / w Integer-Divide DIV + 10 v % w Remainder of DIV MOD + 9 v + w Add + 9 v - w Subtract + 8 v << w Shift left ASL, LSL + 8 v >> w Arithmetic shift right ASR + 8 v >>> w Logical shift right LSR + 7 < v Lowbyte of + 7 > v Highbyte of + 7 ^ v Bankbyte of + 6 v <= w Lower or equal + 6 v < w Lower than + 6 v >= w Higher or equal + 6 v > w Higher than + 5 v != w Not equal <>, >< + 4 v = w Equal + 3 v & w Bit-wise AND AND + 2 Bit-wise exclusive OR EOR, XOR + 1 v | w Bit-wise OR OR + +Operations with higher priority are done first. Of course you can +change this using parentheses. If you prefer the aliases over the +shorthand characters, note that they must be written in capital +letters. +Note that though there are operators to extract the "low byte", the +"high byte" and the "bank byte", there is no operator to extract the +fourth byte. If you want to access that, shift it down using ">>>" or +"LSR". +In cases where it's not clear which operator was wanted, ACME takes +the longest possible one: + v<>w ...checks for "v not equal w" + v< >w ...checks for "v smaller than high byte of w" +So you may have to separate operators with spaces to make sure ACME +does what you want. + + +This is a list of the value formats currently known by ACME: + + Example Meaning Prefix +--------------------------------------------------------------------- + GetByte Global label None + .Loop Local label "." + 9260 Decimal value None + $1a8e Hexadecimal value "$" + 0x8b4f Hexadecimal value "0x" + &1054 Octal value "&" + %10010 Binary value "%" +In binary values you can substitute the characters "0" and "1" by "." +and "#" respectively. This way the values are much more readable. + "r" Character value Double quotes + 'r' Character value Single quotes +The value depends on the current conversion table, chosen using the +"!ct" pseudo opcode. + * The current PC "*" +During offset assembly, "*" gives the value of the "Pseudo PC". Just +to make sure: The value of the program counter is always the value +that was valid at the start of the current statement, so + + !word *, *, *, * + +will give the same value four times. I think most assemblers do it +this way. Calculating 0^0 (zero to the power of zero) will give 1. If +you don't know why I'm telling you this, ask a mathematician. :) + + +---------------------------------------------------------------------- +Section: Almost, but not quite, entirely useless syntax +---------------------------------------------------------------------- + +Every ACME source code file consists of a non-negative number of +"lines". The lines have to be separated from each other using CR, LF +or CRLF characters. + +Every line consists of a non-negative number of "statements" and an +optional comment. Statements have to be separated from each other +using colon (":") characters, the comment has to be prefixed with a +semicolon (";") character. + +Every statement consists of an optional "implicit label definition" +and an optional "command". These are separated from each other using +any number of SPACE or TAB characters. If an implicit label definition +has blanks before it, a warning is given (to spot typing errors - see +Errors.txt for more info). + +Every label consists of these characters: "a" to "z", "A" to "Z", "0" +to "9", the underscore character "_" and all characters with values +beyond 127. The first character must not be a digit though. But it can +be a dot ("."), making the label a local one. Two other possibilities +for label names are "all-characters-are-minus" (then it's an anonymous +backward label) and "all-characters-are-plus" (then it's an anonymous +forward label). + +Every command is one of the following: + An assembler opcode + A pseudo opcode, beginning with a "!" character + An explicit label definition (label=value) + A pc definition, beginning with a "*" character + A macro call, beginning with a "+" character +...and the syntax of those things varies. :) + +Assembler mnemonics and pseudo opcodes are case insensitive, so +whether you write "LDA" or "lda" or "LdA" does not make a difference. + +Arithmetic operators like MOD, XOR, LSL must be written in UPPER CASE; +this rule simply serves to make them stand out. + +Label names are case sensitive, so "label" and "Label" are two +different things. diff --git a/Platform/Apple/tools/ACME/docs/Source.txt b/Platform/Apple/tools/ACME/docs/Source.txt new file mode 100644 index 00000000..d248999e --- /dev/null +++ b/Platform/Apple/tools/ACME/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/Platform/Apple/tools/ACME/docs/Upgrade.txt b/Platform/Apple/tools/ACME/docs/Upgrade.txt new file mode 100644 index 00000000..33580b11 --- /dev/null +++ b/Platform/Apple/tools/ACME/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/Platform/Apple/tools/ACME/examples/ddrv128.exp b/Platform/Apple/tools/ACME/examples/ddrv128.exp new file mode 100644 index 00000000..34ae5347 Binary files /dev/null and b/Platform/Apple/tools/ACME/examples/ddrv128.exp differ diff --git a/Platform/Apple/tools/ACME/examples/ddrv64.exp b/Platform/Apple/tools/ACME/examples/ddrv64.exp new file mode 100644 index 00000000..d422a4b0 Binary files /dev/null and b/Platform/Apple/tools/ACME/examples/ddrv64.exp differ diff --git a/Platform/Apple/tools/ACME/examples/macedit.exp b/Platform/Apple/tools/ACME/examples/macedit.exp new file mode 100644 index 00000000..613bbaa5 Binary files /dev/null and b/Platform/Apple/tools/ACME/examples/macedit.exp differ diff --git a/Platform/Apple/tools/ACME/examples/me/tables.bin b/Platform/Apple/tools/ACME/examples/me/tables.bin new file mode 100644 index 00000000..4e72161a Binary files /dev/null and b/Platform/Apple/tools/ACME/examples/me/tables.bin differ diff --git a/Platform/Apple/tools/ACME/src/Makefile b/Platform/Apple/tools/ACME/src/Makefile new file mode 100644 index 00000000..7d4d0cbd --- /dev/null +++ b/Platform/Apple/tools/ACME/src/Makefile @@ -0,0 +1,66 @@ +CFLAGS = -O3 -Wall +#LIBS = -lm +CC = gcc +RM = rm + +#SRC = + +PROGS = acme +BINDIR = /usr/local/bin +USERBIN = $(HOME)/bin +JAR = acme.jar + +all: $(PROGS) $(JAR) + +acme: acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o + $(CC) $(LIBS) $(CFLAGS) -o acme acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o + strip acme + +$(JAR): + docker run -t -v "$(shell pwd):/project" mhaye/nestedvm:v4 /bin/bash -c "cd /project && nestedvm-c2jar $(JAR) acme.Acme acme.c alu.c cliargs.c cpu.c dynabuf.c encoding.c flow.c global.c input.c label.c macro.c mnemo.c output.c platform.c section.c tree.c" + +acme.o: config.h acme.h alu.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h platform.h section.h acme.h acme.c + +alu.o: config.h cpu.h dynabuf.h encoding.h global.h input.h label.h platform.h section.h tree.h alu.h alu.c + +cliargs.o: cliargs.h cliargs.c + +cpu.o: config.h alu.h dynabuf.h global.h input.h mnemo.h output.h tree.h cpu.h cpu.c + +dynabuf.o: config.h acme.h global.h input.h dynabuf.h dynabuf.c + +encoding.o: config.h alu.h acme.h dynabuf.h global.h output.h input.h tree.h encoding.h encoding.c + +flow.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h macro.h mnemo.h tree.h flow.h flow.c + +global.o: config.h acme.h cpu.h input.h label.h macro.h platform.h section.h global.h global.c + +input.o: config.h dynabuf.h global.h section.h tree.h input.h input.c + +label.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h mnemo.h section.h tree.h label.h label.c + +macro.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h section.h tree.h macro.h macro.c + +mnemo.o: config.h alu.h cpu.h dynabuf.h global.h input.h output.h tree.h mnemo.h mnemo.c + +output.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h tree.h output.h output.c + +platform.o: config.h platform.h platform.c + +section.o: config.h dynabuf.h global.h section.h tree.h section.h section.c + +tree.o: config.h dynabuf.h global.h label.h tree.h tree.c + +clean: + -$(RM) -f *.o $(PROGS) *~ core *.jar + + +install: all + install -d $(BINDIR) + install $(PROGS) $(BINDIR) + +userinstall: all + install -d $(USERBIN) + install $(PROGS) $(USERBIN) + +# DO NOT DELETE diff --git a/Platform/Apple/tools/ACME/src/Makefile.dos b/Platform/Apple/tools/ACME/src/Makefile.dos new file mode 100644 index 00000000..b1c1e0a3 --- /dev/null +++ b/Platform/Apple/tools/ACME/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 acme.h alu.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h platform.h section.h acme.h acme.c + +alu.o: config.h cpu.h dynabuf.h encoding.h global.h input.h label.h platform.h section.h tree.h alu.h alu.c + +cliargs.o: cliargs.h cliargs.c + +cpu.o: config.h alu.h dynabuf.h global.h input.h mnemo.h output.h tree.h cpu.h cpu.c + +dynabuf.o: config.h acme.h global.h input.h dynabuf.h dynabuf.c + +encoding.o: config.h alu.h acme.h dynabuf.h global.h output.h input.h tree.h encoding.h encoding.c + +flow.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h macro.h mnemo.h tree.h flow.h flow.c + +global.o: config.h acme.h cpu.h input.h label.h macro.h platform.h section.h global.h global.c + +input.o: config.h dynabuf.h global.h section.h tree.h input.h input.c + +label.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h mnemo.h section.h tree.h label.h label.c + +macro.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h section.h tree.h macro.h macro.c + +mnemo.o: config.h alu.h cpu.h dynabuf.h global.h input.h output.h tree.h mnemo.h mnemo.c + +output.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h tree.h output.h output.c + +platform.o: config.h platform.h platform.c + +section.o: config.h dynabuf.h global.h section.h tree.h section.h section.c + +tree.o: config.h dynabuf.h global.h label.h tree.h tree.c + +clean: + del *.o +# -$(RM) -f *.o $(PROGS) *~ core + +#install: all +# install -d $(BINDIR) +# install $(PROGS) $(BINDIR) + +#userinstall: all +# install -d $(USERBIN) +# install $(PROGS) $(USERBIN) + +# DO NOT DELETE diff --git a/Platform/Apple/tools/ACME/src/Makefile.riscos b/Platform/Apple/tools/ACME/src/Makefile.riscos new file mode 100644 index 00000000..8d5e47d5 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/Makefile.riscos @@ -0,0 +1,64 @@ +CFLAGS = -O3 -mthrowback -mlibscl -Wall +#LIBS = -lm +CC = gcc +RM = rm + +#SRC = + +PROGS = acme +#BINDIR = /usr/local/bin +#USERBIN = $(HOME)/bin + +all: $(PROGS) + +acme: acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o + $(CC) $(LIBS) $(CFLAGS) -o !Unsqueezed acme.o alu.o cliargs.o cpu.o dynabuf.o encoding.o flow.o global.o input.o label.o macro.o mnemo.o output.o platform.o section.o tree.o + Squeeze -f -v !Unsqueezed !ACME.!RunImage + + + +acme.o: config.h acme.h alu.h cpu.h dynabuf.h encoding.h flow.h global.h input.h label.h macro.h mnemo.h output.h platform.h section.h acme.h acme.c + +alu.o: config.h cpu.h dynabuf.h encoding.h global.h input.h label.h platform.h section.h tree.h alu.h alu.c + +cliargs.o: cliargs.h cliargs.c + +cpu.o: config.h alu.h dynabuf.h global.h input.h mnemo.h output.h tree.h cpu.h cpu.c + +dynabuf.o: config.h acme.h global.h input.h dynabuf.h dynabuf.c + +encoding.o: config.h alu.h acme.h dynabuf.h global.h output.h input.h tree.h encoding.h encoding.c + +flow.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h macro.h mnemo.h tree.h flow.h flow.c + +global.o: config.h acme.h cpu.h input.h label.h macro.h platform.h section.h global.h global.c + +input.o: config.h dynabuf.h global.h section.h tree.h input.h input.c + +label.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h mnemo.h section.h tree.h label.h label.c + +macro.o: config.h acme.h alu.h dynabuf.h global.h input.h label.h section.h tree.h macro.h macro.c + +mnemo.o: config.h alu.h cpu.h dynabuf.h global.h input.h output.h tree.h mnemo.h mnemo.c + +output.o: config.h acme.h alu.h cpu.h dynabuf.h global.h input.h tree.h output.h output.c + +platform.o: config.h platform.h platform.c + +section.o: config.h dynabuf.h global.h section.h tree.h section.h section.c + +tree.o: config.h dynabuf.h global.h label.h tree.h tree.c + +clean: + wipe o.* ~c +# -$(RM) -f *.o $(PROGS) *~ core + +#install: all +# install -d $(BINDIR) +# install $(PROGS) $(BINDIR) + +#userinstall: all +# install -d $(USERBIN) +# install $(PROGS) $(USERBIN) + +# DO NOT DELETE diff --git a/Platform/Apple/tools/ACME/src/_amiga.h b/Platform/Apple/tools/ACME/src/_amiga.h new file mode 100644 index 00000000..4eb08799 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/_amiga.h @@ -0,0 +1,53 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff (in this case, for AmigaOS) +#ifndef platform_H +#define platform_H + + +// Symbolic constants and macros + +// Called once on program startup (could register exit handler, if needed) +#define PLATFORM_INIT + +// Convert UNIX-style pathname to Amiga-style pathname (no change) +#define PLATFORM_CONVERTPATHCHAR(a) (a) + +// String containing the prefix for accessing files from the library tree +#define PLATFORM_LIBPREFIX "progdir:acme_lib/" +#define NO_NEED_FOR_ENV_VAR + +// Setting file types of created files +#define PLATFORM_SETFILETYPE_CBM(a) +#define PLATFORM_SETFILETYPE_PLAIN(a) +#define PLATFORM_SETFILETYPE_TEXT(a) + +// Platform specific message output +#define PLATFORM_WARNING(a) +#define PLATFORM_ERROR(a) +#define PLATFORM_SERIOUS(a) + +// Integer-to-character conversion routine +#define PLATFORM_INT2BYTE(x) do {\ + x ^= x >> 16;\ + x ^= x >> 8;\ + x &= 255;\ +} while(0) + +// Output of platform-specific command line switches +#define PLATFORM_OPTION_HELP + +// Processing of platform-specific command line switches +#define PLATFORM_SHORTOPTION_CODE +#define PLATFORM_LONGOPTION_CODE + +// other stuff +#ifdef __SASC__ +#define inline +#endif + + +#endif diff --git a/Platform/Apple/tools/ACME/src/_dos.c b/Platform/Apple/tools/ACME/src/_dos.c new file mode 100644 index 00000000..aa655130 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/_dos.c @@ -0,0 +1,47 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff (in this case, for DOS, OS/2 and Windows) +#ifndef platform_C +#define platform_C + +#include +#include "dynabuf.h" + + +// Variables +char *DOS_lib_prefix = NULL; // header string of library tree + + +// Functions + +// Called on program entry - set up DOS-specific stuff +void DOS_entry(void) { + char *env_var; + + // Find out the path of ACME's library + env_var = getenv("ACME"); + // if environment variable was found, make a copy + if(env_var) { + DYNABUF_CLEAR(GlobalDynaBuf); + // copy environment variable to global dynamic buffer + DynaBuf_add_string(GlobalDynaBuf, env_var); + DynaBuf_append(GlobalDynaBuf, '\\'); // add dir separator + DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator + DOS_lib_prefix = DynaBuf_get_copy(GlobalDynaBuf); + } +} + +// Convert UNIX-style pathname to DOS-style pathname +char DOS_convert_path_char(char byte) { + if(byte == '/') + return('\\'); + if(byte == '\\') + return('/'); + return(byte); +} + + +#endif diff --git a/Platform/Apple/tools/ACME/src/_dos.h b/Platform/Apple/tools/ACME/src/_dos.h new file mode 100644 index 00000000..c9b66452 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/_dos.h @@ -0,0 +1,58 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff (in this case, for DOS, OS/2 and Windows) +#ifndef platform_H +#define platform_H + +#include "config.h" + + +// Symbolic constants and macros + +// Called once on program startup (could register exit handler, if needed) +#define PLATFORM_INIT DOS_entry() + +// Convert UNIX-style pathname to DOS-style pathname +#define PLATFORM_CONVERTPATHCHAR(a) DOS_convert_path_char(a) + +// String containing the prefix for accessing files from the library tree +#define PLATFORM_LIBPREFIX DOS_lib_prefix + +// Setting file types of created files +#define PLATFORM_SETFILETYPE_CBM(a) +#define PLATFORM_SETFILETYPE_PLAIN(a) +#define PLATFORM_SETFILETYPE_TEXT(a) + +// Platform specific message output +#define PLATFORM_WARNING(a) +#define PLATFORM_ERROR(a) +#define PLATFORM_SERIOUS(a) + +// Integer-to-character conversion routine +#define PLATFORM_INT2BYTE(x) do {\ + x ^= x >> 16;\ + x ^= x >> 8;\ + x &= 255;\ +} while(0) + +// Output of platform-specific command line switches +#define PLATFORM_OPTION_HELP + +// Processing of platform-specific command line switches +#define PLATFORM_SHORTOPTION_CODE +#define PLATFORM_LONGOPTION_CODE + + +// Variables +extern char *DOS_lib_prefix; // header string of library tree + + +// Prototypes +extern void DOS_entry(void); +extern char DOS_convert_path_char(char); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/_riscos.c b/Platform/Apple/tools/ACME/src/_riscos.c new file mode 100644 index 00000000..1563e5a2 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/_riscos.c @@ -0,0 +1,97 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff (in this case, for RISC OS) +#ifndef platform_C +#define platform_C + +#include +#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 + + +// Functions + +// Exit handler: If throwback was used, de-register now +void RISCOS_exit(void) { + _kernel_swi_regs regs; + + if(RISCOS_flags & RISCOSFLAG_THROWN) { + _kernel_swi(XDDEUTILS_THROWBACKEND, ®s, ®s); + RISCOS_flags &= ~RISCOSFLAG_THROWN; + } +} + +// Called on program entry - set up RISC OS-specific stuff +void RISCOS_entry(void) { + atexit(RISCOS_exit); +} + +// Convert UNIX-style pathname to RISC OS-style pathname +char RISCOS_convert_path_char(char byte) { + if(byte == '.') + return('/'); + if(byte == '/') + return('.'); + if(byte == '?') + return('#'); + if(byte == '#') + return('?'); + return(byte); +} + +// Setting the created files' types +void RISCOS_set_filetype(const char* filename, int file_type) { + _kernel_swi_regs regs; + + regs.r[0] = 18; // reason code (set file type) + regs.r[1] = (int) filename; + regs.r[2] = file_type; + _kernel_swi(OS_FILE, ®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/Platform/Apple/tools/ACME/src/_riscos.h b/Platform/Apple/tools/ACME/src/_riscos.h new file mode 100644 index 00000000..e0244acd --- /dev/null +++ b/Platform/Apple/tools/ACME/src/_riscos.h @@ -0,0 +1,69 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff (in this case, for RISC OS) +#ifndef platform_H +#define platform_H + +#include "config.h" + + +// Symbolic constants and macros + +// Called once on program startup (could register exit handler, if needed) +#define PLATFORM_INIT RISCOS_entry() + +// Convert UNIX-style pathname to RISC OS-style pathname +#define PLATFORM_CONVERTPATHCHAR(a) RISCOS_convert_path_char(a) + +// String containing the prefix for accessing files from the library tree +#define PLATFORM_LIBPREFIX "ACME_Lib:" +#define NO_NEED_FOR_ENV_VAR + +// Setting file types of created files +#define PLATFORM_SETFILETYPE_CBM(a) RISCOS_set_filetype(a, 0x064) +#define PLATFORM_SETFILETYPE_PLAIN(a) RISCOS_set_filetype(a, 0xffd) +#define PLATFORM_SETFILETYPE_TEXT(a) RISCOS_set_filetype(a, 0xfff) + +// Platform specific message output +#define PLATFORM_WARNING(a) RISCOS_throwback(a, 0) +#define PLATFORM_ERROR(a) RISCOS_throwback(a, 1) +#define PLATFORM_SERIOUS(a) RISCOS_throwback(a, 2) + +// Integer-to-character conversion routine +#define PLATFORM_INT2BYTE(x) do {\ + x ^= x >> 16;\ + x ^= x >> 8;\ + x &= 255;\ +} while(0) + +// Output of platform-specific command line switches +#define PLATFORM_OPTION_HELP \ +" -t, --throwback use the DDEUtils module's \"throwback\" protocol.\n" + +// Processing of platform-specific command line switches +#define PLATFORM_SHORTOPTION_CODE \ + case 't': \ + RISCOS_flags |= RISCOSFLAG_THROWBACK; \ + break; +#define PLATFORM_LONGOPTION_CODE \ + else if(strcmp(string, "throwback") == 0) \ + RISCOS_flags |= RISCOSFLAG_THROWBACK; + + +// Variables +extern int RISCOS_flags; // Holds platform-specific flags +#define RISCOSFLAG_THROWBACK (1u << 0) // use throwback protocol +#define RISCOSFLAG_THROWN (1u << 1) // throwback is active + + +// Prototypes +extern void RISCOS_entry(void); +extern char RISCOS_convert_path_char(char); +extern void RISCOS_set_filetype(const char*, int); +extern void RISCOS_throwback(const char*, int); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/_std.c b/Platform/Apple/tools/ACME/src/_std.c new file mode 100644 index 00000000..9b274df5 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/_std.c @@ -0,0 +1,38 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff (in this case, for unknown OSes) +#ifndef platform_C +#define platform_C + +#include +#include "dynabuf.h" + + +// Variables +char *AnyOS_lib_prefix = NULL; // header string of library tree + + +// Functions + +// Called on program entry - set up platform-specific stuff +void AnyOS_entry(void) { + char *env_var; + + // Find out the path of ACME's library + env_var = getenv("ACME"); + // if environment variable was found, make a copy + if(env_var) { + DYNABUF_CLEAR(GlobalDynaBuf); + // copy environment variable to global dynamic buffer + DynaBuf_add_string(GlobalDynaBuf, env_var); + DynaBuf_append(GlobalDynaBuf, '/'); // add dir separator + DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator + AnyOS_lib_prefix = DynaBuf_get_copy(GlobalDynaBuf); + } +} + + +#endif diff --git a/Platform/Apple/tools/ACME/src/_std.h b/Platform/Apple/tools/ACME/src/_std.h new file mode 100644 index 00000000..1d4af752 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/_std.h @@ -0,0 +1,55 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff (in this case, for unknown OSes) +#ifndef platform_H +#define platform_H + + +// Symbolic constants and macros + +// Called once on program startup (could register exit handler, if needed) +#define PLATFORM_INIT AnyOS_entry() + +// Convert UNIX-style pathname to AnyOS-style pathname (no change) +#define PLATFORM_CONVERTPATHCHAR(a) (a) + +// String containing the prefix for accessing files from the library tree +#define PLATFORM_LIBPREFIX AnyOS_lib_prefix + +// Setting the created files' types +#define PLATFORM_SETFILETYPE_CBM(a) +#define PLATFORM_SETFILETYPE_PLAIN(a) +#define PLATFORM_SETFILETYPE_TEXT(a) + +// Platform specific message output +#define PLATFORM_WARNING(a) +#define PLATFORM_ERROR(a) +#define PLATFORM_SERIOUS(a) + +// Integer-to-character conversion routine +#define PLATFORM_INT2BYTE(x) do {\ + x ^= x >> 16;\ + x ^= x >> 8;\ + x &= 255;\ +} while(0) + +// Output of platform-specific command line switches +#define PLATFORM_OPTION_HELP + +// Processing of platform-specific command line switches +#define PLATFORM_SHORTOPTION_CODE +#define PLATFORM_LONGOPTION_CODE + + +// Variables +extern char *AnyOS_lib_prefix; // header string of library tree + + +// Prototypes +extern void AnyOS_entry(void); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/acme.c b/Platform/Apple/tools/ACME/src/acme.c new file mode 100644 index 00000000..ce5ddf7f --- /dev/null +++ b/Platform/Apple/tools/ACME/src/acme.c @@ -0,0 +1,376 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; either version 2 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program; if not, write to the Free Software +// Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + +#define RELEASE "0.91" // update before release (FIXME) +#define CODENAME "Gargravarr" // update before release +#define CHANGE_DATE "26 Mar" // update before release +#define CHANGE_YEAR "2006" // update before release +#define HOME_PAGE "http://home.pages.de/~mac_bacon/smorbrod/acme/" + +#include +#include +#include +#include "acme.h" +#include "alu.h" +#include "cliargs.h" +#include "config.h" +#include "cpu.h" +#include "dynabuf.h" +#include "encoding.h" +#include "flow.h" +#include "global.h" +#include "input.h" +#include "label.h" +#include "macro.h" +#include "mnemo.h" +#include "output.h" +#include "platform.h" +#include "section.h" + + +// Constants +static const char FILE_WRITETEXT[] = "w"; +static const char FILE_WRITEBINARY[] = "wb"; +// names for error messages +static const char name_outfile[] = "output filename"; +static const char name_dumpfile[] = "label dump filename"; +// long options +#define OPTION_HELP "help" +#define OPTION_FORMAT "format" +#define OPTION_OUTFILE "outfile" +#define OPTION_LABELDUMP "labeldump" +#define OPTION_SETPC "setpc" +#define OPTION_CPU "cpu" +#define OPTION_INITMEM "initmem" +#define OPTION_MAXERRORS "maxerrors" +#define OPTION_MAXDEPTH "maxdepth" +#define OPTION_VERSION "version" + + +// Variables +static const char **toplevel_sources; +static int toplevel_src_count = 0; +static signed long starting_pc = PC_UNDEFINED; +static signed long fill_value = MEMINIT_USE_DEFAULT; +static struct cpu_t *default_cpu = NULL; +const char* labeldump_filename = NULL; +const char* output_filename = NULL; +// maximum recursion depth for macro calls and "!source" +signed long macro_recursions_left = MAX_NESTING; +signed long source_recursions_left = MAX_NESTING; + +// Functions + +// Show release and platform info (and exit, if wanted) +static void show_version(bool exit_after) { + puts( +"This is ACME, release " RELEASE " (\"" CODENAME "\"), " CHANGE_DATE " " CHANGE_YEAR "\n" +" " PLATFORM_VERSION + ); + if(exit_after) + exit(EXIT_SUCCESS); +} + +// Show full help (headline, release/platform/version, copyright, dedication, +// warranty disclaimer, usage) and exit program (SUCCESS) +static void show_help_and_exit(void) { + puts("\n" +"ACME - the ACME Crossassembler for Multiple Environments\n" +" Copyright (C) 1998-" CHANGE_YEAR " Marco Baye"); + show_version(FALSE); + puts( +"ACME comes with ABSOLUTELY NO WARRANTY; for details read the help file.\n" +" This is free software, and you are welcome to redistribute it under\n" +" certain conditions; as outlined in the GNU General Public License.\n" +"Dedicated to the wisest being I ever had the pleasure of reading\n" +" books of (currently spending some time dead for tax reasons).\n" +"The newest version can be found at the ACME homepage:\n" +" " HOME_PAGE "\n" +"\n" +"Usage: acme [OPTION...] [FILE]...\n" +" -h, --" OPTION_HELP " show this help and exit.\n" +" -f, --" OPTION_FORMAT " FORMAT select output format.\n" +" -o, --" OPTION_OUTFILE " FILE select output file.\n" +" -l, --" OPTION_LABELDUMP " FILE select label dump file.\n" +" --" OPTION_SETPC " NUMBER set program counter.\n" +" --" OPTION_CPU " CPU select target processor.\n" +" --" OPTION_INITMEM " NUMBER define 'empty' memory.\n" +" --" OPTION_MAXERRORS " NUMBER set number of errors before exiting.\n" +" --" OPTION_MAXDEPTH " NUMBER set recursion depth for macro calls and !src.\n" +" -vDIGIT set verbosity level.\n" +PLATFORM_OPTION_HELP +" -V, --" OPTION_VERSION " show version and exit.\n" + ); + exit(EXIT_SUCCESS); +} + + +// Error handling + +// Tidy up before exiting by saving label dump +int ACME_finalize(int exit_code) { + FILE* fd; + + if(labeldump_filename) { + fd = fopen(labeldump_filename, FILE_WRITETEXT); + if(fd) { + Label_dump_all(fd); + fclose(fd); + } else { + fprintf(stderr, + "Error: Cannot open label dump file \"%s\".\n", + labeldump_filename); + exit_code = EXIT_FAILURE; + } + } + return(exit_code); +} + +// Save output file +static void save_output_file(void) { + FILE* fd; + + // if no output file chosen, tell user and do nothing + if(output_filename == NULL) { + fputs("No output file specified (use the \"-o\" option or the \"!to\" pseudo opcode).", stderr); + return; + } + fd = fopen(output_filename, FILE_WRITEBINARY); + if(fd == NULL) { + fprintf(stderr, "Error: Cannot open output file \"%s\".\n", + output_filename); + return; + } + Output_save_file(fd, Mem_lowest_pc, Mem_highest_pc); + fclose(fd); +} + +// Perform a single pass. Returns number of "NeedValue" type errors. +static int perform_pass(int flags) { + FILE* fd; + int i; + + // be verbose + if(Process_verbosity > 1) + puts((flags & PASS_ISFIRST) ? "First pass." : ( + (flags & PASS_ISERROR) ? + "Further pass needed to find error." : + "Further pass." + )); + // call modules' "pass init" functions + CPU_passinit(default_cpu, starting_pc);// set default cpu values + Encoding_passinit(); // set default encoding + Section_passinit(); // set initial zone (untitled) + // init variables + pass_flags = flags; + pass_undefined_count = 0; // no "NeedValue" errors yet + pass_real_errors = 0; // no real errors yet + pc_inc = 0; // Increase PCs by this amount at end of line + // Process toplevel files + for(i = 0; i -1 + && starting_pc < 65536) + return; + fprintf(stderr, "%sProgram counter out of range (0-0xffff).\n", cliargs_error); + exit(EXIT_FAILURE); +} + +// set initial memory contents +static void set_mem_contents(void) { + fill_value = cliargs_get_long("initmem value"); + if(fill_value >= -128 + && fill_value <= 255) + return; + fprintf(stderr, "%sInitmem value out of range (0-0xff).\n", cliargs_error); + exit(EXIT_FAILURE); +} + +// handle long options (like "--example"). Return unknown string. +static const char* long_option(const char* string) { + if(strcmp(string, OPTION_HELP) == 0) + show_help_and_exit(); + else if(strcmp(string, OPTION_FORMAT) == 0) + set_output_format(); + else if(strcmp(string, OPTION_OUTFILE) == 0) + output_filename = cliargs_get_string(name_outfile); + else if(strcmp(string, OPTION_LABELDUMP) == 0) + labeldump_filename = cliargs_get_string(name_dumpfile); + else if(strcmp(string, OPTION_SETPC) == 0) + set_starting_pc(); + else if(strcmp(string, OPTION_CPU) == 0) + set_starting_cpu(); + else if(strcmp(string, OPTION_INITMEM) == 0) + set_mem_contents(); + else if(strcmp(string, OPTION_MAXERRORS) == 0) + max_errors = cliargs_get_long("maximum error count"); + else if(strcmp(string, OPTION_MAXDEPTH) == 0) + macro_recursions_left = (source_recursions_left = cliargs_get_long("recursion depth")); +// else if(strcmp(string, "strictsyntax") == 0) +// strict_syntax = TRUE; + PLATFORM_LONGOPTION_CODE + else if(strcmp(string, OPTION_VERSION) == 0) + show_version(TRUE); + else return(string); + return(NULL); +} + +// Handle short options (like "-e"). Return unknown character. +static char short_option(const char* argument) { + while(*argument) { + switch(*argument) { + + case 'h': // "-h" shows help + show_help_and_exit(); + + case 'f': // "-f" selects output format + set_output_format(); + break; + + case 'o': // "-o" selects output filename + output_filename = cliargs_get_string(name_outfile); + break; + + case 'l': // "-l" selects label dump filename + labeldump_filename = cliargs_get_string(name_dumpfile); + break; + + case 'v': // "-v" changes verbosity + Process_verbosity++; + if((argument[1] >= '0') && (argument[1] <= '9')) + Process_verbosity = *(++argument) - '0'; + break; + + // platform specific switches are inserted here + PLATFORM_SHORTOPTION_CODE + + case 'V': // "-V" shows version + show_version(TRUE); + break; + + default: // unknown ones: program termination + return(*argument); + + } + argument++; + } + return('\0'); +} + +// guess what +int main(int argc, const char *argv[]) { + // if called without any arguments, show usage info (not full help) + if(argc == 1) + show_help_and_exit(); + cliargs_init(argc, argv); + DynaBuf_init();// inits *global* dynamic buffer - important, so first + // Init platform-specific stuff. + // For example, this could read the library path from an + // environment variable, which in turn may need DynaBuf already. + PLATFORM_INIT; + // init some keyword trees needed for argument handling + CPUtype_init(); + Outputfile_init(); + // handle command line arguments + cliargs_handle_options(short_option, long_option); + // generate list of files to process + cliargs_get_rest(&toplevel_src_count, &toplevel_sources, + "No top level sources given"); + // Init modules (most of them will just build keyword trees) + ALU_init(); + CPU_init(); + Encoding_init(); + Flow_init(); + Input_init(); + Label_init(); + Macro_init(); + Mnemo_init(); + Output_init(fill_value); + Section_init(); + if(do_actual_work()) + save_output_file(); + return(ACME_finalize(EXIT_SUCCESS)); // dump labels, if wanted +} diff --git a/Platform/Apple/tools/ACME/src/acme.h b/Platform/Apple/tools/ACME/src/acme.h new file mode 100644 index 00000000..519f232c --- /dev/null +++ b/Platform/Apple/tools/ACME/src/acme.h @@ -0,0 +1,23 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Main definitions +#ifndef acme_H +#define acme_H + +#include "config.h" + +// Variables +extern const char* labeldump_filename; +extern const char* output_filename; +// maximum recursion depth for macro calls and "!source" +extern signed long macro_recursions_left; +extern signed long source_recursions_left; + +// Prototypes +extern int ACME_finalize(int exit_code); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/acme.jar b/Platform/Apple/tools/ACME/src/acme.jar new file mode 100644 index 00000000..4a8b62ef Binary files /dev/null and b/Platform/Apple/tools/ACME/src/acme.jar differ diff --git a/Platform/Apple/tools/ACME/src/alu.c b/Platform/Apple/tools/ACME/src/alu.c new file mode 100644 index 00000000..802f48e0 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/alu.c @@ -0,0 +1,1053 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Arithmetic/logic unit + +#include +#include "platform.h" // done first in case "inline" is redefined +#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 HALF_INITIAL_STACK_SIZE 8 +static const char exception_div_by_zero[] = "Division by zero."; +static const char exception_need_value[] = "Value not yet defined."; +static const char exception_paren_open[] = "Too many '('."; + +// Operator handles (FIXME - use function pointers instead? or too slow?) +enum op_handle_t { +// special values (pseudo operators) + OPHANDLE_END, // "reached end of expression" + OPHANDLE_RETURN, // "return value to caller" +// 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_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 inline value_t my_asr(value_t left, value_t right) { + value_t result = left >>= right; + + if((left >= 0) || (result < 0)) + return(result); + return(result | (-1L << (8 * sizeof(value_t) - right))); +} + +// Lookup (and create, if necessary) label tree item and return its value. +// DynaBuf holds the label's name and "zone" its zone. +// +// This routine is not allowed to change DynaBuf because that's where the +// label name is stored! +static void get_label_value(zone_t zone) { + label_t* label; + int flags; + + // If the label gets created now, mark it as unsure + label = Label_find(zone, MVALUE_UNSURE); + flags = label->flags; + // in first pass, count usage + if(pass_flags & PASS_ISFIRST) + label->usage++; + PUSH_OPERAND(label->value, flags | MVALUE_EXISTS); +} + +// Routine for parsing a quoted character. The character will be converted +// using the current encoding. +static inline void parse_quoted_character(char closing_quote) { + value_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 = (value_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_OPERAND(value, MVALUE_GIVEN | MVALUE_ISBYTE); + // Now GotByte = char following closing quote (or CHAR_EOS on error) +} + +// Routine for parsing a 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; + bool go_on; // continue loop flag + int digits = -1, // digit counter + flags = MVALUE_GIVEN; + value_t value = 0; + + do { + digits++; + go_on = FALSE; + 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 = TRUE;// keep going + } + // if legal ("a-f") character, add character value + if((byte >= 'a') && (byte <= 'f')) { + value = (value << 4) + (byte - 'a') + 10; + go_on = TRUE;// 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_OPERAND(value, flags); + // Now GotByte = non-hexadecimal char +} + +// Routine for parsing a decimal value. It accepts "0" to "9". The current +// value is stored as soon as a character is read that is none of those given +// above. Unlike the others, the "decimal" routine expects the first digit to +// be read already, because decimal values don't use any prefixes. +// If the first two digits are "0x", this routine branches to the one for +// parsing hexadecimal values. +static inline void parse_decimal_value(void) {// Now GotByte = first digit + value_t value = (GotByte & 15); // this works. it's ASCII. + + GetByte(); + if((value == 0) && (GotByte == 'x')) + parse_hexadecimal_value(); + else { + while((GotByte >= '0') && (GotByte <= '9')) { + value *= 10; + value += (GotByte & 15);// this works. it's ASCII. + GetByte(); + } + PUSH_OPERAND(value, MVALUE_GIVEN); + } + // Now GotByte = non-decimal char +} + +// Routine for parsing an 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 inline void parse_octal_value(void) {// Now GotByte = "&" + value_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_OPERAND(value, flags); + // Now GotByte = non-octal char +} + +// Routine for parsing a binary value. Apart from "0" and "1", it also accepts +// 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 inline void parse_binary_value(void) {// Now GotByte = "%" + value_t value = 0; + bool go_on = TRUE; // continue loop flag + int 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 = FALSE; + } + } 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_OPERAND(value, flags); + // Now GotByte = non-binary char +} + +// Expect operand or monadic operator (hopefully inlined) +static inline void expect_operand_or_monadic_operator(void) { + op_t* operator; + bool 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; + } // goto means we don't need an "else {" here + if(perform_negation) + PUSH_OPERATOR(&OPSTRCT_NEGATE); + // State doesn't change + break; + +// Real monadic operators (state doesn't change, still ExpectMonadic) + + case '!':// NOT operator + operator = &OPSTRCT_NOT; + goto get_byte_and_push_monadic; + + case '<':// LOWBYTE operator + operator = &OPSTRCT_LOWBYTEOF; + goto get_byte_and_push_monadic; + + case '>':// HIGHBYTE operator + operator = &OPSTRCT_HIGHBYTEOF; + goto get_byte_and_push_monadic; + + case '^':// BANKBYTE operator + operator = &OPSTRCT_BANKBYTEOF; + goto get_byte_and_push_monadic; + +// Faked monadic operators + + case '(':// left parenthesis + operator = &OPSTRCT_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) + + // Quoted character + case '"': + case '\'': + // Character will be converted using current encoding + parse_quoted_character(GotByte); + // Now GotByte = char following closing quote + goto now_expect_dyadic; + + // Binary value + case '%': + parse_binary_value();// Now GotByte = non-binary char + goto now_expect_dyadic; + + // Octal value + case '&': + parse_octal_value();// Now GotByte = non-octal char + goto now_expect_dyadic; + + // Hexadecimal value + case '$': + parse_hexadecimal_value(); + // Now GotByte = non-hexadecimal char + goto now_expect_dyadic; + + // Program counter + case '*': + GetByte();// proceed with next char + CPU_ensure_defined_pc(); + PUSH_OPERAND(CPU_pc, MVALUE_GIVEN); + // Now GotByte = char after closing quote + goto now_expect_dyadic; + + // Local label + case '.': + operand_stack[operand_sp].value = 0; + GetByte();// start after '.' + 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; + } // goto means we don't need an "else {" here + 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] == 'N') + && (GlobalDynaBuf->buffer[1] == 'O') + && (GlobalDynaBuf->buffer[2] == 'T')) { + PUSH_OPERATOR(&OPSTRCT_NOT); + // state doesn't change + } else { + get_label_value(ZONE_GLOBAL); + alu_state = STATE_EXPECT_DYADIC_OPERATOR; + } + } else { + // illegal character read - so don't go on + PUSH_OPERAND(0, 0); + // push pseudo value, EXISTS flag is clear + if(operator_stack[operator_sp-1] == &OPSTRCT_RETURN) { + PUSH_OPERATOR(&OPSTRCT_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 inline void expect_dyadic_operator(void) { + void* node_body; + op_t* operator; + + SKIPSPACE(); + switch(GotByte) { + +// Single-character dyadic operators + + case '^':// "to the power of" + operator = &OPSTRCT_POWEROF; + goto get_byte_and_push_dyadic; + + case '+':// add + operator = &OPSTRCT_ADD; + goto get_byte_and_push_dyadic; + + case '-':// subtract + operator = &OPSTRCT_SUBTRACT; + goto get_byte_and_push_dyadic; + + case '*':// multiply + operator = &OPSTRCT_MULTIPLY; + goto get_byte_and_push_dyadic; + + case '/':// divide + operator = &OPSTRCT_DIVIDE; + goto get_byte_and_push_dyadic; + + case '%':// modulo + operator = &OPSTRCT_MODULO; + goto get_byte_and_push_dyadic; + + case '&':// bitwise AND + operator = &OPSTRCT_AND; + goto get_byte_and_push_dyadic; + + case '|':// bitwise OR + operator = &OPSTRCT_OR; + goto get_byte_and_push_dyadic; + +// This part is commented out because there is no XOR character defined +// case '':// bitwise exclusive OR +// operator = &OPSTRCT_XOR; +// goto get_byte_and_push_dyadic; + + case '=':// is equal + operator = &OPSTRCT_EQUALS; + goto get_byte_and_push_dyadic; + + case ')':// closing parenthesis + operator = &OPSTRCT_CLOSING; + goto get_byte_and_push_dyadic; + +// Multi-character dyadic operators + + // "!=" + case '!': + if(GetByte() == '=') { + operator = &OPSTRCT_NOTEQUAL; + goto get_byte_and_push_dyadic; + } // goto means we don't need an "else {" here + Throw_error(exception_syntax); + alu_state = STATE_ERROR; + break; + + // "<", "<=", "<<" and "<>" + case '<': + switch(GetByte()) { + + case '=':// "<=", less or equal + operator = &OPSTRCT_LE; + goto get_byte_and_push_dyadic; + + case '<':// "<<", shift left + operator = &OPSTRCT_SL; + goto get_byte_and_push_dyadic; + + case '>':// "<>", not equal + operator = &OPSTRCT_NOTEQUAL; + goto get_byte_and_push_dyadic; + + default:// "<", less than + operator = &OPSTRCT_LESSTHAN; + goto push_dyadic; + } + break; + + // ">", ">=", ">>", ">>>" and "><" + case '>': + switch(GetByte()) { + + case '=':// ">=", greater or equal + operator = &OPSTRCT_GE; + goto get_byte_and_push_dyadic; + + case '<':// "><", not equal + operator = &OPSTRCT_NOTEQUAL; + goto get_byte_and_push_dyadic; + + case '>':// ">>" or ">>>", shift right + operator = &OPSTRCT_ASR;// arithmetic shift right + if(GetByte() != '>') + goto push_dyadic; + operator = &OPSTRCT_LSR;// logical shift right + goto get_byte_and_push_dyadic; + + default:// ">", greater than + operator = &OPSTRCT_GREATERTHAN; + goto push_dyadic; + } + break; + +// end of expression or text version of dyadic operator + default: + // check string version of operators + if(BYTEFLAGS(GotByte) & STARTS_KEYWORD) { + Input_read_keyword(); + // Now GotByte = illegal char + // search for tree item + if(Tree_easy_scan(operator_tree, &node_body, GlobalDynaBuf)) { + operator = node_body; + goto push_dyadic; + } + // goto means we don't need an "else {" here + Throw_error("Unknown operator."); + alu_state = STATE_ERROR; + } else { + operator = &OPSTRCT_END; + goto push_dyadic; + } + break; + +// no other possibilities, so here are the shared endings + +get_byte_and_push_dyadic: + GetByte(); +push_dyadic: + PUSH_OPERATOR(operator); + alu_state = STATE_TRY_TO_REDUCE_STACKS; + break; + + } +} + +// Try to reduce stacks by performing high-priority operations +static inline 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; + +// monadic operators + + case OPHANDLE_NOT: + RIGHTVALUE = ~(RIGHTVALUE); + RIGHTFLAGSG &= ~MVALUE_ISBYTE; + goto remove_next_to_last_operator; + + case OPHANDLE_NEGATE: + RIGHTVALUE = -(RIGHTVALUE); + RIGHTFLAGSG &= ~MVALUE_ISBYTE; + goto remove_next_to_last_operator; + + case OPHANDLE_LOWBYTEOF: + RIGHTVALUE = (RIGHTVALUE) & 255; + RIGHTFLAGSG |= MVALUE_ISBYTE; + RIGHTFLAGSG &= ~MVALUE_FORCEBITS; + goto remove_next_to_last_operator; + + case OPHANDLE_HIGHBYTEOF: + RIGHTVALUE = ((RIGHTVALUE) >> 8) & 255; + RIGHTFLAGSG |= MVALUE_ISBYTE; + RIGHTFLAGSG &= ~MVALUE_FORCEBITS; + goto remove_next_to_last_operator; + + case OPHANDLE_BANKBYTEOF: + RIGHTVALUE = ((RIGHTVALUE) >> 16) & 255; + RIGHTFLAGSG |= MVALUE_ISBYTE; + RIGHTFLAGSG &= ~MVALUE_FORCEBITS; + goto remove_next_to_last_operator; + +// dyadic operators + + case OPHANDLE_POWEROF: + if(RIGHTVALUE >= 0) + LEFTVALUE = my_pow(LEFTVALUE, RIGHTVALUE); + else { + if(RIGHTFLAGSG & MVALUE_DEFINED) + Throw_error("Exponent is negative."); + LEFTVALUE = 0; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_MULTIPLY: + LEFTVALUE *= RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_DIVIDE: + if(RIGHTVALUE) LEFTVALUE /= RIGHTVALUE; + else { + if(RIGHTFLAGSG & MVALUE_DEFINED) + Throw_error(exception_div_by_zero); + LEFTVALUE = 0; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_MODULO: + if(RIGHTVALUE) LEFTVALUE %= RIGHTVALUE; + else { + if(RIGHTFLAGSG & MVALUE_DEFINED) + Throw_error(exception_div_by_zero); + LEFTVALUE = 0; + } + goto handle_flags_and_dec_stacks; + + case OPHANDLE_ADD: + LEFTVALUE += RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_SUBTRACT: + LEFTVALUE -= RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_SL: + LEFTVALUE <<= RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_ASR: + LEFTVALUE = my_asr(LEFTVALUE, RIGHTVALUE); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_LSR: + LEFTVALUE = ((uvalue_t) LEFTVALUE) >> RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_LE: + LEFTVALUE = (LEFTVALUE <= RIGHTVALUE); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_LESSTHAN: + LEFTVALUE = (LEFTVALUE < RIGHTVALUE); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_GE: + LEFTVALUE = (LEFTVALUE >= RIGHTVALUE); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_GREATERTHAN: + LEFTVALUE = (LEFTVALUE > RIGHTVALUE); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_NOTEQUAL: + LEFTVALUE = (LEFTVALUE != RIGHTVALUE); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_EQUALS: + LEFTVALUE = (LEFTVALUE == RIGHTVALUE); + goto handle_flags_and_dec_stacks; + + case OPHANDLE_AND: + LEFTVALUE &= RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_XOR: + LEFTVALUE ^= RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + case OPHANDLE_OR: + LEFTVALUE |= RIGHTVALUE; + goto handle_flags_and_dec_stacks; + + default: + Bug_found("IllegalOperatorHandle", operator_stack[operator_sp-2]->handle); + break; + +// no other possibilities, so here are the 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 + LEFTFLAGSG |= RIGHTFLAGSG & + (MVALUE_EXISTS|MVALUE_UNSURE|MVALUE_FORCEBITS); + // "AND" DEFINED flag + LEFTFLAGSG &= (RIGHTFLAGSG | ~MVALUE_DEFINED); + LEFTFLAGSG &= ~MVALUE_ISBYTE;// clear ISBYTE flag + operand_sp--; + /*FALLTHROUGH*/ + +// entry point for monadic operators +remove_next_to_last_operator: + // toplevel operation was something other than parentheses + indirect_flag = 0; + /*FALLTHROUGH*/ + +// 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 + break; + } +} + +// The core of it. Returns number of parentheses left open. +// FIXME - make state machine using function pointers? +static int parse_expression(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(&OPSTRCT_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); + // okay, no errors + *result = operand_stack[0]; // copy struct + result->flags |= indirect_flag; // and OR indirect flag + // only allow *one* force bit + if(result->flags & MVALUE_FORCE24) + result->flags &= ~(MVALUE_FORCE16 | MVALUE_FORCE08); + if(result->flags & MVALUE_FORCE16) + result->flags &= ~MVALUE_FORCE08; + // if value is sure, check to set ISBYTE + if((result->flags & MVALUE_UNSURE) == 0) + if((result->value <= 255) && (result->value >= -128)) + result->flags |= MVALUE_ISBYTE; + // if there was nothing to parse, mark as undefined + // (so ALU_parse_expr_strict() can react) + if((result->flags & MVALUE_EXISTS) == 0) + result->flags &= ~MVALUE_DEFINED; + // if undefined, return zero + if((result->flags & MVALUE_DEFINED) == 0) + result->value = 0;// if undefined, return 0. + } 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 routines handle numerical expressions. There are operators for +// arithmetic, logic, shift and comparison operations. +// There are four different ways to call the core routine: +// void ALU_parse_expr_strict (result_t*) +// void ALU_parse_expr_empty_strict (result_t*) +// void ALU_parse_expr_medium (result_t*) +// int ALU_parse_expr_liberal (result_t*) +// After calling one of these functions, the given result_t structure holds +// some information: +// value holds the numerical result. +// flags holds some additional informational flags about the result: +// Unsure A label was referenced that had its "unsure"-flag set +// Defined The result is known (no "undefined"-label referenced) +// ForceBits To enforce oversized addressing modes +// IsByte If the value fits in 8 bits +// Exists Expression wasn't empty +// Indirect Needless parentheses indicate indirect addressing +// "Unsure" is 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. +// "Defined" shows that the value really could be computed - so if an undefined +// label was referenced, this flag will be cleared. +// "Indirect" is needed to recognize unnecessary parentheses (which imply use +// of indirect adressing modes). +// The return value of "ALU_parse_expr_liberal(result_t*)" is the number of +// parentheses still open. + +// This routine needs a defined value. If the result's "defined" flag is +// clear, it throws a serious error and therefore stops assembly. +void ALU_parse_expr_strict(result_t* result) { + if(parse_expression(result)) + Throw_error(exception_paren_open); + if((result->flags & MVALUE_DEFINED) == 0) + Throw_serious_error(exception_need_value); +} + +// This routine 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. +void ALU_parse_expr_empty_strict(result_t* result) { + if(parse_expression(result)) + Throw_error(exception_paren_open); + if((result->flags & MVALUE_GIVEN) == MVALUE_EXISTS) + Throw_serious_error(exception_need_value); +} + +// The 'normal' call, it parses an expression and returns the result. +// 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_parse_expr_medium(result_t* result) { + if(parse_expression(result)) + Throw_error(exception_paren_open); + if((result->flags & MVALUE_EXISTS) == 0) + Throw_error(exception_need_value); + else + if((result->flags & MVALUE_DEFINED) == 0) + result_is_undefined(); +} + +// This routine 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_parse_expr_liberal(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) + if((result->flags & MVALUE_DEFINED) == 0) + result_is_undefined(); + return(parentheses_still_open); +} diff --git a/Platform/Apple/tools/ACME/src/alu.h b/Platform/Apple/tools/ACME/src/alu.h new file mode 100644 index 00000000..e03362ce --- /dev/null +++ b/Platform/Apple/tools/ACME/src/alu.h @@ -0,0 +1,53 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// ALU stuff (the expression parser) +#ifndef alu_H +#define alu_H + +#include "config.h" + + +// result structure type definition +struct result_t { + value_t value; // Expression value + int flags; // Expression flags +}; + + +// Constants + +// Meaning of bits in "Flags" of result_t structure +// Needless parentheses indicate use of indirect addressing +#define MVALUE_INDIRECT (1u << 7) +// There was *something* to parse +#define MVALUE_EXISTS (1u << 6) +// Value once was related to undefined expression +#define MVALUE_UNSURE (1u << 5) +// Value is defined (if this is cleared, the value will be zero) +#define MVALUE_DEFINED (1u << 4) +// Value is guaranteed to fit in one byte +#define MVALUE_ISBYTE (1u << 3) +// Value usage forces 24-bit usage +#define MVALUE_FORCE24 (1u << 2) +// Value usage forces 16-bit usage +#define MVALUE_FORCE16 (1u << 1) +// Value usage forces 8-bit usage +#define MVALUE_FORCE08 (1u << 0) +// Bit mask for force bits +#define MVALUE_FORCEBITS (MVALUE_FORCE08| MVALUE_FORCE16| MVALUE_FORCE24) +// Bit mask for fixed values (defined and existing) +#define MVALUE_GIVEN (MVALUE_DEFINED | MVALUE_EXISTS) + + +// Prototypes +extern void ALU_init(void); +extern void ALU_parse_expr_strict(result_t*); +extern void ALU_parse_expr_empty_strict(result_t*); +extern void ALU_parse_expr_medium(result_t*); +extern int ALU_parse_expr_liberal(result_t*); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/cliargs.c b/Platform/Apple/tools/ACME/src/cliargs.c new file mode 100644 index 00000000..fa23b098 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/cliargs.c @@ -0,0 +1,131 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// CLI argument stuff + +#include +#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 + + +// Exported functions + +// Return pointer to next command line argument (NULL if no more) +const char* cliargs_get_next(void) { + if(arguments_left == 0) + return(NULL); + arguments_left--; + return(*next_argument++); +} + +// parse command line switches +void cliargs_handle_options(char (*fn_short)(const char*), const char* (*fn_long)(const char*)) { + const char *problem_string, + *argument; + char problem_char; + + do { + // if there are no more arguments, return immediately + if(arguments_left == 0) + return; + // if next argument is not an option, return immediately + if((**next_argument) != '-') + return; + // officially fetch argument. We already know the + // first character is a '-', so advance pointer. + argument = cliargs_get_next() + 1; + // Check for "--" + if(*argument == '-') { + // long argument + if(argument[1] == '\0') + return; // when encountering "--", return + else { + problem_string = fn_long(argument + 1); + if(problem_string) { + fprintf(stderr, "%sUnknown option (--%s).\n", cliargs_error, problem_string); + exit(EXIT_FAILURE); + } + } + } else { + problem_char = fn_short(argument); + if(problem_char) { + fprintf(stderr, "%sUnknown switch (-%c).\n", cliargs_error, problem_char); + exit(EXIT_FAILURE); + } + } + } while(TRUE); +} + +// Return next arg. If there is none, complain and exit +const char* cliargs_get_string(const char name[]) { + const char* string; + + string = cliargs_get_next(); + if(string) + return(string); + fprintf(stderr, "%sMissing %s.\n", cliargs_error, name); + exit(EXIT_FAILURE); +} + +// Return signed long representation of next arg. +// If there is none, complain and exit. +// Copes with hexadecimal if prefixed with "$", "0x" or "0X". +// Copes with octal if prefixed with "&". +// Copes with binary if prefixed with "%". +// Assumes decimal otherwise. +signed long cliargs_get_long(const char name[]) { + signed long result; + const char *start; + char *end; + int base = 10; + + start = cliargs_get_string(name); + if(*start == '%') { + base = 2; + start++; + } else if(*start == '&') { + base = 8; + start++; + } else if(*start == '$') { + base = 16; + start++; + } else if((*start == '0') && ((start[1] == 'x') || (start[1] == 'X'))) { + base = 16; + start += 2; + } + result = strtol(start, &end, base); + if(*end == '\0') + return(result); + fprintf(stderr, "%sCould not parse '%s'.\n", cliargs_error, end); + exit(EXIT_FAILURE); +} + +// init command line handling stuff +const char* cliargs_init(int argc, const char *argv[]) { + arguments_left = argc; + next_argument = argv; + return(cliargs_get_next()); +} + +// return unhandled (non-option) arguments. Complains if none. +void cliargs_get_rest(int *argc, const char***argv, const char error[]) { + *argc = arguments_left; + *argv = next_argument; + + if(error && (arguments_left == 0)) { + fprintf(stderr, "%s%s.\n", cliargs_error, error); + exit(EXIT_FAILURE); + } +} diff --git a/Platform/Apple/tools/ACME/src/cliargs.h b/Platform/Apple/tools/ACME/src/cliargs.h new file mode 100644 index 00000000..bdf6ba4d --- /dev/null +++ b/Platform/Apple/tools/ACME/src/cliargs.h @@ -0,0 +1,32 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// CLI argument stuff +#ifndef cliargs_H +#define cliargs_H + + +// Constants +extern const char cliargs_error[]; + + +// Prototypes + +// Handle options. Call fn_short for short options, fn_long for long ones. +extern void cliargs_handle_options(char (*fn_short)(const char*), const char* (*fn_long)(const char*)); +// Return next argument. +extern const char* cliargs_get_next(void); +// Return next argument. If none left, complain with given name. +extern const char* cliargs_get_string(const char name[]); +// Return next argument as signed long. If no arguments left, complain with +// given name. On parse error, exit with error message. +extern signed long cliargs_get_long(const char name[]); +// Initialise argument handler. Returns program name (argv[0]). +extern const char* cliargs_init(int argc, const char *argv[]); +// Get unhandled args. If none left, complain with given error message. +extern void cliargs_get_rest(int *argc, const char***argv, const char error[]); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/config.h b/Platform/Apple/tools/ACME/src/config.h new file mode 100644 index 00000000..2f75e2cb --- /dev/null +++ b/Platform/Apple/tools/ACME/src/config.h @@ -0,0 +1,45 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Configuration +#ifndef config_H +#define config_H + + +// types +typedef unsigned int zone_t; +typedef signed long value_t; // At least 32 bits +typedef unsigned long uvalue_t; // just for logical shift right +typedef struct label_t label_t; +typedef struct node_ra_t node_ra_t; +typedef struct node_t node_t; +typedef struct result_t result_t; + +// Debugging flag, should be undefined in release version +// #define FDEBUG + +// Maximum nesting depth of "!src" and macro calls +// Is not actually a limitation, but a means of finding recursions +#define MAX_NESTING 64 +// Default value for output buffer +#define FILLVALUE_INITIAL 0 +// Default value for "!fill" +#define FILLVALUE_FILL 0 +// Default value for "!align" (234 = NOP) +#define FILLVALUE_ALIGN 234 + +// Nullpointer definition +#ifndef NULL +#define NULL ((void *)0) +#endif +// Boolean values +#ifndef FALSE +typedef int bool; +#define FALSE 0 +#define TRUE 1 +#endif + + +#endif diff --git a/Platform/Apple/tools/ACME/src/cpu.c b/Platform/Apple/tools/ACME/src/cpu.c new file mode 100644 index 00000000..6dd7b3e9 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/cpu.c @@ -0,0 +1,319 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// CPU stuff + +#include "alu.h" +#include "config.h" +#include "cpu.h" +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "mnemo.h" +#include "output.h" +#include "tree.h" + + +// Structure for linked list of segment data +typedef struct segment_t segment_t; +struct segment_t { + segment_t* next; + value_t start; + value_t length; +}; + + +// Constants +static cpu_t CPU_6502 = {keyword_is_6502mnemo, CPU_FLAG_INDJMP_BUGGY}; +static cpu_t CPU_6510 = {keyword_is_6510mnemo, CPU_FLAG_INDJMP_BUGGY}; +static cpu_t CPU_65c02 = {keyword_is_65c02mnemo, 0}; +//static cpu_t CPU_Rockwell65c02 = {keyword_is_Rockwell65c02mnemo, 0}; +//static cpu_t CPU_WDC65c02 = {keyword_is_WDC65c02mnemo, 0}; +static cpu_t CPU_65816 = { + keyword_is_65816mnemo, + CPU_FLAG_LONG_REGS | CPU_FLAG_IMM16 +}; + + +// Variables +cpu_t *CPU_now; // Struct of current CPU type (default 6502) +bool CPU65816_long_a;// Flag for long accumulator (default off) +bool CPU65816_long_r;// Flag for long index registers (default off) +value_t CPU_pc; // (Pseudo) program counter at start of statement +static segment_t* segment_list; // linked list of segment structures +// predefined stuff +static node_t* CPU_tree = NULL;// tree to hold CPU types +static node_t CPUs[] = { +// PREDEFNODE("z80", &CPU_Z80), + PREDEFNODE("6502", &CPU_6502), + PREDEFNODE("6510", &CPU_6510), + PREDEFNODE("65c02", &CPU_65c02), +// PREDEFNODE("Rockwell65c02", &CPU_Rockwell65c02), +// PREDEFNODE("WDC65c02", &CPU_WDC65c02), + PREDEFLAST("65816", &CPU_65816), + // ^^^^ this marks the last element +}; + + +// init lowest and highest address +static void init_borders(value_t first_pc) { + Mem_lowest_pc = first_pc; + Mem_highest_pc = first_pc; +} + +// set new program counter value +static void set_new_pc(value_t new_pc) { + segment_start = new_pc; + CPU_pc = new_pc; + Mem_current_pc = new_pc; +} + +// Link segment data into segment chain +static void link_segment(value_t start, value_t length) { + segment_t *new_segment; + + // may be faster if list is ordered! + new_segment = ALLOC_PASS(sizeof(segment_t)); + new_segment->start = start; + new_segment->length = length; + new_segment->next = segment_list; + segment_list = new_segment; +} + +// Show start and end of current segment +void CPU_end_segment(void) { + if(Mem_current_pc != CPU_pc) + Throw_warning("Offset assembly still active at end of segment. Switched it off."); + if(pass_flags & PASS_ISFIRST) { + link_segment(segment_start, Mem_current_pc - segment_start); + if(Process_verbosity > 1) + printf("Segment size is %ld ($%lx) bytes ($%lx - $%lx exclusive).\n", Mem_current_pc - segment_start, Mem_current_pc - segment_start, segment_start, Mem_current_pc); + } +} + +// Set up new segment_max value according to the given program counter. Just +// find the next segment start and subtract 1. +void CPU_find_segment_max(value_t new_pc) { + segment_t* test_segment; + + // may be faster if list is ordered! + segment_max = OUTBUFFERSIZE;// will be decremented later! + test_segment = segment_list; + while(test_segment) { + if((test_segment->start > new_pc) + && (test_segment->start < segment_max)) + segment_max = test_segment->start; + test_segment = test_segment->next; + } + segment_max--;// last free address available +} + +// Check whether given PC is inside segment. +static void check_segment(value_t new_pc) { + segment_t* test_segment; + + test_segment = segment_list; + while(test_segment) { + if((new_pc >= test_segment->start) + && (new_pc < (test_segment->start) + (test_segment->length))) + Throw_warning("Segment starts inside another one, overwriting it."); + test_segment = test_segment->next; + } +} + +// Parse (re-)definitions of program counter +void CPU_set_pc(void) {// Now GotByte = "*" + result_t new_pc; + + NEXTANDSKIPSPACE(); // proceed with next char + // re-definitions of program counter change segment + if(GotByte == '=') { + GetByte();// proceed with next char + ALU_parse_expr_strict(&new_pc); + if(CPU_pc != PC_UNDEFINED) { + // It's a redefinition. Check some things: + // check whether new low + if(new_pc.value < Mem_lowest_pc) + Mem_lowest_pc = new_pc.value; + // show status of previous segment + CPU_end_segment(); + // in first pass, maybe issue warning + if(pass_flags & PASS_ISFIRST) { + check_segment(new_pc.value); + CPU_find_segment_max(new_pc.value); + } + } else // it's the first pc definition + init_borders(new_pc.value); + set_new_pc(new_pc.value); + Input_ensure_EOS(); + } else { + Throw_error(exception_syntax); + Input_skip_remainder(); + } +} + +// make sure PC is defined. If not, complain and set to dummy value +// FIXME - get rid of this function as it slows down Output_byte +inline void CPU_ensure_defined_pc(void) { + if(CPU_pc == PC_UNDEFINED) { + Throw_error("Program counter is unset."); + CPU_pc = PC_DUMMY; + } +} + +// Insert byte until PC fits conditions +static enum eos_t PO_align(void) { + result_t and, + equal, + fill; + value_t test = CPU_pc; + + CPU_ensure_defined_pc(); + ALU_parse_expr_strict(&and); + fill.value = FILLVALUE_ALIGN; + if(!Input_accept_comma()) + Throw_error(exception_syntax); + ALU_parse_expr_strict(&equal); + if(Input_accept_comma()) + ALU_parse_expr_medium(&fill); + while((test++ & and.value) != equal.value) + Output_8b(fill.value); + return(ENSURE_EOS); +} + +// Try to find CPU type held in DynaBuf. Returns whether succeeded. +bool CPU_find_cpu_struct(cpu_t** target) { + void* node_body; + + if(!Tree_easy_scan(CPU_tree, &node_body, GlobalDynaBuf)) + return(FALSE); + *target = node_body; + return(TRUE); +} + +// Select CPU ("!cpu" pseudo opcode) +static enum eos_t PO_cpu(void) { + cpu_t* cpu_buffer = CPU_now; // remember current cpu + + if(Input_read_and_lower_keyword()) + if(!CPU_find_cpu_struct(&CPU_now)) + Throw_error("Unknown processor."); + // If there's a block, parse that and then restore old value! + if(Parse_optional_block()) + CPU_now = cpu_buffer; + return(ENSURE_EOS); +} + +static const char Warning_old_offset_assembly[] = + "\"!pseudopc/!realpc\" is deprecated; use \"!pseudopc {}\" instead."; + +// Start offset assembly +static enum eos_t PO_pseudopc(void) { + result_t result; + value_t offset = CPU_pc - Mem_current_pc; + + // set new + ALU_parse_expr_strict(&result); + CPU_pc = result.value; + // If there's a block, parse that and then restore old value! + if(Parse_optional_block()) + CPU_pc = (Mem_current_pc + offset) & 0xffff; // restore old + else if(pass_flags & PASS_ISFIRST) + Throw_warning(Warning_old_offset_assembly); + return(ENSURE_EOS); +} + +// End offset assembly +static enum eos_t PO_realpc(void) { + if(pass_flags & PASS_ISFIRST) + Throw_warning(Warning_old_offset_assembly); + CPU_pc = Mem_current_pc;// deactivate offset assembly + return(ENSURE_EOS); +} + +// If cpu type and value match, set register length variable to value. +// If cpu type and value don't match, complain instead. +static void check_and_set_reg_length(int *var, bool long_reg) { + if(long_reg && ((CPU_now->flags & CPU_FLAG_LONG_REGS) == 0)) + Throw_error("Chosen CPU does not support long registers."); + else + *var = long_reg; +} + +// Set register length, block-wise if needed. +static enum eos_t set_register_length(int* var, bool long_reg) { + int buffer = *var; + + // Set new register length (or complain - whichever is more fitting) + check_and_set_reg_length(var, long_reg); + // If there's a block, parse that and then restore old value! + if(Parse_optional_block()) + check_and_set_reg_length(var, buffer);// restore old length + return(ENSURE_EOS); +} + +// Switch to long accu ("!al" pseudo opcode) +static enum eos_t PO_al(void) { + return(set_register_length(&CPU65816_long_a, TRUE)); +} + +// Switch to short accu ("!as" pseudo opcode) +static enum eos_t PO_as(void) { + return(set_register_length(&CPU65816_long_a, FALSE)); +} + +// Switch to long index registers ("!rl" pseudo opcode) +static enum eos_t PO_rl(void) { + return(set_register_length(&CPU65816_long_r, TRUE)); +} + +// Switch to short index registers ("!rs" pseudo opcode) +static enum eos_t PO_rs(void) { + return(set_register_length(&CPU65816_long_r, FALSE)); +} + +// pseudo opcode table +static node_t pseudo_opcodes[] = { + PREDEFNODE("align", PO_align), + PREDEFNODE("cpu", PO_cpu), + PREDEFNODE("pseudopc", PO_pseudopc), + PREDEFNODE("realpc", PO_realpc), + PREDEFNODE("al", PO_al), + PREDEFNODE("as", PO_as), + PREDEFNODE("rl", PO_rl), + PREDEFLAST("rs", PO_rs), + // ^^^^ this marks the last element +}; + +// Set default values for pass +void CPU_passinit(cpu_t* cpu_type, signed long starting_pc) { + // handle cpu type (default is 6502) + CPU_now = cpu_type ? cpu_type : &CPU_6502; + CPU65816_long_a = FALSE; // short accumulator + CPU65816_long_r = FALSE; // short index registers + // handle program counter + if(starting_pc == PC_UNDEFINED) { + init_borders(0); // set to _something_ (for !initmem) + segment_start = 0; + CPU_pc = PC_UNDEFINED; + Mem_current_pc = 0; + } else { + init_borders(starting_pc); + set_new_pc(starting_pc); + } + // other stuff + segment_list = NULL; + segment_max = OUTBUFFERSIZE-1; +} + +// init cpu type tree (is done early) +void CPUtype_init(void) { + Tree_add_table(&CPU_tree, CPUs); +} + +// init other stuff (done later) +void CPU_init(void) { + Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes); +} diff --git a/Platform/Apple/tools/ACME/src/cpu.h b/Platform/Apple/tools/ACME/src/cpu.h new file mode 100644 index 00000000..fc52fcb4 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/cpu.h @@ -0,0 +1,47 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// CPU stuff +#ifndef cpu_H +#define cpu_H + +#include "config.h" + + +// CPU type structure definition +struct cpu_t { + // This routine is not allowed to change GlobalDynaBuf + // because that's where the mnemonic is stored! + bool (*keyword_is_mnemonic)(int); + int flags; +}; +#define CPU_FLAG_LONG_REGS (1u << 0) +#define CPU_FLAG_IMM16 (1u << 1) +#define CPU_FLAG_INDJMP_BUGGY (1u << 2) +typedef struct cpu_t cpu_t; + + +// Variables +extern cpu_t *CPU_now; // Struct of current CPU type (default 6502) +extern bool CPU65816_long_a; // Flags for long accumulator and long +extern bool CPU65816_long_r; // index registers (both default off) + // FIXME - move the 65816 stuff out of general CPU stuff? +extern value_t CPU_pc; // Current program counter (pseudo value) +#define PC_UNDEFINED -1 // value of PC when undefined +#define PC_DUMMY 512 // dummy value to use when looking for errors + + +// Prototypes +extern void CPUtype_init(void); +extern void CPU_init(void); +extern void CPU_passinit(cpu_t* cpu_type, signed long starting_pc); +extern void CPU_end_segment(void); +extern void CPU_find_segment_max(value_t); +extern void CPU_set_pc(void); +extern inline void CPU_ensure_defined_pc(void); +extern bool CPU_find_cpu_struct(cpu_t** target); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/dynabuf.c b/Platform/Apple/tools/ACME/src/dynabuf.c new file mode 100644 index 00000000..d884f123 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/dynabuf.c @@ -0,0 +1,138 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Dynamic buffer stuff + +#include +#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 +dynabuf_t* GlobalDynaBuf; // global dynamic buffer + + +// Functions + +// get new buffer of given size +static void resize(dynabuf_t* db, size_t new_size) { + char* new_buf; + + new_buf = realloc(db->buffer, new_size); + if(new_buf == NULL) + Throw_serious_error(exception_no_memory_left); + db->reserved = new_size; + db->buffer = new_buf; +} + + +// Exported functions + +// Create and init a dynamic buffer and return pointer +dynabuf_t* DynaBuf_create(int initial_size) { + dynabuf_t* db; + + if(initial_size < DYNABUF_MINIMUM_INITIALSIZE) + initial_size = DYNABUF_MINIMUM_INITIALSIZE; + if((db = malloc(sizeof(dynabuf_t)))) { + db->size = 0; + db->reserved = initial_size; + db->buffer = malloc(initial_size); + if(db->buffer) + return(db);// if both pointers are != NULL, no error + } + // otherwise, complain + fputs("Error: No memory for dynamic buffer.\n", stderr); + exit(EXIT_FAILURE); +} + +// Enlarge buffer +void DynaBuf_enlarge(dynabuf_t* db) { + resize(db, MAKE_LARGER_THAN(db->reserved)); +} + +// Claim enough memory to hold a copy of the current buffer contents, +// make that copy and return it. +// The copy must be released by calling free(). +char* DynaBuf_get_copy(dynabuf_t* db) { + char *copy; + + copy = safe_malloc(db->size); + memcpy(copy, db->buffer, db->size); + return(copy); +} + +// add char to buffer +void DynaBuf_append(dynabuf_t* db, char byte) { + DYNABUF_APPEND(db, byte); +} + +// Append string to buffer (without terminator) +void DynaBuf_add_string(dynabuf_t* db, const char* string) { + char byte; + + while((byte = *string++)) + DYNABUF_APPEND(db, byte); +} + +// add string version of value to buffer (without terminator) +void DynaBuf_add_value(dynabuf_t* db, value_t value) { + value_t header = value / 10, + last = value % 10; + + // cater for negative values. "value = -value" would fail for -2^31 + if(value < 0) { + DynaBuf_append(db, '-'); + header = -header; + last = -last; + } + if(last < 0) { + last += 10; + header--; + } + if(header) + DynaBuf_add_value(db, header); + DynaBuf_append(db, "0123456789"[last]); +} + +// Convert buffer contents to lower case (target and source may be identical) +void DynaBuf_to_lower(dynabuf_t* target, dynabuf_t* source) { + char *read, + *write; + + // make sure target can take it + if(source->size > target->reserved) + resize(target, source->size); + // convert to lower case + read = source->buffer;// CAUTION - ptr may change when buf grows! + write = target->buffer;// CAUTION - ptr may change when buf grows! + while(*read) + *write++ = (*read++) | 32; + // Okay, so this method of converting to lowercase is lousy. + // But actually it doesn't matter, because only pre-defined + // keywords are converted, and all of those are plain + // old-fashioned 7-bit ASCII anyway. So I guess it'll do. + *write = '\0'; // terminate +} + +// Initialisation - allocate global dynamic buffer +void DynaBuf_init(void) { + GlobalDynaBuf = DynaBuf_create(GLOBALDYNABUF_INITIALSIZE); +} diff --git a/Platform/Apple/tools/ACME/src/dynabuf.h b/Platform/Apple/tools/ACME/src/dynabuf.h new file mode 100644 index 00000000..911b3b25 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/dynabuf.h @@ -0,0 +1,59 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Dynamic buffer stuff +#ifndef dynabuf_H +#define dynabuf_H + +#include "config.h" + + +// Macros +#define DYNABUF_CLEAR(db) {db->size = 0;} +#define DYNABUF_APPEND(db, byte) do {\ + if(db->size == db->reserved)\ + DynaBuf_enlarge(db);\ + db->buffer[(db->size)++] = byte;\ +} while(0) +// The next one is dangerous - the buffer location can change when a character +// is appended. So after calling this, don't change the buffer as long as you +// use the address. +#define GLOBALDYNABUF_CURRENT (GlobalDynaBuf->buffer) + + +// dynamic buffer structure +struct dynabuf_t { + char* buffer; // pointer to buffer + int size; // size of buffer's used portion + int reserved; // total size of buffer +}; +typedef struct dynabuf_t dynabuf_t; + + +// Variables +extern dynabuf_t* GlobalDynaBuf; // global dynamic buffer + + +// Prototypes + +// create global DynaBuf (call once on program startup) +extern void DynaBuf_init(void); +// create (private) DynaBuf +extern dynabuf_t* DynaBuf_create(int initial_size); +// call whenever buffer is too small +extern void DynaBuf_enlarge(dynabuf_t* db); +// returns copy of buffer's contents +extern char* DynaBuf_get_copy(dynabuf_t* db); +// copies string to buffer (without terminator) +extern void DynaBuf_add_string(dynabuf_t* db, const char*); +// add string version of value to buffer (without terminator) +extern void DynaBuf_add_value(dynabuf_t* db, value_t value); +// converts buffer contents to lower case +extern void DynaBuf_to_lower(dynabuf_t* db, dynabuf_t*); +// add char to buffer +extern void DynaBuf_append(dynabuf_t* db, char); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/encoding.c b/Platform/Apple/tools/ACME/src/encoding.c new file mode 100644 index 00000000..19e2d66a --- /dev/null +++ b/Platform/Apple/tools/ACME/src/encoding.c @@ -0,0 +1,245 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Character encoding stuff + +#include +#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 + +// Conversion function pointer. No init needed: gets set before each pass. +char (*Encoding_encode_char)(char); +static char outermost_table[256]; // space for encoding table... +static char* loaded_table = outermost_table; // ...loaded from file +// predefined stuff +static node_t* encoder_tree = NULL; // tree to hold encoders + + +// Functions + +// Insert string(s) +static enum eos_t encode_string(encoder_t inner_encoder, char eor) { + result_t result; + encoder_t outer_encoder = Encoding_encode_char;// buffer encoder + + // make given encoder the current one (for ALU-parsed values) + Encoding_encode_char = inner_encoder; + do { + if(GotByte == '"') { + // read initial character + GetQuotedByte(); + // send characters until closing quote is reached + while(GotByte && (GotByte != '"')) { + Output_8b(eor ^ Encoding_encode_char(GotByte)); + GetQuotedByte(); + } + if(GotByte == CHAR_EOS) + return(AT_EOS_ANYWAY); + // after closing quote, proceed with next char + GetByte(); + } else { + // Parse value. No problems with single characters + // because the current encoding is + // temporarily set to the given one. + ALU_parse_expr_medium(&result); + Output_8b(result.value); + } + } while(Input_accept_comma()); + Encoding_encode_char = outer_encoder; // reactivate buffered encoder + return(ENSURE_EOS); +} + +// Insert text string (default format) +static enum eos_t PO_text(void) { + return(encode_string(Encoding_encode_char, 0)); +} + +// convert raw to raw (do not convert at all) +static char encoder_raw(char byte) { + return(byte); +} + +// Insert raw string +static enum eos_t PO_raw(void) { + return(encode_string(encoder_raw, 0)); +} + +// convert raw to petscii +static char encoder_pet(char byte) { + if((byte >= 'A') && (byte <= 'Z')) + return((char) (byte | 0x80)); // FIXME - check why SAS-C + if((byte >= 'a') && (byte <= 'z')) // wants these casts. + return((char)(byte - 32)); // There are more below. + return(byte); +} + +// Insert PetSCII string +static enum eos_t PO_pet(void) { + return(encode_string(encoder_pet, 0)); +} + +// convert raw to C64 screencode +static char encoder_scr(char byte) { + if((byte >= 'a') && (byte <= 'z')) + return((char)(byte - 96)); // shift uppercase down + if((byte >= '[') && (byte <= '_')) + return((char)(byte - 64)); // shift [\]^_ down + if(byte == '`') + return(64); // shift ` down + if(byte == '@') + return(0); // shift @ down + return(byte); +} + +// Insert screencode string +static enum eos_t PO_scr(void) { + return(encode_string(encoder_scr, 0)); +} + +// Insert screencode string, EOR'd +static enum eos_t PO_scrxor(void) { + result_t result; + + ALU_parse_expr_medium(&result); + if(Input_accept_comma()) + return(encode_string(encoder_scr, result.value)); + Throw_error(exception_syntax); + return(SKIP_REMAINDER); +} + +// Switch to CBM mode ("!cbm" pseudo opcode) +static enum eos_t PO_cbm(void) { + Encoding_encode_char = encoder_pet; + // output deprecation warning + if(pass_flags & PASS_ISFIRST) + Throw_warning("\"!cbm\" is deprecated; use \"!ct pet\" instead."); + return(ENSURE_EOS); +} + +// +static char encoder_file(char byte) { + return(loaded_table[(unsigned char) byte]); +} + +// read encoding table from file +static enum eos_t user_defined_encoding(void) { + FILE* fd; + char *filename, + local_table[256], + *buffered_table = loaded_table; + encoder_t buffered_encoder = Encoding_encode_char; + + // if file name is missing, don't bother continuing + if((filename = Input_read_filename(TRUE)) == NULL) + return(SKIP_REMAINDER); + fd = fopen(filename, FILE_READBINARY); + if(fd) { + if(fread(local_table, sizeof(char), 256, fd) != 256) + Throw_error("Conversion table incomplete."); + fclose(fd); + } else + Throw_error(exception_cannot_open_input_file); + free(filename); // filename no longer needed + Encoding_encode_char = encoder_file; // activate new encoding + loaded_table = local_table; // activate local table + // If there's a block, parse that and then restore old values + if(Parse_optional_block()) + Encoding_encode_char = buffered_encoder; + else + // if there's *no* block, the table must be used from now on. + // copy the local table to the "outer" table + memcpy(buffered_table, local_table, 256); + // re-activate "outer" table (it might have been changed by memcpy()) + loaded_table = buffered_table; + return(ENSURE_EOS); +} + +// use one of the pre-defined encodings (raw, pet, scr) +static enum eos_t predefined_encoding(void) { + void* node_body; + char local_table[256], + *buffered_table = loaded_table; + encoder_t buffered_encoder = Encoding_encode_char; + + // use one of the pre-defined encodings + if(Input_read_and_lower_keyword()) { + // search for tree item + if(Tree_easy_scan(encoder_tree, &node_body, GlobalDynaBuf)) + Encoding_encode_char = (encoder_t) node_body;// activate new encoder + else + Throw_error("Unknown encoding."); + } + loaded_table = local_table; // activate local table + // If there's a block, parse that and then restore old values + if(Parse_optional_block()) + Encoding_encode_char = buffered_encoder; + // re-activate "outer" table + loaded_table = buffered_table; + return(ENSURE_EOS); +} + +// Set current encoding ("!convtab" pseudo opcode) +static enum eos_t PO_convtab(void) { + if((GotByte == '<') || (GotByte == '"')) + return(user_defined_encoding()); + else + return(predefined_encoding()); +} + +// pseudo opcode table +static node_t pseudo_opcodes[] = { + PREDEFNODE(s_cbm, PO_cbm), + PREDEFNODE("ct", PO_convtab), + PREDEFNODE("convtab", PO_convtab), + PREDEFNODE(s_pet, PO_pet), + PREDEFNODE(s_raw, PO_raw), + PREDEFNODE(s_scr, PO_scr), + PREDEFNODE("scrxor", PO_scrxor), + PREDEFNODE("text", PO_text), + PREDEFLAST("tx", PO_text), + // ^^^^ this marks the last element +}; + +// keywords for "!convtab" pseudo opcode +static node_t encoders[] = { + PREDEFNODE(s_pet, encoder_pet), + PREDEFNODE(s_raw, encoder_raw), + PREDEFLAST(s_scr, encoder_scr), + // ^^^^ this marks the last element +}; + + +// Exported functions + +// Init (add to tree) +void Encoding_init(void) { + Tree_add_table(&encoder_tree, encoders); + Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes); +} + +// Set default encoding (called before each pass) +void Encoding_passinit(void) { + Encoding_encode_char = encoder_raw; +} diff --git a/Platform/Apple/tools/ACME/src/encoding.h b/Platform/Apple/tools/ACME/src/encoding.h new file mode 100644 index 00000000..c5c93b7f --- /dev/null +++ b/Platform/Apple/tools/ACME/src/encoding.h @@ -0,0 +1,20 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Character encoding stuff +#ifndef encoding_H +#define encoding_H + + +// Variables +extern char (*Encoding_encode_char)(char); // conversion function pointer + + +// Prototypes +extern void Encoding_init(void); +extern void Encoding_passinit(void); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/flow.c b/Platform/Apple/tools/ACME/src/flow.c new file mode 100644 index 00000000..445eedbc --- /dev/null +++ b/Platform/Apple/tools/ACME/src/flow.c @@ -0,0 +1,403 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Flow control stuff (loops, conditional assembly etc.) +// +// Macros, conditional assembly, loops and sourcefile-includes are all based on +// parsing blocks of code. When defining macros or using loops or conditional +// assembly, the block starts with "{" and ends with "}". In the case of +// "!source", the given file is treated like a block - the outermost assembler +// routine uses the same technique to parse the top level file. + +#include +#include "acme.h" +#include "alu.h" +#include "config.h" +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "label.h" +#include "macro.h" +#include "mnemo.h" +#include "tree.h" + + +// type definitions +enum cond_key_t { + ID_UNTIL, // Handles to store instead of + ID_WHILE, // the UNTIL and WHILE keywords +}; +typedef struct { + enum cond_key_t type; // either ID_UNTIL or ID_WHILE + int line; // original line number + char* body; // pointer to actual expression +} loopcond_t; + + +// Variables + +// predefined stuff +static node_t* condkey_tree = NULL;// tree to hold UNTIL and WHILE +static node_t condkeys[] = { + PREDEFNODE("until", ID_UNTIL), + PREDEFLAST("while", ID_WHILE), + // ^^^^ this marks the last element +}; + + +// Helper functions for "!for" and "!do" + +// Parse a loop body (FIXME - or a macro?) +static void parse_ram_block(int line_number, char* body) { + Input_now->line_number = line_number;// set line number to loop start + Input_now->src.ram_ptr = body;// set RAM read pointer to loop + // Parse loop body + Parse_until_eob_or_eof(); + if(GotByte != CHAR_EOB) + Bug_found("IllegalBlockTerminator", GotByte); +} + +// Try to read a condition into DynaBuf and store copy pointer in +// given loopcond_t structure. +// If no condition given, NULL is written to structure. +// Call with GotByte = first interesting character +static void store_condition(loopcond_t* condition, char terminator) { + void* node_body; + + // write line number + condition->line = Input_now->line_number; + // Check for empty condition + if(GotByte == terminator) { + // Write NULL condition, then return + condition->body = NULL; + return; + } + // Seems as if there really *is* a condition. + // Read UNTIL/WHILE keyword + if(Input_read_and_lower_keyword()) { + // Search for new tree item + if(!Tree_easy_scan(condkey_tree, &node_body, GlobalDynaBuf)) { + Throw_error(exception_syntax); + condition->body = NULL; + return; + } + condition->type = (enum cond_key_t) node_body; + // Write given condition into buffer + SKIPSPACE(); + DYNABUF_CLEAR(GlobalDynaBuf); + Input_until_terminator(terminator); + DynaBuf_append(GlobalDynaBuf, CHAR_EOS);// ensure terminator + condition->body = DynaBuf_get_copy(GlobalDynaBuf); + } + return; +} + +// Check a condition expression +static bool check_condition(loopcond_t* condition) { + result_t expression; + + // First, check whether there actually *is* a condition + if(condition->body == NULL) + return(TRUE); // non-existant conditions are always true + // set up input for expression evaluation + Input_now->line_number = condition->line; + Input_now->src.ram_ptr = condition->body; + GetByte(); // proceed with next char + ALU_parse_expr_strict(&expression); + if(GotByte) + Throw_serious_error(exception_syntax); + if(condition->type == ID_UNTIL) + return(!expression.value); + return(expression.value != FALSE); +} + +// Looping assembly ("!do"). Has to be re-entrant. +static enum eos_t PO_do(void) { // Now GotByte = illegal char + loopcond_t condition1, + condition2; + input_t loop_input, + *outer_input; + char* loop_body; + bool go_on; + int loop_start;// line number of loop pseudo opcode + + // Read head condition to buffer + SKIPSPACE(); + store_condition(&condition1, CHAR_SOB); + if(GotByte != CHAR_SOB) + Throw_serious_error(exception_no_left_brace); + // Remember line number of loop body, + // then read block and get copy + loop_start = Input_now->line_number; + loop_body = Input_skip_or_store_block(TRUE); // changes line number! + // now GotByte = '}' + NEXTANDSKIPSPACE();// Now GotByte = first non-blank char after block + // Read tail condition to buffer + store_condition(&condition2, CHAR_EOS); + // now GotByte = CHAR_EOS + // set up new input + loop_input = *Input_now;// copy current input structure into new + loop_input.source_is_ram = TRUE; // set new byte source + // remember old input + outer_input = Input_now; + // activate new input (not useable yet, as pointer and + // line number are not yet set up) + Input_now = &loop_input; + do { + // Check head condition + go_on = check_condition(&condition1); + if(go_on) { + parse_ram_block(loop_start, loop_body); + // Check tail condition + go_on = check_condition(&condition2); + } + } while(go_on); + // Free memory + free(condition1.body); + free(loop_body); + free(condition2.body); + // restore previous input: + Input_now = outer_input; + GotByte = CHAR_EOS; // CAUTION! Very ugly kluge. + // But by switching input, we lost the outer input's GotByte. We know + // it was CHAR_EOS. We could just call GetByte() to get real input, but + // then the main loop could choke on unexpected bytes. So we pretend + // that we got the outer input's GotByte value magically back. + return(AT_EOS_ANYWAY); +} + +// Looping assembly ("!for"). Has to be re-entrant. +static enum eos_t PO_for(void) {// Now GotByte = illegal char + input_t loop_input, + *outer_input; + result_t loop_counter; + value_t maximum; + char* loop_body;// pointer to loop's body block + label_t* label; + zone_t zone; + int force_bit, + loop_start;// line number of "!for" pseudo opcode + + if(Input_read_zone_and_keyword(&zone) == 0) // skips spaces before + return(SKIP_REMAINDER); + // Now GotByte = illegal char + force_bit = Mnemo_get_force_bit(); // skips spaces after + label = Label_find(zone, 0); // FIXME - is it really useful that !for can set/clear force bits at will? + if(force_bit) { + label->flags &= ~MVALUE_FORCEBITS;// clear force bits + label->flags |= force_bit;// set desired force bit + } + if(Input_accept_comma() == FALSE) { + Throw_error(exception_syntax); + return(SKIP_REMAINDER); + } + ALU_parse_expr_strict(&loop_counter); +// prepare flags, size of *end* value doesn't matter for *all* loops + loop_counter.flags &= ~MVALUE_FORCEBITS; // clear force bits + if(loop_counter.value < 0) + Throw_serious_error("Loop count is negative."); + if(GotByte != CHAR_SOB) + Throw_serious_error(exception_no_left_brace); + // remember line number of loop pseudo opcode + loop_start = Input_now->line_number; + // read loop body into DynaBuf and get copy + loop_body = Input_skip_or_store_block(TRUE); // changes line number! + // switching input makes us lose GotByte. But we know it's '}' anyway! + // set up new input + loop_input = *Input_now;// copy current input structure into new + loop_input.source_is_ram = TRUE; // set new byte source + // remember old input + outer_input = Input_now; + // activate new input + // (not yet useable; pointer and line number are still missing) + Input_now = &loop_input; + if(loop_counter.value) {// skip loop if counter == 0 + maximum = loop_counter.value;// remember maximum value + loop_counter.value = 1;// start counting + do { + // set counter + Label_set_value(label, &loop_counter, TRUE); + parse_ram_block(loop_start, loop_body); + // increase loop counter + loop_counter.value++; + } while(loop_counter.value <= maximum); + } else + Label_set_value(label, &loop_counter, TRUE); // set counter + // Free memory + free(loop_body); + // restore previous input: + Input_now = outer_input; + // GotByte of OuterInput would be '}' (if it would still exist) + GetByte(); // fetch next byte + return(ENSURE_EOS); +} + +// Helper functions for "!if" and "!ifdef" + +// Parse or skip a block. Returns whether block's '}' terminator was missing. +// Afterwards: GotByte = '}' +static bool skip_or_parse_block(bool parse) { + if(!parse) { + Input_skip_or_store_block(FALSE); + return(FALSE); + } + // if block was correctly terminated, return FALSE + Parse_until_eob_or_eof(); + // if block isn't correctly terminated, complain and exit + if(GotByte != CHAR_EOB) + Throw_serious_error(exception_no_right_brace); + return(FALSE); +} + +// Parse {block} [else {block}] +static void parse_block_else_block(bool parse_first) { + // Parse first block. + // If it's not correctly terminated, return immediately (because + // in that case, there's no use in checking for an "else" part). + if(skip_or_parse_block(parse_first)) + return; + // now GotByte = '}'. Check for "else" part. + // If end of statement, return immediately. + NEXTANDSKIPSPACE(); + if(GotByte == CHAR_EOS) + return; + // read keyword and check whether really "else" + if(Input_read_and_lower_keyword()) { + if(strcmp(GlobalDynaBuf->buffer, "else")) + Throw_error(exception_syntax); + else { + SKIPSPACE(); + if(GotByte != CHAR_SOB) + Throw_serious_error(exception_no_left_brace); + skip_or_parse_block(!parse_first); + // now GotByte = '}' + GetByte(); + } + } + Input_ensure_EOS(); +} + +// Conditional assembly ("!if"). Has to be re-entrant. +static enum eos_t PO_if(void) {// Now GotByte = illegal char + result_t result; + + ALU_parse_expr_strict(&result); + if(GotByte != CHAR_SOB) + Throw_serious_error(exception_no_left_brace); + parse_block_else_block(!!(result.value)); + return(ENSURE_EOS); +} + +// Conditional assembly ("!ifdef"). Has to be re-entrant. +static enum eos_t PO_ifdef(void) {// Now GotByte = illegal char + node_ra_t* node; + label_t* label; + zone_t zone; + bool defined = FALSE; + + if(Input_read_zone_and_keyword(&zone) == 0) // skips spaces before + return(SKIP_REMAINDER); + Tree_hard_scan(&node, Label_forest, zone, FALSE); + if(node) { + label = (label_t*) node->body; + // in first pass, count usage + if(pass_flags & PASS_ISFIRST) + label->usage++; + if(label->flags & MVALUE_DEFINED) + defined = TRUE; + } + SKIPSPACE(); + if(GotByte == CHAR_SOB) + parse_block_else_block(defined); + else { + if(defined) + return(PARSE_REMAINDER); + return(SKIP_REMAINDER); + } + return(ENSURE_EOS); +} + +// Macro definition ("!macro"). +static enum eos_t PO_macro(void) {// Now GotByte = illegal char + // In first pass, parse. In all other passes, skip. + if(pass_flags & PASS_ISFIRST) + Macro_parse_definition(); // now GotByte = '}' + else { + // skip until CHAR_SOB ('{') is found. + // no need to check for end-of-statement, because such an + // error would already have been detected in first pass. + // for the same reason, there is no need to check for quotes. + while(GotByte != CHAR_SOB) + GetByte(); + Input_skip_or_store_block(FALSE); // now GotByte = '}' + } + GetByte(); // Proceed with next character + return(ENSURE_EOS); +} + +// Parse a whole source code file +void Parse_and_close_file(FILE* fd, const char* filename) { + // be verbose + if(Process_verbosity > 2) + printf("Parsing source file '%s'\n", filename); + // set up new input + Input_new_file(filename, fd); + // Parse block and check end reason + Parse_until_eob_or_eof(); + if(GotByte != CHAR_EOF) + Throw_error("Found '}' instead of end-of-file."); + // close sublevel src + fclose(Input_now->src.fd); +} + +// Include source file ("!source" or "!src"). Has to be re-entrant. +static enum eos_t PO_source(void) {// Now GotByte = illegal char + FILE* fd; + char local_gotbyte, + *filename; + input_t new_input, + *outer_input; + + // Enter new nesting level. + // Quit program if recursion too deep. + if(--source_recursions_left < 0) + Throw_serious_error("Too deeply nested. Recursive \"!source\"?"); + // Read file name. Quit function on error. + if((filename = Input_read_filename(TRUE)) == NULL) // uses copy + return(SKIP_REMAINDER); + // If file could be opened, parse it. Otherwise, complain. + if((fd = fopen(filename, FILE_READBINARY))) { + outer_input = Input_now;// remember old input + local_gotbyte = GotByte;// CAUTION - ugly kluge + Input_now = &new_input;// activate new input + Parse_and_close_file(fd, filename); + Input_now = outer_input;// restore previous input + GotByte = local_gotbyte;// CAUTION - ugly kluge + } else + Throw_error(exception_cannot_open_input_file); + // Release dynamically allocated file name + free(filename); + // Leave nesting level + source_recursions_left++; + return(ENSURE_EOS); +} + +// pseudo opcode table +static node_t pseudo_opcodes[] = { + PREDEFNODE("do", PO_do), + PREDEFNODE("for", PO_for), + PREDEFNODE("if", PO_if), + PREDEFNODE("ifdef", PO_ifdef), + PREDEFNODE("macro", PO_macro), + PREDEFNODE("source", PO_source), + PREDEFLAST("src", PO_source), + // ^^^^ this marks the last element +}; + +// Init (add stuff to tree) +void Flow_init(void) { + Tree_add_table(&condkey_tree, condkeys); + Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes); +} diff --git a/Platform/Apple/tools/ACME/src/flow.h b/Platform/Apple/tools/ACME/src/flow.h new file mode 100644 index 00000000..7389818f --- /dev/null +++ b/Platform/Apple/tools/ACME/src/flow.h @@ -0,0 +1,19 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Flow control stuff (loops, conditional assembly etc.) +#ifndef flow_H +#define flow_H + +#include +#include "config.h" + + +// Prototypes +extern void Flow_init(void); +extern void Parse_and_close_file(FILE* fd, const char* filename); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/global.c b/Platform/Apple/tools/ACME/src/global.c new file mode 100644 index 00000000..c51b3dd5 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/global.c @@ -0,0 +1,381 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Global stuff - things that are needed by several modules + +#include +#include "platform.h" // done first in case "inline" is redefined +#include "acme.h" +#include "cpu.h" +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "label.h" +#include "macro.h" +#include "output.h" +#include "section.h" +#include "tree.h" + + +// Constants + +const char s_cbm[] = "cbm"; +// Exception messages during assembly +const char exception_cannot_open_input_file[] = "Cannot open input file."; +const char exception_missing_string[] = "No string given."; +const char exception_no_left_brace[] = "Missing '{'."; +const char exception_no_memory_left[] = "Out of memory."; +const char exception_no_right_brace[]= "Found end-of-file instead of '}'."; +//const char exception_not_yet[] = "Sorry, feature not yet implemented."; +const char exception_syntax[] = "Syntax error."; +const char exception_number_out_of_range[] = "Number out of range."; +// default value for number of errors before exiting +#define MAXERRORS 10 + +// Flag table: +// This table contains flags for all the 256 possible byte values. The +// assembler reads the table whenever it needs to know whether a byte is +// allowed to be in a label name, for example. +// Bits Meaning when set +// 7....... Byte allowed to start keyword +// .6...... Byte allowed in keyword +// ..5..... Byte is upper case, can be lowercased by OR-ing this bit(!) +// ...4.... special character for input syntax: 0x00 TAB LF CR SPC : ; } +// ....3... preceding sequence of '-' characters is anonymous backward +// label. Currently only set for ')', ',' and CHAR_EOS. +// .....210 unused +const char Byte_flags[256] = { +/*$00*/ 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// control characters + 0x00, 0x10, 0x10, 0x00, 0x00, 0x10, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, +/*$20*/ 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,// " !"#$%&'" + 0x00, 0x08, 0x00, 0x00, 0x08, 0x00, 0x00, 0x00,// "()*+,-./" + 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40, 0x40,// "01234567" + 0x40, 0x40, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00,// "89:;<=>?" +/*$40*/ 0x00, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,// "@ABCDEFG" + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,// "HIJKLMNO" + 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0, 0xe0,// "PQRSTUVW" + 0xe0, 0xe0, 0xe0, 0x00, 0x00, 0x00, 0x00, 0xc0,// "XYZ[\]^_" +/*$60*/ 0x00, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// "`abcdefg" + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// "hijklmno" + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// "pqrstuvw" + 0xc0, 0xc0, 0xc0, 0x00, 0x00, 0x10, 0x00, 0x00,// "yxz{|}~" BACKSPACE +/*$80*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0,// umlauts etc. ... + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, +/*$a0*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, +/*$c0*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, +/*$e0*/ 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, + 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, 0xc0, +}; + + +// Variables + +node_t* pseudo_opcode_tree = NULL; // tree to hold pseudo opcodes +int pass_flags; // Pass flags and its bitfields (FIXME - remove?) +int pass_count; // number of current pass (starts 0) +char GotByte; // Last byte read (processed) +int Process_verbosity = 0; // Level of additional output +// Global counters +int pass_undefined_count; // "NeedValue" type errors +int pass_real_errors; // Errors yet +value_t pc_inc; // Increase PC by this amount after statement +value_t segment_start; // Start of current segment +value_t segment_max; // Highest address segment may use +void* autofree_list_pass = NULL; // linked list, holds malloc blocks +signed long max_errors = MAXERRORS;// errors before giving up + + +// Functions + + +// Memory allocation stuff + +// Allocate memory and die if not available +void* safe_malloc(size_t size) { + void* block; + + if((block = malloc(size)) == NULL) + Throw_serious_error(exception_no_memory_left); + return(block); +} + +// Allocate memory which gets auto-freed at end of pass +void* autofree_malloc(size_t size, void** list) { + void** block; + + block = malloc(size + sizeof(void*)); + if(block == NULL) + Throw_serious_error(exception_no_memory_left); + *block = *list;// make block point to current list + *list = block;// make list point to new block + return(block + 1); +} + +// Free all linked memory blocks (called at end of pass) +void autofree_free(void* *head) { + void **p1, + **p2; + + p1 = *head;// get pointer to list's first item + *head = NULL;// make start point to NULL + // free blocks until no more + while((p2 = p1)) { + p1 = *p2; + free(p2); + } +} + + +// Parser stuff + +// Parse pseudo opcodes. Has to be re-entrant. +static inline void parse_pseudo_opcode(void) {// Now GotByte = "!" + void* node_body; + enum eos_t (*fn)(void); + enum eos_t then = SKIP_REMAINDER; // prepare for errors + + GetByte();// read next byte + // on missing keyword, return (complaining will have been done) + if(Input_read_and_lower_keyword()) { + // search for tree item + if((Tree_easy_scan(pseudo_opcode_tree, &node_body, GlobalDynaBuf)) + && node_body) { + fn = (enum eos_t (*)(void)) node_body; + SKIPSPACE(); + // call function + then = fn(); + } else + Throw_error("Unknown pseudo opcode."); + } + if(then == SKIP_REMAINDER) + Input_skip_remainder(); + else if(then == ENSURE_EOS) + Input_ensure_EOS(); + // the other two possibilities (PARSE_REMAINDER and AT_EOS_ANYWAY) + // will lead to the remainder of the line being parsed by the mainloop. +} + +// Check and return whether first label of statement. Complain if not. +static bool first_label_of_statement(int *statement_flags) { + if((*statement_flags) & SF_IMPLIED_LABEL) { + Throw_error(exception_syntax); + Input_skip_remainder(); + return(FALSE); + } + (*statement_flags) |= SF_IMPLIED_LABEL; // now there has been one + return(TRUE); +} + +// Parse global label definition or assembler mnemonic +static void parse_mnemo_or_global_label_def(int *statement_flags) { + // It is only a label if it isn't a mnemonic + if((CPU_now->keyword_is_mnemonic(Input_read_keyword()) == FALSE) + && first_label_of_statement(statement_flags)) { + // Now GotByte = illegal char + // 04 Jun 2005 - this fix should help to + // explain "strange" error messages. + if((*GLOBALDYNABUF_CURRENT == ' ') + && (pass_flags & PASS_ISFIRST)) + Throw_warning("Label name starts with a shift-space character."); + Label_parse_definition(ZONE_GLOBAL, *statement_flags); + } +} + +// Parse local label definition +static void parse_local_label_def(int *statement_flags) { + if(!first_label_of_statement(statement_flags)) + return; + GetByte();// start after '.' + if(Input_read_keyword()) + Label_parse_definition(Section_now->zone, *statement_flags); +} + +// Parse anonymous backward label definition. Called with GotByte == '-' +static void parse_backward_anon_def(int *statement_flags) { + if(!first_label_of_statement(statement_flags)) + return; + DYNABUF_CLEAR(GlobalDynaBuf); + do + DYNABUF_APPEND(GlobalDynaBuf, '-'); + while(GetByte() == '-'); + DynaBuf_append(GlobalDynaBuf, '\0'); + Label_implicit_definition(Section_now->zone, *statement_flags, 0, TRUE); +} + +// Parse anonymous forward label definition. Called with GotByte == ? +static void parse_forward_anon_def(int *statement_flags) { + label_t* counter_label; + + if(!first_label_of_statement(statement_flags)) + return; + DYNABUF_CLEAR(GlobalDynaBuf); + DynaBuf_append(GlobalDynaBuf, '+'); + while(GotByte == '+') { + DYNABUF_APPEND(GlobalDynaBuf, '+'); + GetByte(); + } + counter_label = Label_fix_forward_name(); + counter_label->value++; + DynaBuf_append(GlobalDynaBuf, '\0'); + Label_implicit_definition(Section_now->zone, *statement_flags, 0, TRUE); +} + +// Parse block, beginning with next byte. +// End reason (either CHAR_EOB or CHAR_EOF) can be found in GotByte afterwards +// Has to be re-entrant. +void Parse_until_eob_or_eof(void) { + int statement_flags; + +// // start with next byte, don't care about spaces +// NEXTANDSKIPSPACE(); + // start with next byte + GetByte(); + // loop until end of block or end of file + while((GotByte != CHAR_EOB) && (GotByte != CHAR_EOF)) { + // process one statement + statement_flags = 0;// no "label = pc" definition yet + // Parse until end of statement. Only loops if statement + // contains "label = pc" definition and something else; or + // if "!ifdef" is true. + do { + switch(GotByte) { + + case CHAR_EOS: // end of statement + // Ignore now, act later + // (stops from being "default") + break; + + case ' ': // space + statement_flags |= SF_FOUND_BLANK; + /*FALLTHROUGH*/ + + case CHAR_SOL: // start of line + GetByte();// skip + break; + + case '-': + parse_backward_anon_def(&statement_flags); + break; + + case '+': + GetByte(); + if((GotByte == '.') + || (BYTEFLAGS(GotByte) & CONTS_KEYWORD)) + Macro_parse_call(); + else + parse_forward_anon_def(&statement_flags); + break; + + case '!': + parse_pseudo_opcode(); + break; + + case '*': + CPU_set_pc(); + break; + + case '.': + parse_local_label_def(&statement_flags); + break; + + default: + if(BYTEFLAGS(GotByte) & STARTS_KEYWORD) { + parse_mnemo_or_global_label_def(&statement_flags); + } else { + Throw_error(exception_syntax); + Input_skip_remainder(); + } + } + } while(GotByte != CHAR_EOS); // until end-of-statement + // change program counter and memory pointer + CPU_pc += pc_inc; + Mem_current_pc += pc_inc; + // check for new max address + if(Mem_current_pc > Mem_highest_pc) + Mem_highest_pc = Mem_current_pc; + pc_inc = 0; + // go on with next byte + GetByte();//NEXTANDSKIPSPACE(); + } +} + +// Skip space. If GotByte is CHAR_SOB ('{'), parse block and return TRUE. +// Otherwise (if there is no block), return FALSE. +// Don't forget to call EnsureEOL() afterwards. +bool Parse_optional_block(void) { + SKIPSPACE(); + if(GotByte != CHAR_SOB) + return(FALSE); + Parse_until_eob_or_eof(); + if(GotByte != CHAR_EOB) + Throw_serious_error(exception_no_right_brace); + GetByte(); + return(TRUE); +} + + +// Error handling + +// This routine will do the actual output for warnings, errors and serious +// errors. It shows the given message string, as well as the current +// context: file name, line number, source type and source title. +static void throw_message(const char* message, const char* type) { + fprintf(stderr, "%s - File %s, line %d (%s %s): %s\n", type, + Input_now->original_filename, Input_now->line_number, + Section_now->type, Section_now->title, + message + ); +} + +// Output a warning. +// This means the produced code looks as expected. But there has been a +// situation that should be reported to the user, for example ACME may have +// assembled a 16-bit parameter with an 8-bit value. +void Throw_warning(const char* message) { + PLATFORM_WARNING(message); + throw_message(message, "Warning"); +} + +// Output an error. +// This means something went wrong in a way that implies that the output +// almost for sure won't look like expected, for example when there was a +// syntax error. The assembler will try to go on with the assembly though, so +// the user gets to know about more than one of his typos at a time. +void Throw_error(const char* message) { + PLATFORM_ERROR(message); + throw_message(message, "Error"); + pass_real_errors++; + if(pass_real_errors >= max_errors) + exit(ACME_finalize(EXIT_FAILURE)); +} + +// Output a serious error, stopping assembly. +// Serious errors are those that make it impossible to go on with the +// assembly. Example: "!fill" without a parameter - the program counter cannot +// be set correctly in this case, so proceeding would be of no use at all. +void Throw_serious_error(const char* message) { + PLATFORM_SERIOUS(message); + throw_message(message, "Serious error"); + exit(ACME_finalize(EXIT_FAILURE)); +} + +// Handle bugs +void Bug_found(const char* message, int code) { + Throw_warning("Bug in ACME, code follows"); + fprintf(stderr, "(0x%x:)", code); + Throw_serious_error(message); +} diff --git a/Platform/Apple/tools/ACME/src/global.h b/Platform/Apple/tools/ACME/src/global.h new file mode 100644 index 00000000..3391c44b --- /dev/null +++ b/Platform/Apple/tools/ACME/src/global.h @@ -0,0 +1,88 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Global stuff - things that are needed by several modules +#ifndef global_H +#define global_H + +#include +#include +#include "config.h" + + +// Constants + +#define SF_FOUND_BLANK (1u) // statement had space or tab +#define SF_IMPLIED_LABEL (2u) // statement had implied label def +extern const char s_cbm[]; +// Error messages during assembly +extern const char exception_cannot_open_input_file[]; +extern const char exception_missing_string[]; +extern const char exception_no_left_brace[]; +extern const char exception_no_memory_left[]; +extern const char exception_no_right_brace[]; +//extern const char exception_not_yet[]; +extern const char exception_syntax[]; +extern const char exception_number_out_of_range[]; +// Byte flags table +extern const char Byte_flags[]; +#define BYTEFLAGS(c) (Byte_flags[(unsigned char) c]) +#define STARTS_KEYWORD (1u << 7) // Byte is allowed to start a keyword +#define CONTS_KEYWORD (1u << 6) // Byte is allowed in a keyword +#define BYTEIS_UPCASE (1u << 5) // Byte is upper case and can be + // converted to lower case by OR-ing this bit(!) +#define BYTEIS_SYNTAX (1u << 4) // special character for input syntax +#define FOLLOWS_ANON (1u << 3) // preceding '-' are backward label +// bits 2, 1 and 0 are unused + + +// Variables + +extern node_t* pseudo_opcode_tree;// tree to hold pseudo opcodes +// structures +enum eos_t { + SKIP_REMAINDER, // skip remainder of line - (after errors) + ENSURE_EOS, // make sure there's nothing left in statement + PARSE_REMAINDER, // parse what's left + AT_EOS_ANYWAY, // actually, same as PARSE_REMAINDER +}; +extern int pass_flags; // Pass flags and its bitfields +// Do stuff that only has to be done once (only in first pass) +#define PASS_ISFIRST (1u << 0) +// Show errors when values are undefined (only in additional pass) +#define PASS_ISERROR (1u << 1) +extern int pass_count; +extern int Process_verbosity;// Level of additional output +extern char GotByte;// Last byte read (processed) +// Global counters +extern int pass_undefined_count;// "NeedValue" type errors in current pass +extern int pass_real_errors; // Errors yet +extern value_t pc_inc; // Increase PC by this amount after statement +extern value_t segment_start; // Start of current segment +extern value_t segment_max; // Highest address segment may use +extern signed long max_errors; // errors before giving up + +extern void* autofree_list_pass; // linked list, holds malloc blocks + +// Macros for skipping a single space character +#define SKIPSPACE() do {if(GotByte == ' ') GetByte();} while(0) +#define NEXTANDSKIPSPACE() do {if(GetByte() == ' ') GetByte();} while(0) + + +// Prototypes +extern inline void* safe_malloc(size_t); +extern void* autofree_malloc(size_t, void**); +// Macro for claiming auto-freed memory blocks +#define ALLOC_PASS(Size) autofree_malloc(Size, &autofree_list_pass) +extern void autofree_free(void**); +extern void Parse_until_eob_or_eof(void); +extern int Parse_optional_block(void); +extern void Throw_warning(const char*); +extern void Throw_error(const char*); +extern void Throw_serious_error(const char*); +extern void Bug_found(const char*, int); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/input.c b/Platform/Apple/tools/ACME/src/input.c new file mode 100644 index 00000000..8cc4671b --- /dev/null +++ b/Platform/Apple/tools/ACME/src/input.c @@ -0,0 +1,495 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Input stuff + +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "platform.h" +#include "section.h" +#include "tree.h" + + +// Constants +const char FILE_READBINARY[] = "rb"; +#define CHAR_TAB (9) // Tab character +#define CHAR_LF (10) // line feed (in file) + // (10) // start of line (in high-level format) +#define CHAR_CR (13) // carriage return (in file) + // (13) // end of file (in high-level format) +#define CHAR_STATEMENT_DELIMITER ':' +#define CHAR_COMMENT_SEPARATOR ';' +// If the characters above are changed, don't forget to adjust ByteFlags[]! + +// fake input structure (for error msgs before any real input is established) +static input_t outermost = { + "", // file name + 0, // line number + FALSE, // Faked file access, so no RAM read + INPUTSTATE_EOF, // state of input + { + NULL // RAM read pointer or file handle + } +}; + + +// Variables +input_t* Input_now = &outermost; // current input structure + + +// End of source file ("!endoffile" or "!eof") +static enum eos_t PO_eof(void) { + // Well, it doesn't end right here and now, but at end-of-line! :-) + Input_ensure_EOS(); + Input_now->state = INPUTSTATE_EOF; + return(AT_EOS_ANYWAY); +} + +// predefined stuff +static node_t pseudo_opcodes[] = { + PREDEFNODE("eof", PO_eof), + PREDEFLAST("endoffile", PO_eof), + // ^^^^ this marks the last element +}; + + +// Functions + +// Init (add to tree) +void Input_init(void) { + Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes); +} + +// Let current input point to start of file +void Input_new_file(const char* filename, FILE* fd) { + Input_now->original_filename = filename; + Input_now->line_number = 1; + Input_now->source_is_ram = FALSE; + Input_now->state = INPUTSTATE_NORMAL; + Input_now->src.fd = fd; +} + +// Deliver source code from current file (!) in shortened high-level format +static char get_processed_from_file(void) { + int from_file; + + do { + switch(Input_now->state) { + + case INPUTSTATE_NORMAL: + // fetch a fresh byte from the current source file + from_file = getc(Input_now->src.fd); + // now process it + /*FALLTHROUGH*/ + + case INPUTSTATE_AGAIN: + // Process the latest byte again. Of course, this only + // makes sense if the loop has executed at least once, + // otherwise the contents of from_file are undefined. + // If the source is changed so there is a possibility + // to enter INPUTSTATE_AGAIN mode without first having + // defined "from_file", trouble may arise... + Input_now->state = INPUTSTATE_NORMAL; + // EOF must be checked first because it cannot be used + // as an index into Byte_flags[] + if(from_file == EOF) { + // remember to send an end-of-file + Input_now->state = INPUTSTATE_EOF; + return(CHAR_EOS);// end of statement + } + // check whether character is special one + // if not, everything's cool and froody, so return it + if((BYTEFLAGS(from_file) & BYTEIS_SYNTAX) == 0) + return((char) from_file); + // check special characters ("0x00 TAB LF CR SPC :;}") + switch(from_file) { + + case CHAR_TAB:// TAB character + case ' ': + // remember to skip all following blanks + Input_now->state = INPUTSTATE_SKIPBLANKS; + return(' '); + + case CHAR_LF:// LF character + // remember to send a start-of-line + Input_now->state = INPUTSTATE_LF; + return(CHAR_EOS);// end of statement + + case CHAR_CR:// CR character + // remember to check CRLF + send start-of-line + Input_now->state = INPUTSTATE_CR; + return(CHAR_EOS);// end of statement + + case CHAR_EOB: + // remember to send an end-of-block + Input_now->state = INPUTSTATE_EOB; + return(CHAR_EOS);// end of statement + + case CHAR_STATEMENT_DELIMITER: + // just deliver an EOS instead + return(CHAR_EOS);// end of statement + + case CHAR_COMMENT_SEPARATOR: + // remember to skip remainder of line + Input_now->state = INPUTSTATE_COMMENT; + return(CHAR_EOS);// end of statement + + default: + // complain if byte is 0 + Throw_error("Source file contains illegal character."); + return((char) from_file); + } + + case INPUTSTATE_SKIPBLANKS: + // read until non-blank, then deliver that + do + from_file = getc(Input_now->src.fd); + while((from_file == CHAR_TAB) || (from_file == ' ')); + // re-process last byte + Input_now->state = INPUTSTATE_AGAIN; + break; + + case INPUTSTATE_LF: + // return start-of-line, then continue in normal mode + Input_now->state = INPUTSTATE_NORMAL; + return(CHAR_SOL);// new line + + case INPUTSTATE_CR: + // return start-of-line, remember to check for LF + Input_now->state = INPUTSTATE_SKIPLF; + return(CHAR_SOL);// new line + + case INPUTSTATE_SKIPLF: + from_file = getc(Input_now->src.fd); + // if LF, ignore it and fetch another byte + // otherwise, process current byte + if(from_file == CHAR_LF) + Input_now->state = INPUTSTATE_NORMAL; + else + Input_now->state = INPUTSTATE_AGAIN; + break; + + case INPUTSTATE_COMMENT: + // read until end-of-line or end-of-file + do + from_file = getc(Input_now->src.fd); + while((from_file != EOF) && (from_file != CHAR_CR) && (from_file != CHAR_LF)); + // re-process last byte + Input_now->state = INPUTSTATE_AGAIN; + break; + + case INPUTSTATE_EOB: + // deliver EOB + Input_now->state = INPUTSTATE_NORMAL; + return(CHAR_EOB);// end of block + + case INPUTSTATE_EOF: + // deliver EOF + Input_now->state = INPUTSTATE_NORMAL; + return(CHAR_EOF);// end of file + + default: + Bug_found("StrangeInputMode", Input_now->state); + } + } while(TRUE); +} + +// This function delivers the next byte from the currently active byte source +// in shortened high-level format. +// When inside quotes, use GetQuotedByte() instead! +char GetByte(void) { +// do { + // If byte source is RAM, then no conversions are + // necessary, because in RAM the source already has + // high-level format + // Otherwise, the source is a file. This means we will call + // GetFormatted() which will do a shit load of conversions. + if(Input_now->source_is_ram) + GotByte = *(Input_now->src.ram_ptr++); + else + GotByte = get_processed_from_file(); +// // if start-of-line was read, increment line counter and repeat +// if(GotByte != CHAR_SOL) +// return(GotByte); +// Input_now->line_number++; +// } while(TRUE); + if(GotByte == CHAR_SOL) + Input_now->line_number++; + return(GotByte); +} + +// This function delivers the next byte from the currently active byte source +// in un-shortened high-level format. +// This function complains if CHAR_EOS (end of statement) is read. +char GetQuotedByte(void) { + int from_file; // must be an int to catch EOF + + // If byte source is RAM, then no conversion is necessary, + // because in RAM the source already has high-level format + if(Input_now->source_is_ram) + GotByte = *(Input_now->src.ram_ptr++); + // Otherwise, the source is a file. + else { + // fetch a fresh byte from the current source file + from_file = getc(Input_now->src.fd); + switch(from_file) { + + case EOF: + // remember to send an end-of-file + Input_now->state = INPUTSTATE_EOF; + GotByte = CHAR_EOS; // end of statement + break; + + case CHAR_LF:// LF character + // remember to send a start-of-line + Input_now->state = INPUTSTATE_LF; + GotByte = CHAR_EOS; // end of statement + break; + + case CHAR_CR:// CR character + // remember to check for CRLF + send a start-of-line + Input_now->state = INPUTSTATE_CR; + GotByte = CHAR_EOS; // end of statement + break; + + default: + GotByte = from_file; + } + + } + // now check for end of statement + if(GotByte == CHAR_EOS) + Throw_error("Quotes still open at end of line."); + return(GotByte); +} + +// Skip remainder of statement, for example on error +void Input_skip_remainder(void) { + while(GotByte) + GetByte();// Read characters until end-of-statement +} + +// Ensure that the remainder of the current statement is empty, for example +// after mnemonics using implied addressing. +void Input_ensure_EOS(void) {// Now GotByte = first char to test + SKIPSPACE(); + if(GotByte) { + Throw_error("Garbage data at end of statement."); + Input_skip_remainder(); + } +} + +// Skip or store block (starting with next byte, so call directly after +// reading opening brace). +// If "Store" is TRUE, the block is read into GlobalDynaBuf, then a copy +// is made and a pointer to that is returned. +// If "Store" is FALSE, NULL is returned. +// After calling this function, GotByte holds '}'. Unless EOF was found first, +// but then a serious error would have been thrown. +char* Input_skip_or_store_block(bool store) { + char byte; + int depth = 1; // to find matching block end + + // prepare global dynamic buffer + DYNABUF_CLEAR(GlobalDynaBuf); + do { + byte = GetByte(); + // if wanted, store + if(store) + DYNABUF_APPEND(GlobalDynaBuf, byte); + // now check for some special characters + switch(byte) { + + case CHAR_EOF: // End-of-file in block? Sorry, no way. + Throw_serious_error(exception_no_right_brace); + + case '"': // Quotes? Okay, read quoted stuff. + case '\'': + do { + GetQuotedByte(); + // if wanted, store + if(store) + DYNABUF_APPEND(GlobalDynaBuf, GotByte); + } while((GotByte != CHAR_EOS) && (GotByte != byte)); + break; + + case CHAR_SOB: + depth++; + break; + + case CHAR_EOB: + depth--; + break; + + } + } while(depth); + // in case of skip, return now + if(!store) + return(NULL); + // otherwise, prepare to return copy of block + // add EOF, just to make sure block is never read too far + DynaBuf_append(GlobalDynaBuf, CHAR_EOS); + DynaBuf_append(GlobalDynaBuf, CHAR_EOF); + // return pointer to copy + return(DynaBuf_get_copy(GlobalDynaBuf)); +} + +// Read bytes and add to GlobalDynaBuf until the given terminator (or CHAR_EOS) +// is found. Act upon single and double quotes by entering (and leaving) quote +// mode as needed (So the terminator does not terminate when inside quotes). +void Input_until_terminator(char terminator) { + char byte = GotByte; + + do { + // Terminator? Exit. EndOfStatement? Exit. + if((byte == terminator) || (byte == CHAR_EOS)) + return; + // otherwise, append to GlobalDynaBuf and check for quotes + DYNABUF_APPEND(GlobalDynaBuf, byte); + if((byte == '"') || (byte == '\'')) { + do { + // Okay, read quoted stuff. + GetQuotedByte();// throws error on EOS + DYNABUF_APPEND(GlobalDynaBuf, GotByte); + } while((GotByte != CHAR_EOS) && (GotByte != byte)); + // on error, exit now, before calling GetByte() + if(GotByte != byte) + return; + } + byte = GetByte(); + } while(TRUE); +} + +// Append to GlobalDynaBuf while characters are legal for keywords. +// Throws "missing string" error if none. +// Returns number of characters added. +int Input_append_keyword_to_global_dynabuf(void) { + int length = 0; + + // add characters to buffer until an illegal one comes along + while(BYTEFLAGS(GotByte) & CONTS_KEYWORD) { + DYNABUF_APPEND(GlobalDynaBuf, GotByte); + length++; + GetByte(); + } + if(length == 0) + Throw_error(exception_missing_string); + return(length); +} + +// Check whether GotByte is a dot. +// If not, store global zone value. +// If yes, store current zone value and read next byte. +// Then jump to Input_read_keyword(), which returns length of keyword. +int Input_read_zone_and_keyword(zone_t *zone) { + SKIPSPACE(); + if(GotByte == '.') { + GetByte(); + *zone = Section_now->zone; + } else + *zone = ZONE_GLOBAL; + return(Input_read_keyword()); +} + +// Clear dynamic buffer, then append to it until an illegal (for a keyword) +// character is read. Zero-terminate the string. Return its length (without +// terminator). +// Zero lengths will produce a "missing string" error. +int Input_read_keyword(void) { + int length; + + DYNABUF_CLEAR(GlobalDynaBuf); + length = Input_append_keyword_to_global_dynabuf(); + // add terminator to buffer (increments buffer's length counter) + DynaBuf_append(GlobalDynaBuf, '\0'); + return(length); +} + +// Clear dynamic buffer, then append to it until an illegal (for a keyword) +// character is read. Zero-terminate the string, then convert to lower case. +// Return its length (without terminator). +// Zero lengths will produce a "missing string" error. +int Input_read_and_lower_keyword(void) { + int length; + + DYNABUF_CLEAR(GlobalDynaBuf); + length = Input_append_keyword_to_global_dynabuf(); + // add terminator to buffer (increments buffer's length counter) + DynaBuf_append(GlobalDynaBuf, '\0'); + DynaBuf_to_lower(GlobalDynaBuf, GlobalDynaBuf);// convert to lower case + return(length); +} + +// Try to read a file name. If "allow_library" is TRUE, library access by using +// <...> quoting is possible as well. The file name given in the assembler +// source code is converted from UNIX style to platform style. +// Returns: A pointer to the resulting file name string (NULL on error). +// Errors are handled and reported, but caller should call +// Input_skip_remainder() then. +// Note: The file name is malloc()'d and therefore should be free()'d when no +// longer used. +char* Input_read_filename(bool allow_library) { + char *lib_prefix, + end_quote; + + DYNABUF_CLEAR(GlobalDynaBuf); + SKIPSPACE(); + // check for library access + if(GotByte == '<') { + // if library access forbidden, complain + if(allow_library == FALSE) { + Throw_error("Writing to library not supported."); + return(NULL); + } + // read platform's lib prefix + lib_prefix = PLATFORM_LIBPREFIX; +#ifndef NO_NEED_FOR_ENV_VAR + // if lib prefix not set, complain + if(lib_prefix == NULL) { + Throw_error("\"ACME\" environment variable not found."); + return(NULL); + } +#endif + // copy lib path and set quoting char + DynaBuf_add_string(GlobalDynaBuf, lib_prefix); + end_quote = '>'; + } else { + if(GotByte == '"') + end_quote = '"'; + else { + Throw_error("File name quotes not found (\"\" or <>)."); + return(NULL); + } + } + // read first character, complain if closing quote + if(GetQuotedByte() == end_quote) { + Throw_error("No file name given."); + return(NULL); + } + // read characters until closing quote (or EOS) is reached + // append platform-converted characters to current string + while((GotByte != CHAR_EOS) && (GotByte != end_quote)) { + DYNABUF_APPEND(GlobalDynaBuf, PLATFORM_CONVERTPATHCHAR(GotByte)); + GetQuotedByte(); + } + // on error, return NULL + if(GotByte == CHAR_EOS) + return(NULL); + GetByte(); // fetch next to forget closing quote + // terminate string and return copy + DynaBuf_append(GlobalDynaBuf, '\0'); // add terminator + return(DynaBuf_get_copy(GlobalDynaBuf)); +} + +// Try to read a comma, skipping spaces before and after. Return TRUE if comma +// found, otherwise FALSE. +bool Input_accept_comma(void) { + SKIPSPACE(); + if(GotByte != ',') + return(FALSE); + NEXTANDSKIPSPACE(); + return(TRUE); +} diff --git a/Platform/Apple/tools/ACME/src/input.h b/Platform/Apple/tools/ACME/src/input.h new file mode 100644 index 00000000..92c6ea66 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/input.h @@ -0,0 +1,72 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Input stuff +#ifndef input_H +#define input_H + +#include + + +// type definitions + +// values for input_t component "Src.State" +enum inputstate_t { + INPUTSTATE_NORMAL, // everything's fine + INPUTSTATE_AGAIN, // re-process last byte + INPUTSTATE_SKIPBLANKS, // shrink multiple spaces + INPUTSTATE_LF, // send start-of-line after end-of-statement + INPUTSTATE_CR, // same, but also remember to skip LF + INPUTSTATE_SKIPLF, // skip LF if that's next + INPUTSTATE_COMMENT, // skip characters until newline or EOF + INPUTSTATE_EOB, // send end-of-block after end-of-statement + INPUTSTATE_EOF, // send end-of-file after end-of-statement +}; +typedef struct { + const char* original_filename;// during RAM reads, too + int line_number; // in file (on RAM reads, too) + bool source_is_ram; // TRUE if RAM, FALSE if file + enum inputstate_t state; // state of input + union { + FILE* fd; // file descriptor + char* ram_ptr; // RAM read ptr (loop or macro block) + } src; +} input_t; + + +// Constants +extern const char FILE_READBINARY[]; +// Special characters +// The program *heavily* relies on CHAR_EOS (end of statement) being 0x00! +#define CHAR_EOS (0) // end of statement (in high-level format) +#define CHAR_SOB '{' // start of block +#define CHAR_EOB '}' // end of block +#define CHAR_SOL (10) // start of line (in high-level format) +#define CHAR_EOF (13) // end of file (in high-level format) +// If the characters above are changed, don't forget to adjust Byte_flags[]! + + +// Variables +extern input_t* Input_now; // current input structure + + +// Prototypes +extern void Input_init(void); +extern void Input_new_file(const char* filename, FILE* fd); +extern char GetByte(void); +extern char GetQuotedByte(void); +extern void Input_skip_remainder(void); +extern void Input_ensure_EOS(void); +extern char* Input_skip_or_store_block(bool store); +extern void Input_until_terminator(char terminator); +extern int Input_append_keyword_to_global_dynabuf(void);// returns length +extern int Input_read_zone_and_keyword(zone_t*); +extern int Input_read_keyword(void);// returns length +extern int Input_read_and_lower_keyword(void);// returns length +extern char* Input_read_filename(bool library_allowed); +extern bool Input_accept_comma(void);// skips spaces before and after + + +#endif diff --git a/Platform/Apple/tools/ACME/src/label.c b/Platform/Apple/tools/ACME/src/label.c new file mode 100644 index 00000000..327a50aa --- /dev/null +++ b/Platform/Apple/tools/ACME/src/label.c @@ -0,0 +1,251 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Label stuff + +#include +#include "acme.h" +#include "alu.h" +#include "cpu.h" +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "label.h" +#include "mnemo.h" +#include "platform.h" +#include "section.h" +#include "tree.h" + + +// Variables +node_ra_t* Label_forest[256];// ... (because of 8-bit hash) + + +// Dump label value and flags to dump file +static void dump_one_label(node_ra_t* node, FILE* fd) { + label_t* label = node->body; + + // output name + fprintf(fd, "%s", node->id_string); + switch(label->flags & MVALUE_FORCEBITS) { + + case MVALUE_FORCE16: + fprintf(fd, "+2="); + break; + + case MVALUE_FORCE16 | MVALUE_FORCE24: + /*FALLTHROUGH*/ + + case MVALUE_FORCE24: + fprintf(fd, "+3="); + break; + + default: + fprintf(fd, " ="); + + } + if(label->flags & MVALUE_DEFINED) + fprintf(fd, "$%x", (unsigned int) label->value); + else + fprintf(fd, " ?"); + if(label->flags & MVALUE_UNSURE) + fprintf(fd, "; ?"); + if(label->usage == 0) + fprintf(fd, "; unused"); + fprintf(fd, "\n"); +} + +// Search for label. Create if nonexistant. If created, give it flags "Flags". +// The label name must be held in GlobalDynaBuf. +label_t* Label_find(zone_t zone, int flags) { + node_ra_t* node; + label_t* label; + bool node_created; + int force_bits = flags & MVALUE_FORCEBITS; + + node_created = Tree_hard_scan(&node, Label_forest, zone, TRUE); + // if node has just been created, create label as well + if(node_created) { + // Create new label structure + label = safe_malloc(sizeof(label_t)); + // Finish empty label item + label->flags = flags; + label->value = 0; + label->usage = 0; // usage count + label->pass = pass_count; + node->body = label; + } else + label = node->body; + // make sure the force bits don't clash + if((node_created == FALSE) && force_bits) + if((label->flags & MVALUE_FORCEBITS) != force_bits) + Throw_error("Too late for postfix."); + return(label); +} + +// Assign value to label. The routine acts upon the label's flag bits and +// produces an error if needed. +void Label_set_value(label_t* label, result_t* new, bool change) { + int flags = label->flags; + + // value stuff + if((flags & MVALUE_DEFINED) && (change == FALSE)) { + // Label is already defined, so compare new and old values + if(new->value != label->value) + Throw_error("Label already defined."); + } else + // Label is not defined yet OR redefinitions are allowed + label->value = new->value; + // flags stuff + // Ensure that "unsure" labels without "isByte" state don't get that + if((flags & (MVALUE_UNSURE | MVALUE_ISBYTE)) == MVALUE_UNSURE) + new->flags &= ~MVALUE_ISBYTE; + if(change) + flags = (flags & MVALUE_UNSURE) | new->flags; + else { + if((flags & MVALUE_FORCEBITS) == 0) + if((flags & (MVALUE_UNSURE | MVALUE_DEFINED)) == 0) + flags |= new->flags & MVALUE_FORCEBITS; + flags |= new->flags & ~MVALUE_FORCEBITS; + } + label->flags = flags; +} + +// (Re)set label +static enum eos_t PO_set(void) {// Now GotByte = illegal char + result_t result; + int force_bit; + label_t* label; + zone_t zone; + + if(Input_read_zone_and_keyword(&zone) == 0) // skips spaces before + // Now GotByte = illegal char + return(SKIP_REMAINDER); + force_bit = Mnemo_get_force_bit();// skips spaces after + label = Label_find(zone, force_bit); + if(GotByte != '=') { + Throw_error(exception_syntax); + return(SKIP_REMAINDER); + } + // label = parsed value + GetByte();// proceed with next char + ALU_parse_expr_medium(&result); + // clear label's force bits and set new ones + label->flags &= ~(MVALUE_FORCEBITS | MVALUE_ISBYTE); + if(force_bit) { + label->flags |= force_bit; + result.flags &= ~(MVALUE_FORCEBITS | MVALUE_ISBYTE); + } + Label_set_value(label, &result, TRUE); + return(ENSURE_EOS); +} + +// Select dump file +static enum eos_t PO_sl(void) { + // only process this pseudo opcode in the first pass + if((pass_flags & PASS_ISFIRST) == 0) + return(SKIP_REMAINDER); + // if label dump file already chosen, complain and exit + if(labeldump_filename) { + Throw_warning("Label dump file already chosen."); + return(SKIP_REMAINDER); + } + // read filename (returns a malloc'd copy) + // if no file name given, exit (complaining will have been done) + if((labeldump_filename = Input_read_filename(FALSE)) == NULL) + return(SKIP_REMAINDER); + // ensure there's no garbage at end of line + return(ENSURE_EOS); +} + +// predefined stuff +static node_t pseudo_opcodes[] = { + PREDEFNODE("set", PO_set), + PREDEFLAST("sl", PO_sl), + // ^^^^ this marks the last element +}; + +// Parse implicit label definition (can be either global or local). +// GlobalDynaBuf holds the label name. +void Label_implicit_definition(zone_t zone, int stat_flags, int force_bit, bool change) { + result_t result; + label_t* label; + + label = Label_find(zone, force_bit); + // implicit label definition (label) + if((stat_flags & SF_FOUND_BLANK) + && (pass_flags & PASS_ISFIRST) + && (1 == 1)) // FIXME - make configurable via CLI switch + Throw_warning("Implicit label definition not in leftmost column."); + CPU_ensure_defined_pc(); + result.value = CPU_pc; + result.flags = MVALUE_DEFINED; + Label_set_value(label, &result, change); +} + +// Parse label definition (can be either global or local). +// GlobalDynaBuf holds the label name. +void Label_parse_definition(zone_t zone, int stat_flags) { + result_t result; + label_t* label; + int force_bit = Mnemo_get_force_bit();// skips spaces after + + if(GotByte == '=') { + // explicit label definition (label = ) + label = Label_find(zone, force_bit); + // label = parsed value + GetByte(); // skip '=' + ALU_parse_expr_medium(&result); + Label_set_value(label, &result, FALSE); + Input_ensure_EOS(); + } else + Label_implicit_definition(zone, stat_flags, force_bit, FALSE); +} + +// Dump global labels into file +void Label_dump_all(FILE* fd) { + Tree_dump_forest(Label_forest, ZONE_GLOBAL, dump_one_label, fd); + PLATFORM_SETFILETYPE_TEXT(labeldump_filename); +} + +// Init (add to tree and clear pointer table) +void Label_init(void) { + node_ra_t** ptr; + int i; + + Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes); + // Cut down all the trees (clear pointer table) + ptr = Label_forest; + // Clear pointertable + for(i = 255; i >= 0; i--) + *ptr++ = NULL; +} + +// Fix name of anonymous forward label (held in DynaBuf, NOT TERMINATED!) so it +// references the *next* anonymous forward label definition. The tricky bit is, +// each name length would need its own counter. But hey, ACME's real quick in +// finding labels, so I'll just abuse the label system to store those counters. +label_t* Label_fix_forward_name(void) { + label_t* counter_label; + unsigned long number; + + // terminate name, find "counter" label and read value + DynaBuf_append(GlobalDynaBuf, '\0'); + counter_label = Label_find(Section_now->zone, 0); + // make sure it gets reset to zero in each new pass + if(counter_label->pass != pass_count) { + counter_label->pass = pass_count; + counter_label->value = 0; + } + number = (unsigned long) counter_label->value; + // now append to the name to make it unique + GlobalDynaBuf->size--; // forget terminator, we want to append + do { + DYNABUF_APPEND(GlobalDynaBuf, "0123456789abcdef"[number & 15]); + number >>= 4; + } while(number); + DynaBuf_append(GlobalDynaBuf, '\0'); + return(counter_label); +} diff --git a/Platform/Apple/tools/ACME/src/label.h b/Platform/Apple/tools/ACME/src/label.h new file mode 100644 index 00000000..2b5db311 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/label.h @@ -0,0 +1,37 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Label stuff +#ifndef label_H +#define label_H + +#include + + +// "label" structure type definition +struct label_t { + value_t value; // Expression value + int flags; // Expression flags + // FIXME - integrate value and flags to result_t struct! + int usage; // usage count + int pass; // set to pass number on creation (for anon counters) +}; + + +// Variables +extern node_ra_t* Label_forest[]; // trees (because of 8-bit hash) + + +// Prototypes +extern void Label_init(void); +extern void Label_set_value(label_t*, result_t*, bool change); +extern void Label_implicit_definition(zone_t zone, int stat_flags, int force_bit, bool change); +extern void Label_parse_definition(zone_t zone, int stat_flags); +extern label_t* Label_find(zone_t, int); +extern void Label_dump_all(FILE* fd); +extern label_t* Label_fix_forward_name(void); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/macro.c b/Platform/Apple/tools/ACME/src/macro.c new file mode 100644 index 00000000..fd1a807f --- /dev/null +++ b/Platform/Apple/tools/ACME/src/macro.c @@ -0,0 +1,346 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Macro stuff + +#include // needs strlen() + memcpy() +#include "platform.h" // done first in case "inline" is redefined +#include "acme.h" +#include "alu.h" +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "label.h" +#include "section.h" +#include "tree.h" +#include "macro.h" + + +// Constants +#define MACRONAME_DYNABUF_INITIALSIZE 128 +#define ARG_SEPARATOR ' ' // separates macro title from arg types +#define ARGTYPE_NUM_VAL 'v' +#define ARGTYPE_NUM_REF 'V' +//#define ARGTYPE_STR_VAL 's' +//#define ARGTYPE_STR_REF 'S' +#define REFERENCE_CHAR '~' // prefix for call-by-reference +#define HALF_INITIAL_ARG_TABLE_SIZE 4 +static const char exception_macro_twice[] = "Macro already defined."; + + +// macro struct type definition +struct macro_t { + int def_line_number;// line number of definition for error msgs + char* def_filename; // file name of definition for error msgs + char* original_name; // user-supplied name for error msgs + char* parameter_list; // parameters (whole line) + char* body; // RAM block containing macro body +}; +// there's no need to make this a struct and add a type component: +// when the macro has been found, accessing its parameter_list component +// gives us the possibility to find out which args are call-by-value and +// which ones are call-by-reference. +union macro_arg_t { + result_t result; // value and flags (call by value) + label_t* label; // pointer to label struct (call by reference) +}; + + +// Variables +static dynabuf_t* user_macro_name; // original macro title +static dynabuf_t* internal_name; // plus param type chars +static node_ra_t* macro_forest[256]; // trees (because of 8b hash) +// Dynamic argument table +static union macro_arg_t* arg_table = NULL; +static int argtable_size = HALF_INITIAL_ARG_TABLE_SIZE; + + +// Functions + +// Enlarge the argument table +static void enlarge_arg_table(void) { + argtable_size *= 2; + arg_table = + realloc(arg_table, argtable_size * sizeof(union macro_arg_t)); + if(arg_table == NULL) + Throw_serious_error(exception_no_memory_left); +} + +// Initialisation - allocate dynamic buffer for macro name and create arg table +void Macro_init(void) { + user_macro_name = DynaBuf_create(MACRONAME_DYNABUF_INITIALSIZE); + internal_name = DynaBuf_create(MACRONAME_DYNABUF_INITIALSIZE); + enlarge_arg_table(); +} + +// Read macro zone and title. Title is read to GlobalDynaBuf and then copied +// over to internal_name DynaBuf, where ARG_SEPARATOR is added. +// In user_macro_name DynaBuf, the original name is reconstructed (even with +// '.' prefix) so a copy can be linked to the resulting macro struct. +static zone_t get_zone_and_title(void) { + zone_t macro_zone; + + Input_read_zone_and_keyword(¯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 inline bool pipe_comma(void) { + bool result; + + result = Input_accept_comma(); + if(result) + DYNABUF_APPEND(GlobalDynaBuf, ','); + return(result); +} + +// Return malloc'd copy of string +static char* get_string_copy(const char* original) { + size_t size; + char* copy; + + size = strlen(original) + 1; + copy = safe_malloc(size); + memcpy(copy, original, size); + return(copy); +} + +// This function is called from both macro definition and macro call. +// Terminate macro name and copy from internal_name to GlobalDynaBuf +// (because that's where Tree_hard_scan() looks for the search string). +// Then try to find macro and return whether it was created. +static bool search_for_macro(node_ra_t** result, zone_t zone, bool create) { + DynaBuf_append(internal_name, '\0'); // terminate macro name + // now internal_name = macro_title SPC argument_specifiers NUL + DYNABUF_CLEAR(GlobalDynaBuf); + DynaBuf_add_string(GlobalDynaBuf, internal_name->buffer); + DynaBuf_append(GlobalDynaBuf, '\0'); + return(Tree_hard_scan(result, macro_forest, zone, create)); +} + +// This function is called when an already existing macro is re-defined. +// It first outputs a warning and then a serious error, stopping assembly. +// Showing the first message as a warning guarantees that ACME does not reach +// the maximum error limit inbetween. +static void report_redefinition(node_ra_t* macro_node) { + struct macro_t* original_macro = macro_node->body; + + // show warning with location of current definition + Throw_warning(exception_macro_twice); + // CAUTION, ugly kluge: fiddle with Input_now and Section_now + // data to generate helpful error messages + Input_now->original_filename = original_macro->def_filename; + Input_now->line_number = original_macro->def_line_number; + Section_now->type = "original"; + Section_now->title = "definition"; + // show serious error with location of original definition + Throw_serious_error(exception_macro_twice); +} + +// This function is only called during the first pass, so there's no need to +// check whether to skip the definition or not. +// Return with GotByte = '}' +void Macro_parse_definition(void) {// Now GotByte = illegal char after "!macro" + char* formal_parameters; + node_ra_t* macro_node; + struct macro_t* new_macro; + zone_t macro_zone = get_zone_and_title(); + + // now GotByte = first non-space after title + DYNABUF_CLEAR(GlobalDynaBuf); // prepare to hold formal parameters + // GlobalDynaBuf = "" (will hold formal parameter list) + // user_macro_name = ['.'] MacroTitle NUL + // internal_name = MacroTitle ARG_SEPARATOR (grows to signature) + // Accept n>=0 comma-separated formal parameters before CHAR_SOB ('{'). + // Valid argument formats are: + // .LOCAL_LABEL_BY_VALUE + // ~.LOCAL_LABEL_BY_REFERENCE + // GLOBAL_LABEL_BY_VALUE global args are very uncommon, + // ~GLOBAL_LABEL_BY_REFERENCE but not forbidden + // now GotByte = non-space + if(GotByte != CHAR_SOB) { // any at all? + do { + // handle call-by-reference character ('~') + if(GotByte != REFERENCE_CHAR) + DynaBuf_append(internal_name, ARGTYPE_NUM_VAL); + else { + DynaBuf_append(internal_name, ARGTYPE_NUM_REF); + DynaBuf_append(GlobalDynaBuf, REFERENCE_CHAR); + GetByte(); + } + // handle prefix for local labels ('.') + if(GotByte == '.') { + DynaBuf_append(GlobalDynaBuf, '.'); + GetByte(); + } + // handle label name + Input_append_keyword_to_global_dynabuf(); + } while(pipe_comma()); + // ensure CHAR_SOB ('{') + if(GotByte != CHAR_SOB) + Throw_serious_error(exception_no_left_brace); + } + DynaBuf_append(GlobalDynaBuf, CHAR_EOS); // terminate param list + // now GlobalDynaBuf = comma-separated parameter list without spaces, + // but terminated with CHAR_EOS. + formal_parameters = DynaBuf_get_copy(GlobalDynaBuf); + // now GlobalDynaBuf = unused + // Reading the macro body would change the line number. To have correct + // error messages, we're checking for "macro twice" *now*. + // Search for macro. Create if not found. + // But if found, complain (macro twice). + if(search_for_macro(¯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(struct macro_t)); + new_macro->def_line_number = Input_now->line_number; + new_macro->def_filename = get_string_copy(Input_now->original_filename); + new_macro->original_name = get_string_copy(user_macro_name->buffer); + new_macro->parameter_list = formal_parameters; + new_macro->body = Input_skip_or_store_block(TRUE);// changes LineNumber + macro_node->body = new_macro; // link macro struct to tree node + // and that about sums it up +} + +// Parse macro call ("+MACROTITLE"). Has to be re-entrant. +void Macro_parse_call(void) { // Now GotByte = dot or first char of macro name + char local_gotbyte; + label_t* label; + section_t new_section, + *outer_section; + input_t new_input, + *outer_input; + struct macro_t* actual_macro; + node_ra_t *macro_node, + *label_node; + zone_t macro_zone, + label_zone; + int arg_count = 0; + + // Enter deeper nesting level + // Quit program if recursion too deep. + if(--macro_recursions_left < 0) + Throw_serious_error("Too deeply nested. Recursive macro calls?"); + macro_zone = get_zone_and_title(); + // now GotByte = first non-space after title + // internal_name = MacroTitle ARG_SEPARATOR (grows to signature) + // Accept n>=0 comma-separated arguments before CHAR_EOS. + // Valid argument formats are: + // EXPRESSION (everything that does NOT start with '~' + // ~.LOCAL_LABEL_BY_REFERENCE + // ~GLOBAL_LABEL_BY_REFERENCE + // now GotByte = non-space + if(GotByte != CHAR_EOS) { // any at all? + do { + // if arg table cannot take another element, enlarge + if(argtable_size <= arg_count) + enlarge_arg_table(); + // Decide whether call-by-reference or call-by-value + // In both cases, GlobalDynaBuf may be used. + if(GotByte == REFERENCE_CHAR) { + // read call-by-reference arg + DynaBuf_append(internal_name, ARGTYPE_NUM_REF); + GetByte(); // skip '~' character + Input_read_zone_and_keyword(&label_zone); + // GotByte = illegal char + arg_table[arg_count].label = + Label_find(label_zone, 0); + } else { + // read call-by-value arg + DynaBuf_append(internal_name, ARGTYPE_NUM_VAL); + ALU_parse_expr_medium( + &(arg_table[arg_count].result)); + } + arg_count++; + } while(Input_accept_comma()); + } + // now arg_table contains the arguments + // now GlobalDynaBuf = unused + // check for "unknown macro" + // Search for macro. Do not create if not found. + search_for_macro(¯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_flags & PASS_ISFIRST)) + Throw_error("Macro parameter twice."); + label_node->body = arg_table[arg_count].label; + } else { + // assign call-by-value arg + Input_read_zone_and_keyword(&label_zone); + label = Label_find(label_zone, 0); +// FIXME - add a possibility to Label_find to make it possible to find out +// whether label was just created. Then check for the same error message here +// as above ("Macro parameter twice."). + label->value = arg_table[arg_count].result.value; + label->flags = arg_table[arg_count].result.flags; + } + arg_count++; + } while(Input_accept_comma()); + } + // and now, finally, parse the actual macro body + Input_now->state = INPUTSTATE_NORMAL; // FIXME - fix others! + Input_now->src.ram_ptr = actual_macro->body; + Parse_until_eob_or_eof(); + if(GotByte != CHAR_EOB) + Bug_found("IllegalBlockTerminator", GotByte); + // FIXME - call parse_ram_block(actual_macro->def_line_number, + // actual_macro->body) instead? + // end section (free title memory, if needed) + Section_finalize(&new_section); + // restore previous section + Section_now = outer_section; + // restore previous input: + Input_now = outer_input; + // restore old Gotbyte context + GotByte = local_gotbyte;// CAUTION - ugly kluge + Input_ensure_EOS(); + } + macro_recursions_left++; // leave this nesting level +} diff --git a/Platform/Apple/tools/ACME/src/macro.h b/Platform/Apple/tools/ACME/src/macro.h new file mode 100644 index 00000000..ce8f248d --- /dev/null +++ b/Platform/Apple/tools/ACME/src/macro.h @@ -0,0 +1,19 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Macro stuff +#ifndef macro_H +#define macro_H + +#include "config.h" + + +// Prototypes +extern void Macro_init(void); // create private dynabuf +extern void Macro_parse_definition(void); +extern void Macro_parse_call(void); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/mnemo.c b/Platform/Apple/tools/ACME/src/mnemo.c new file mode 100644 index 00000000..a9d05927 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/mnemo.c @@ -0,0 +1,1024 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Mnemonics stuff + +#include "alu.h" +#include "config.h" +#include "cpu.h" +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "output.h" +#include "tree.h" + + +// Constants +#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 routine to +// be dealt with: +// G_IMPLIED Mnemonics using only implied addressing. Byte value = opcode +// G_MAIN The main accumulator stuff (plus PEI) Byte value = table index +// G_MISC Most of the other opcodes Byte value = table index +// G_REL_SHORT The short branch instructions. Byte value = opcode +// G_REL_LONG Mnemonics with 16bit relative addressing. Byte value = opcode +// G_JUMP The jump instructions Byte value = table index +// G_MOVE The "move" commands. Byte value = opcode +enum mnemogroup_t { + G_IMPLIED, // only implied addressing + G_MAIN, // main accumulator stuff (plus PEI) + G_MISC, // misc + G_REL_SHORT, // short relative + G_REL_LONG, // long relative + G_JUMP, // jump + G_MOVE // MVP, MVN +}; + +// 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 G_MAIN: +// 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. +enum { IDX_ORA, IDX_AND, IDX_EOR, IDX_ADC, IDX_STA, IDX_LDA, IDX_CMP, IDX_SBC, + IDXC_ORA, IDXC_AND, IDXC_EOR, IDXC_ADC, IDXC_STA, IDXC_LDA, IDXC_CMP, IDXC_SBC, + IDX8_ORA, IDX8_AND, IDX8_EOR, IDX8_ADC, IDX8_STA, IDX8_LDA, IDX8_CMP, IDX8_SBC, IDX8_PEI, + IDXI_SLO, IDXI_RLA, IDXI_SRE, IDXI_RRA, IDXI_SAX, IDXI_LAX, IDXI_DCP, IDXI_ISC}; +// | 6502 | 65c02 | 65816 | 6510 | +// | ora and eor adc sta lda cmp sbc| ora and eor adc sta lda cmp sbc| ora and eor adc sta lda cmp sbc pei| slo rla sre rra sax lax dcp isc| +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_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_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_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_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_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_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 +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 + +// Code tables for group G_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. +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, + IDXC_TSB, IDXC_TRB, IDXC_BIT, IDXC_DEC, IDXC_INC, IDXC_STZ, + IDX8_COP, IDX8_REP, IDX8_SEP, IDX8_PEA, + IDXI_ANC, IDXI_ASR, IDXI_ARR, IDXI_SBX, IDXI_DOP, IDXI_TOP, IDXI_JAM}; +// | 6502 | 65c02 | 65816 | 6510 | +// | bit asl rol lsr ror sty stx ldy ldx cpy cpx dec inc| tsb trb bit dec inc stz| cop rep sep pea| anc asr arr sbx dop top jam| +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 +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 +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 + +// Code tables for group G_JUMP: +// +// 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. +enum { IDX_JMP, IDX_JSR, IDXC_JMP, IDX8_JMP, IDX8_JML, IDX8_JSR, IDX8_JSL}; +// | 6502 | 65c02| 65816 | +// | jmp jsr | jmp | jmp jml jsr jsl | +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 dynabuf_t* mnemo_dyna_buf; // dynamic buffer for mnemonics +// predefined stuff +static node_t* mnemo_6502_tree = NULL;// holds 6502 mnemonics +static node_t* mnemo_6510_tree = NULL;// holds 6510 extensions +static node_t* mnemo_65c02_tree= NULL;// holds 65c02 extensions +//static node_t* mnemo_Rockwell65c02_tree = NULL;// Rockwell +static node_t* mnemo_WDC65c02_tree = NULL;// WDC's "stp"/"wai" +static 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 node_t mnemos_6502[] = { + PREDEFNODE("ora", MERGE(G_MAIN , IDX_ORA | IMM_ACCU)), + PREDEFNODE("and", MERGE(G_MAIN , IDX_AND | IMM_ACCU)), + PREDEFNODE("eor", MERGE(G_MAIN , IDX_EOR | IMM_ACCU)), + PREDEFNODE("adc", MERGE(G_MAIN , IDX_ADC | IMM_ACCU)), + PREDEFNODE("sta", MERGE(G_MAIN , IDX_STA)), + PREDEFNODE("lda", MERGE(G_MAIN , IDX_LDA | IMM_ACCU)), + PREDEFNODE("cmp", MERGE(G_MAIN , IDX_CMP | IMM_ACCU)), + PREDEFNODE("sbc", MERGE(G_MAIN , IDX_SBC | IMM_ACCU)), + PREDEFNODE("bit", MERGE(G_MISC , IDX_BIT | IMM_ACCU)), + PREDEFNODE("asl", MERGE(G_MISC , IDX_ASL)), + PREDEFNODE("rol", MERGE(G_MISC , IDX_ROL)), + PREDEFNODE("lsr", MERGE(G_MISC , IDX_LSR)), + PREDEFNODE("ror", MERGE(G_MISC , IDX_ROR)), + PREDEFNODE("sty", MERGE(G_MISC , IDX_STY)), + PREDEFNODE("stx", MERGE(G_MISC , IDX_STX)), + PREDEFNODE("ldy", MERGE(G_MISC , IDX_LDY | IMM_IDX)), + PREDEFNODE("ldx", MERGE(G_MISC , IDX_LDX | IMM_IDX)), + PREDEFNODE("cpy", MERGE(G_MISC , IDX_CPY | IMM_IDX)), + PREDEFNODE("cpx", MERGE(G_MISC , IDX_CPX | IMM_IDX)), + PREDEFNODE("dec", MERGE(G_MISC , IDX_DEC)), + PREDEFNODE("inc", MERGE(G_MISC , IDX_INC)), + PREDEFNODE("bpl", MERGE(G_REL_SHORT , 16)), + PREDEFNODE("bmi", MERGE(G_REL_SHORT , 48)), + PREDEFNODE("bvc", MERGE(G_REL_SHORT , 80)), + PREDEFNODE("bvs", MERGE(G_REL_SHORT , 112)), + PREDEFNODE("bcc", MERGE(G_REL_SHORT , 144)), + PREDEFNODE("bcs", MERGE(G_REL_SHORT , 176)), + PREDEFNODE("bne", MERGE(G_REL_SHORT , 208)), + PREDEFNODE("beq", MERGE(G_REL_SHORT , 240)), + PREDEFNODE("jmp", MERGE(G_JUMP , IDX_JMP)), + PREDEFNODE("jsr", MERGE(G_JUMP , IDX_JSR)), + PREDEFNODE("brk", MERGE(G_IMPLIED , 0)), + PREDEFNODE("php", MERGE(G_IMPLIED , 8)), + PREDEFNODE("clc", MERGE(G_IMPLIED , 24)), + PREDEFNODE("plp", MERGE(G_IMPLIED , 40)), + PREDEFNODE("sec", MERGE(G_IMPLIED , 56)), + PREDEFNODE("rti", MERGE(G_IMPLIED , 64)), + PREDEFNODE("pha", MERGE(G_IMPLIED , 72)), + PREDEFNODE("cli", MERGE(G_IMPLIED , 88)), + PREDEFNODE("rts", MERGE(G_IMPLIED , 96)), + PREDEFNODE("pla", MERGE(G_IMPLIED , 104)), + PREDEFNODE("sei", MERGE(G_IMPLIED , 120)), + PREDEFNODE("dey", MERGE(G_IMPLIED , 136)), + PREDEFNODE("txa", MERGE(G_IMPLIED , 138)), + PREDEFNODE("tya", MERGE(G_IMPLIED , 152)), + PREDEFNODE("txs", MERGE(G_IMPLIED , 154)), + PREDEFNODE("tay", MERGE(G_IMPLIED , 168)), + PREDEFNODE("tax", MERGE(G_IMPLIED , 170)), + PREDEFNODE("clv", MERGE(G_IMPLIED , 184)), + PREDEFNODE("tsx", MERGE(G_IMPLIED , 186)), + PREDEFNODE("iny", MERGE(G_IMPLIED , 200)), + PREDEFNODE("dex", MERGE(G_IMPLIED , 202)), + PREDEFNODE("cld", MERGE(G_IMPLIED , 216)), + PREDEFNODE("inx", MERGE(G_IMPLIED , 232)), + PREDEFNODE("nop", MERGE(G_IMPLIED , 234)), + PREDEFLAST("sed", MERGE(G_IMPLIED , 248)), + // ^^^^ this marks the last element +}; + +static node_t mnemos_6510[] = { + PREDEFNODE("slo", MERGE(G_MAIN, IDXI_SLO)),// ASL+ORA, ASO + PREDEFNODE("rla", MERGE(G_MAIN, IDXI_RLA)),// ROL+AND + PREDEFNODE("sre", MERGE(G_MAIN, IDXI_SRE)),// LSR+EOR, LSE + PREDEFNODE("rra", MERGE(G_MAIN, IDXI_RRA)),// ROR+ADC + PREDEFNODE("sax", MERGE(G_MAIN, IDXI_SAX)),// STX+STA, AAX, AXS + PREDEFNODE("lax", MERGE(G_MAIN, IDXI_LAX)),// LDX+LDA + PREDEFNODE("dcp", MERGE(G_MAIN, IDXI_DCP)),// DEC+CMP, DCM + PREDEFNODE("isc", MERGE(G_MAIN, IDXI_ISC)),// INC+SBC, ISB, INS + PREDEFNODE("anc", MERGE(G_MISC, IDXI_ANC)),// ROL+AND, ASL+ORA, AAC + PREDEFNODE("asr", MERGE(G_MISC, IDXI_ASR)),// LSR+EOR, ALR + PREDEFNODE("arr", MERGE(G_MISC, IDXI_ARR)),// ROR+ADC + PREDEFNODE("sbx", MERGE(G_MISC, IDXI_SBX)),// DEX+CMP, AXS, SAX + PREDEFNODE("dop", MERGE(G_MISC, IDXI_DOP)),// skip next byte + PREDEFNODE("top", MERGE(G_MISC, IDXI_TOP)),// skip next two bytes + PREDEFLAST("jam", MERGE(G_MISC, IDXI_JAM)),// jam/crash/halt/kill + // ^^^^ this marks the last element +}; + +static node_t mnemos_65c02[] = { + PREDEFNODE("ora", MERGE(G_MAIN , IDXC_ORA | IMM_ACCU)), + PREDEFNODE("and", MERGE(G_MAIN , IDXC_AND | IMM_ACCU)), + PREDEFNODE("eor", MERGE(G_MAIN , IDXC_EOR | IMM_ACCU)), + PREDEFNODE("adc", MERGE(G_MAIN , IDXC_ADC | IMM_ACCU)), + PREDEFNODE("sta", MERGE(G_MAIN , IDXC_STA)), + PREDEFNODE("lda", MERGE(G_MAIN , IDXC_LDA | IMM_ACCU)), + PREDEFNODE("cmp", MERGE(G_MAIN , IDXC_CMP | IMM_ACCU)), + PREDEFNODE("sbc", MERGE(G_MAIN , IDXC_SBC | IMM_ACCU)), + PREDEFNODE("jmp", MERGE(G_JUMP , IDXC_JMP)), + PREDEFNODE("bit", MERGE(G_MISC , IDXC_BIT | IMM_ACCU)), + PREDEFNODE("dec", MERGE(G_MISC , IDXC_DEC)), + PREDEFNODE("inc", MERGE(G_MISC , IDXC_INC)), + PREDEFNODE("bra", MERGE(G_REL_SHORT , 128)), + PREDEFNODE("phy", MERGE(G_IMPLIED , 90)), + PREDEFNODE("ply", MERGE(G_IMPLIED , 122)), + PREDEFNODE("phx", MERGE(G_IMPLIED , 218)), + PREDEFNODE("plx", MERGE(G_IMPLIED , 250)), + PREDEFNODE("tsb", MERGE(G_MISC , IDXC_TSB)), + PREDEFNODE("trb", MERGE(G_MISC , IDXC_TRB)), + PREDEFLAST("stz", MERGE(G_MISC , IDXC_STZ)), + // ^^^^ this marks the last element +}; + +//static 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 node_t mnemos_WDC65c02[] = { + PREDEFNODE("stp", MERGE(G_IMPLIED , 219)), + PREDEFLAST("wai", MERGE(G_IMPLIED , 203)), + // ^^^^ this marks the last element +}; + +static node_t mnemos_65816[] = { + PREDEFNODE("ora", MERGE(G_MAIN , IDX8_ORA | IMM_ACCU)), + PREDEFNODE("and", MERGE(G_MAIN , IDX8_AND | IMM_ACCU)), + PREDEFNODE("eor", MERGE(G_MAIN , IDX8_EOR | IMM_ACCU)), + PREDEFNODE("adc", MERGE(G_MAIN , IDX8_ADC | IMM_ACCU)), + PREDEFNODE("sta", MERGE(G_MAIN , IDX8_STA)), + PREDEFNODE("lda", MERGE(G_MAIN , IDX8_LDA | IMM_ACCU)), + PREDEFNODE("cmp", MERGE(G_MAIN , IDX8_CMP | IMM_ACCU)), + PREDEFNODE("sbc", MERGE(G_MAIN , IDX8_SBC | IMM_ACCU)), + PREDEFNODE("pei", MERGE(G_MAIN , IDX8_PEI)), + PREDEFNODE("jmp", MERGE(G_JUMP , IDX8_JMP)), + PREDEFNODE("jsr", MERGE(G_JUMP , IDX8_JSR)), + PREDEFNODE("jml", MERGE(G_JUMP , IDX8_JML)), + PREDEFNODE("jsl", MERGE(G_JUMP , IDX8_JSL)), + PREDEFNODE("mvp", MERGE(G_MOVE , 0x44)), + PREDEFNODE("mvn", MERGE(G_MOVE , 0x54)), + PREDEFNODE("per", MERGE(G_REL_LONG , 98)), + PREDEFNODE("brl", MERGE(G_REL_LONG , 130)), + PREDEFNODE("cop", MERGE(G_MISC , IDX8_COP)), + PREDEFNODE("rep", MERGE(G_MISC , IDX8_REP)), + PREDEFNODE("sep", MERGE(G_MISC , IDX8_SEP)), + PREDEFNODE("pea", MERGE(G_MISC , IDX8_PEA)), + PREDEFNODE("phd", MERGE(G_IMPLIED , 11)), + PREDEFNODE("tcs", MERGE(G_IMPLIED , 27)), + PREDEFNODE("pld", MERGE(G_IMPLIED , 43)), + PREDEFNODE("tsc", MERGE(G_IMPLIED , 59)), + PREDEFNODE("wdm", MERGE(G_IMPLIED , 66)), + PREDEFNODE("phk", MERGE(G_IMPLIED , 75)), + PREDEFNODE("tcd", MERGE(G_IMPLIED , 91)), + PREDEFNODE("rtl", MERGE(G_IMPLIED , 107)), + PREDEFNODE("tdc", MERGE(G_IMPLIED , 123)), + PREDEFNODE("phb", MERGE(G_IMPLIED , 139)), + PREDEFNODE("txy", MERGE(G_IMPLIED , 155)), + PREDEFNODE("plb", MERGE(G_IMPLIED , 171)), + PREDEFNODE("tyx", MERGE(G_IMPLIED , 187)), + PREDEFNODE("xba", MERGE(G_IMPLIED , 235)), + PREDEFLAST("xce", MERGE(G_IMPLIED , 251)), + // ^^^^ this marks the last element +}; + + +// Functions + +// Init (add stuff to tree) +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 + +// Service routine to read optional info about parameter length. +int Mnemo_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); +} + +// Utility routine for parsing indices. +static int get_index(bool 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 routine stores the command's argument in the given result_t structure +// (using the valueparser). The addressing mode is returned. +static int get_argument(result_t* result) { + int open_paren, + addressing_mode = HAM_ABS; + + SKIPSPACE(); + switch(GotByte) { + + case '[': + GetByte();// proceed with next char + ALU_parse_expr_medium(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_parse_expr_medium(result); + break; + + default: + // liberal, to allow for "(...," + open_paren = ALU_parse_expr_liberal(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); + break; + + } + // 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, result_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->value <= 255) && (argument->value >= -128)) + Throw_warning(exception_highbyte_zero); + } else + // check 24-bit argument for bank byte zero + if((argument->value <= 65535) && (argument->value >= -32768)) + Throw_warning(exception_highbyte_zero); + return(size_bit);// pass on result +} + +// Utility routine 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, result_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->value < 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->value < 256)) + return(MVALUE_FORCE08); + // If there is a 16-bit addressing mode and value fits, use 16 bits + if((addressing_modes & MVALUE_FORCE16) && (argument->value < 65536)) + return(MVALUE_FORCE16); + // If there is a 24-bit addressing mode, use that. In case the + // value doesn't fit, the output routine 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) { + result_t result; + + ALU_parse_expr_medium(&result); + if(result.flags & MVALUE_DEFINED) { + result.value -= (CPU_pc + 2); + if((result.value > 127) || (result.value < -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.value); + Input_ensure_EOS(); +} + +// Mnemonics using only 16bit relative addressing (BRL and PER). +static void group_only_relative16_addressing(int opcode) { + result_t result; + + ALU_parse_expr_medium(&result); + if(result.flags & MVALUE_DEFINED) { + result.value -= (CPU_pc + 3); + if((result.value > 32767) || (result.value < -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.value); + Output_byte(result.value >> 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, result_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->value); + break; + + case MVALUE_FORCE16: + Output_byte((opcodes >> 8) & 255); + Output_16b(result->value); + break; + + case MVALUE_FORCE24: + Output_byte((opcodes >> 16) & 255); + Output_24b(result->value); + break; + + } +} + +// 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 & CPU_FLAG_IMM16) == 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) ? + CPU65816_long_a : CPU65816_long_r) ? + 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; + result_t result; + int force_bit = Mnemo_get_force_bit();// skips spaces after + + switch(get_argument(&result)) { + + // #$ff or #$ffff (depending on accu length) + case HAM_IMM: + imm_opcodes = imm_ops(&force_bit, accu_imm[index], imm_flag); + // CAUTION - do not incorporate the line above into the line + // below - "fb" might be undefined (depends on compiler). + make_command(force_bit, &result, imm_opcodes); + break; + + // $ff, $ffff, $ffffff + case HAM_ABS: + make_command(force_bit, &result, accu_abs[index]); + break; + + // $ff,x, $ffff,x, $ffffff,x + case HAM_ABSX: + make_command(force_bit, &result, accu_xabs[index]); + break; + + // $ffff,y (in theory, "$ff,y" as well) + case HAM_ABSY: + make_command(force_bit, &result, accu_yabs[index]); + break; + + // $ff,s + case HAM_ABSS: + make_command(force_bit, &result, accu_sabs8[index]); + break; + + // ($ff,x) + case HAM_XIND: + make_command(force_bit, &result, accu_xind8[index]); + break; + + // ($ff) + case HAM_IND: + make_command(force_bit, &result, accu_ind8[index]); + break; + + // ($ff),y + case HAM_INDY: + make_command(force_bit, &result, accu_indy8[index]); + break; + + // [$ff] + case HAM_LIND: + make_command(force_bit, &result, accu_lind8[index]); + break; + + // [$ff],y + case HAM_LINDY: + make_command(force_bit, &result, accu_lindy8[index]); + break; + + // ($ff,s),y + case HAM_SINDY: + make_command(force_bit, &result, accu_sindy8[index]); + break; + + // other combinations are illegal + default: + Throw_error(exception_illegal_combination); + + } +} + +// Various mnemonics with different addressing modes. +static void group_misc(int index, int imm_flag) { + unsigned long imm_opcodes; + result_t result; + int force_bit = Mnemo_get_force_bit();// skips spaces after + + switch(get_argument(&result)) { + + // implied addressing + case HAM_IMP: + if(misc_impl[index]) + Output_byte(misc_impl[index]); + else + Throw_error(exception_illegal_combination); + break; + + // #$ff or #$ffff (depending on index register length) + case HAM_IMM: + imm_opcodes = imm_ops(&force_bit, misc_imm[index], imm_flag); + // CAUTION - do not incorporate the line above into the line + // below - "fb" might be undefined (depends on compiler). + make_command(force_bit, &result, imm_opcodes); + break; + + // $ff or $ffff + case HAM_ABS: + make_command(force_bit, &result, misc_abs[index]); + break; + + // $ff,x or $ffff,x + case HAM_ABSX: + make_command(force_bit, &result, misc_xabs[index]); + break; + + // $ff,y or $ffff,y + case HAM_ABSY: + make_command(force_bit, &result, misc_yabs[index]); + break; + + // other combinations are illegal + default: + Throw_error(exception_illegal_combination); + } +} + +// Mnemonics using "8bit, 8bit" addressing. Only applies to "MVN" and "MVP". +static void group_move(int opcode) { + result_t target, + source; + + ALU_parse_expr_medium(&source); + if(Input_accept_comma()) { + ALU_parse_expr_medium(&target); + Output_byte(opcode); + // assembler syntax: "opcode, source, target" + // machine language: "opcode, target, source" + Output_8b(target.value); + Output_8b(source.value); + Input_ensure_EOS(); + } else + Throw_error(exception_syntax); +} + +// The jump instructions. +static void group_jump(int index) { + result_t result; + int force_bit = Mnemo_get_force_bit();// skips spaces after + + switch(get_argument(&result)) { + + // absolute16 or absolute24 + case HAM_ABS: + make_command(force_bit, &result, jump_abs[index]); + break; + + // ($ffff) + case HAM_IND: + make_command(force_bit, &result, jump_ind[index]); + // check whether to warn about 6502's JMP() bug + if(((result.value & 0xff) == 0xff) + && (result.flags & MVALUE_DEFINED) + && (CPU_now->flags & CPU_FLAG_INDJMP_BUGGY)) + Throw_warning("Assembling buggy JMP($xxff) instruction"); + break; + + // ($ffff,x) + case HAM_XIND: + make_command(force_bit, &result, jump_xind[index]); + break; + + // [$ffff] + case HAM_LIND: + make_command(force_bit, &result, jump_lind[index]); + break; + + // other combinations are illegal + default: + Throw_error(exception_illegal_combination); + + } +} + +// Work routine +static bool check_mnemo_tree(node_t* tree, 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) { + + // mnemonics with only implied addressing + case G_IMPLIED: + group_only_implied_addressing(code); + break; + + // main accumulator stuff + case G_MAIN: + group_main(code, imm_flag); + break; + + // misc + case G_MISC: + group_misc(code, imm_flag); + break; + + // short relative + case G_REL_SHORT: + group_only_relative8_addressing(code); + break; + + // long relative + case G_REL_LONG: + group_only_relative16_addressing(code); + break; + + // the jump instructions + case G_JUMP: + group_jump(code); + break; + + // "mvp" and "mvn" + case G_MOVE: + group_move(code); + break; + + // others indicate bugs + default: + Bug_found("IllegalGroupIndex", code); + + } + return(TRUE); +} + +// Check whether mnemonic in GlobalDynaBuf is supported by 6502 cpu. +bool 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); + if(check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf)) + return(TRUE); + return(FALSE); +} + +// Check whether mnemonic in GlobalDynaBuf is supported by 6510 cpu. +bool 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 + if(check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf)) + return(TRUE); + return(FALSE); +} + +// Check whether mnemonic in GlobalDynaBuf is supported by 65c02 cpu. +bool 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 + if(check_mnemo_tree(mnemo_6502_tree, mnemo_dyna_buf)) + return(TRUE); + return(FALSE); +} + +//// Check whether mnemonic in GlobalDynaBuf is supported by Rockwell 65c02 cpu. +//bool 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) +// if(check_mnemo_tree(mnemo_Rockwell65c02_tree, mnemo_dyna_buf)) +// return(TRUE); +// return(FALSE); +//} + +//// Check whether mnemonic in GlobalDynaBuf is supported by WDC 65c02 cpu. +//bool 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) +// if(check_mnemo_tree(mnemo_WDC65c02_tree, mnemo_dyna_buf)) +// return(TRUE); +// return(FALSE); +//} + +// Check whether mnemonic in GlobalDynaBuf is supported by 65816 cpu. +bool 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" + if(check_mnemo_tree(mnemo_WDC65c02_tree, mnemo_dyna_buf)) + return(TRUE); + return(FALSE); +} diff --git a/Platform/Apple/tools/ACME/src/mnemo.h b/Platform/Apple/tools/ACME/src/mnemo.h new file mode 100644 index 00000000..5570538e --- /dev/null +++ b/Platform/Apple/tools/ACME/src/mnemo.h @@ -0,0 +1,22 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Mnemonic definitions +#ifndef mnemo_H +#define mnemo_H + + +// Prototypes +extern void Mnemo_init(void); +extern int Mnemo_get_force_bit(void); +extern bool keyword_is_6502mnemo(int length); +extern bool keyword_is_6510mnemo(int length); +extern bool keyword_is_65c02mnemo(int length); +//extern bool keyword_is_Rockwell65c02mnemo(int length); +//extern bool keyword_is_WDC65c02mnemo(int length); +extern bool keyword_is_65816mnemo(int length); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/output.c b/Platform/Apple/tools/ACME/src/output.c new file mode 100644 index 00000000..e8ed9723 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/output.c @@ -0,0 +1,427 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Output stuff + +#include +#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" + + +// Constants +#define USERMSG_DYNABUF_INITIALSIZE 80 + +// Variables +static bool memory_initialised = FALSE; +value_t Mem_current_pc; // Current program counter (real memory address) +value_t Mem_lowest_pc; // Start address of program (first PC given) +value_t Mem_highest_pc; // End address of program plus one +static char* output_buffer = NULL; // to hold assembled code +static dynabuf_t* user_message; // dynamic buffer (!warn/error/serious) +// Chosen output file format +static enum out_format_t output_format = OUTPUT_FORMAT_UNSPECIFIED; +// predefined stuff +static node_t* file_format_tree = NULL;// tree to hold output formats +// Possible output file formats +static node_t file_formats[] = { + PREDEFNODE(s_cbm, OUTPUT_FORMAT_CBM), +// PREDEFNODE("o65", OUTPUT_FORMAT_O65), FIXME - add! + PREDEFLAST("plain", OUTPUT_FORMAT_PLAIN), + // ^^^^ this marks the last element +}; +static const char s_08[] = "08"; +#define s_8 (&s_08[1]) // Yes, I know I'm sick + + +// Functions + +// Send low byte to output file, automatically increasing program counter +void Output_byte(value_t byte) { + int offset; + + CPU_ensure_defined_pc(); + offset = Mem_current_pc + pc_inc; + if(offset == segment_max + 1) { + if(offset == OUTBUFFERSIZE) + Throw_serious_error("Produced too much code."); + Throw_warning("Segment reached another one, overwriting it."); + if(pass_flags & PASS_ISFIRST) + CPU_find_segment_max(offset + 1); + } + output_buffer[offset] = byte & 255; + pc_inc++; +} + +// Output 8-bit value with range check +void Output_8b(value_t value) { + if((value <= 255) && (value >= -128)) + Output_byte(value); + else + Throw_error(exception_number_out_of_range); +} + +// Output 16-bit values with range check +void Output_16b(value_t value) { + if((value <= 65535) && (value >= -32768)) { + Output_byte(value); + Output_byte(value >> 8); + } else + Throw_error(exception_number_out_of_range); +} + +// Output 24-bit values with range check +void Output_24b(value_t value) { + if((value <= 0xffffff) && (value >= -0x800000)) { + Output_byte(value); + Output_byte(value >> 8); + Output_byte(value >> 16); + } else + Throw_error(exception_number_out_of_range); +} + +// Output 32-bit values (without range check) +void Output_32b(value_t value) { +// if((Value <= 0x7fffffff) && (Value >= -0x80000000)) { + Output_byte(value); + Output_byte(value >> 8); + Output_byte(value >> 16); + Output_byte(value >> 24); +// } else +// Throw_error(exception_number_out_of_range); +} + +// Helper function for !8, !16, !24 and !32 pseudo opcodes +static enum eos_t output_objects(void (*fn)(value_t)) { + result_t result; + + do { + ALU_parse_expr_medium(&result); + fn(result.value); + } while(Input_accept_comma()); + return(ENSURE_EOS); +} + +// Insert 8-bit values ("!08" / "!8" / "!by" / "!byte" pseudo opcode) +static enum eos_t PO_08(void) { + return(output_objects(Output_8b)); +} + +// Insert 16-bit values ("!16" / "!wo" / "!word" pseudo opcode) +static enum eos_t PO_16(void) { + return(output_objects(Output_16b)); +} + +// Insert 24-bit values ("!24" pseudo opcode) +static enum eos_t PO_24(void) { + return(output_objects(Output_24b)); +} + +// Insert 32-bit values ("!32" pseudo opcode) +static enum eos_t PO_32(void) { + return(output_objects(Output_32b)); +} + +// Helper function for PO_binary() +static FILE* open_bin_and_skip(char* filename, int skip) { + FILE* fd = fopen(filename, FILE_READBINARY); + + if(fd) + fseek(fd, skip, SEEK_SET); // skip "Skip" bytes + else + Throw_error(exception_cannot_open_input_file);// on error, complain + return(fd); +} + +// Include binary file +static enum eos_t PO_binary(void) { + result_t result; + FILE* fd; + char* filename; + int byte, + size_given = 0; + value_t size, + skip = 0; + + // if file name is missing, don't bother continuing + if((filename = Input_read_filename(TRUE)) == NULL) // uses copy + return(SKIP_REMAINDER); + // read arguments, if any + if(Input_accept_comma()) { + ALU_parse_expr_empty_strict(&result);// read size argument + size = result.value; + size_given = result.flags & MVALUE_EXISTS; + // read skip argument, if given + if(Input_accept_comma()) { + ALU_parse_expr_empty_strict(&result); + if(result.flags & MVALUE_EXISTS) + skip = result.value; + } + } + if(size_given) { + // explicit size info, so truncate or pad file + if(pass_undefined_count || pass_real_errors) + // don't insert file if it's just a waste of time + pc_inc += size; + else { + // really try to open file and insert it + fd = open_bin_and_skip(filename, skip); + free(filename); // file name no longer needed + if(fd == NULL) + return(SKIP_REMAINDER); + + // copy "Size" bytes from file to output + while(size--) { + byte = getc(fd); + if(byte == EOF) + byte = 0; + Output_8b(byte); + } + fclose(fd); + } + } else { + // no explicit size info, so read file until EOF + fd = open_bin_and_skip(filename, skip); + free(filename); // file name no longer needed + if(fd == NULL) + return(SKIP_REMAINDER); + // copy bytes from file to output until EOF + while((byte = getc(fd)) != EOF) + Output_8b(byte); + fclose(fd); + } + // if verbose, produce some output + if((pass_flags & PASS_ISFIRST) && (Process_verbosity > 1)) + printf("Loaded %ld ($%lx) bytes from file offset %ld ($%lx).\n", + pc_inc, pc_inc, skip, skip); + return(ENSURE_EOS); +} + +// Reserve space by sending bytes of given value ("!fi" / "!fill" pseudo opcode) +static enum eos_t PO_fill(void) { + result_t size; + result_t fill; + + fill.value = FILLVALUE_FILL; + ALU_parse_expr_strict(&size); + if(Input_accept_comma()) + ALU_parse_expr_medium(&fill); + while(size.value--) + Output_8b(fill.value); + return(ENSURE_EOS); +} + +// Fill output buffer with given byte value +static void fill_completely(char value) { + memset(output_buffer, value, OUTBUFFERSIZE); +} + +// Define default value for empty memory ("!initmem" pseudo opcode) +static enum eos_t PO_initmem(void) { + result_t result; + + if((pass_flags & PASS_ISFIRST) == 0) + return(SKIP_REMAINDER); + // if MemInit flag is already set, complain + if(memory_initialised) { + Throw_warning("Memory already initialised."); + return(SKIP_REMAINDER); + } + // set MemInit flag + memory_initialised = TRUE; + // get value and init memory + ALU_parse_expr_strict(&result); + if((result.value > 255) || (result.value < -128)) + Throw_error(exception_number_out_of_range); + // init memory + fill_completely(result.value & 0xff); + // tricky bit + // enforce another pass + if((pass_undefined_count == 0) + && (Mem_lowest_pc < Mem_highest_pc)) + pass_undefined_count = 1; + return(ENSURE_EOS); +} + +// show user-defined message +static enum eos_t throw_string(const char prefix[], void (*fn)(const char*)) { + result_t result; + + DYNABUF_CLEAR(user_message); + DynaBuf_add_string(user_message, prefix); + do { + if(GotByte == '"') { + // parse string + GetQuotedByte(); // read initial character + // send characters until closing quote is reached + while(GotByte && (GotByte != '"')) { + DYNABUF_APPEND(user_message, GotByte); + GetQuotedByte(); + } + if(GotByte == CHAR_EOS) + return(AT_EOS_ANYWAY); + // after closing quote, proceed with next char + GetByte(); + } else { + // parse value + ALU_parse_expr_medium(&result); + if(result.flags & MVALUE_DEFINED) + DynaBuf_add_value(user_message, result.value); + else + DynaBuf_add_string(user_message, ""); + } + } while(Input_accept_comma()); + DynaBuf_append(user_message, '\0'); + fn(user_message->buffer); + return(ENSURE_EOS); +} + +//// +//static enum eos_t PO_print(void) { +// return(throw_string(FIXME)); +//} + +// throw warning as given in source code +static enum eos_t PO_warn(void) { + return(throw_string("!warn: ", Throw_warning)); +} + +// throw error as given in source code +static enum eos_t PO_error(void) { + return(throw_string("!error: ", Throw_error)); +} + +// throw serious error as given in source code +static enum eos_t PO_serious(void) { + return(throw_string("!serious: ", Throw_serious_error)); +} + +// Try to set output format in DynaBuf. Returns whether succeeded. +bool Output_set_output_format(void) { + void* node_body; + + if(!Tree_easy_scan(file_format_tree, &node_body, GlobalDynaBuf)) + return(FALSE); + output_format = (enum out_format_t) node_body; + return(TRUE); +} + +// Select output file and format ("!to" pseudo opcode) +static enum eos_t PO_to(void) { + // only act upon this pseudo opcode in first pass + if((pass_flags & PASS_ISFIRST) == 0) + return(SKIP_REMAINDER); + // if output file already chosen, complain + if(output_filename) { + Throw_warning("Output file already chosen."); + return(SKIP_REMAINDER); + } + // on file name error, give up + if((output_filename = Input_read_filename(FALSE)) == NULL) // uses copy + return(SKIP_REMAINDER); + // select output format + // if no comma found, use default file format + if(Input_accept_comma() == FALSE) { + if(output_format == OUTPUT_FORMAT_UNSPECIFIED) { + output_format = OUTPUT_FORMAT_CBM; + // output deprecation warning + if(pass_flags & PASS_ISFIRST) + Throw_warning("Used \"!to\" without file format indicator. Defaulting to \"cbm\"."); + } + return(ENSURE_EOS); + } + // parse output format name + // if no keyword given, give up + if(Input_read_and_lower_keyword() == 0) + return(SKIP_REMAINDER); + if(Output_set_output_format()) + return(ENSURE_EOS); // success + // error occurred + Throw_error("Unknown output format."); + return(SKIP_REMAINDER); +} + +// pseudo ocpode table +static node_t pseudo_opcodes[] = { + PREDEFNODE(s_08, PO_08), + PREDEFNODE(s_8, PO_08), + PREDEFNODE("by", PO_08), + PREDEFNODE("byte", PO_08), + PREDEFNODE("16", PO_16), + PREDEFNODE("wo", PO_16), + PREDEFNODE("word", PO_16), + PREDEFNODE("24", PO_24), + PREDEFNODE("32", PO_32), + PREDEFNODE("bin", PO_binary), + PREDEFNODE("binary", PO_binary), + PREDEFNODE("fi", PO_fill), + PREDEFNODE("fill", PO_fill), + PREDEFNODE("initmem", PO_initmem), +// PREDEFNODE("print", PO_print), + PREDEFNODE("warn", PO_warn), + PREDEFNODE("error", PO_error), + PREDEFNODE("serious", PO_serious), + PREDEFLAST("to", PO_to), + // ^^^^ this marks the last element +}; + +// Init file format tree (is done early) +void Outputfile_init(void) { + Tree_add_table(&file_format_tree, file_formats); +} + +// Init other stuff (done later) +void Output_init(signed long fill_value) { + // FIXME - call safe_malloc instead (after having it fixed so it can + // be called before a section is set up) + output_buffer = malloc(OUTBUFFERSIZE); + if(output_buffer == NULL) { + fputs("Error: No memory for output buffer.\n", stderr); + exit(EXIT_FAILURE); + } + if(fill_value == MEMINIT_USE_DEFAULT) + fill_value = FILLVALUE_INITIAL; + else + memory_initialised = TRUE; + user_message = DynaBuf_create(USERMSG_DYNABUF_INITIALSIZE); + Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes); + // init output buffer (fill memory with initial value) + fill_completely(fill_value & 0xff); +} + +// Dump memory buffer into output file +void Output_save_file(FILE* fd, value_t start, value_t end) { + if(Process_verbosity) + printf("Saving %ld ($%lx) bytes ($%lx - $%lx exclusive).\n", + end - start, end - start, start, end); + + // Output file header according to file format + switch(output_format) { + + case OUTPUT_FORMAT_UNSPECIFIED: + case OUTPUT_FORMAT_PLAIN: + PLATFORM_SETFILETYPE_PLAIN(output_filename); + break; + + case OUTPUT_FORMAT_CBM: + PLATFORM_SETFILETYPE_CBM(output_filename); + // output 16-bit load address in little-endian byte order + putc(start & 255, fd); + putc(start >> 8, fd); + break; + + } + // Dump output buffer to file + fwrite(output_buffer + start, sizeof(char), end - start, fd); +} diff --git a/Platform/Apple/tools/ACME/src/output.h b/Platform/Apple/tools/ACME/src/output.h new file mode 100644 index 00000000..e170fe3f --- /dev/null +++ b/Platform/Apple/tools/ACME/src/output.h @@ -0,0 +1,42 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Output stuff +#ifndef output_H +#define output_H + +#include +#include "config.h" + + +// Constants +#define OUTBUFFERSIZE 65536 +#define MEMINIT_USE_DEFAULT 256 +enum out_format_t { + OUTPUT_FORMAT_UNSPECIFIED, // default value. (results in + OUTPUT_FORMAT_PLAIN, // "plain" being used) + OUTPUT_FORMAT_CBM, // default for "!to" pseudo opcode +}; + + +// Variables +extern value_t Mem_current_pc; // Current memory pointer +extern value_t Mem_lowest_pc; // Start address of used memory +extern value_t Mem_highest_pc; // End address of used memory plus one + + +// Prototypes +extern void Outputfile_init(void); +extern void Output_init(signed long fill_value); +extern void Output_8b(value_t); +extern void Output_16b(value_t); +extern void Output_24b(value_t); +extern void Output_32b(value_t); +extern void Output_byte(value_t); +extern bool Output_set_output_format(void); +extern void Output_save_file(FILE* fd, value_t, value_t); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/platform.c b/Platform/Apple/tools/ACME/src/platform.c new file mode 100644 index 00000000..f2552fb9 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/platform.c @@ -0,0 +1,39 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff + +#include "config.h" +#include "platform.h" + + +// Amiga +#ifdef _AMIGA +#ifndef platform_C +#define platform_C +// Nothing here - Amigas don't need no stinkin' platform-specific stuff! +#endif +#endif + +// DOS and OS/2 +#ifdef __DJGPP__ +#include "_dos.c" +#endif +#ifdef __OS2__ +#include "_dos.c" +#endif +//#ifdef __Windows__ +//#include "_dos.c" +//#endif + +// RISC OS +#ifdef __riscos__ +#include "_riscos.c" +#endif + +// add further platform files here + +// Unix/Linux/others (surprisingly also works on Windows) +#include "_std.c" diff --git a/Platform/Apple/tools/ACME/src/platform.h b/Platform/Apple/tools/ACME/src/platform.h new file mode 100644 index 00000000..792f0fd1 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/platform.h @@ -0,0 +1,41 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Platform specific stuff + + +// Amiga +#ifdef _AMIGA +#define PLATFORM_VERSION "Ported to AmigaOS by Christoph Mammitzsch." +#include "_amiga.h" +#endif + +// DOS and OS/2 +#ifdef __DJGPP__ +#define PLATFORM_VERSION "Ported to DOS by Marco Baye." +#include "_dos.h" +#endif +#ifdef __OS2__ +#define PLATFORM_VERSION "Ported to OS/2 by Malte Eckhardt." +#include "_dos.h" +#endif +//#ifdef __Windows__ +//#define PLATFORM_VERSION "Ported to Windows by ?" +//#include "_dos.h" +//#endif + +// RISC OS +#ifdef __riscos__ +#define PLATFORM_VERSION "Ported to RISC OS by Marco Baye." +#include "_riscos.h" +#endif + +// add further platform files here + +// Unix/Linux/others (surprisingly also works on Windows) +#ifndef PLATFORM_VERSION +#define PLATFORM_VERSION "Platform independent version.\nCurrent maintainer Krzysztof Dabrowski aka BruSH/ElysiuM" +#endif +#include "_std.h" diff --git a/Platform/Apple/tools/ACME/src/section.c b/Platform/Apple/tools/ACME/src/section.c new file mode 100644 index 00000000..ac829e0a --- /dev/null +++ b/Platform/Apple/tools/ACME/src/section.c @@ -0,0 +1,117 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Section stuff + +#include "config.h" +#include "dynabuf.h" +#include "global.h" +#include "input.h" +#include "tree.h" +#include "section.h" + + +// Constants +static const char type_zone[] = "Zone"; +static const char s_subzone[] = "subzone"; +#define s_zone (&s_subzone[3]) // Yes, I know I'm sick +static char untitled[] = ""; +// ...is actually constant, but flagging it "const" results in heap of warnings + +// fake section structure (for error msgs before any real section is in use) +static section_t initial_section = { + 0, // zone value + "during", // "type" => normally "zone Title" or + "init", // "title" => "macro test", now "during init" + FALSE, // no, title was not malloc'd +}; + + +// Variables +section_t* Section_now = &initial_section;// current section +static section_t outer_section; // outermost section struct +static zone_t zone_max; // Highest zone number yet + +// Write given info into given zone structure and activate it +void Section_new_zone(section_t* section, const char* type, char* title, bool allocated) { + section->zone = ++zone_max; + section->type = type; + section->title = title; + section->allocated = allocated; + // activate new section + Section_now = section; +} + +// Tidy up: If necessary, release section title. +// Warning - the state of the component "Allocd" may have +// changed in the meantime, so don't rely on a local variable. +void Section_finalize(section_t* section) { + if(section->allocated) + free(section->title); +} + +// Switch to new zone ("!zone" or "!zn"). Has to be re-entrant. +static enum eos_t PO_zone(void) { + section_t entry_values;// buffer for outer zone + char* new_title; + bool allocated; + + // remember everything about current structure + entry_values = *Section_now; + // set default values in case there is no valid title + new_title = untitled; + allocated = FALSE; + // Check whether a zone title is given. If yes and it can be read, + // get copy, remember pointer and remember to free it later on. + if(BYTEFLAGS(GotByte) & CONTS_KEYWORD) { + // Because we know of one character for sure, + // there's no need to check the return value. + Input_read_keyword(); + new_title = DynaBuf_get_copy(GlobalDynaBuf); + allocated = TRUE; + } + // setup new section + // section type is "subzone", just in case a block follows + Section_new_zone(Section_now, "Subzone", new_title, allocated); + if(Parse_optional_block()) { + // Block has been parsed, so it was a SUBzone. + Section_finalize(Section_now);// end inner zone + *Section_now = entry_values;// restore entry values + } else { + // no block found, so it's a normal zone change + Section_finalize(&entry_values);// end outer zone + Section_now->type = type_zone;// change type to "zone" + } + return(ENSURE_EOS); +} + +// Start subzone ("!subzone" or "!sz"). Has to be re-entrant. +static enum eos_t PO_subzone(void) { + // output deprecation warning + if(pass_flags & PASS_ISFIRST) + Throw_warning("\"!subzone {}\" is deprecated; use \"!zone {}\" instead."); + // call "!zone" instead + return(PO_zone()); +} + +// predefined stuff +static node_t pseudo_opcodes[] = { + PREDEFNODE(s_zone, PO_zone), + PREDEFNODE("zn", PO_zone), + PREDEFNODE(s_subzone, PO_subzone), + PREDEFLAST("sz", PO_subzone), + // ^^^^ this marks the last element +}; + +// Init (add to tree and register function to call at start of each pass) +void Section_init(void) { + Tree_add_table(&pseudo_opcode_tree, pseudo_opcodes); +} + +// Setup outermost section (called before each pass) +void Section_passinit(void) { + zone_max = ZONE_GLOBAL;// will be incremented by next line + Section_new_zone(&outer_section, type_zone, untitled, FALSE); +} diff --git a/Platform/Apple/tools/ACME/src/section.h b/Platform/Apple/tools/ACME/src/section.h new file mode 100644 index 00000000..37af3bc0 --- /dev/null +++ b/Platform/Apple/tools/ACME/src/section.h @@ -0,0 +1,37 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Section stuff +#ifndef section_H +#define section_H + +#include "config.h" + + +// "section" structure type definition +typedef struct { + zone_t zone; // current zone value + const char* type; // "Zone", "Subzone" or "Macro" + char* title; // zone title, subzone title or macro title + int allocated; // whether title was malloc()'d +} section_t; + + +// Constants +#define ZONE_GLOBAL 0 // Number of "global zone" + + +// Variables +extern section_t *Section_now; // current section structure + + +// Prototypes +extern void Section_new_zone(section_t*, const char* type, char* title, bool allocated); +extern void Section_init(void); +extern void Section_passinit(void); +extern void Section_finalize(section_t* section); + + +#endif diff --git a/Platform/Apple/tools/ACME/src/tree.c b/Platform/Apple/tools/ACME/src/tree.c new file mode 100644 index 00000000..6c5e9c8b --- /dev/null +++ b/Platform/Apple/tools/ACME/src/tree.c @@ -0,0 +1,198 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Tree stuff + +#include "config.h" +#include "dynabuf.h" +#include "global.h" +#include "tree.h" +#include "platform.h" + + +// Functions + +// Compute hash value by exclusive ORing the node's ID string and write +// output to struct. +// This routine is not allowed to change GlobalDynaBuf! +hash_t make_hash(node_t* node) { + register char byte; + register const char* read; + register hash_t tmp = 0; + + read = node->id_string; + while((byte = *read++)) + tmp = ((tmp << 7) | (tmp >> (8 * sizeof(hash_t) - 7))) ^ byte; + node->hash_value = tmp; + return(tmp); +} + +// Link a predefined data set to a tree +void add_node_to_tree(node_t** tree, node_t* node_to_add) { + hash_t hash; + + // compute hash value + hash = make_hash(node_to_add); + while(*tree) { + // compare HashValue + if(hash > (*tree)->hash_value) + tree = &((*tree)->greater_than); + else + tree = &((*tree)->less_than_or_equal); + } + *tree = node_to_add;// add new leaf to tree +// New nodes are always added as leaves, so there's no need to copy a second +// pointer. And because the PREDEF* macros contain NULL as init values, it is +// not necessary to clear the new node's greater_than and less_than_or_equal +// fields. +} + +// Add predefined tree items to given tree. The PREDEF* macros set HashValue +// to 1 in all entries but the last. The last entry contains 0. +void Tree_add_table(node_t** tree, node_t* table_to_add) { + // Caution when trying to optimise this. :) + while(table_to_add->hash_value) + add_node_to_tree(tree, table_to_add++); + add_node_to_tree(tree, table_to_add); +} + +// Search for a given ID string in a given tree. +// Compute the hash of the given string and then use that to try to find a +// tree item that matches the given data (HashValue and DynaBuf-String). +// Store "Body" component in NodeBody and return TRUE. +// Return FALSE if no matching item found. +bool Tree_easy_scan(node_t* tree, void** node_body, struct dynabuf_t* dyna_buf) { + node_t wanted;// temporary storage + const char *p1, + *p2; + char b1, + b2; + hash_t hash; + + wanted.id_string = dyna_buf->buffer; + hash = make_hash(&wanted); + while(tree) { + // compare HashValue + if(hash > tree->hash_value) { + // wanted hash is bigger than current, so go + // to tree branch with bigger hashes + tree = tree->greater_than; + continue; + } + if(hash == tree->hash_value) { + p1 = wanted.id_string; + p2 = tree->id_string; + do { + b1 = *p1++; + b2 = *p2++; + } while((b1 == b2) && b1); + if(b1 == b2) { + // store body data + *node_body = tree->body; + return(TRUE); + } + } + // either the wanted hash is smaller or + // it was exact but didn't match + tree = tree->less_than_or_equal; + } + return(FALSE); // indicate failure +} + +// Search for a "RAM tree" item. Compute the hash of string in GlobalDynaBuf +// and then use that to try to find a tree item that matches the given data +// (HashValue, ID_Number, GlobalDynaBuf-String). Save pointer to found tree +// item in given location. +// If no matching item is found, check the "Create" flag. If it is set, create +// a new tree item, link to tree, fill with data and store its pointer. If the +// "Create" flag is clear, store NULL as result. +// Returns whether item was created. +bool Tree_hard_scan(node_ra_t** result, node_ra_t** forest, int id_number, bool create) { + node_t wanted; // temporary storage + node_ra_t **current_node; + node_ra_t *new_leaf_node; + const char *p1, + *p2; + char b1, + b2; + hash_t byte_hash; + + wanted.id_string = GLOBALDYNABUF_CURRENT; + // incorporate ID number into hash value + byte_hash = make_hash(&wanted) ^ id_number; + wanted.hash_value = byte_hash;// correct struct's hash + PLATFORM_INT2BYTE(byte_hash);// transform into byte + + current_node = &(forest[byte_hash]);// point into table + while(*current_node) { + // compare HashValue + if(wanted.hash_value > (*current_node)->hash_value) { + // wanted hash is bigger than current, so go + // to tree branch with bigger hashes + current_node = &((*current_node)->greater_than); + continue; + } + if(wanted.hash_value == (*current_node)->hash_value) { + if(id_number == (*current_node)->id_number) { + p1 = wanted.id_string; + p2 = (*current_node)->id_string; + do { + b1 = *p1++; + b2 = *p2++; + } while((b1 == b2) && b1); + if(b1 == b2) { + // store node pointer + *result = *current_node; + // return FALSE because node + // was not created + return(FALSE); + } + } + } + // either the wanted hash is smaller or + // it was exact but didn't match + current_node = &((*current_node)->less_than_or_equal); + } + // node wasn't found. Check whether to create it + if(create == FALSE) { + *result = NULL;// indicate failure + return(FALSE);// return FALSE because node was not created + } + // create new node + new_leaf_node = safe_malloc(sizeof(node_ra_t)); + new_leaf_node->greater_than = NULL; + new_leaf_node->less_than_or_equal = NULL; + new_leaf_node->hash_value = wanted.hash_value; + new_leaf_node->id_number = id_number; + new_leaf_node->id_string = DynaBuf_get_copy(GlobalDynaBuf);// make permanent copy + // add new leaf to tree + *current_node = new_leaf_node; + // store pointer to new node in result location + *result = new_leaf_node; + return(TRUE);// return TRUE because node was created +} + +// Call given function for each object of matching type in the given tree. +// Calls itself recursively. +void dump_tree(node_ra_t* node, int id_number, void (*fn)(node_ra_t*, FILE*), FILE* env) { + + if(node->id_number == id_number) + fn(node, env); + if(node->greater_than) + dump_tree(node->greater_than, id_number, fn, env); + if(node->less_than_or_equal) + dump_tree(node->less_than_or_equal, id_number, fn, env); +} + +// Calls Tree_dump_tree for each non-zero entry of the given tree table. +void Tree_dump_forest(node_ra_t** forest, int id_number, void (*fn)(node_ra_t*, FILE*), FILE* env) { + int i; + + for(i = 255; i >= 0; i--) { + if(*forest) + dump_tree(*forest, id_number, fn, env); + forest++; + } +} diff --git a/Platform/Apple/tools/ACME/src/tree.h b/Platform/Apple/tools/ACME/src/tree.h new file mode 100644 index 00000000..e8efa55f --- /dev/null +++ b/Platform/Apple/tools/ACME/src/tree.h @@ -0,0 +1,50 @@ +// +// ACME - a crossassembler for producing 6502/65c02/65816 code. +// Copyright (C) 1998-2006 Marco Baye +// Have a look at "acme.c" for further info +// +// Tree stuff +#ifndef tree_H +#define tree_H + +#include "config.h" + + +// Macros +#define PREDEFNODE(s, v) {NULL, NULL, 1, s, (void*) (v)} +#define PREDEFLAST(s, v) {NULL, NULL, 0, s, (void*) (v)} + + +// type definitions + +typedef unsigned int hash_t; +// Must be unsigned, otherwise the hash algorithm won't be very useful! + +// tree node structure type definition (for easy lookups) +struct node_t { + node_t* greater_than; // pointer to sub-tree + node_t* less_than_or_equal;// pointer to sub-tree + hash_t hash_value; + const char* id_string; // name, zero-terminated + void* body; // bytes, handles or handler function +}; + +// tree node structure type definition (for macros/labels) +struct node_ra_t { + node_ra_t* greater_than; // pointer to sub-tree + node_ra_t* less_than_or_equal;// pointer to sub-tree + hash_t hash_value; + char* id_string; // name, zero-terminated + void* body; // macro/label body + unsigned int id_number; // zone number +}; + + +// Prototypes +extern void Tree_add_table(node_t** tree, node_t* table_to_add); +extern bool Tree_easy_scan(node_t* tree, void** node_body, struct dynabuf_t* dyna_buf); +extern bool Tree_hard_scan(node_ra_t**, node_ra_t**, int, bool); +extern void Tree_dump_forest(node_ra_t**, int, void (*)(node_ra_t*, FILE*), FILE*); + + +#endif diff --git a/Platform/Apple/tools/PLASMA/src/codegen.h b/Platform/Apple/tools/PLASMA/src/codegen.h index 9046ddae..14ff6a38 100755 --- a/Platform/Apple/tools/PLASMA/src/codegen.h +++ b/Platform/Apple/tools/PLASMA/src/codegen.h @@ -26,6 +26,7 @@ void emit_def(char *name, int is_bytecode); int emit_data(int vartype, int consttype, long constval, int constsize); void emit_codetag(int tag); void emit_const(int cval); +void emit_conststr(long conststr, int strsize); void emit_lb(void); void emit_lw(void); void emit_llb(int index); diff --git a/Platform/Apple/tools/PLASMA/src/lex.c b/Platform/Apple/tools/PLASMA/src/lex.c index 938c3997..39390c10 100755 --- a/Platform/Apple/tools/PLASMA/src/lex.c +++ b/Platform/Apple/tools/PLASMA/src/lex.c @@ -75,6 +75,18 @@ void parse_error(char *errormsg) fprintf(stderr, "^\nError: %s\n", errormsg); exit(1); } + +int hexdigit(char ch) +{ + ch = toupper(ch); + if (ch >= '0' && ch <= '9') + return ch - '0'; + else if (ch >= 'A' && ch <= 'F') + return ch - 'A' + 10; + else + return -1; +} + t_token scan(void) { prevtoken = scantoken; @@ -152,12 +164,8 @@ t_token scan(void) constval = 0; while (scanpos++) { - if (*scanpos >= '0' && *scanpos <= '9') - constval = constval * 16 + *scanpos - '0'; - else if (*scanpos >= 'A' && *scanpos <= 'F') - constval = constval * 16 + *scanpos - 'A' + 10; - else if (*scanpos >= 'a' && *scanpos <= 'f') - constval = constval * 16 + *scanpos - 'a' + 10; + if (hexdigit(*scanpos) >= 0) + constval = constval * 16 + hexdigit(*scanpos); else break; } @@ -216,6 +224,7 @@ t_token scan(void) else if (scanpos[0] == '\"') { char *scanshift; + int scanoffset; /* * String constant. */ @@ -225,6 +234,7 @@ t_token scan(void) { if (*scanpos == '\\') { + scanoffset = 1; switch (scanpos[1]) { case 'n': @@ -248,12 +258,20 @@ t_token scan(void) case '0': *scanpos = '\0'; break; + case '$': + if (hexdigit(scanpos[2]) < 0 || hexdigit(scanpos[3]) < 0) { + parse_error("Bad string constant"); + return (-1); + } + *scanpos = hexdigit(scanpos[2]) * 16 + hexdigit(scanpos[3]); + scanoffset = 3; + break; default: parse_error("Bad string constant"); return (-1); } for (scanshift = scanpos + 1; *scanshift; scanshift++) - scanshift[0] = scanshift[1]; + scanshift[0] = scanshift[scanoffset]; } scanpos++; } diff --git a/Platform/Apple/tools/PLASMA/src/makefile b/Platform/Apple/tools/PLASMA/src/makefile index 5857a62c..89a02ec4 100755 --- a/Platform/Apple/tools/PLASMA/src/makefile +++ b/Platform/Apple/tools/PLASMA/src/makefile @@ -5,8 +5,9 @@ PLVM = plvm PLVM02 = PLVM02.SYSTEM.sys#2000 CMD = CMD\#FF2000 PLASM = plasm +JAR = $(PLASM).jar INCS = tokens.h symbols.h lex.h parse.h codegen.h -OBJS = plasm.c parse.o lex.o codegen.o +SRCS = plasm.c parse.c lex.c codegen.c # # Image filetypes for Virtual ][ # @@ -22,13 +23,18 @@ TXTTYPE = .TXT #SYSTYPE = \#ff0000 #TXTTYPE = \#040000 -all: $(PLASM) $(PLVM) $(PLVM02) $(CMD) +all: $(PLASM) $(JAR) $(PLVM) $(PLVM02) $(CMD) clean: -rm *.o *~ *.a *FE1000 $(PLVM02) $(CMD) $(PLASM) $(PLVM) -$(PLASM): $(OBJS) $(INCS) - cc $(OBJS) -o $(PLASM) +jar: $(JAR) + +$(PLASM): $(SRCS) $(INCS) + cc $(SRCS) -o $(PLASM) + +$(JAR): $(SRCS) $(INCS) + docker run -t -v "$(shell pwd):/project" mhaye/nestedvm:v4 /bin/bash -c "cd /project && nestedvm-c2jar $(JAR) plasma.Plasma $(SRCS)" $(PLVM): plvm.c cc plvm.c -o $(PLVM) diff --git a/Platform/Apple/tools/PLASMA/src/plasm.jar b/Platform/Apple/tools/PLASMA/src/plasm.jar new file mode 100644 index 00000000..e06f418b Binary files /dev/null and b/Platform/Apple/tools/PLASMA/src/plasm.jar differ diff --git a/Platform/Apple/tools/PLASMA/src/test.pla b/Platform/Apple/tools/PLASMA/src/test.pla index 6a521338..7a74d8e6 100755 --- a/Platform/Apple/tools/PLASMA/src/test.pla +++ b/Platform/Apple/tools/PLASMA/src/test.pla @@ -125,4 +125,5 @@ puti(mystruc) putln puts(@constr); puti(constval); putln puts("Hello from in-line string!\n") +puts("Hi \$41pple.\n"); done diff --git a/Platform/Apple/tools/PackPartitions/nbproject/project.properties b/Platform/Apple/tools/PackPartitions/nbproject/project.properties index 4f9befbe..ce4de102 100644 --- a/Platform/Apple/tools/PackPartitions/nbproject/project.properties +++ b/Platform/Apple/tools/PackPartitions/nbproject/project.properties @@ -27,12 +27,20 @@ dist.jar=${dist.dir}/PackPartitions.jar dist.javadoc.dir=${dist.dir}/javadoc endorsed.classpath= excludes= +file.reference.A2Copy.jar=../A2Copy/dist/A2Copy.jar +file.reference.ac.jar=../A2Copy/dist/lib/ac.jar +file.reference.acme.jar=../ACME/src/acme.jar file.reference.lz4-1.1.1.jar=/Users/mhaye/LL/repo/Platform/Apple/tools/PackPartitions/lib/lz4-1.1.1.jar +file.reference.plasm.jar=../PLASMA/src/plasm.jar includes=** jar.compress=false javac.classpath=\ ${file.reference.lz4-1.1.1.jar}:\ - ${libs.groovy-all.classpath} + ${libs.groovy-all.classpath}:\ + ${file.reference.plasm.jar}:\ + ${file.reference.acme.jar}:\ + ${file.reference.A2Copy.jar}:\ + ${file.reference.ac.jar} # Space-separated list of extra javac options javac.compilerargs= javac.deprecation=false diff --git a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy index bb37bb1e..3891a962 100644 --- a/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy +++ b/Platform/Apple/tools/PackPartitions/src/org/demo/PackPartitions.groovy @@ -18,6 +18,11 @@ package org.demo import java.nio.ByteBuffer import java.nio.channels.Channels import net.jpountz.lz4.LZ4Factory +import java.nio.charset.StandardCharsets +import java.nio.file.Files +import java.util.zip.GZIPInputStream +import java.security.MessageDigest +import javax.xml.bind.DatatypeConverter /** * @@ -40,6 +45,7 @@ class PackPartitions def TYPE_PORTRAIT = 11 def mapNames = [:] // map name (and short name also) to map.2dor3d, map.num + def sysCode = [:] // memory manager def code = [:] // code name to code.num, code.buf def maps2D = [:] // map name to map.num, map.buf def maps3D = [:] // map name to map.num, map.buf @@ -59,11 +65,12 @@ class PackPartitions def debugCompression = false - def javascriptOut = null - def currentContext = [] def nWarnings = 0 + def binaryStubsOnly = false + def cache = [:] + /** * Keep track of context within the XML file, so we can spit out more useful * error and warning messages. @@ -116,6 +123,14 @@ class PackPartitions } } + def calcImageHash(imgEl) + { + def data = imgEl.displayData?.find { it.@platform == "AppleII" } + assert data : "image '${imgEl.@name}' missing AppleII platform data" + byte[] bytes = MessageDigest.getInstance("MD5").digest(data.toString().getBytes()) + return DatatypeConverter.printHexBinary(bytes) + } + def pixelize(dataEl) { def nBytes = dataEl.@width as int @@ -524,54 +539,6 @@ class PackPartitions } } - /** - * Dump map data to Javascript code, to help in debugging the raycaster. This way, - * the Javascript version can run the same map, and we can compare its results to - * the 6502 results. - */ - def dumpJsMap(rows, texMap) - { - def width = rows[0].size()+2 - - // Write the map data. First comes the sentinel row. - javascriptOut.println("var map = [") - javascriptOut.print(" [") - (0.. - javascriptOut.print(" [-1,") - row.each { tile -> - def b = texMap[tile?.@id] - if ((b & 0x80) == 0) - javascriptOut.format("%2d,", b) - else - javascriptOut.print(" 0,") - } - javascriptOut.println("-1,],") - } - - // Finish the map data with another sentinel row - javascriptOut.print(" [") - (0.. - row.eachWithIndex { tile, x -> - def b = texMap[tile?.@id] - if ((b & 0x80) != 0) { - // y+1 below to account for initial sentinel row - javascriptOut.format(" {type:%2d, x:%2d.5, y:%2d.5},\n", b & 0x7f, x, y+1) - } - } - } - javascriptOut.println("];\n") - } - def write3DMap(buf, mapName, rows, scriptModule, locationsWithTriggers) // [ref BigBlue1_50] { def width = rows[0].size() + 2 // Sentinel $FF at start and end of each row @@ -639,9 +606,6 @@ class PackPartitions // Sentinel row of $FF at end of map (0.. + def num = addTo.size() + 1 + addTo[ent.name] = [num: ent.num, buf: wrapByteArray(ent.data)] + } + return true + } + return false + } + + def addEntireToCache(kind, addTo, hash) + { + def ents = [] + addTo.each { name, ent -> + def buf = ent.buf + def uncompressedLen = buf.position() + def uncompressedData = new byte[uncompressedLen] + buf.position(0) + buf.get(uncompressedData) + ents << [name:name, num:ent.num, data:uncompressedData] + } + cache[kind] = [hash:hash, ents:ents] + } def packTexture(imgEl) { - def num = textures.size() + 1 - def name = imgEl.@name ?: "img$num" - //println "Packing texture #$num named '${imgEl.@name}'." - def pixels = parseTexture(imgEl) - calcTransparency(pixels) - def buf = ByteBuffer.allocate(50000) - writeTexture(buf, pixels) - textures[stripName(imgEl.@name)] = [num:num, buf:buf] + def name = stripName(imgEl.@name) + def hash = calcImageHash(imgEl) + if (!grabFromCache("texture", textures, name, hash)) { + def pixels = parseTexture(imgEl) + calcTransparency(pixels) + def buf = ByteBuffer.allocate(50000) + writeTexture(buf, pixels) + addToCache("texture", textures, name, hash, buf) + } } def packFrameImage(imgEl) { - def num = frames.size() + 1 def name = imgEl.@name ?: "img$num" - //println "Packing frame image #$num named '${imgEl.@name}'." - def buf = parseFrameData(imgEl) - frames[imgEl.@name] = [num:num, buf:buf] + def hash = calcImageHash(imgEl) + if (!grabFromCache("frame", frames, name, hash)) + addToCache("frame", frames, name, hash, parseFrameData(imgEl)) } def packPortrait(imgEl) @@ -905,7 +920,9 @@ class PackPartitions { def inBuf = new byte[256] def outBuf = ByteBuffer.allocate(50000) - def stream = new File(path).withInputStream { stream -> + if (binaryStubsOnly) + return outBuf + new File(path).withInputStream { stream -> while (true) { def got = stream.read(inBuf) if (got < 0) break @@ -927,6 +944,10 @@ class PackPartitions def num = modules.size() + 1 //println "Reading module #$num from '$path'." def bufObj = readBinary(path) + if (binaryStubsOnly) { + modules[name] = [num:num, buf:bufObj] + return + } def bufLen = bufObj.position() def buf = new byte[bufLen] @@ -1044,9 +1065,7 @@ class PackPartitions } newFixup.add((byte)0xFF) - modules[name] = [num:num, buf:wrapByteArray(newAsmCode)] - bytecodes[name] = [num:num, buf:wrapByteList(byteCode)] - fixups[name] = [num:num, buf:wrapByteList(newFixup)] + return [wrapByteArray(newAsmCode), wrapByteList(byteCode), wrapByteList(newFixup)] } def wrapByteArray(array) { @@ -1258,34 +1277,184 @@ class PackPartitions stream.write(it.buf.data, 0, it.buf.len) } } + + def runNestedvm(programClass, programName, args, inDir, inFile, outFile) + { + def prevStdin = System.in + def prevStdout = System.out + def prevUserDir = System.getProperty("user.dir") + def result + try + { + System.setProperty("user.dir", new File(inDir).getAbsolutePath()) + if (inFile) { + inFile.withInputStream { inStream -> + System.in = inStream + outFile.withOutputStream { outStream -> + System.out = new PrintStream(outStream) + result = programClass.newInstance().run(args) + } + } + } + else { + result = programClass.newInstance().run(args) + } + } finally { + System.in = prevStdin + System.out = prevStdout + System.setProperty("user.dir", prevUserDir) + } + if (result != 0) + throw new Exception("$programName failed with code $result") + } + + def getCodeDeps(codeFile) + { + def baseDir = codeFile.getParentFile() + + // If we've cached deps for this file, just return that. + def key = "codeDeps:" + codeFile.toString() + def hash = codeFile.lastModified() + def deps = [] + if (cache.containsKey(key) && cache[key].hash == hash) + deps = cache[key].deps + else { + codeFile.eachLine { line -> + def m = line =~ /^\s*include\s+"([^"]+)"\s*$/ + if (m) + deps << new File(baseDir, m.group(1)) + m = line =~ /\s*!source "([^"]+)"\s*$/ + if (m) { + if (codeFile ==~ /.*\.pla$/) { + // Asm includes inside a plasma file have an extra level of ".." + // because they end up getting processed within the "build" dir. + deps << new File(new File(baseDir, "build"), m.group(1)).getCanonicalFile() + } + else + deps << new File(baseDir, m.group(1)).getCanonicalFile() + } + } + cache[key] = [hash:hash, deps:deps] + } + + // Recursively calc deps of the deps + deps.each { dep -> getCodeDeps(dep) } + + // And return everything we found. + return deps + } + + def getLastDep(codeFile) + { + codeFile = codeFile.getCanonicalFile() + def time = codeFile.lastModified() + getCodeDeps(codeFile).each { dep -> + time = Math.max(time, getLastDep(dep)) + } + return time + } + + def assembleCode(codeName, inDir) + { + if (binaryStubsOnly) + return addToCache("code", code, codeName, 1, ByteBuffer.allocate(1)) + + def hash = getLastDep(new File(inDir, codeName + ".s")) + if (grabFromCache("code", code, codeName, hash)) + return + + println "Assembling ${codeName}.s" + new File(inDir + "build").mkdir() + String[] args = ["acme", "-f", "plain", + "-o", "build/" + codeName + ".b", + codeName + ".s"] + runNestedvm(acme.Acme.class, "ACME assembler", args, inDir, null, null) + addToCache("code", code, codeName, hash, readBinary(inDir + "build/" + codeName + ".b")) + } + + def assembleCore(inDir) + { + if (binaryStubsOnly) + return addToCache("sysCode", sysCode, "mem", 1, ByteBuffer.allocate(1)) + + def hash = getLastDep(new File(inDir, "mem.s")) + if (grabFromCache("sysCode", sysCode, "mem", hash)) + return + + println "Assembling mem.s" + new File(inDir + "build").mkdir() + String[] args = ["acme", "-o", "build/cmd.sys#2000", "mem.s"] + runNestedvm(acme.Acme.class, "ACME assembler", args, inDir, null, null) + addToCache("sysCode", sysCode, "mem", hash, readBinary(inDir + "build/cmd.sys#2000")) + } + + def compileModule(moduleName, codeDir) + { + if (binaryStubsOnly) + return addToCache("modules", modules, moduleName, 1, ByteBuffer.allocate(1)) + + def hash = getLastDep(new File(codeDir + moduleName + ".pla")) + if (grabFromCache("modules", modules, moduleName, hash) && + grabFromCache("bytecodes", bytecodes, moduleName, hash) && + grabFromCache("fixups", fixups, moduleName, hash)) + { + return + } + + println "Compiling ${moduleName}.pla" + String[] args = ["plasm", "-AM"] + new File(codeDir + "build").mkdir() + runNestedvm(plasma.Plasma.class, "PLASMA compiler", args, codeDir, + new File(codeDir + moduleName + ".pla"), + new File(codeDir + "build/" + moduleName + ".a")) + + args = ["acme", "--setpc", "4096", + "-o", moduleName + ".b", + moduleName + ".a"] + runNestedvm(acme.Acme.class, "ACME assembler", args, codeDir + "build/", null, null) + def module, bytecode, fixup + (module, bytecode, fixup) = readModule(moduleName, codeDir + "build/" + moduleName + ".b") + addToCache("modules", modules, moduleName, hash, module) + addToCache("bytecodes", bytecodes, moduleName, hash, bytecode) + addToCache("fixups", fixups, moduleName, hash, fixup) + } def readAllCode() { - readCode("render", "src/raycast/build/render.b") - readCode("expand", "src/raycast/build/expand.b") - readCode("fontEngine", "src/font/build/fontEngine.b") - readCode("tileEngine", "src/tile/build/tile.b") + assembleCore("src/core/") + + assembleCode("render", "src/raycast/") + assembleCode("expand", "src/raycast/") + assembleCode("fontEngine", "src/font/") + assembleCode("tileEngine", "src/tile/") - readModule("gameloop", "src/plasma/build/gameloop.b") - readModule("globalScripts", "src/plasma/build/globalScripts.b") - readModule("combat", "src/plasma/build/combat.b") - readModule("gen_enemies", "src/plasma/build/gen_enemies.b") + compileModule("gameloop", "src/plasma/") + compileModule("globalScripts", "src/plasma/") + compileModule("combat", "src/plasma/") + compileModule("gen_enemies", "src/plasma/") } - def pack(xmlPath, binPath, javascriptPath) + def pack(xmlPath) { + // Save time by using cache of previous run + File cacheFile = new File(xmlPath.toString()+".cache") + if (cacheFile.exists()) { + ObjectInputStream out = new ObjectInputStream(new FileInputStream(cacheFile)); + cache = out.readObject(); + out.close() + } + // Read in code chunks. For now these are hard coded, but I guess they ought to // be configured in a config file somewhere...? // - println "Reading code resources." readAllCode() // We have only one font, for now at least. - println "Reading fonts." readFont("font", "data/fonts/font.bin") // Open the XML data file produced by Outlaw Editor def dataIn = new XmlParser().parse(xmlPath) + def xmlLastMod = xmlPath.lastModified() // Pre-pack the data for each tile println "Packing tiles." @@ -1296,66 +1465,52 @@ class PackPartitions // Pack the global tile set before other tile sets (contains the player avatar, etc.) packGlobalTileSet(dataIn) - // Pack each image, which has the side-effect of filling in the - // image name map. Handle frame images separately. - // - def titleFound = false - def uiFramesFound = 0 - def imageNamesPacked = [:] - for (def pass = 0; pass < 5; pass++) - { - switch (pass) { - case 0: println "Packing title screen."; break - case 1: println "Packing UI frames."; break - case 2: println "Packing other frame images."; break - case 3: println "Packing textures."; break - case 4: println "Packing portraits."; break - } - dataIn.image.sort{it.@name.toLowerCase()}.each { image -> - def category = image.@category?.toLowerCase() - def name = image.@name.toLowerCase() - if (category == "fullscreen" && name == "title") { - if (pass == 0) { - packFrameImage(image) - titleFound = true - imageNamesPacked[name] = true - } - } - else if (category == "uiframe") { - if (pass == 1) { - packFrameImage(image) - ++uiFramesFound - imageNamesPacked[name] = true - } - } - else if (category == "fullscreen") { - if (pass == 2) { - packFrameImage(image) - imageNamesPacked[name] = true - } - } - else if (category == "wall" || category == "sprite") { - if (pass == 3) { - packTexture(image) - imageNamesPacked[name] = true - } - } - else if (category == "portrait") { - if (pass == 4) { - packPortrait(image) - imageNamesPacked[name] = true - } - } - } - } - assert titleFound : "Couldn't find title image. Should be category='FULLSCREEN', name='title'" - assert uiFramesFound == 2 : "Need exactly 2 UI frames, found $uiFramesFound instead." + // Divvy up the images by category + def titleImgs = [] + def uiFrameImgs = [] + def fullscreenImgs = [] + def textureImgs = [] + def portraitImgs = [] - dataIn.image.each { image -> - if (!(image.@name.toLowerCase() in imageNamesPacked)) - println "Warning: couldn't classify image named '${image.@name}', category '${image.@category}'." + dataIn.image.sort{it.@name.toLowerCase()}.each { image -> + def category = image.@category?.toLowerCase() + def name = image.@name.toLowerCase() + if (category == "fullscreen" && name == "title") + titleImgs << image + else if (category == "uiframe") + uiFrameImgs << image + else if (category == "fullscreen") + fullscreenImgs << image + else if (category == "wall" || category == "sprite") + textureImgs << image + else if (category == "portrait") + portraitImgs << image + else + println "Warning: couldn't classify image named '${name}', category '${category}'." } - + + assert titleImgs.size() == 1 : "Couldn't find title image. Should be category='FULLSCREEN', name='title'" + assert uiFrameImgs.size() == 2 : "Need exactly 2 UI frames, found ${uiFramesImgs.size()} instead." + + // Pack each image, which has the side-effect of filling in the image name map. + println "Packing title screen." + titleImgs.each { image -> packFrameImage(image) } + + println "Packing UI frames." + uiFrameImgs.each { image -> packFrameImage(image) } + + println "Packing other frame images." + fullscreenImgs.each { image -> packFrameImage(image) } + + println "Packing textures." + textureImgs.each { image -> packTexture(image) } + + println "Packing portraits." + if (!grabEntireFromCache("portraits", portraits, xmlLastMod)) { + portraitImgs.each { image -> packPortrait(image) } + addEntireToCache("portraits", portraits, xmlLastMod) + } + // Number all the maps and record them with names def num2D = 0 def num3D = 0 @@ -1387,15 +1542,13 @@ class PackPartitions printWarning "map name '${map?.@name}' should contain '2D' or '3D'. Skipping." } - // Ready to start writing the output file. + // Ready to write the output file. println "Writing output file." + new File("build/root").mkdir() + def binPath = new File("build/root/game.part.0.bin").path new File(binPath).withOutputStream { stream -> writePartition(stream) } - // Finish up Javacript if necessary - if (javascriptPath) - javascriptOut.close() - - // Lastly, print stats + // Print stats println "Compression saved $compressionSavings bytes." if (compressionSavings > 0) { def endSize = new File(binPath).length() @@ -1404,7 +1557,12 @@ class PackPartitions println "Size $origSize -> $endSize ($savPct% savings)" } - println "Done." + // Write a new cache file + File newCacheFile = new File(xmlPath.toString()+".cache.new") + ObjectOutputStream out = new ObjectOutputStream(new FileOutputStream(newCacheFile)); + out.writeObject(cache); + out.close() + newCacheFile.renameTo(cacheFile) } def isAlnum(ch) @@ -1540,13 +1698,31 @@ class PackPartitions out.println("end") } + def replaceIfDiff(oldFile) + { + def newFile = new File(oldFile + ".new") + oldFile = new File(oldFile) + + def newText = newFile.text + def oldText = oldFile.text + + if (newText == oldText) { + //println "Same text, deleting $newFile" + newFile.delete() + } + else { + //println "Changed text, renaming $newFile to $oldFile" + newFile.renameTo(oldFile) + } + } + def dataGen(xmlPath) { // Open the XML data file produced by Outlaw Editor def dataIn = new XmlParser().parse(xmlPath) // Translate image names to constants - new File("src/plasma/gen_images.plh").withWriter { out -> + new File("src/plasma/gen_images.plh.new").withWriter { out -> def portraitNum = 0 dataIn.image.sort{it.@name.toLowerCase()}.each { image -> def category = image.@category?.toLowerCase() @@ -1567,17 +1743,19 @@ class PackPartitions } } } + replaceIfDiff("src/plasma/gen_images.plh") // Translate enemies to code def enemyLines = new File("data/world/enemies.tsv").readLines() - new File("src/plasma/gen_enemies.plh").withWriter { out -> + new File("src/plasma/gen_enemies.plh.new").withWriter { out -> out.println("// Generated code - DO NOT MODIFY BY HAND\n") enemyLines[1..-1].eachWithIndex { line, index -> out.println("const CE${humanNameToSymbol(line.split("\t")[0], false)} = ${index*2}") } out.println("const NUM_ENEMIES = ${enemyLines.size - 1}") } - new File("src/plasma/gen_enemies.pla").withWriter { out -> + replaceIfDiff("src/plasma/gen_enemies.plh") + new File("src/plasma/gen_enemies.pla.new").withWriter { out -> out.println("// Generated code - DO NOT MODIFY BY HAND") out.println() out.println("include \"gamelib.plh\"") @@ -1600,10 +1778,13 @@ class PackPartitions out.println("return @funcTbl") out.println("done") } + replaceIfDiff("src/plasma/gen_enemies.pla") // Produce a list of assembly and PLASMA code segments + binaryStubsOnly = true readAllCode() - new File("src/plasma/gen_modules.plh").withWriter { out -> + binaryStubsOnly = false + new File("src/plasma/gen_modules.plh.new").withWriter { out -> code.each { k, v -> out.println "const CODE_${humanNameToSymbol(k, true)} = ${v.num}" } @@ -1611,6 +1792,23 @@ class PackPartitions out.println "const MODULE_${humanNameToSymbol(k, true)} = ${v.num}" } } + replaceIfDiff("src/plasma/gen_modules.plh") + } + + def createImage() + { + // Copy the PLASMA VM file to the output directory + Files.copy(new File("PLVM02.SYSTEM.sys").toPath(), new File("build/root/PLVM02.SYSTEM.sys").toPath()) + + // Copy the memory manager to the output directory + Files.copy(new File("src/core/build/cmd.sys#2000").toPath(), new File("build/root/cmd.sys#2000").toPath()) + + // Decompress the base image + Files.copy(new GZIPInputStream(new FileInputStream("data/disks/base.2mg.gz")), new File("build/game.2mg").toPath()) + + // Now put the files into the image + String[] args = ["-put", "build/game.2mg", "/", "build/root"] + new a2copy.A2Copy().main(args) } static void main(String[] args) @@ -1632,12 +1830,11 @@ class PackPartitions } // Check the arguments - if (!(args.size() == 2 || args.size() == 3)) { - println "Usage: convert yourOutlawFile.xml game.part.0.bin [intcastMap.js]" - println " (where intcastMap.js is to aid in debugging the Javascript raycaster)" - println " or: convert yourOutlawFile.xml -dataGen" + if (args.size() != 1) { + println "Usage: packPartitions yourOutlawFile.xml" System.exit(1); } + def xmlFile = new File(args[0]) // If there's an existing error file, remote it. def errorFile = new File("pack_error.txt") @@ -1647,10 +1844,20 @@ class PackPartitions // Go for it. def inst = new PackPartitions() try { - if (args[1] == "-dataGen") - inst.dataGen(args[0]) - else - inst.pack(args[0], args[1], args.size() > 2 ? args[2] : null) + // Blow away everything in the build directory, and recreate it + def buildDir = new File("build") + if (buildDir.exists()) + buildDir.deleteDir() + buildDir.mkdirs() + + // Create PLASMA headers + new PackPartitions().dataGen(xmlFile) + + // Pack everything into a binary file + inst.pack(xmlFile) + + // And create the final disk image + inst.createImage() } catch (Throwable t) { errorFile.withWriter { out -> diff --git a/Platform/Apple/virtual/PLVM02.SYSTEM.sys b/Platform/Apple/virtual/PLVM02.SYSTEM.sys new file mode 120000 index 00000000..472679cd --- /dev/null +++ b/Platform/Apple/virtual/PLVM02.SYSTEM.sys @@ -0,0 +1 @@ +../tools/PLASMA/src/PLVM02.SYSTEM.sys \ No newline at end of file diff --git a/Platform/Apple/virtual/build.xml b/Platform/Apple/virtual/build.xml index f4096138..625947c0 100644 --- a/Platform/Apple/virtual/build.xml +++ b/Platform/Apple/virtual/build.xml @@ -29,55 +29,11 @@ - - Generating code from tables. - - - - - - - Building core. - - Building game loop. - - Building raycast. - - Building font engine. - - Building tile engine. - - Packing game and code resources. - - - - - - - - - - - - - - - - - - Adding files to image. - - - - - - - diff --git a/Platform/Apple/virtual/data/disks/base.2mg.bz2 b/Platform/Apple/virtual/data/disks/base.2mg.bz2 deleted file mode 100644 index e4fb3717..00000000 Binary files a/Platform/Apple/virtual/data/disks/base.2mg.bz2 and /dev/null differ diff --git a/Platform/Apple/virtual/data/disks/base.2mg.gz b/Platform/Apple/virtual/data/disks/base.2mg.gz new file mode 100644 index 00000000..6b9e9da5 Binary files /dev/null and b/Platform/Apple/virtual/data/disks/base.2mg.gz differ diff --git a/Platform/Apple/virtual/src/plasma/build.xml b/Platform/Apple/virtual/src/plasma/build.xml index c34caa84..25db98ab 100644 --- a/Platform/Apple/virtual/src/plasma/build.xml +++ b/Platform/Apple/virtual/src/plasma/build.xml @@ -20,6 +20,9 @@ + + + diff --git a/Platform/Apple/virtual/src/tile/tile.s b/Platform/Apple/virtual/src/tile/tileEngine.s similarity index 100% rename from Platform/Apple/virtual/src/tile/tile.s rename to Platform/Apple/virtual/src/tile/tileEngine.s