diff --git a/Makefile b/Makefile index a634c8e..3967af9 100644 --- a/Makefile +++ b/Makefile @@ -1,64 +1,64 @@ -VERSION = 3.1.0 - -prefix = /usr/local -bindir = $(prefix)/bin -mandir = $(prefix)/share/man/man1 - -SEVENZIP = 7z a -mx=9 -bd - -all: xasm xasm.html - -xasm: xasm.d - dmd -of$@ -O -release $< - -xasm.html: xasm.1.asciidoc - asciidoc -o - $< | sed -e "s/527bbd;/20a0a0;/" >$@ - -xasm.1: xasm.1.asciidoc - a2x -f manpage $< - -install: xasm xasm.1 - mkdir -p $(DESTDIR)$(bindir) && install xasm $(DESTDIR)$(bindir)/xasm - mkdir -p $(DESTDIR)$(mandir) && install -m 644 xasm.1 $(DESTDIR)$(mandir)/xasm.1 - -uninstall: - $(RM) $(DESTDIR)$(bindir)/xasm $(DESTDIR)$(mandir)/xasm.1 - -install-scite: xasm.properties - mkdir -p $(DESTDIR)$(prefix)/share/scite && install $< $(DESTDIR)$(prefix)/share/scite/xasm.properties - -uninstall-scite: - $(RM) $(DESTDIR)$(prefix)/share/scite/xasm.properties - -dist: srcdist ../xasm-$(VERSION)-windows.zip - -srcdist: MANIFEST - $(RM) ../xasm-$(VERSION).tar.gz && tar -c --numeric-owner --owner=0 --group=0 --mode=644 -T MANIFEST --transform=s,,xasm-$(VERSION)/, | $(SEVENZIP) -tgzip -si ../xasm-$(VERSION).tar.gz - -MANIFEST: - if test -e .git; then (git ls-files | grep -vF .gitignore && echo MANIFEST) | sort >$@ ; fi - -../xasm-$(VERSION)-windows.zip: xasm xasm.html xasm.properties - $(RM) $@ && $(SEVENZIP) -tzip $@ xasm.exe xasm.html xasm.properties - -deb: - debuild -b -us -uc - -osx: ../xasm-$(VERSION)-osx.dmg - -../xasm-$(VERSION)-osx.dmg: osx/xasm osx/bin - hdiutil create -volname xasm-$(VERSION)-osx -srcfolder osx -imagekey zlib-level=9 -ov $@ - -osx/xasm: xasm.d - mkdir -p osx && dmd -of$@ -O -release -m32 -L-macosx_version_min -L10.6 $< && rm -f osx/xasm.o - -osx/bin: - mkdir -p osx && ln -s /usr/bin $@ - -clean: - $(RM) xasm xasm.exe xasm.obj xasm.html xasm.1 - rm -rf osx - -.PHONY: all install uninstall install-scite uninstall-scite dist srcdist MANIFEST deb osx clean - -.DELETE_ON_ERROR: +VERSION = 3.1.0 + +prefix = /usr/local +bindir = $(prefix)/bin +mandir = $(prefix)/share/man/man1 + +SEVENZIP = 7z a -mx=9 -bd + +all: xasm xasm.html + +xasm: xasm.d + dmd -of$@ -O -release $< + +xasm.html: xasm.1.asciidoc + asciidoc -o - $< | sed -e "s/527bbd;/20a0a0;/" >$@ + +xasm.1: xasm.1.asciidoc + a2x -f manpage $< + +install: xasm xasm.1 + mkdir -p $(DESTDIR)$(bindir) && install xasm $(DESTDIR)$(bindir)/xasm + mkdir -p $(DESTDIR)$(mandir) && install -m 644 xasm.1 $(DESTDIR)$(mandir)/xasm.1 + +uninstall: + $(RM) $(DESTDIR)$(bindir)/xasm $(DESTDIR)$(mandir)/xasm.1 + +install-scite: xasm.properties + mkdir -p $(DESTDIR)$(prefix)/share/scite && install $< $(DESTDIR)$(prefix)/share/scite/xasm.properties + +uninstall-scite: + $(RM) $(DESTDIR)$(prefix)/share/scite/xasm.properties + +dist: srcdist ../xasm-$(VERSION)-windows.zip + +srcdist: MANIFEST + $(RM) ../xasm-$(VERSION).tar.gz && tar -c --numeric-owner --owner=0 --group=0 --mode=644 -T MANIFEST --transform=s,,xasm-$(VERSION)/, | $(SEVENZIP) -tgzip -si ../xasm-$(VERSION).tar.gz + +MANIFEST: + if test -e .git; then (git ls-files | grep -vF .gitignore && echo MANIFEST) | sort >$@ ; fi + +../xasm-$(VERSION)-windows.zip: xasm xasm.html xasm.properties + $(RM) $@ && $(SEVENZIP) -tzip $@ xasm.exe xasm.html xasm.properties + +deb: + debuild -b -us -uc + +osx: ../xasm-$(VERSION)-osx.dmg + +../xasm-$(VERSION)-osx.dmg: osx/xasm osx/bin + hdiutil create -volname xasm-$(VERSION)-osx -srcfolder osx -imagekey zlib-level=9 -ov $@ + +osx/xasm: xasm.d + mkdir -p osx && dmd -of$@ -O -release -m32 -L-macosx_version_min -L10.6 $< && rm -f osx/xasm.o + +osx/bin: + mkdir -p osx && ln -s /usr/bin $@ + +clean: + $(RM) xasm xasm.exe xasm.obj xasm.html xasm.1 + rm -rf osx + +.PHONY: all install uninstall install-scite uninstall-scite dist srcdist MANIFEST deb osx clean + +.DELETE_ON_ERROR: diff --git a/xasm.1.asciidoc b/xasm.1.asciidoc index 7317410..4592abf 100644 --- a/xasm.1.asciidoc +++ b/xasm.1.asciidoc @@ -1,878 +1,878 @@ -XASM (1) -======== -:doctype: manpage - -NAME ----- -xasm - 6502 cross-assembler - -SYNOPSIS --------- -*xasm* '[OPTIONS] SOURCE_FILE' - -DESCRIPTION ------------ -*xasm* is a cross-assembler for the 6502 family processors. - -'SOURCE_FILE' is the name of the source file -(you may omit the default `.asx` extension). -When invoked without any options, *xasm* assembles 'SOURCE_FILE' -and writes the result to an object file named 'SOURCE_FILE' -with the extension changed to `.obx`. - -OPTIONS -------- - -*/c*:: -Specifies that lines skipped due to a false condition -should be included in the listing file. - -[[new_deflabel]]*/d:*'LABEL'='VALUE':: -Defines a label. -'LABEL' should be a valid label name. -'VALUE' may be any expression (may reference to labels defined in source files). -You may use several */d* options to define many labels from the command line. - -*/i*:: -Excludes included files from the listing file. - -*/l*'[:LISTING_FILE]':: -Generates listing file. -If 'LISTING_FILE' is omitted, the listing filename -is 'SOURCE_FILE' with the extension changed to `.lst`. - -[[new_makefile]]*/M*:: -Prints a rule for use in a `Makefile`. -First line of the rule lists 'OBJECT_FILE' as the target of the rule -and all source files (including the ones specified with `icl` and `ins` directives) -as dependencies. The second line contains the command line with `OBJECT_FILE` -replaced by the *make* macro `$@` and `SOURCE_FILE` replaced by the macro `$<`. -Dollars in the command line are doubled. -Your `make` or shell may require further escaping. - -*/o*':OBJECT_FILE':: -Sets output file name. -The default is 'SOURCE_FILE' with the extension changed to `.obx`. - -[[new_fullpaths]]*/p*:: -Prints absolute paths in listing and error messages. - -[[new_quiet]]*/q*:: -Quiet mode. Prevents *xasm* from printing its banner and compilation summary. - -*/t*'[:LABEL_FILE]':: -Generates label table. -If 'LABEL_FILE' is omitted then the table is appended at the end of the listing. - -[[new_unlabels]]*/u*:: -Issues a warning message for each label whose value is unused. - -Alternatively, you may use Unix-style options, for example: - ------------------------------------------------------------ -xasm -i -d DEBUG=1 -l listing.lst source.asx ------------------------------------------------------------ - -SYNTAX ------- - -Source files should be plain ASCII files. -LF, CR, CR/LF and Atari ($9b) line terminators are supported. -Labels and instructions are case-insensitive. - -*xasm* is backward compatible with Quick Assembler. -To compile QA sources with *xasm*, simply replace ATASCII-specific characters -with their integer codes. You also have to update all `OPT` directives, -but usually you can simply remove them. - -'Label' is a symbol that represents a signed 32-bit integer. -You define a label by putting its name at the beginning of a line -(with no spaces before). -The label will be assigned the current value of the 'origin counter' -(i.e. the address of the compiled instruction), -unless you use it with the `EQU` directive where it is assigned -the value of the `EQU` argument. - -Instructions and directives must be preceded with some whitespace. -Without leading whitespace they are treated as label names. -For example: ----- - nop ----- -is a 6502 instruction, whereas ----- -nop ----- -(without leading space) defines a label called `nop`. - -Whole-line comments must start with a semicolon, an asterisk or a pipe, -with optional label definition and spaces before. -Here are examples of whole-line comments: --------------------- -; this is a comment - * so it is -label | and this too --------------------- - -[[new_linerep]] -Lines with instructions (and selected directives) may be 'repeated'. -To assemble a single line several times, -precede the repeat count with a colon, for example: ------------------ -:4 asl @ -mask_lookup :32 dta $80,$40,$20,$10,8,4,2,1 ------------------ - -In lines with instructions or directives, a comment starts immediately -after the instruction/directive has been successfully parsed. -That is, in such lines *xasm* does not require a special character -to start a comment. -------------------------------------------------------------- - lda foo ; this is a comment - sta bar this too - tax #0 tax has no operand, therefore #0 starts this comment -------------------------------------------------------------- - -[[new_pairing]] -You may put two instructions in one line so they share their operand. -For example: ------------- - eor:sta foo ------------- -is equivalent to ------------- - eor foo - sta foo ------------- - -Note that ------------- - lda:tax #0 ------------- -is allowed because `#0` is treated as a comment for `tax`. - -EXPRESSIONS ------------ -Expressions are numbers combined with operators and brackets. -You should use square brackets, because parentheses are reserved -for 6502 indirect addressing. - -A number is: - -- a 32-bit decimal integer, e.g. `-12345` -- a 32-bit hexadecimal integer, e.g. `$abcd` -- a 32-bit binary integer, e.g. `%10100101` -- an ASCII character, e.g. `'a'` or `"a"` -- origin counter: `*` -- a hardware register (see below), e.g. `^4e` -- [[new_opcode]]an opcode (see below), e.g. `{lda #0}` is `$a9` -- [[new_linecnt]]the line repeat counter (see below): `#` - -Abbreviations of Atari hardware registers are provided -to save two characters (`$d40e` vs `^4e`) -and to facilitate porting software between Atari 8-bit computers -and the Atari 5200 console. -These are very similar machines, one of the biggest differences -is the location of hardware registers. - -[cols="^m,^d,^m,^d",options="header"] -|================================================ -|Syntax|Chip |Value|Value in Atari 5200 mode (`opt g+`) -| ^0x |GTIA |$D00x|`$C00x` -| ^1x |GTIA |$D01x|`$C01x` -| ^2x |POKEY|$D20x|`$E80x` -| ^3x |PIA |$D30x|'error (there's no PIA chip)' -| ^4x |ANTIC|$D40x|`$D40x` -|================================================ - -The opcode syntax represents the opcode byte of the instruction inside braces. -The operand of the instruction is discarded and is needed only to recognize -the addressing mode. The instruction should begin right after the left brace -and the right brace should immediately follow the operand 'or' the instruction. -[[new_op_op]]You can skip the operand if the addressing mode is fixed. -Examples: `{lda #}`, `{jsr}`, `{bne}`, `{jmp ()}`, `{sta a:,x}`. - -You can use the line repeat counter (`#`) in the repeated lines. -It counts the iterations starting from zero. Examples: ----------------------------------------------------- -:3 dta # ; generates three bytes: 0, 1, 2. -line_lo :192 dta l(screen+40*#) -line_hi :192 dta h(screen+40*#) -dl :59 dta $4f,a(screen+40*#),0,$4f,a(screen+40*#),0 ----------------------------------------------------- - -The following 'binary operators' are supported: - -- `+` Addition -- `-` Subtraction -- `*` Multiplication -- `/` Division -- `%` Remainder -- `&` Bitwise AND -- `|` Bitwise OR -- `^` Bitwise XOR -- `<<` Arithmetic shift left -- `>>` Arithmetic shift right -- `==` Equal -- `=` Equal (same as `==`) -- `!=` Not equal -- `<>` Not equal (same as `!=`) -- `<` Less than -- `>` Greater than -- `<=` Less or equal -- `>=` Greater or equal -- `&&` Logical AND -- `||` Logical OR - -[[new_unary]] -The following 'unary operators' are supported: - -- `+` Plus (no operation) -- `-` Minus (changes the sign) -- `~` Bitwise NOT (complements all bits) -- `!` Logical NOT (changes true to false and vice versa) -- `<` Low (extracts the low byte) -- `>` High (extracts the high byte) - -The operator precedence is following: - -- first: `[]` (brackets) -- `+ - ~ < >` (unary) -- `* / % & << >>` (binary) -- `+ - | ^` (binary) -- `= == <> != < > <= >=` (binary) -- `!` (unary) -- `&&` (binary) -- last: `||` (binary) - -NOTE: Although the operators are similar to those used in C, C++ and Java, -their priorities are different. - -Compare and logical operators assume that zero is false and a non-zero -is true. They return 1 for true. - -Expressions are calculated in signed 32-bit arithmetic. -"Arithmetic overflow" error signals overflow of the 32-bit range. - -DIRECTIVES ----------- - -*EQU* - assign value of expression to label:: - -Examples: -+ ----------- -five equ 5 -here equ * ----------- - -[[new_opt]]*OPT* - set assembler options:: - -Six options are available: - -- `F` - fill the space between memory areas with `$FF` -- `G` - Atari 5200 mode for hardware register abbreviations -- `H` - generate Atari executable headers -- `L` - write to the listing -- `O` - write to the object file -- `U` - warn of unused labels - -+ -You can turn any of these on or off. -The default (if no `OPT` specified) is `opt f-g-h+l+o+u+`. -Examples: -+ ------------------------------------------------------------------------------- - opt l- listing off - opt l+o- listing on, object file off - opt f+g+h- useful for Atari 5200 cartridges - raw output, 5200 hw regs ------------------------------------------------------------------------------- - -*ORG* - change value of the origin counter:: - -If Atari executable headers are enabled, you can include an operand prefix: - -- `a:` starts a new block even if it's superfluous - because the new address equals the current address. -- `f:` is same as `a:`, but additionally generates a double-`$FF` prefix - before the new header. This prefix is automatically generated - at the beginning of the file (no need to include `f:` in the first `ORG`). - -+ -Examples: -+ ---------------- - org $600 - org f:$700 -table org *+100 ---------------- -+ -In the latter example `table` points to 100 bytes -of uninitialized data (label is assigned with `*` -before the `ORG` directive is executed). -+ -[[new_orgr]]Starting with version 2.6.0, *xasm* supports code -that is relocated at run time. Let's say you want your code -to be located on page zero. You can't normally load it directly into this -place, so you load it at a different address and then move in your program. -`org r:` changes the address that it used for code generation -but not the address used for generating Atari executable headers. -Example: -+ --------------------------------------- - org $8000 - ldx #code_length-1 - mva:rpl code_loaded,x z:code_zpage,x- - jmp code_zpage - -code_loaded - org r:$30 -code_zpage - jmp * ; ... or something more sensible -code_length equ *-code_zpage --------------------------------------- -+ -Note that both `*` and label definitions use the counter used -for code generation. There is no direct access to the other counter. -You can only calculate it: -+ ---------------------------------------- -where_am_i equ *-code_zpage+code_loaded ---------------------------------------- - -[[new_dta]]*DTA* - define data:: - -- integers -+ --- -* bytes: `b(200)` or simply `200` -* words: `a(10000)` -* low bytes of words: `l(511)` (byte 255) -* high bytes of words: `h(511)` (byte 1) - -You may enter many expressions in parentheses and combine different types -of data in single line, separating things with commas. - -You may also build a sine lookup table. The syntax is: -------------------------------- -sin(center,amp,period,first,last) -------------------------------- -where: - -* `center` is an integer which is added to every sine value -* `amp` is the sine amplitude -* `period` is the number of values per sine period -* `first,last` define the range of sine arguments. - They are optional. The default are `0,period-1`. - -Example: ----------------------------- - dta a(sin(0,1000,256,0,63)) ----------------------------- -defines a table of 64 words representing a quarter of sine with the amplitude of 1000. --- - -- real numbers: `r(-1.23456e12)` -+ -Real numbers are stored in the 6-byte Atari Floating-Point format. - -- text strings -+ --- -* ASCII strings: `c'Text'` or `c"Text"` -* ANTIC strings: `d'Text'` or `d"Text"` - -A character string consists of any number of characters surrounded by quotation -marks. You can include the quotation marks in the string by doubling them. -Placing a `*` character after a string inverts -the highest bit in every byte of the string. --- -+ -Examples of `DTA`: -+ ------------------------------------------------- - dta b(1,2),3,a(1000,-1),l(12345,sin(0,127,256)) - dta d"ANTIC"*,c'It''s a string',$9b ------------------------------------------------- - -*ICL* - include another source file:: - -Specifies another file to be included in the assembly as if the contents -of the referenced file appeared in place of the `ICL` statement. -The included file may contain other `ICL` statements. -The `.asx` extension is added if none given. -Examples: -+ ------------------ - icl 'macros.asx' - icl 'lib/fileio' ------------------ -+ -NOTE: for portability, use only relative paths and slash as the separator. -This way your sources will compile under Windows and Linux. - -*END* - end assembling file:: - -May be used if the source file ends with something which shouldn't -be read by *xasm* (e.g. your notes). - -*INS* - insert contents of file:: - -Copies every byte of the specified file into the object file and updates -the origin counter, as if these bytes were written using `DTA`. -You may specify a range of the file to insert. The syntax is: -+ ------------------------------ - ins 'file'[,offset[,length]] ------------------------------ -+ -The first byte in a file has the offset of zero. -If the offset is negative, it counts from the end of the file. -Examples: -+ ------------------------------------------------ - ins 'picture.raw' - ins 'file',-256 insert last 256 bytes of file - ins 'file',10,10 insert bytes 10..19 of file ------------------------------------------------ - -*RUN* - set run address in the Atari executable format:: - -+ ---------- - run main ---------- -+ -is equivalent to: -+ ------------- - org $2e0 - dta a(main) ------------- - -*INI* - set init address in the Atari executable format:: - -Example: -+ ------------- - ini showloadingpic ------------- - -*ERT* - generate error if expression evaluates to true:: - -Examples: -+ ------------------------ - ert *>$c000 - ert len1>$ff||len2>$ff ------------------------ - -[[new_eli]]*IFT* - assemble if expression is true:: -*ELI* - else if:: -*ELS* - else:: -*EIF* - end if:: - -With these directives you can construct fragments which -are assembled only when a condition is met. -Conditional constructions can be nested. -Example: -+ -------------- -noscr equ 1 -widescr equ 1 - ift noscr - lda #0 - eli widescr - lda #$23 - els - lda #$22 - eif - sta $22f -------------- -+ -NOTE: The above example may be rewritten using the 'repeat line' feature: -+ --------------------------- -noscr equ 1 -widescr equ 1 -:noscr lda #0 -:!noscr&&widescr lda #$23 -:!noscr&&!widescr lda #$22 - sta $22f --------------------------- - -PSEUDO COMMANDS ---------------- -'Pseudo commands' are built-in macros. There are no user-defined macros in *xasm*. - -*ADD* - add without carry:: - -If you have ever programmed a 6502, you must have noticed that you had -to use a `CLC` before `ADC` for every simple addition. -+ -*xasm* can do it for you. `ADD` replaces two instructions: `CLC` and `ADC`. - -*SUB* - subtract:: - -It is `SEC` and `SBC`. - -[[new_repskip]]*RCC, RCS, REQ, RMI, RNE, RPL, RVC, RVS* - conditional repeat:: - -These are branches to the previous instruction. -They take no operand, because the branch target is the address -of the previously assembled instruction or pseudo command. -Example: -+ ------------------------ - ldx #0 - mva:rne $500,x $600,x+ ------------------------ -+ -The above code copies a 256-byte memory block from $500 to $600. -Here is the same written with standard 6502 commands only: -+ --------------------- - ldx #0 -copy_loop lda $500,x - sta $600,x - inx - bne copy_loop --------------------- - -*SCC, SCS, SEQ, SMI, SNE, SPL, SVC, SVS* - conditional skip:: - -These are branches over the next instruction. No operand is required, -because the target is the address of the instruction following -the next instruction. -Example: -+ --------------- - lda #40 - add:sta ptr - scc:inc ptr+1 --------------- -+ -In the above example the 16-bit variable `ptr` is incremented by 40. - -*JCC, JCS, JEQ, JMI, JNE, JPL, JVC, JVS* - conditional jump:: - -These are long branches. While standard branches (such as `BNE`) -have range of -128..+127, these jumps have range of 64 kB. -For example: -+ ---------- - jne dest ---------- -+ -is equivalent to: -+ -------------- - seq:jmp dest -------------- - -*INW* - increment word:: - -Increments a 16-bit word in the memory. -Example: -+ ---------- - inw dest ---------- -+ -is equivalent to: -+ ---------------- - inc dest - sne:inc dest+1 ---------------- - -*MVA, MVX, MVY* - move byte using accumulator, X or Y:: - -Each of these pseudo commands requires two operands -and substitutes two commands: -+ ----------------------------------------- - mva source dest = lda source : sta dest - mvx source dest = ldx source : stx dest - mvy source dest = ldy source : sty dest ----------------------------------------- - -[[new_mwinde]]*MWA, MWX, MWY* - move word using accumulator, X or Y:: - -These pseudo commands require two operands and are combinations of two `MVA`/`MVX`/`MWY`: -one to move the low byte, and the other to move the high byte. -You can't use indirect nor pseudo addressing mode with `MWA`/`MWX`/`MWY`. -Destination must be an absolute or zeropage address, optionally indexed. -When source is also an absolute or zeropage address, an `mwa source dest` expands to: -+ --------------------- - mva source dest - mva source+1 dest+1 --------------------- -+ -When source is an immediate value, an `mwa #immed dest` expands to: -+ ------------------- - mva immed dest+1 ------------------- -+ -When `immed` and `immed` is not forward-referenced, -*xasm* skips the second `LDA`, generating the following code: -+ ----------------- - mva `, which use the low/high byte of a 16-bit word constant. -Unlike in Quick Assembler, you can alternatively use -the more common syntax: `#<` and `#>`. -Note the difference: -------------------------------- - lda >$ff+5 ; loads 1 (>$104) - lda #>$ff+5 ; loads 5 (0+5) -------------------------------- - -You can explicitly specify absolute (`a:`) and zero-page (`z:`) addressing modes. - -Examples: --------------------------------------- - nop - asl @ - lda >$1234 assembles to lda #$12 - lda $100,x - lda 0 zero-page (8-bit address) - lda a:0 absolute (16-bit address) - jmp ($0a) - lda ($80),y --------------------------------------- - -[[new_adrmodes]] -There are 'pseudo addressing modes', which are similar to pseudo commands. -You may use them just like standard addressing modes in all 6502 commands -and pseudo commands, except for `MWA`, `MWX` and `MWY`: ------------------------------------------- - cmd a,x+ = cmd a,x : inx - cmd a,x- = cmd a,x : dex - cmd a,y+ = cmd a,y : iny - cmd a,y- = cmd a,y : dey - cmd (z),y+ = cmd (z),y : iny - cmd (z),y- = cmd (z),y : dey - cmd (z,0) = ldx #0 : cmd (z,x) - cmd (z),0 = ldy #0 : cmd (z),y - cmd (z),0+ = ldy #0 : cmd (z),y : iny - cmd (z),0- = ldy #0 : cmd (z),y : dey ------------------------------------------- - -HISTORY -------- - -Version 3.1.0 (2014-07-20) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- OS X, Ubuntu and Fedora distributions -- `INS` can be repeated (suggested by Marek Pavlik) and taken "opcode" of -- `OPT U-` disables <> unused label warnings - (suggested by Marek Pavlik) -- if the file to be included cannot be opened, report error in the `ICL` line - (suggested by Peter Dell) -- removed duplicate filenames for <> -- implemented <> outside Windows -- source code updated from D1 to D2 -- project moved to GitHub - -Version 3.0.2 (2009-10-17) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed "Branch out of range" error message - was overstated by 256 bytes - for backward branches -- <> -- command-line options are now case-insensitive -- on Windows error messages are printed in red, warnings in yellow - -Version 3.0.1 (2007-04-22) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed a bug in `OPT H-` mode -- made *xasm* compilable with the latest D compiler v1.010 - (there were incompatible changes in the D language and library) - -Version 3.0.0 (2005-05-22) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- rewritten from the x86 assembly language to the - http://dlang.org/[D programming language] - Linux version - is now available and DOS is no longer supported -- no limits for line length, number of `ICLs`, `ORGs`,`IFTs` and labels -- Unix-style command-line options are supported -- */e* option is no longer supported -- the label table is now sorted alphabetically - -Version 2.6.1 (2005-05-21) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- no more "Arithmetic overflow" and "Division by zero" errors for correct - use of forward-referenced labels (bug found by Marcin Lewandowski) -- an error was reported in the following correct code: -+ ---------- - ift 0 -foo equ 1 - ift foo - eif - eif ---------- -+ -(bug found by Adrian Matoga) - -- errors for non-existing `INC @` and `DEC @` -- negative numbers fixed in the listing - -Version 2.6.0 (2005-02-07) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- long file names are supported under Windows -- <> -- <> -- label values are now 32-bit, not just 17-bit -- command-line options */n* and */s* are no longer supported -- fatal I/O errors (such as floppy not ready) no longer print the annoying - "Abort, Retry, Ignore" message - -Version 2.5.2 (2002-10-03) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- version 2.5.1 broke Unix EOLs - fixed -- version 2.5.1 omitted all blank/comment/label lines, unless */c* was used - -Version 2.5.1 (2002-08-21) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed assembling sources with Atari EOLs -- blank/comment/label lines in false conditionals are now correctly omitted - in listing - -Version 2.5 (2002-07-08) -~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed another bug, very similar to the previous one, e.g. -+ ----------- - ift 0 -:label nop - eif ----------- -+ -reported "Label not defined before" error for the repeat count - -- <> -- <> - -Version 2.4.1 (2002-06-27) -~~~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed a bug related to label definitions in conditionally skipped code, -e.g. -+ ----------- - ift 0 -label - eif ----------- -+ -reported "No ORG specified" error for the label definition - -Version 2.4 (2002-05-22) -~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed incorrect unary operator precedence -- fixed wrong label value after a skip pseudo command -- the assembler is .EXE (.COM caused problems with DJGPP *make* due - to a bug in the DJGPP runtime) -- the assembler executable is not compressed (so it occupies less space in the ZIP) -- improved command-line parsing: options may be used before source file name, - tab character is a valid separator, slash may be used as a directory separator -- error and warning messages are written to stderr, not stdout -- added `==` (equals) operator, which is equivalent to `=`, - but more natural for C/C++/Java programmers -- <> -- <> -- <> -- <> -- <> -- <> -- <> - -Version 2.3 (2002-02-10) -~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed double skip (e.g. `SCC:SNE`) -- fixed real numbers with two-digit exponent -- trailing spaces are trimmed from listing lines -- label definitions allowed in blank, comment and repeated lines -- <> -- <> -- <> - -Version 2.2 (1999-09-10) -~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed invalid opcodes of absolute `CPX` and `CPY` -- fixed: addressing mode not checked for branch commands -- fixed `ICL` in last line -- fixed `OPT H-H+` -- fixed first `ORG *` -- no need to set origin counter until it's used -- allow Unix, Macintosh and Atari EOLs -- value of 'true' changed to 1 -- command-line option to set environment variables on error -- commane-line option to assemble only if source is newer than object file -- <> -- <> -- <> -- <> -- <> - -Version 2.0 (1998-11-12) -~~~~~~~~~~~~~~~~~~~~~~~~ -- fixed: name of object file was truncated -- fixed forward references in `EQU` and `DTA` -- fixed hex numbers -- `.OBX` is now the default extension for the object file -- options (command-line switches and `OPT`) -- listing -- label table -- conditional assembly -- user errors (`ERT`) -- warnings -- 6 new pseudo commands (memory-to-memory move) -- 8 pseudo addressing modes -- indirect conditional jumps -- Atari floating-point numbers -- object file headers optimization -- improved expressions - 19 operators and brackets, 32-bit arithmetic -- improved signed numbers -- improved `INS`: inserting specified part of file - -Version 1.2 (1998-08-14) -~~~~~~~~~~~~~~~~~~~~~~~~ -- first release - -AUTHOR ------- -Piotr Fusik - -SEE ALSO --------- - -Website: https://github.com/pfusik/xasm[] +XASM (1) +======== +:doctype: manpage + +NAME +---- +xasm - 6502 cross-assembler + +SYNOPSIS +-------- +*xasm* '[OPTIONS] SOURCE_FILE' + +DESCRIPTION +----------- +*xasm* is a cross-assembler for the 6502 family processors. + +'SOURCE_FILE' is the name of the source file +(you may omit the default `.asx` extension). +When invoked without any options, *xasm* assembles 'SOURCE_FILE' +and writes the result to an object file named 'SOURCE_FILE' +with the extension changed to `.obx`. + +OPTIONS +------- + +*/c*:: +Specifies that lines skipped due to a false condition +should be included in the listing file. + +[[new_deflabel]]*/d:*'LABEL'='VALUE':: +Defines a label. +'LABEL' should be a valid label name. +'VALUE' may be any expression (may reference to labels defined in source files). +You may use several */d* options to define many labels from the command line. + +*/i*:: +Excludes included files from the listing file. + +*/l*'[:LISTING_FILE]':: +Generates listing file. +If 'LISTING_FILE' is omitted, the listing filename +is 'SOURCE_FILE' with the extension changed to `.lst`. + +[[new_makefile]]*/M*:: +Prints a rule for use in a `Makefile`. +First line of the rule lists 'OBJECT_FILE' as the target of the rule +and all source files (including the ones specified with `icl` and `ins` directives) +as dependencies. The second line contains the command line with `OBJECT_FILE` +replaced by the *make* macro `$@` and `SOURCE_FILE` replaced by the macro `$<`. +Dollars in the command line are doubled. +Your `make` or shell may require further escaping. + +*/o*':OBJECT_FILE':: +Sets output file name. +The default is 'SOURCE_FILE' with the extension changed to `.obx`. + +[[new_fullpaths]]*/p*:: +Prints absolute paths in listing and error messages. + +[[new_quiet]]*/q*:: +Quiet mode. Prevents *xasm* from printing its banner and compilation summary. + +*/t*'[:LABEL_FILE]':: +Generates label table. +If 'LABEL_FILE' is omitted then the table is appended at the end of the listing. + +[[new_unlabels]]*/u*:: +Issues a warning message for each label whose value is unused. + +Alternatively, you may use Unix-style options, for example: + +----------------------------------------------------------- +xasm -i -d DEBUG=1 -l listing.lst source.asx +----------------------------------------------------------- + +SYNTAX +------ + +Source files should be plain ASCII files. +LF, CR, CR/LF and Atari ($9b) line terminators are supported. +Labels and instructions are case-insensitive. + +*xasm* is backward compatible with Quick Assembler. +To compile QA sources with *xasm*, simply replace ATASCII-specific characters +with their integer codes. You also have to update all `OPT` directives, +but usually you can simply remove them. + +'Label' is a symbol that represents a signed 32-bit integer. +You define a label by putting its name at the beginning of a line +(with no spaces before). +The label will be assigned the current value of the 'origin counter' +(i.e. the address of the compiled instruction), +unless you use it with the `EQU` directive where it is assigned +the value of the `EQU` argument. + +Instructions and directives must be preceded with some whitespace. +Without leading whitespace they are treated as label names. +For example: +---- + nop +---- +is a 6502 instruction, whereas +---- +nop +---- +(without leading space) defines a label called `nop`. + +Whole-line comments must start with a semicolon, an asterisk or a pipe, +with optional label definition and spaces before. +Here are examples of whole-line comments: +-------------------- +; this is a comment + * so it is +label | and this too +-------------------- + +[[new_linerep]] +Lines with instructions (and selected directives) may be 'repeated'. +To assemble a single line several times, +precede the repeat count with a colon, for example: +----------------- +:4 asl @ +mask_lookup :32 dta $80,$40,$20,$10,8,4,2,1 +----------------- + +In lines with instructions or directives, a comment starts immediately +after the instruction/directive has been successfully parsed. +That is, in such lines *xasm* does not require a special character +to start a comment. +------------------------------------------------------------- + lda foo ; this is a comment + sta bar this too + tax #0 tax has no operand, therefore #0 starts this comment +------------------------------------------------------------- + +[[new_pairing]] +You may put two instructions in one line so they share their operand. +For example: +------------ + eor:sta foo +------------ +is equivalent to +------------ + eor foo + sta foo +------------ + +Note that +------------ + lda:tax #0 +------------ +is allowed because `#0` is treated as a comment for `tax`. + +EXPRESSIONS +----------- +Expressions are numbers combined with operators and brackets. +You should use square brackets, because parentheses are reserved +for 6502 indirect addressing. + +A number is: + +- a 32-bit decimal integer, e.g. `-12345` +- a 32-bit hexadecimal integer, e.g. `$abcd` +- a 32-bit binary integer, e.g. `%10100101` +- an ASCII character, e.g. `'a'` or `"a"` +- origin counter: `*` +- a hardware register (see below), e.g. `^4e` +- [[new_opcode]]an opcode (see below), e.g. `{lda #0}` is `$a9` +- [[new_linecnt]]the line repeat counter (see below): `#` + +Abbreviations of Atari hardware registers are provided +to save two characters (`$d40e` vs `^4e`) +and to facilitate porting software between Atari 8-bit computers +and the Atari 5200 console. +These are very similar machines, one of the biggest differences +is the location of hardware registers. + +[cols="^m,^d,^m,^d",options="header"] +|================================================ +|Syntax|Chip |Value|Value in Atari 5200 mode (`opt g+`) +| ^0x |GTIA |$D00x|`$C00x` +| ^1x |GTIA |$D01x|`$C01x` +| ^2x |POKEY|$D20x|`$E80x` +| ^3x |PIA |$D30x|'error (there's no PIA chip)' +| ^4x |ANTIC|$D40x|`$D40x` +|================================================ + +The opcode syntax represents the opcode byte of the instruction inside braces. +The operand of the instruction is discarded and is needed only to recognize +the addressing mode. The instruction should begin right after the left brace +and the right brace should immediately follow the operand 'or' the instruction. +[[new_op_op]]You can skip the operand if the addressing mode is fixed. +Examples: `{lda #}`, `{jsr}`, `{bne}`, `{jmp ()}`, `{sta a:,x}`. + +You can use the line repeat counter (`#`) in the repeated lines. +It counts the iterations starting from zero. Examples: +---------------------------------------------------- +:3 dta # ; generates three bytes: 0, 1, 2. +line_lo :192 dta l(screen+40*#) +line_hi :192 dta h(screen+40*#) +dl :59 dta $4f,a(screen+40*#),0,$4f,a(screen+40*#),0 +---------------------------------------------------- + +The following 'binary operators' are supported: + +- `+` Addition +- `-` Subtraction +- `*` Multiplication +- `/` Division +- `%` Remainder +- `&` Bitwise AND +- `|` Bitwise OR +- `^` Bitwise XOR +- `<<` Arithmetic shift left +- `>>` Arithmetic shift right +- `==` Equal +- `=` Equal (same as `==`) +- `!=` Not equal +- `<>` Not equal (same as `!=`) +- `<` Less than +- `>` Greater than +- `<=` Less or equal +- `>=` Greater or equal +- `&&` Logical AND +- `||` Logical OR + +[[new_unary]] +The following 'unary operators' are supported: + +- `+` Plus (no operation) +- `-` Minus (changes the sign) +- `~` Bitwise NOT (complements all bits) +- `!` Logical NOT (changes true to false and vice versa) +- `<` Low (extracts the low byte) +- `>` High (extracts the high byte) + +The operator precedence is following: + +- first: `[]` (brackets) +- `+ - ~ < >` (unary) +- `* / % & << >>` (binary) +- `+ - | ^` (binary) +- `= == <> != < > <= >=` (binary) +- `!` (unary) +- `&&` (binary) +- last: `||` (binary) + +NOTE: Although the operators are similar to those used in C, C++ and Java, +their priorities are different. + +Compare and logical operators assume that zero is false and a non-zero +is true. They return 1 for true. + +Expressions are calculated in signed 32-bit arithmetic. +"Arithmetic overflow" error signals overflow of the 32-bit range. + +DIRECTIVES +---------- + +*EQU* - assign value of expression to label:: + +Examples: ++ +---------- +five equ 5 +here equ * +---------- + +[[new_opt]]*OPT* - set assembler options:: + +Six options are available: + +- `F` - fill the space between memory areas with `$FF` +- `G` - Atari 5200 mode for hardware register abbreviations +- `H` - generate Atari executable headers +- `L` - write to the listing +- `O` - write to the object file +- `U` - warn of unused labels + ++ +You can turn any of these on or off. +The default (if no `OPT` specified) is `opt f-g-h+l+o+u+`. +Examples: ++ +------------------------------------------------------------------------------ + opt l- listing off + opt l+o- listing on, object file off + opt f+g+h- useful for Atari 5200 cartridges - raw output, 5200 hw regs +------------------------------------------------------------------------------ + +*ORG* - change value of the origin counter:: + +If Atari executable headers are enabled, you can include an operand prefix: + +- `a:` starts a new block even if it's superfluous + because the new address equals the current address. +- `f:` is same as `a:`, but additionally generates a double-`$FF` prefix + before the new header. This prefix is automatically generated + at the beginning of the file (no need to include `f:` in the first `ORG`). + ++ +Examples: ++ +--------------- + org $600 + org f:$700 +table org *+100 +--------------- ++ +In the latter example `table` points to 100 bytes +of uninitialized data (label is assigned with `*` +before the `ORG` directive is executed). ++ +[[new_orgr]]Starting with version 2.6.0, *xasm* supports code +that is relocated at run time. Let's say you want your code +to be located on page zero. You can't normally load it directly into this +place, so you load it at a different address and then move in your program. +`org r:` changes the address that it used for code generation +but not the address used for generating Atari executable headers. +Example: ++ +-------------------------------------- + org $8000 + ldx #code_length-1 + mva:rpl code_loaded,x z:code_zpage,x- + jmp code_zpage + +code_loaded + org r:$30 +code_zpage + jmp * ; ... or something more sensible +code_length equ *-code_zpage +-------------------------------------- ++ +Note that both `*` and label definitions use the counter used +for code generation. There is no direct access to the other counter. +You can only calculate it: ++ +--------------------------------------- +where_am_i equ *-code_zpage+code_loaded +--------------------------------------- + +[[new_dta]]*DTA* - define data:: + +- integers ++ +-- +* bytes: `b(200)` or simply `200` +* words: `a(10000)` +* low bytes of words: `l(511)` (byte 255) +* high bytes of words: `h(511)` (byte 1) + +You may enter many expressions in parentheses and combine different types +of data in single line, separating things with commas. + +You may also build a sine lookup table. The syntax is: +------------------------------- +sin(center,amp,period,first,last) +------------------------------- +where: + +* `center` is an integer which is added to every sine value +* `amp` is the sine amplitude +* `period` is the number of values per sine period +* `first,last` define the range of sine arguments. + They are optional. The default are `0,period-1`. + +Example: +---------------------------- + dta a(sin(0,1000,256,0,63)) +---------------------------- +defines a table of 64 words representing a quarter of sine with the amplitude of 1000. +-- + +- real numbers: `r(-1.23456e12)` ++ +Real numbers are stored in the 6-byte Atari Floating-Point format. + +- text strings ++ +-- +* ASCII strings: `c'Text'` or `c"Text"` +* ANTIC strings: `d'Text'` or `d"Text"` + +A character string consists of any number of characters surrounded by quotation +marks. You can include the quotation marks in the string by doubling them. +Placing a `*` character after a string inverts +the highest bit in every byte of the string. +-- ++ +Examples of `DTA`: ++ +------------------------------------------------ + dta b(1,2),3,a(1000,-1),l(12345,sin(0,127,256)) + dta d"ANTIC"*,c'It''s a string',$9b +------------------------------------------------ + +*ICL* - include another source file:: + +Specifies another file to be included in the assembly as if the contents +of the referenced file appeared in place of the `ICL` statement. +The included file may contain other `ICL` statements. +The `.asx` extension is added if none given. +Examples: ++ +----------------- + icl 'macros.asx' + icl 'lib/fileio' +----------------- ++ +NOTE: for portability, use only relative paths and slash as the separator. +This way your sources will compile under Windows and Linux. + +*END* - end assembling file:: + +May be used if the source file ends with something which shouldn't +be read by *xasm* (e.g. your notes). + +*INS* - insert contents of file:: + +Copies every byte of the specified file into the object file and updates +the origin counter, as if these bytes were written using `DTA`. +You may specify a range of the file to insert. The syntax is: ++ +----------------------------- + ins 'file'[,offset[,length]] +----------------------------- ++ +The first byte in a file has the offset of zero. +If the offset is negative, it counts from the end of the file. +Examples: ++ +----------------------------------------------- + ins 'picture.raw' + ins 'file',-256 insert last 256 bytes of file + ins 'file',10,10 insert bytes 10..19 of file +----------------------------------------------- + +*RUN* - set run address in the Atari executable format:: + ++ +--------- + run main +--------- ++ +is equivalent to: ++ +------------ + org $2e0 + dta a(main) +------------ + +*INI* - set init address in the Atari executable format:: + +Example: ++ +------------ + ini showloadingpic +------------ + +*ERT* - generate error if expression evaluates to true:: + +Examples: ++ +----------------------- + ert *>$c000 + ert len1>$ff||len2>$ff +----------------------- + +[[new_eli]]*IFT* - assemble if expression is true:: +*ELI* - else if:: +*ELS* - else:: +*EIF* - end if:: + +With these directives you can construct fragments which +are assembled only when a condition is met. +Conditional constructions can be nested. +Example: ++ +------------- +noscr equ 1 +widescr equ 1 + ift noscr + lda #0 + eli widescr + lda #$23 + els + lda #$22 + eif + sta $22f +------------- ++ +NOTE: The above example may be rewritten using the 'repeat line' feature: ++ +-------------------------- +noscr equ 1 +widescr equ 1 +:noscr lda #0 +:!noscr&&widescr lda #$23 +:!noscr&&!widescr lda #$22 + sta $22f +-------------------------- + +PSEUDO COMMANDS +--------------- +'Pseudo commands' are built-in macros. There are no user-defined macros in *xasm*. + +*ADD* - add without carry:: + +If you have ever programmed a 6502, you must have noticed that you had +to use a `CLC` before `ADC` for every simple addition. ++ +*xasm* can do it for you. `ADD` replaces two instructions: `CLC` and `ADC`. + +*SUB* - subtract:: + +It is `SEC` and `SBC`. + +[[new_repskip]]*RCC, RCS, REQ, RMI, RNE, RPL, RVC, RVS* - conditional repeat:: + +These are branches to the previous instruction. +They take no operand, because the branch target is the address +of the previously assembled instruction or pseudo command. +Example: ++ +----------------------- + ldx #0 + mva:rne $500,x $600,x+ +----------------------- ++ +The above code copies a 256-byte memory block from $500 to $600. +Here is the same written with standard 6502 commands only: ++ +-------------------- + ldx #0 +copy_loop lda $500,x + sta $600,x + inx + bne copy_loop +-------------------- + +*SCC, SCS, SEQ, SMI, SNE, SPL, SVC, SVS* - conditional skip:: + +These are branches over the next instruction. No operand is required, +because the target is the address of the instruction following +the next instruction. +Example: ++ +-------------- + lda #40 + add:sta ptr + scc:inc ptr+1 +-------------- ++ +In the above example the 16-bit variable `ptr` is incremented by 40. + +*JCC, JCS, JEQ, JMI, JNE, JPL, JVC, JVS* - conditional jump:: + +These are long branches. While standard branches (such as `BNE`) +have range of -128..+127, these jumps have range of 64 kB. +For example: ++ +--------- + jne dest +--------- ++ +is equivalent to: ++ +------------- + seq:jmp dest +------------- + +*INW* - increment word:: + +Increments a 16-bit word in the memory. +Example: ++ +--------- + inw dest +--------- ++ +is equivalent to: ++ +--------------- + inc dest + sne:inc dest+1 +--------------- + +*MVA, MVX, MVY* - move byte using accumulator, X or Y:: + +Each of these pseudo commands requires two operands +and substitutes two commands: ++ +---------------------------------------- + mva source dest = lda source : sta dest + mvx source dest = ldx source : stx dest + mvy source dest = ldy source : sty dest +---------------------------------------- + +[[new_mwinde]]*MWA, MWX, MWY* - move word using accumulator, X or Y:: + +These pseudo commands require two operands and are combinations of two `MVA`/`MVX`/`MWY`: +one to move the low byte, and the other to move the high byte. +You can't use indirect nor pseudo addressing mode with `MWA`/`MWX`/`MWY`. +Destination must be an absolute or zeropage address, optionally indexed. +When source is also an absolute or zeropage address, an `mwa source dest` expands to: ++ +-------------------- + mva source dest + mva source+1 dest+1 +-------------------- ++ +When source is an immediate value, an `mwa #immed dest` expands to: ++ +------------------ + mva immed dest+1 +------------------ ++ +When `immed` and `immed` is not forward-referenced, +*xasm* skips the second `LDA`, generating the following code: ++ +---------------- + mva `, which use the low/high byte of a 16-bit word constant. +Unlike in Quick Assembler, you can alternatively use +the more common syntax: `#<` and `#>`. +Note the difference: +------------------------------- + lda >$ff+5 ; loads 1 (>$104) + lda #>$ff+5 ; loads 5 (0+5) +------------------------------- + +You can explicitly specify absolute (`a:`) and zero-page (`z:`) addressing modes. + +Examples: +-------------------------------------- + nop + asl @ + lda >$1234 assembles to lda #$12 + lda $100,x + lda 0 zero-page (8-bit address) + lda a:0 absolute (16-bit address) + jmp ($0a) + lda ($80),y +-------------------------------------- + +[[new_adrmodes]] +There are 'pseudo addressing modes', which are similar to pseudo commands. +You may use them just like standard addressing modes in all 6502 commands +and pseudo commands, except for `MWA`, `MWX` and `MWY`: +------------------------------------------ + cmd a,x+ = cmd a,x : inx + cmd a,x- = cmd a,x : dex + cmd a,y+ = cmd a,y : iny + cmd a,y- = cmd a,y : dey + cmd (z),y+ = cmd (z),y : iny + cmd (z),y- = cmd (z),y : dey + cmd (z,0) = ldx #0 : cmd (z,x) + cmd (z),0 = ldy #0 : cmd (z),y + cmd (z),0+ = ldy #0 : cmd (z),y : iny + cmd (z),0- = ldy #0 : cmd (z),y : dey +------------------------------------------ + +HISTORY +------- + +Version 3.1.0 (2014-07-20) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- OS X, Ubuntu and Fedora distributions +- `INS` can be repeated (suggested by Marek Pavlik) and taken "opcode" of +- `OPT U-` disables <> unused label warnings + (suggested by Marek Pavlik) +- if the file to be included cannot be opened, report error in the `ICL` line + (suggested by Peter Dell) +- removed duplicate filenames for <> +- implemented <> outside Windows +- source code updated from D1 to D2 +- project moved to GitHub + +Version 3.0.2 (2009-10-17) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed "Branch out of range" error message - was overstated by 256 bytes + for backward branches +- <> +- command-line options are now case-insensitive +- on Windows error messages are printed in red, warnings in yellow + +Version 3.0.1 (2007-04-22) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed a bug in `OPT H-` mode +- made *xasm* compilable with the latest D compiler v1.010 + (there were incompatible changes in the D language and library) + +Version 3.0.0 (2005-05-22) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- rewritten from the x86 assembly language to the + http://dlang.org/[D programming language] - Linux version + is now available and DOS is no longer supported +- no limits for line length, number of `ICLs`, `ORGs`,`IFTs` and labels +- Unix-style command-line options are supported +- */e* option is no longer supported +- the label table is now sorted alphabetically + +Version 2.6.1 (2005-05-21) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- no more "Arithmetic overflow" and "Division by zero" errors for correct + use of forward-referenced labels (bug found by Marcin Lewandowski) +- an error was reported in the following correct code: ++ +--------- + ift 0 +foo equ 1 + ift foo + eif + eif +--------- ++ +(bug found by Adrian Matoga) + +- errors for non-existing `INC @` and `DEC @` +- negative numbers fixed in the listing + +Version 2.6.0 (2005-02-07) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- long file names are supported under Windows +- <> +- <> +- label values are now 32-bit, not just 17-bit +- command-line options */n* and */s* are no longer supported +- fatal I/O errors (such as floppy not ready) no longer print the annoying + "Abort, Retry, Ignore" message + +Version 2.5.2 (2002-10-03) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- version 2.5.1 broke Unix EOLs - fixed +- version 2.5.1 omitted all blank/comment/label lines, unless */c* was used + +Version 2.5.1 (2002-08-21) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed assembling sources with Atari EOLs +- blank/comment/label lines in false conditionals are now correctly omitted + in listing + +Version 2.5 (2002-07-08) +~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed another bug, very similar to the previous one, e.g. ++ +---------- + ift 0 +:label nop + eif +---------- ++ +reported "Label not defined before" error for the repeat count + +- <> +- <> + +Version 2.4.1 (2002-06-27) +~~~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed a bug related to label definitions in conditionally skipped code, +e.g. ++ +---------- + ift 0 +label + eif +---------- ++ +reported "No ORG specified" error for the label definition + +Version 2.4 (2002-05-22) +~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed incorrect unary operator precedence +- fixed wrong label value after a skip pseudo command +- the assembler is .EXE (.COM caused problems with DJGPP *make* due + to a bug in the DJGPP runtime) +- the assembler executable is not compressed (so it occupies less space in the ZIP) +- improved command-line parsing: options may be used before source file name, + tab character is a valid separator, slash may be used as a directory separator +- error and warning messages are written to stderr, not stdout +- added `==` (equals) operator, which is equivalent to `=`, + but more natural for C/C++/Java programmers +- <> +- <> +- <> +- <> +- <> +- <> +- <> + +Version 2.3 (2002-02-10) +~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed double skip (e.g. `SCC:SNE`) +- fixed real numbers with two-digit exponent +- trailing spaces are trimmed from listing lines +- label definitions allowed in blank, comment and repeated lines +- <> +- <> +- <> + +Version 2.2 (1999-09-10) +~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed invalid opcodes of absolute `CPX` and `CPY` +- fixed: addressing mode not checked for branch commands +- fixed `ICL` in last line +- fixed `OPT H-H+` +- fixed first `ORG *` +- no need to set origin counter until it's used +- allow Unix, Macintosh and Atari EOLs +- value of 'true' changed to 1 +- command-line option to set environment variables on error +- commane-line option to assemble only if source is newer than object file +- <> +- <> +- <> +- <> +- <> + +Version 2.0 (1998-11-12) +~~~~~~~~~~~~~~~~~~~~~~~~ +- fixed: name of object file was truncated +- fixed forward references in `EQU` and `DTA` +- fixed hex numbers +- `.OBX` is now the default extension for the object file +- options (command-line switches and `OPT`) +- listing +- label table +- conditional assembly +- user errors (`ERT`) +- warnings +- 6 new pseudo commands (memory-to-memory move) +- 8 pseudo addressing modes +- indirect conditional jumps +- Atari floating-point numbers +- object file headers optimization +- improved expressions - 19 operators and brackets, 32-bit arithmetic +- improved signed numbers +- improved `INS`: inserting specified part of file + +Version 1.2 (1998-08-14) +~~~~~~~~~~~~~~~~~~~~~~~~ +- first release + +AUTHOR +------ +Piotr Fusik + +SEE ALSO +-------- + +Website: https://github.com/pfusik/xasm[] diff --git a/xasm.d b/xasm.d index 6be643f..2a27129 100644 --- a/xasm.d +++ b/xasm.d @@ -1,3022 +1,3022 @@ -// xasm 3.1.0 by Piotr Fusik -// http://xasm.atari.org -// Can be compiled with DMD v2.087.1. - -// Poetic License: -// -// This work 'as-is' we provide. -// No warranty express or implied. -// We've done our best, -// to debug and test. -// Liability for damages denied. -// -// Permission is granted hereby, -// to copy, share, and modify. -// Use as is fit, -// free or for profit. -// These rights, on this notice, rely. - -import std.algorithm; -import std.array; -import std.conv; -import std.math; -import std.path; -import std.stdio; - -version (Windows) { - import core.sys.windows.windows; -} - -int readByte(File *file) { - char c; - if (file.readf("%c", &c) != 1) - return -1; - return c; -} - -const string TITLE = "xasm 3.1.0"; - -string sourceFilename = null; -bool[26] options; -string[26] optionParameters; -string[] commandLineDefinitions = null; -string makeTarget; -string[] makeSources = null; - -int exitCode = 0; - -int totalLines; - -bool pass2 = false; - -bool optionFill; // opt f -bool option5200; // opt g -bool optionHeaders; // opt h -bool optionListing; // opt l -bool optionObject; // opt o -bool optionUnusedLabels; // opt u - -string currentFilename; -int lineNo; -int includeLevel = 0; -string line; -int column; - -bool foundEnd; - -class AssemblyError : Exception { - this(string msg) { - super(msg); - } -} - -class Label { - int value; - bool unused = true; - bool unknownInPass1 = false; - bool passed = false; - - this(int value) { - this.value = value; - } -} - -Label[string] labelTable; -Label currentLabel; - -alias int function(int a, int b) OperatorFunction; - -bool inOpcode = false; - -struct ValOp { - int value; - OperatorFunction func; - int priority; -} - -ValOp[] valOpStack; - -int value; -bool unknownInPass1; - -enum AddrMode { - ACCUMULATOR = 0, - IMMEDIATE = 1, - ABSOLUTE = 2, - ZEROPAGE = 3, - ABSOLUTE_X = 4, - ZEROPAGE_X = 5, - ABSOLUTE_Y = 6, - ZEROPAGE_Y = 7, - INDIRECT_X = 8, - INDIRECT_Y = 9, - INDIRECT = 10, - ABS_OR_ZP = 11, // temporarily used in readAddrMode - STANDARD_MASK = 15, - INCREMENT = 0x20, - DECREMENT = 0x30, - ZERO = 0x40 -} - -AddrMode addrMode; - -enum OrgModifier { - NONE, - FORCE_HEADER, - FORCE_FFFF, - RELOCATE -} - -int origin = -1; -int loadOrigin; -int loadingOrigin; -ushort[] blockEnds; -int blockIndex; - -bool repeating; // line -int repeatCounter; // line - -bool instructionBegin; -bool pairing; - -bool willSkip; -bool skipping; -ushort[] skipOffsets; -int skipOffsetsIndex = 0; - -int repeatOffset; // instruction repeat - -bool wereManyInstructions; - -alias void function(int move) MoveFunction; - -int value1; -AddrMode addrMode1; -int value2; -AddrMode addrMode2; - -struct IfContext { - bool condition; - bool wasElse; - bool aConditionMatched; -} - -IfContext[] ifContexts; - -File listingStream; -char[32] listingLine; -int listingColumn; -string lastListedFilename = null; - -File objectStream; - -int objectBytes = 0; - -bool getOption(char letter) { - assert(letter >= 'a' && letter <= 'z'); - return options[letter - 'a']; -} - -void warning(string msg, bool error = false) { - stdout.flush(); - version (Windows) { - HANDLE stderrHandle = GetStdHandle(STD_ERROR_HANDLE); - CONSOLE_SCREEN_BUFFER_INFO csbi; - GetConsoleScreenBufferInfo(stderrHandle, &csbi); - SetConsoleTextAttribute(stderrHandle, (csbi.wAttributes & ~0xf) | (error ? 12 : 14)); - scope (exit) SetConsoleTextAttribute(stderrHandle, csbi.wAttributes); - } - if (line !is null) - stderr.writeln(line); - stderr.writefln("%s (%d) %s: %s", currentFilename, lineNo, error ? "ERROR" : "WARNING", msg); - exitCode = 1; -} - -void illegalCharacter() { - throw new AssemblyError("Illegal character"); -} - -bool eol() { - return column >= line.length; -} - -char readChar() { - if (eol()) - throw new AssemblyError("Unexpected end of line"); - return line[column++]; -} - -int readDigit(int base) { - if (eol()) return -1; - int r = line[column]; - if (r >= '0' && r <= '9') { - r -= '0'; - } else { - r &= 0xdf; - if (r >= 'A' && r <= 'Z') - r -= 'A' - 10; - else - return -1; - } - if (r < base) { - column++; - return r; - } - return -1; -} - -int readNumber(int base) { - long r = readDigit(base); - if (r < 0) - illegalCharacter(); - do { - int d = readDigit(base); - if (d < 0) - return cast(int) r; - r = r * base + d; - } while (r <= 0x7fffffff); - throw new AssemblyError("Number too big"); -} - -void readSpaces() { - switch (readChar()) { - case '\t': - case ' ': - break; - default: - throw new AssemblyError("Space expected"); - } - while (!eol()) { - switch (line[column]) { - case '\t': - case ' ': - column++; - break; - default: - return; - } - } -} - -string readLabel() { - string label; - while (!eol()) { - char c = line[column++]; - if (c >= '0' && c <= '9' || c == '_') { - label ~= c; - continue; - } - c &= 0xdf; - if (c >= 'A' && c <= 'Z') { - label ~= c; - continue; - } - column--; - break; - } - return label >= "A" ? label : null; -} - -void readComma() { - if (readChar() != ',') - throw new AssemblyError("Bad or missing function parameter"); -} - -string readInstruction() { - string r; - for (int i = 0; i < 3; i++) { - char c = readChar() & 0xdf; - if (c < 'A' || c > 'Z') - throw new AssemblyError("Illegal instruction"); - r ~= c; - } - return r; -} - -string readFunction() { - if (column + 5 >= line.length) return null; - if (line[column + 3] != '(') return null; - string r; - for (int i = 0; i < 3; i++) { - char c = line[column + i] & 0xdf; - if (c < 'A' || c > 'Z') return null; - r ~= c; - } - column += 4; - return r; -} - -string readFilename() { - readSpaces(); - char delimiter = readChar(); - switch (delimiter) { - case '"': - case '\'': - string filename; - char c; - while ((c = readChar()) != delimiter) - filename ~= c; - return filename; - default: - illegalCharacter(); - } - assert(0); -} - -void readStringChar(char c) { - if (readChar() != c) - throw new AssemblyError("String error"); -} - -ubyte[] readString() { - if (eol()) return null; - char delimiter = readChar(); - switch (delimiter) { - case '"': - case '\'': - ubyte[] r; - for (;;) { - char c = readChar(); - if (c == delimiter) { - if (eol()) return r; - if (line[column] != delimiter) { - if (line[column] == '*') { - column++; - foreach (ref ubyte b; r) - b ^= 0x80; - } - return r; - } - column++; - } - r ~= cast(ubyte) c; - } - default: - column--; - return null; - } -} - -void checkNoExtraCharacters() { - if (eol()) return; - switch (line[column]) { - case '\t': - case ' ': - return; - default: - throw new AssemblyError("Extra characters on line"); - } -} - -void checkOriginDefined() { - if (origin < 0) - throw new AssemblyError("No ORG specified"); -} - -int operatorPlus(int a, int b) { - return b; -} - -int operatorMinus(int a, int b) { - return -b; -} - -int operatorLow(int a, int b) { - return b & 0xff; -} - -int operatorHigh(int a, int b) { - return (b >> 8) & 0xff; -} - -int operatorLogicalNot(int a, int b) { - return !b; -} - -int operatorBitwiseNot(int a, int b) { - return ~b; -} - -int operatorAdd(int a, int b) { - long r = cast(long) a + b; - if (r < -0x80000000L || r > 0x7fffffffL) - throw new AssemblyError("Arithmetic overflow"); - return a + b; -} - -int operatorSubtract(int a, int b) { - long r = cast(long) a - b; - if (r < -0x80000000L || r > 0x7fffffffL) - throw new AssemblyError("Arithmetic overflow"); - return a - b; -} - -int operatorMultiply(int a, int b) { - long r = cast(long) a * b; - if (r < -0x80000000L || r > 0x7fffffffL) - throw new AssemblyError("Arithmetic overflow"); - return a * b; -} - -int operatorDivide(int a, int b) { - if (b == 0) - throw new AssemblyError("Divide by zero"); - return a / b; -} - -int operatorModulus(int a, int b) { - if (b == 0) - throw new AssemblyError("Divide by zero"); - return a % b; -} - -int operatorAnd(int a, int b) { - return a & b; -} - -int operatorOr(int a, int b) { - return a | b; -} - -int operatorXor(int a, int b) { - return a ^ b; -} - -int operatorEqual(int a, int b) { - return a == b; -} - -int operatorNotEqual(int a, int b) { - return a != b; -} - -int operatorLess(int a, int b) { - return a < b; -} - -int operatorGreater(int a, int b) { - return a > b; -} - -int operatorLessEqual(int a, int b) { - return a <= b; -} - -int operatorGreaterEqual(int a, int b) { - return a >= b; -} - -int operatorShiftLeft(int a, int b) { - if (b < 0) - return operatorShiftRight(a, -b); - if (a != 0 && b >= 32) - throw new AssemblyError("Arithmetic overflow"); - long r = cast(long) a << b; - if (r & 0xffffffff00000000L) - throw new AssemblyError("Arithmetic overflow"); - return a << b; -} - -int operatorShiftRight(int a, int b) { - if (b < 0) - return operatorShiftLeft(a, -b); - if (b >= 32) - b = 31; - return a >> b; -} - -int operatorLogicalAnd(int a, int b) { - return a && b; -} - -int operatorLogicalOr(int a, int b) { - return a || b; -} - -void pushValOp(int value, OperatorFunction func, int priority) { - ValOp valOp; - valOp.value = value; - valOp.func = func; - valOp.priority = priority; - valOpStack ~= valOp; -} - -void readValue() { - assert(valOpStack.length == 0); - unknownInPass1 = false; - int priority = 0; - do { - int operand; - char c = readChar(); - switch (c) { - case '[': - priority += 10; - continue; - case '+': - pushValOp(0, &operatorPlus, priority + 8); - continue; - case '-': - pushValOp(0, &operatorMinus, priority + 8); - continue; - case '<': - pushValOp(0, &operatorLow, priority + 8); - continue; - case '>': - pushValOp(0, &operatorHigh, priority + 8); - continue; - case '!': - pushValOp(0, &operatorLogicalNot, priority + 4); - continue; - case '~': - pushValOp(0, &operatorBitwiseNot, priority + 8); - continue; - case '(': - throw new AssemblyError("Use square brackets instead"); - case '*': - checkOriginDefined(); - operand = origin; - break; - case '#': - if (!repeating) - throw new AssemblyError("'#' is allowed only in repeated lines"); - operand = repeatCounter; - break; - case '\'': - case '"': - operand = readChar(); - if (operand == c) - readStringChar(c); - readStringChar(c); - if (!eol() && line[column] == '*') { - column++; - operand ^= 0x80; - } - break; - case '^': - switch (readChar()) { - case '0': - operand = option5200 ? 0xc000 : 0xd000; - break; - case '1': - operand = option5200 ? 0xc010 : 0xd010; - break; - case '2': - operand = option5200 ? 0xe800 : 0xd200; - break; - case '3': - if (option5200) - throw new AssemblyError("There's no PIA chip in Atari 5200"); - operand = 0xd300; - break; - case '4': - operand = 0xd400; - break; - default: - illegalCharacter(); - } - int d = readDigit(16); - if (d < 0) - illegalCharacter(); - operand += d; - break; - case '{': - if (inOpcode) - throw new AssemblyError("Nested opcodes not supported"); - ValOp[] savedValOpStack = valOpStack; - AddrMode savedAddrMode = addrMode; - bool savedUnknownInPass1 = unknownInPass1; - bool savedInstructionBegin = instructionBegin; - valOpStack.length = 0; - inOpcode = true; - assemblyInstruction(readInstruction()); - if (readChar() != '}') - throw new AssemblyError("Missing '}'"); - assert(!instructionBegin); - inOpcode = false; - valOpStack = savedValOpStack; - addrMode = savedAddrMode; - unknownInPass1 = savedUnknownInPass1; - instructionBegin = savedInstructionBegin; - operand = value; - break; - case '$': - operand = readNumber(16); - break; - case '%': - operand = readNumber(2); - break; - default: - column--; - if (c >= '0' && c <= '9') { - operand = readNumber(10); - break; - } - string label = readLabel(); - if (label is null) - illegalCharacter(); - if (label in labelTable) { - Label l = labelTable[label]; - operand = l.value; - l.unused = false; - if (pass2) { - if (l.passed) { - if (l.unknownInPass1) - unknownInPass1 = true; - } else { - if (l.unknownInPass1) - throw new AssemblyError("Illegal forward reference"); - unknownInPass1 = true; - } - } else { - if (l.unknownInPass1) - unknownInPass1 = true; - } - } else { - if (pass2) - throw new AssemblyError("Undeclared label: " ~ label); - unknownInPass1 = true; - } - break; - } - while (!eol() && line[column] == ']') { - column++; - priority -= 10; - if (priority < 0) - throw new AssemblyError("Unmatched bracket"); - } - if (eol()) { - if (priority != 0) - throw new AssemblyError("Unmatched bracket"); - pushValOp(operand, &operatorPlus, 1); - } else { - switch (line[column++]) { - case '+': - pushValOp(operand, &operatorAdd, priority + 6); - break; - case '-': - pushValOp(operand, &operatorSubtract, priority + 6); - break; - case '*': - pushValOp(operand, &operatorMultiply, priority + 7); - break; - case '/': - pushValOp(operand, &operatorDivide, priority + 7); - break; - case '%': - pushValOp(operand, &operatorModulus, priority + 7); - break; - case '<': - switch (readChar()) { - case '<': - pushValOp(operand, &operatorShiftLeft, priority + 7); - break; - case '=': - pushValOp(operand, &operatorLessEqual, priority + 5); - break; - case '>': - pushValOp(operand, &operatorNotEqual, priority + 5); - break; - default: - column--; - pushValOp(operand, &operatorLess, priority + 5); - break; - } - break; - case '=': - switch (readChar()) { - default: - column--; - goto case '='; - case '=': - pushValOp(operand, &operatorEqual, priority + 5); - break; - } - break; - case '>': - switch (readChar()) { - case '>': - pushValOp(operand, &operatorShiftRight, priority + 7); - break; - case '=': - pushValOp(operand, &operatorGreaterEqual, priority + 5); - break; - default: - column--; - pushValOp(operand, &operatorGreater, priority + 5); - break; - } - break; - case '!': - switch (readChar()) { - case '=': - pushValOp(operand, &operatorNotEqual, priority + 5); - break; - default: - illegalCharacter(); - } - break; - case '&': - switch (readChar()) { - case '&': - pushValOp(operand, &operatorLogicalAnd, priority + 3); - break; - default: - column--; - pushValOp(operand, &operatorAnd, priority + 7); - break; - } - break; - case '|': - switch (readChar()) { - case '|': - pushValOp(operand, &operatorLogicalOr, priority + 2); - break; - default: - column--; - pushValOp(operand, &operatorOr, priority + 6); - break; - } - break; - case '^': - pushValOp(operand, &operatorXor, priority + 6); - break; - default: - column--; - if (priority != 0) - throw new AssemblyError("Unmatched bracket"); - pushValOp(operand, &operatorPlus, 1); - break; - } - } - for (;;) { - immutable sp = valOpStack.length - 1; - if (sp <= 0 || valOpStack[sp].priority > valOpStack[sp - 1].priority) - break; - int operand1 = valOpStack[sp - 1].value; - OperatorFunction func1 = valOpStack[sp - 1].func; - valOpStack[sp - 1] = valOpStack[sp]; - valOpStack.length = sp; - if (pass2 || !unknownInPass1) { // skip operations if unknown operands - valOpStack[sp - 1].value = func1(operand1, valOpStack[sp - 1].value); - } - } - } while (valOpStack.length != 1 || valOpStack[0].priority != 1); - value = valOpStack[0].value; - valOpStack.length = 0; -} - -debug int testValue(string l) { - line = l; - column = 0; - readValue(); - writefln("Value of %s is %x", l, value); - return value; -} - -unittest { - assert(testValue("123") == 123); - assert(testValue("$1234abCd") == 0x1234abcd); - assert(testValue("%101") == 5); - assert(testValue("^07") == 0xd007); - assert(testValue("^1f") == 0xd01f); - assert(testValue("^23") == 0xd203); - assert(testValue("^31") == 0xd301); - assert(testValue("^49") == 0xd409); - assert(testValue("!0") == 1); - assert(testValue("<$1234") == 0x34); - assert(testValue(">$1234567") == 0x45); - assert(testValue("1+2") == 3); - assert(testValue("1+2*3") == 7); - assert(testValue("[1+2]*3") == 9); - assert(testValue("{nop}") == 0xea); - assert(testValue("{CLC}+{sec}") == 0x50); - assert(testValue("{Jsr}") == 0x20); - assert(testValue("{bit a:}") == 0x2c); - assert(testValue("{bIt $7d}") == 0x24); -} - -void mustBeKnownInPass1() { - if (unknownInPass1) - throw new AssemblyError("Label not defined before"); -} - -void readWord() { - readValue(); - if ((!unknownInPass1 || pass2) && (value < -0xffff || value > 0xffff)) - throw new AssemblyError("Value out of range"); -} - -void readUnsignedWord() { - readWord(); - if ((!unknownInPass1 || pass2) && value < 0) - throw new AssemblyError("Value out of range"); -} - -void readKnownPositive() { - readValue(); - mustBeKnownInPass1(); - if (value <= 0) - throw new AssemblyError("Value out of range"); -} - -void optionalIncDec() { - if (eol()) return; - switch (line[column]) { - case '+': - column++; - addrMode += AddrMode.INCREMENT; - return; - case '-': - column++; - addrMode += AddrMode.DECREMENT; - return; - default: - return; - } -} - -void readAddrMode() { - readSpaces(); - char c = readChar(); - switch (c) { - case '@': - addrMode = AddrMode.ACCUMULATOR; - return; - case '#': - case '<': - case '>': - addrMode = AddrMode.IMMEDIATE; - if (inOpcode && line[column] == '}') - return; - readWord(); - final switch (c) { - case '#': - break; - case '<': - value &= 0xff; - break; - case '>': - value = (value >> 8) & 0xff; - break; - } - return; - case '(': - if (inOpcode) { - switch (readChar()) { - case ',': - switch (readChar()) { - case 'X': - case 'x': - break; - default: - illegalCharacter(); - } - if (readChar() != ')') - throw new AssemblyError("Need parenthesis"); - addrMode = AddrMode.INDIRECT_X; - return; - case ')': - if (readChar() == ',') { - switch (readChar()) { - case 'Y': - case 'y': - break; - default: - illegalCharacter(); - } - addrMode = AddrMode.INDIRECT_Y; - return; - } else { - column--; - addrMode = AddrMode.INDIRECT; - return; - } - default: - column--; - break; - } - } - readUnsignedWord(); - switch (readChar()) { - case ',': - switch (readChar()) { - case 'X': - case 'x': - addrMode = AddrMode.INDIRECT_X; - break; - case '0': - addrMode = cast(AddrMode) (AddrMode.INDIRECT_X + AddrMode.ZERO); - break; - default: - illegalCharacter(); - } - if (readChar() != ')') - throw new AssemblyError("Need parenthesis"); - return; - case ')': - if (eol()) { - addrMode = AddrMode.INDIRECT; - return; - } - if (line[column] == ',') { - column++; - switch (readChar()) { - case 'Y': - case 'y': - addrMode = AddrMode.INDIRECT_Y; - break; - case '0': - addrMode = cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.ZERO); - break; - default: - illegalCharacter(); - } - optionalIncDec(); - return; - } - addrMode = AddrMode.INDIRECT; - return; - default: - illegalCharacter(); - } - break; - case 'A': - case 'a': - if (!eol() && line[column] == ':') { - column++; - addrMode = AddrMode.ABSOLUTE; - } else { - addrMode = AddrMode.ABS_OR_ZP; - column--; - } - break; - case 'Z': - case 'z': - if (!eol() && line[column] == ':') { - column++; - addrMode = AddrMode.ZEROPAGE; - } else { - addrMode = AddrMode.ABS_OR_ZP; - column--; - } - break; - default: - addrMode = AddrMode.ABS_OR_ZP; - column--; - break; - } - // absolute or zeropage addressing, optionally indexed - if (inOpcode && (addrMode == AddrMode.ABSOLUTE || addrMode == AddrMode.ZEROPAGE)) { - switch (readChar()) { - case '}': - column--; - return; - case ',': - switch (readChar()) { - case 'X': - case 'x': - addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_X - AddrMode.ABSOLUTE); - return; - case 'Y': - case 'y': - addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_Y - AddrMode.ABSOLUTE); - return; - default: - illegalCharacter(); - } - return; - default: - column--; - break; - } - } - readUnsignedWord(); - if (addrMode == AddrMode.ABS_OR_ZP) { - if (unknownInPass1 || value > 0xff) - addrMode = AddrMode.ABSOLUTE; - else - addrMode = AddrMode.ZEROPAGE; - } - if (eol()) return; - if (line[column] == ',') { - column++; - switch (readChar()) { - case 'X': - case 'x': - addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_X - AddrMode.ABSOLUTE); - optionalIncDec(); - return; - case 'Y': - case 'y': - addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_Y - AddrMode.ABSOLUTE); - optionalIncDec(); - return; - default: - illegalCharacter(); - } - } -} - -void readAbsoluteAddrMode() { - if (inOpcode && readChar() == '}') { - column--; - } else { - readAddrMode(); - switch (addrMode) { - case AddrMode.ABSOLUTE: - case AddrMode.ZEROPAGE: - break; - default: - illegalAddrMode(); - } - } - addrMode = AddrMode.ABSOLUTE; -} - -debug AddrMode testAddrMode(string l) { - line = l; - column = 0; - readAddrMode(); - writefln("Addressing mode of \"%s\" is %x", l, addrMode); - return addrMode; -} - -unittest { - assert(testAddrMode(" @") == AddrMode.ACCUMULATOR); - assert(testAddrMode(" #0") == AddrMode.IMMEDIATE); - assert(testAddrMode(" $abc,x-") == cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.DECREMENT)); - assert(testAddrMode(" $ab,Y+") == cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.INCREMENT)); - assert(testAddrMode(" (0,x)") == AddrMode.INDIRECT_X); - assert(testAddrMode(" ($ff),Y+") == cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT)); - assert(testAddrMode(" ($abcd)") == AddrMode.INDIRECT); - inOpcode = true; - assert(testAddrMode(" a:}") == AddrMode.ABSOLUTE); - assert(testAddrMode(" z:}") == AddrMode.ZEROPAGE); - assert(testAddrMode(" a:,x}") == AddrMode.ABSOLUTE_X); - assert(testAddrMode(" z:,y}") == AddrMode.ZEROPAGE_Y); - assert(testAddrMode(" (,X)}") == AddrMode.INDIRECT_X); - assert(testAddrMode(" (),y}") == AddrMode.INDIRECT_Y); - assert(testAddrMode(" ()}") == AddrMode.INDIRECT); - inOpcode = false; -} - -bool inFalseCondition() { - foreach (IfContext ic; ifContexts) { - if (!ic.condition) return true; - } - return false; -} - -string makeEscape(string s) { - return s.replace("$", "$$"); -} - -File openInputFile(string filename) { - if (find(makeSources, filename).empty) - makeSources ~= filename; - try { - return File(filename); - } catch (Exception e) { - throw new AssemblyError(e.msg); - } -} - -File openOutputFile(char letter, string defaultExt) { - string filename = optionParameters[letter - 'a']; - if (filename is null) - filename = sourceFilename.setExtension(defaultExt); - if (letter == 'o') - makeTarget = makeEscape(filename); - try { - return File(filename, "wb"); - } catch (Exception e) { - throw new AssemblyError(e.msg); - } -} - -void ensureListingFileOpen(char letter, string msg) { - if (!listingStream.isOpen) { - listingStream = openOutputFile(letter, "lst"); - if (!getOption('q')) - write(msg); - listingStream.writeln(TITLE); - } -} - -void listNibble(int x) { - listingLine[listingColumn++] = cast(char) (x <= 9 ? x + '0' : x + ('A' - 10)); -} - -void listByte(ubyte x) { - listNibble(x >> 4); - listNibble(x & 0xf); -} - -void listWord(ushort x) { - listByte(cast(ubyte) (x >> 8)); - listByte(cast(ubyte) x); -} - -void listLine() { - if (!optionListing || !getOption('l') || line is null) - return; - assert(pass2); - if (inFalseCondition() && !getOption('c')) - return; - if (getOption('i') && includeLevel > 0) - return; - ensureListingFileOpen('l', "Writing listing file...\n"); - if (currentFilename != lastListedFilename) { - listingStream.writeln("Source: ", currentFilename); - lastListedFilename = currentFilename; - } - int i = 4; - int x = lineNo; - while (x > 0 && i >= 0) { - listingLine[i--] = '0' + x % 10; - x /= 10; - } - while (i >= 0) - listingLine[i--] = ' '; - listingLine[5] = ' '; - while (listingColumn < 32) - listingLine[listingColumn++] = ' '; - listingStream.writefln("%.32s%s", listingLine, line); -} - -void listCommentLine() { - if (currentLabel is null) - listingColumn = 6; - else { - assert(!inFalseCondition()); - checkOriginDefined(); - } - listLine(); -} - -void listLabelTable() { - if (optionParameters['t' - 'a'] !is null && listingStream.isOpen) - listingStream.close(); - ensureListingFileOpen('t', "Writing label table...\n"); - listingStream.writeln("Label table:"); - foreach (string name; sort(labelTable.keys)) { - Label l = labelTable[name]; - listingStream.write(l.unused ? 'n' : ' '); - listingStream.write(l.unknownInPass1 ? '2' : ' '); - int value = l.value; - char sign = ' '; - if (value < 0) { - sign = '-'; - value = -value; - } - listingStream.writefln( - (l.value & 0xffff0000) != 0 ? " %s%08X %s" : " %s%04X %s", - sign, value, name); - } -} - -debug ubyte[] objectBuffer; - -void objectByte(ubyte b) { - debug { - objectBuffer ~= b; - } else { - assert(pass2); - if (!optionObject) return; - if (!objectStream.isOpen) { - objectStream = openOutputFile('o', "obx"); - if (!getOption('q')) - writeln("Writing object file..."); - } - try { - objectStream.write(cast(char) b); - } catch (Exception e) { - throw new AssemblyError("Error writing object file"); - } - } - objectBytes++; -} - -void objectWord(ushort w) { - objectByte(cast(ubyte) w); - objectByte(cast(ubyte) (w >> 8)); -} - -void putByte(ubyte b) { - if (inOpcode) { - if (instructionBegin) { - value = b; - instructionBegin = false; - } - return; - } - if (willSkip) { - assert(!pass2); - willSkip = false; - skipping = true; - } - if (skipping) { - assert(!pass2); - skipOffsets[$ - 1]++; - } - if (instructionBegin) { - repeatOffset = -2; - instructionBegin = false; - } - repeatOffset--; - if (optionFill && loadingOrigin >= 0 && loadingOrigin != loadOrigin) { - if (loadingOrigin > loadOrigin) - throw new AssemblyError("Can't fill from higher to lower memory location"); - if (pass2) { - while (loadingOrigin < loadOrigin) { - objectByte(0xff); - loadingOrigin++; - } - } - } - debug { - objectByte(b); - } - if (pass2) { - debug { - } else { - objectByte(b); - } - if (listingColumn < 29) { - listByte(b); - listingLine[listingColumn++] = ' '; - } else { - listingLine[29] = '+'; - listingColumn = 30; - } - } - if (optionHeaders) { - if (origin < 0) - throw new AssemblyError("No ORG specified"); - assert(blockIndex >= 0); - if (!pass2) { - blockEnds[blockIndex] = cast(ushort) loadOrigin; - } - } - if (origin >= 0) { - origin++; - loadingOrigin = ++loadOrigin; - } -} - -void putWord(ushort w) { - putByte(cast(ubyte) w); - putByte(cast(ubyte) (w >> 8)); -} - -void putCommand(ubyte b) { - putByte(b); - if (inOpcode) return; - switch (addrMode & AddrMode.STANDARD_MASK) { - case AddrMode.ACCUMULATOR: - break; - case AddrMode.IMMEDIATE: - case AddrMode.ZEROPAGE: - case AddrMode.ZEROPAGE_X: - case AddrMode.ZEROPAGE_Y: - case AddrMode.INDIRECT_X: - case AddrMode.INDIRECT_Y: - if (pass2 && (value < -0xff || value > 0xff)) - throw new AssemblyError("Value out of range"); - putByte(cast(ubyte) value); - break; - case AddrMode.ABSOLUTE: - case AddrMode.ABSOLUTE_X: - case AddrMode.ABSOLUTE_Y: - case AddrMode.INDIRECT: - putWord(cast(ushort) value); - break; - default: - assert(0); - } - switch (addrMode) { - case cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.INCREMENT): - case cast(AddrMode) (AddrMode.ZEROPAGE_X + AddrMode.INCREMENT): - putByte(0xe8); - break; - case cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.DECREMENT): - case cast(AddrMode) (AddrMode.ZEROPAGE_X + AddrMode.DECREMENT): - putByte(0xca); - break; - case cast(AddrMode) (AddrMode.ABSOLUTE_Y + AddrMode.INCREMENT): - case cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.INCREMENT): - case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT): - case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT + AddrMode.ZERO): - putByte(0xc8); - break; - case cast(AddrMode) (AddrMode.ABSOLUTE_Y + AddrMode.DECREMENT): - case cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.DECREMENT): - case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.DECREMENT): - case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.DECREMENT + AddrMode.ZERO): - putByte(0x88); - break; - default: - break; - } -} - -void noOpcode() { - if (inOpcode) - throw new AssemblyError("Can't get opcode of this"); -} - -void directive() { - noOpcode(); - if (repeating) - throw new AssemblyError("Can't repeat this directive"); - if (pairing) - throw new AssemblyError("Can't pair this directive"); -} - -void noRepeatSkipDirective() { - directive(); - if (willSkip) - throw new AssemblyError("Can't skip over this"); - repeatOffset = 0; -} - -void illegalAddrMode() { - throw new AssemblyError("Illegal addressing mode"); -} - -void addrModeForMove(int move) { - final switch (move) { - case 0: - readAddrMode(); - break; - case 1: - value = value1; - addrMode = addrMode1; - break; - case 2: - value = value2; - addrMode = addrMode2; - break; - } -} - -void assemblyAccumulator(ubyte b, ubyte prefix, int move) { - addrModeForMove(move); - if (prefix != 0) - putByte(prefix); - switch (addrMode & AddrMode.STANDARD_MASK) { - case AddrMode.ACCUMULATOR: - case AddrMode.INDIRECT: - illegalAddrMode(); - break; - case AddrMode.IMMEDIATE: - if (b == 0x80) { - // STA # - illegalAddrMode(); - } - putCommand(cast(ubyte) (b + 9)); - break; - case AddrMode.ABSOLUTE: - putCommand(cast(ubyte) (b + 0xd)); - break; - case AddrMode.ZEROPAGE: - putCommand(cast(ubyte) (b + 5)); - break; - case AddrMode.ABSOLUTE_X: - putCommand(cast(ubyte) (b + 0x1d)); - break; - case AddrMode.ZEROPAGE_X: - putCommand(cast(ubyte) (b + 0x15)); - break; - case AddrMode.ZEROPAGE_Y: - addrMode -= 1; - goto case AddrMode.ABSOLUTE_Y; - case AddrMode.ABSOLUTE_Y: - putCommand(cast(ubyte) (b + 0x19)); - break; - case AddrMode.INDIRECT_X: - if ((addrMode & AddrMode.ZERO) != 0) - putWord(0x00a2); - putCommand(cast(ubyte) (b + 1)); - break; - case AddrMode.INDIRECT_Y: - if ((addrMode & AddrMode.ZERO) != 0) - putWord(0x00a0); - putCommand(cast(ubyte) (b + 0x11)); - break; - default: - assert(0); - } -} - -void assemblyShift(ubyte b) { - readAddrMode(); - switch (addrMode & AddrMode.STANDARD_MASK) { - case AddrMode.ACCUMULATOR: - if (b == 0xc0 || b == 0xe0) { - // INC @, DEC @ - illegalAddrMode(); - } - putByte(cast(ubyte) (b + 0xa)); - break; - case AddrMode.ABSOLUTE: - putCommand(cast(ubyte) (b + 0xe)); - break; - case AddrMode.ZEROPAGE: - putCommand(cast(ubyte) (b + 6)); - break; - case AddrMode.ABSOLUTE_X: - putCommand(cast(ubyte) (b + 0x1e)); - break; - case AddrMode.ZEROPAGE_X: - putCommand(cast(ubyte) (b + 0x16)); - break; - default: - illegalAddrMode(); - } -} - -void assemblyCompareIndex(ubyte b) { - readAddrMode(); - switch (addrMode) { - case AddrMode.IMMEDIATE: - putCommand(b); - break; - case AddrMode.ABSOLUTE: - putCommand(cast(ubyte) (b + 0xc)); - break; - case AddrMode.ZEROPAGE: - putCommand(cast(ubyte) (b + 4)); - break; - default: - illegalAddrMode(); - } -} - -void assemblyLda(int move) { - assemblyAccumulator(0xa0, 0, move); -} - -void assemblyLdx(int move) { - addrModeForMove(move); - switch (addrMode & AddrMode.STANDARD_MASK) { - case AddrMode.IMMEDIATE: - putCommand(0xa2); - break; - case AddrMode.ABSOLUTE: - putCommand(0xae); - break; - case AddrMode.ZEROPAGE: - putCommand(0xa6); - break; - case AddrMode.ABSOLUTE_Y: - putCommand(0xbe); - break; - case AddrMode.ZEROPAGE_Y: - putCommand(0xb6); - break; - default: - illegalAddrMode(); - } -} - -void assemblyLdy(int move) { - addrModeForMove(move); - switch (addrMode & AddrMode.STANDARD_MASK) { - case AddrMode.IMMEDIATE: - putCommand(0xa0); - break; - case AddrMode.ABSOLUTE: - putCommand(0xac); - break; - case AddrMode.ZEROPAGE: - putCommand(0xa4); - break; - case AddrMode.ABSOLUTE_X: - putCommand(0xbc); - break; - case AddrMode.ZEROPAGE_X: - putCommand(0xb4); - break; - default: - illegalAddrMode(); - } -} - -void assemblySta(int move) { - assemblyAccumulator(0x80, 0, move); -} - -void assemblyStx(int move) { - addrModeForMove(move); - switch (addrMode & AddrMode.STANDARD_MASK) { - case AddrMode.ABSOLUTE: - putCommand(0x8e); - break; - case AddrMode.ZEROPAGE: - putCommand(0x86); - break; - case AddrMode.ABSOLUTE_Y: - addrMode += 1; - goto case AddrMode.ZEROPAGE_Y; - case AddrMode.ZEROPAGE_Y: - putCommand(0x96); - break; - default: - illegalAddrMode(); - } -} - -void assemblySty(int move) { - addrModeForMove(move); - switch (addrMode & AddrMode.STANDARD_MASK) { - case AddrMode.ABSOLUTE: - putCommand(0x8c); - break; - case AddrMode.ZEROPAGE: - putCommand(0x84); - break; - case AddrMode.ABSOLUTE_X: - addrMode += 1; - goto case AddrMode.ZEROPAGE_X; - case AddrMode.ZEROPAGE_X: - putCommand(0x94); - break; - default: - illegalAddrMode(); - } -} - -void assemblyBit() { - readAddrMode(); - switch (addrMode) { - case AddrMode.ABSOLUTE: - putCommand(0x2c); - break; - case AddrMode.ZEROPAGE: - putCommand(0x24); - break; - default: - illegalAddrMode(); - } -} - -void putJump() { - switch (addrMode) { - case AddrMode.ZEROPAGE: - addrMode = AddrMode.ABSOLUTE; - goto case AddrMode.ABSOLUTE; - case AddrMode.ABSOLUTE: - putCommand(0x4c); - break; - case AddrMode.INDIRECT: - if (pass2 && (value & 0xff) == 0xff) - warning("Buggy indirect jump"); - putCommand(0x6c); - break; - default: - illegalAddrMode(); - } -} - -void assemblyJmp() { - readAddrMode(); - putJump(); -} - -void assemblyConditionalJump(ubyte b) { - noOpcode(); - readAddrMode(); - if ((addrMode == AddrMode.ABSOLUTE || addrMode == AddrMode.ZEROPAGE) - && pass2 && origin >= 0 && value - origin - 2 >= -0x80 && value - origin - 2 <= 0x7f) { - warning("Plain branch instruction would be sufficient"); - } - putByte(b); - putByte(3); - putJump(); -} - -void assemblyJsr() { - readAbsoluteAddrMode(); - putCommand(0x20); -} - -ubyte calculateBranch(int offset) { - if (offset < -0x80 || offset > 0x7f) { - int dist = offset < 0 ? -offset - 0x80 : offset - 0x7f; - throw new AssemblyError("Branch out of range by " ~ to!string(dist) ~ " bytes"); - } - return cast(ubyte) offset; -} - -void assemblyBranch(ubyte b) { - readAbsoluteAddrMode(); - if (inOpcode) { - putByte(b); - return; - } - checkOriginDefined(); - putByte(b); - putByte(pass2 ? calculateBranch(value - origin - 1) : 0); -} - -void assemblyRepeat(ubyte b) { - noOpcode(); - int offset = repeatOffset; - if (offset >= 0) - throw new AssemblyError("No instruction to repeat"); - if (pass2 && wereManyInstructions) - warning("Repeating only the last instruction"); - putByte(b); - putByte(calculateBranch(offset)); -} - -void assemblySkip(ubyte b) { - noOpcode(); - if (willSkip) { - skipOffsets[$ - 1] = 2; - willSkip = false; - } - putByte(b); - if (pass2) - putByte(calculateBranch(skipOffsets[skipOffsetsIndex++])); - else { - putByte(0); - skipOffsets ~= 0; - willSkip = true; - } -} - -void assemblyInw() { - noOpcode(); - readAddrMode(); - switch (addrMode) { - case AddrMode.ABSOLUTE: - putCommand(0xee); - putWord(0x03d0); - value++; - putCommand(0xee); - break; - case AddrMode.ZEROPAGE: - putCommand(0xe6); - putWord(0x02d0); - value++; - putCommand(0xe6); - break; - case AddrMode.ABSOLUTE_X: - putCommand(0xfe); - putWord(0x03d0); - value++; - putCommand(0xfe); - break; - case AddrMode.ZEROPAGE_X: - putCommand(0xf6); - putWord(0x02d0); - value++; - putCommand(0xf6); - break; - default: - illegalAddrMode(); - } -} - -void assemblyMove() { - noOpcode(); - readAddrMode(); - value1 = value; - addrMode1 = addrMode; - bool unknown1 = unknownInPass1; - readAddrMode(); - value2 = value; - addrMode2 = addrMode; - unknownInPass1 = unknown1; -} - -void assemblyMoveByte(MoveFunction load, MoveFunction store) { - assemblyMove(); - load(1); - store(2); -} - -void assemblyMoveWord(MoveFunction load, MoveFunction store, ubyte inc, ubyte dec) { - assemblyMove(); - switch (addrMode2) { - case AddrMode.ABSOLUTE: - case AddrMode.ZEROPAGE: - case AddrMode.ABSOLUTE_X: - case AddrMode.ZEROPAGE_X: - case AddrMode.ABSOLUTE_Y: - case AddrMode.ZEROPAGE_Y: - break; - default: - illegalAddrMode(); - } - switch (addrMode1) { - case AddrMode.IMMEDIATE: - int high = value1 >> 8; - value1 &= 0xff; - load(1); - store(2); - value2++; - if (unknownInPass1) { - value1 = high; - load(1); - } else { - if (inc != 0 && cast(ubyte) (value1 + 1) == high) - putByte(inc); - else if (dec != 0 && cast(ubyte) (value1 - 1) == high) - putByte(dec); - else if (value1 != high) { - value1 = high; - load(1); - } - } - store(2); - break; - case AddrMode.ABSOLUTE: - case AddrMode.ZEROPAGE: - case AddrMode.ABSOLUTE_X: - case AddrMode.ZEROPAGE_X: - case AddrMode.ABSOLUTE_Y: - case AddrMode.ZEROPAGE_Y: - load(1); - store(2); - value1++; - value2++; - load(1); - store(2); - break; - default: - illegalAddrMode(); - } -} - -void storeDtaNumber(int val, char letter) { - int limit = 0xffff; - if (letter == 'b') limit = 0xff; - if ((!unknownInPass1 || pass2) && (val < -limit || val > limit)) - throw new AssemblyError("Value out of range"); - final switch (letter) { - case 'a': - putWord(cast(ushort) val); - break; - case 'b': - case 'l': - putByte(cast(ubyte) val); - break; - case 'h': - putByte(cast(ubyte) (val >> 8)); - break; - } -} - -void assemblyDtaInteger(char letter) { - if (readFunction() == "SIN") { - readWord(); - int sinCenter = value; - readComma(); - readWord(); - int sinAmp = value; - readComma(); - readKnownPositive(); - int sinPeriod = value; - int sinMin = 0; - int sinMax = sinPeriod - 1; - switch (readChar()) { - case ')': - break; - case ',': - readUnsignedWord(); - mustBeKnownInPass1(); - sinMin = value; - readComma(); - readUnsignedWord(); - mustBeKnownInPass1(); - sinMax = value; - if (readChar() != ')') { - illegalCharacter(); - } - break; - default: - illegalCharacter(); - } - while (sinMin <= sinMax) { - int val = sinCenter + cast(int) rint(sinAmp * sin(sinMin * 2 * PI / sinPeriod)); - storeDtaNumber(val, letter); - sinMin++; - } - return; - } - readWord(); - storeDtaNumber(value, letter); -} - -bool realSign; - -int realExponent; - -long realMantissa; - -void putReal() { - if (realMantissa == 0) { - putWord(0); - putWord(0); - putWord(0); - return; - } - while (realMantissa < 0x1000000000L) { - realMantissa <<= 4; - realExponent--; - } - if ((realExponent & 1) != 0) { - if (realMantissa & 0xf) - throw new AssemblyError("Out of precision"); - realMantissa >>= 4; - } - realExponent = (realExponent + 0x89) >> 1; - if (realExponent < 64 - 49) - throw new AssemblyError("Out of precision"); - if (realExponent > 64 + 49) - throw new AssemblyError("Number too big"); - putByte(cast(ubyte) (realSign ? realExponent + 0x80 : realExponent)); - putByte(cast(ubyte) (realMantissa >> 32)); - putByte(cast(ubyte) (realMantissa >> 24)); - putByte(cast(ubyte) (realMantissa >> 16)); - putByte(cast(ubyte) (realMantissa >> 8)); - putByte(cast(ubyte) realMantissa); -} - -bool readSign() { - switch (readChar()) { - case '+': - return false; - case '-': - return true; - default: - column--; - return false; - } -} - -void readExponent() { - bool sign = readSign(); - char c = readChar(); - if (c < '0' || c > '9') - illegalCharacter(); - int e = c - '0'; - c = readChar(); - if (c >= '0' && c <= '9') - e = 10 * e + c - '0'; - else - column--; - realExponent += sign ? -e : e; - putReal(); -} - -void readFraction() { - for (;;) { - char c = readChar(); - if (c >= '0' && c <= '9') { - if (c != '0' && realMantissa >= 0x1000000000L) - throw new AssemblyError("Out of precision"); - realMantissa <<= 4; - realMantissa += c - '0'; - realExponent--; - continue; - } - if (c == 'E' || c == 'e') { - readExponent(); - return; - } - column--; - putReal(); - return; - } -} - -void assemblyDtaReal() { - realSign = readSign(); - realExponent = 0; - realMantissa = 0; - char c = readChar(); - if (c == '.') { - readFraction(); - return; - } - if (c < '0' || c > '9') - illegalCharacter(); - do { - if (realMantissa < 0x1000000000L) { - realMantissa <<= 4; - realMantissa += c - '0'; - } else { - if (c != '0') - throw new AssemblyError("Out of precision"); - realExponent++; - } - c = readChar(); - } while (c >= '0' && c <= '9'); - switch (c) { - case '.': - readFraction(); - break; - case 'E': - case 'e': - readExponent(); - break; - default: - column--; - putReal(); - break; - } -} - -void assemblyDtaNumbers(char letter) { - if (eol() || line[column] != '(') { - column--; - assemblyDtaInteger('b'); - return; - } - column++; - for (;;) { - final switch (letter) { - case 'a': - case 'b': - case 'h': - case 'l': - assemblyDtaInteger(letter); - break; - case 'r': - assemblyDtaReal(); - break; - } - switch (readChar()) { - case ')': - return; - case ',': - break; - default: - illegalCharacter(); - } - } -} - -void assemblyDta() { - noOpcode(); - readSpaces(); - for (;;) { - switch (readChar()) { - case 'A': - case 'a': - assemblyDtaNumbers('a'); - break; - case 'B': - case 'b': - assemblyDtaNumbers('b'); - break; - case 'C': - case 'c': - ubyte[] s = readString(); - if (s is null) { - column--; - assemblyDtaInteger('b'); - break; - } - foreach (ubyte b; s) { - putByte(b); - } - break; - case 'D': - case 'd': - ubyte[] s = readString(); - if (s is null) { - column--; - assemblyDtaInteger('b'); - break; - } - foreach (ubyte b; s) { - final switch (b & 0x60) { - case 0x00: - putByte(cast(ubyte) (b + 0x40)); - break; - case 0x20: - case 0x40: - putByte(cast(ubyte) (b - 0x20)); - break; - case 0x60: - putByte(b); - break; - } - } - break; - case 'H': - case 'h': - assemblyDtaNumbers('h'); - break; - case 'L': - case 'l': - assemblyDtaNumbers('l'); - break; - case 'R': - case 'r': - assemblyDtaNumbers('r'); - break; - default: - column--; - assemblyDtaInteger('b'); - break; - } - if (eol() || line[column] != ',') - break; - column++; - } -} - -void assemblyEqu() { - directive(); - if (currentLabel is null) - throw new AssemblyError("Label name required"); - currentLabel.value = 0; - readSpaces(); - readValue(); - currentLabel.value = value; - currentLabel.unknownInPass1 = unknownInPass1; - if (optionListing) { - listingLine[6] = '='; - int val = value; - listingLine[7] = ' '; - if (val < 0) { - listingLine[7] = '-'; - val = -val; - } - listingColumn = 8; - if ((val & 0xffff0000) != 0) - listWord(cast(ushort) (val >> 16)); - else { - while (listingColumn < 12) - listingLine[listingColumn++] = ' '; - } - listWord(cast(ushort) val); - } -} - -void assemblyEnd() { - directive(); - assert(!foundEnd); - foundEnd = true; -} - -void assemblyIftEli() { - ifContexts[$ - 1].condition = true; - if (!inFalseCondition()) { - readSpaces(); - readValue(); - mustBeKnownInPass1(); - if (value != 0) - ifContexts[$ - 1].aConditionMatched = true; - else - listLine(); - ifContexts[$ - 1].condition = value != 0; - } -} - -void checkMissingIft() { - if (ifContexts.length == 0) - throw new AssemblyError("Missing IFT"); -} - -void assemblyIft() { - directive(); - ifContexts.length++; - assemblyIftEli(); -} - -void assemblyEliEls() { - directive(); - checkMissingIft(); - if (ifContexts[$ - 1].wasElse) - throw new AssemblyError("EIF expected"); -} - -void assemblyEli() { - assemblyEliEls(); - if (ifContexts[$ - 1].aConditionMatched) { - ifContexts[$ - 1].condition = false; - return; - } - assemblyIftEli(); -} - -void assemblyEls() { - assemblyEliEls(); - with (ifContexts[$ - 1]) { - if (condition && aConditionMatched) - listLine(); - wasElse = true; - condition = !aConditionMatched; - } -} - -void assemblyEif() { - directive(); - checkMissingIft(); - ifContexts.length--; -} - -void assemblyErt() { - directive(); - readSpaces(); - readValue(); - if (pass2 && value != 0) - throw new AssemblyError("User-defined error"); -} - -bool readOption() { - switch (readChar()) { - case '-': - return false; - case '+': - return true; - default: - illegalCharacter(); - } - assert(0); -} - -void assemblyOpt() { - directive(); - readSpaces(); - while (!eol()) { - switch (line[column++]) { - case 'F': - case 'f': - optionFill = readOption(); - break; - case 'G': - case 'g': - option5200 = readOption(); - break; - case 'H': - case 'h': - optionHeaders = readOption(); - break; - case 'L': - case 'l': - optionListing = readOption() && !pass2; - break; - case 'O': - case 'o': - optionObject = readOption(); - break; - case 'U': - case 'u': - optionUnusedLabels = readOption(); - break; - default: - column--; - return; - } - } -} - -void originWord(ushort value, char listingChar) { - objectWord(value); - listWord(value); - listingLine[listingColumn++] = listingChar; -} - -OrgModifier readOrgModifier() { - readSpaces(); - if (column + 2 < line.length && line[column + 1] == ':') { - switch (line[column]) { - case 'F': - case 'f': - checkHeadersOn(); - column += 2; - return OrgModifier.FORCE_FFFF; - case 'A': - case 'a': - checkHeadersOn(); - column += 2; - return OrgModifier.FORCE_HEADER; - case 'R': - case 'r': - column += 2; - return OrgModifier.RELOCATE; - default: - break; - } - } - return OrgModifier.NONE; -} - -void setOrigin(int addr, OrgModifier modifier) { - origin = loadOrigin = addr; - bool requestedHeader = modifier != OrgModifier.NONE; - if (requestedHeader || loadingOrigin < 0 || (addr != loadingOrigin && !optionFill)) { - blockIndex++; - if (!pass2) { - assert(blockIndex == blockEnds.length); - blockEnds ~= cast(ushort) (addr - 1); - } - if (pass2 && optionHeaders) { - if (addr - 1 == blockEnds[blockIndex]) { - if (requestedHeader) - throw new AssemblyError("Cannot generate an empty block"); - return; - } - if (modifier == OrgModifier.FORCE_FFFF || objectBytes == 0) { - assert(requestedHeader || addr != loadingOrigin); - originWord(0xffff, '>'); - listingLine[listingColumn++] = ' '; - } - if (requestedHeader || addr != loadingOrigin) { - originWord(cast(ushort) addr, '-'); - originWord(blockEnds[blockIndex], '>'); - listingLine[listingColumn++] = ' '; - loadingOrigin = -1; - } - } - } -} - -void checkHeadersOn() { - if (!optionHeaders) - throw new AssemblyError("Illegal when Atari file headers disabled"); -} - -void assemblyOrg() { - noRepeatSkipDirective(); - OrgModifier modifier = readOrgModifier(); - readUnsignedWord(); - mustBeKnownInPass1(); - if (modifier == OrgModifier.RELOCATE) { - checkOriginDefined(); - origin = value; - } - else { - setOrigin(value, modifier); - } -} - -void assemblyRunIni(ushort addr) { - noRepeatSkipDirective(); - checkHeadersOn(); - loadingOrigin = -1; // don't fill - OrgModifier modifier = readOrgModifier(); - if (modifier == OrgModifier.RELOCATE) - throw new AssemblyError("r: invalid here"); - setOrigin(addr, modifier); - readUnsignedWord(); - putWord(cast(ushort) (value)); - loadingOrigin = -1; // don't fill -} - -void assemblyIcl() { - directive(); - string filename = readFilename(); - checkNoExtraCharacters(); - listLine(); - includeLevel++; - assemblyFile(filename); - includeLevel--; - line = null; -} - -void assemblyIns() { - string filename = readFilename(); - int offset = 0; - int length = -1; - if (!eol() && line[column] == ',') { - column++; - readValue(); - mustBeKnownInPass1(); - offset = value; - if (!eol() && line[column] == ',') { - column++; - readKnownPositive(); - length = value; - } - } - File stream = openInputFile(filename); - scope (exit) stream.close(); - try { - stream.seek(offset, offset >= 0 ? SEEK_SET : SEEK_END); - } catch (Exception e) { - throw new AssemblyError("Error seeking file"); - } - if (inOpcode) - length = 1; - while (length != 0) { - int b = readByte(&stream); - if (b < 0) { - if (length > 0) - throw new AssemblyError("File is too short"); - break; - } - putByte(cast(ubyte) b); - if (length > 0) length--; - } -} - -void assemblyInstruction(string instruction) { - if (!inOpcode && origin < 0 && currentLabel !is null && instruction != "EQU") - throw new AssemblyError("No ORG specified"); - instructionBegin = true; - switch (instruction) { - case "ADC": - assemblyAccumulator(0x60, 0, 0); - break; - case "ADD": - assemblyAccumulator(0x60, 0x18, 0); - break; - case "AND": - assemblyAccumulator(0x20, 0, 0); - break; - case "ASL": - assemblyShift(0x00); - break; - case "BCC": - assemblyBranch(0x90); - break; - case "BCS": - assemblyBranch(0xb0); - break; - case "BEQ": - assemblyBranch(0xf0); - break; - case "BIT": - assemblyBit(); - break; - case "BMI": - assemblyBranch(0x30); - break; - case "BNE": - assemblyBranch(0xd0); - break; - case "BPL": - assemblyBranch(0x10); - break; - case "BRK": - putByte(0x00); - break; - case "BVC": - assemblyBranch(0x50); - break; - case "BVS": - assemblyBranch(0x70); - break; - case "CLC": - putByte(0x18); - break; - case "CLD": - putByte(0xd8); - break; - case "CLI": - putByte(0x58); - break; - case "CLV": - putByte(0xb8); - break; - case "CMP": - assemblyAccumulator(0xc0, 0, 0); - break; - case "CPX": - assemblyCompareIndex(0xe0); - break; - case "CPY": - assemblyCompareIndex(0xc0); - break; - case "DEC": - assemblyShift(0xc0); - break; - case "DEX": - putByte(0xca); - break; - case "DEY": - putByte(0x88); - break; - case "DTA": - assemblyDta(); - break; - case "EIF": - assemblyEif(); - break; - case "ELI": - assemblyEli(); - break; - case "ELS": - assemblyEls(); - break; - case "END": - assemblyEnd(); - break; - case "EOR": - assemblyAccumulator(0x40, 0, 0); - break; - case "EQU": - assemblyEqu(); - break; - case "ERT": - assemblyErt(); - break; - case "ICL": - assemblyIcl(); - break; - case "IFT": - assemblyIft(); - break; - case "INC": - assemblyShift(0xe0); - break; - case "INI": - assemblyRunIni(0x2e2); - break; - case "INS": - assemblyIns(); - break; - case "INX": - putByte(0xe8); - break; - case "INY": - putByte(0xc8); - break; - case "INW": - assemblyInw(); - break; - case "JCC": - assemblyConditionalJump(0xb0); - break; - case "JCS": - assemblyConditionalJump(0x90); - break; - case "JEQ": - assemblyConditionalJump(0xd0); - break; - case "JMI": - assemblyConditionalJump(0x10); - break; - case "JMP": - assemblyJmp(); - break; - case "JNE": - assemblyConditionalJump(0xf0); - break; - case "JPL": - assemblyConditionalJump(0x30); - break; - case "JSR": - assemblyJsr(); - break; - case "JVC": - assemblyConditionalJump(0x70); - break; - case "JVS": - assemblyConditionalJump(0x50); - break; - case "LDA": - assemblyAccumulator(0xa0, 0, 0); - break; - case "LDX": - assemblyLdx(0); - break; - case "LDY": - assemblyLdy(0); - break; - case "LSR": - assemblyShift(0x40); - break; - case "MVA": - assemblyMoveByte(&assemblyLda, &assemblySta); - break; - case "MVX": - assemblyMoveByte(&assemblyLdx, &assemblyStx); - break; - case "MVY": - assemblyMoveByte(&assemblyLdy, &assemblySty); - break; - case "MWA": - assemblyMoveWord(&assemblyLda, &assemblySta, 0, 0); - break; - case "MWX": - assemblyMoveWord(&assemblyLdx, &assemblyStx, 0xe8, 0xca); - break; - case "MWY": - assemblyMoveWord(&assemblyLdy, &assemblySty, 0xc8, 0x88); - break; - case "NOP": - putByte(0xea); - break; - case "OPT": - assemblyOpt(); - break; - case "ORA": - assemblyAccumulator(0x00, 0, 0); - break; - case "ORG": - assemblyOrg(); - break; - case "PHA": - putByte(0x48); - break; - case "PHP": - putByte(0x08); - break; - case "PLA": - putByte(0x68); - break; - case "PLP": - putByte(0x28); - break; - case "RCC": - assemblyRepeat(0x90); - break; - case "RCS": - assemblyRepeat(0xb0); - break; - case "REQ": - assemblyRepeat(0xf0); - break; - case "RMI": - assemblyRepeat(0x30); - break; - case "RNE": - assemblyRepeat(0xd0); - break; - case "ROL": - assemblyShift(0x20); - break; - case "ROR": - assemblyShift(0x60); - break; - case "RPL": - assemblyRepeat(0x10); - break; - case "RTI": - putByte(0x40); - break; - case "RTS": - putByte(0x60); - break; - case "RUN": - assemblyRunIni(0x2e0); - break; - case "RVC": - assemblyRepeat(0x50); - break; - case "RVS": - assemblyRepeat(0x70); - break; - case "SBC": - assemblyAccumulator(0xe0, 0, 0); - break; - case "SCC": - assemblySkip(0x90); - break; - case "SCS": - assemblySkip(0xb0); - break; - case "SEC": - putByte(0x38); - break; - case "SED": - putByte(0xf8); - break; - case "SEI": - putByte(0x78); - break; - case "SEQ": - assemblySkip(0xf0); - break; - case "SMI": - assemblySkip(0x30); - break; - case "SNE": - assemblySkip(0xd0); - break; - case "SPL": - assemblySkip(0x10); - break; - case "STA": - assemblyAccumulator(0x80, 0, 0); - break; - case "STX": - assemblyStx(0); - break; - case "STY": - assemblySty(0); - break; - case "SUB": - assemblyAccumulator(0xe0, 0x38, 0); - break; - case "SVC": - assemblySkip(0x50); - break; - case "SVS": - assemblySkip(0x70); - break; - case "TAX": - putByte(0xaa); - break; - case "TAY": - putByte(0xa8); - break; - case "TSX": - putByte(0xba); - break; - case "TXA": - putByte(0x8a); - break; - case "TXS": - putByte(0x9a); - break; - case "TYA": - putByte(0x98); - break; - default: - throw new AssemblyError("Illegal instruction"); - } - skipping = false; -} - -debug ubyte[] testInstruction(string l) { - objectBuffer.length = 0; - line = l; - column = 0; - assemblyInstruction(readInstruction()); - write(line, " assembles to"); - foreach (ubyte b; objectBuffer) - writef(" %02x", b); - writeln(); - return objectBuffer; -} - -unittest { - assert(testInstruction("nop") == cast(ubyte[]) hexString!"ea"); - assert(testInstruction("add (5,0)") == cast(ubyte[]) hexString!"18a2006105"); - assert(testInstruction("mwa #$abcd $1234") == cast(ubyte[]) hexString!"a9cd8d3412a9ab8d3512"); - assert(testInstruction("dta 5,d'Foo'*,a($4589)") == cast(ubyte[]) hexString!"05a6efef8945"); - assert(testInstruction("dta r(1,12,123,1234567890,12345678900000,.5,.03,000.1664534589,1e97)") - == cast(ubyte[]) hexString!"400100000000 401200000000 410123000000 441234567890 461234567890 3f5000000000 3f0300000000 3f1664534589 701000000000"); -} - -void assemblyPair() { - assert(!inOpcode); - string instruction = readInstruction(); - if (!eol() && line[column] == ':') { - pairing = true; - column++; - string instruction2 = readInstruction(); - int savedColumn = column; - if (willSkip) - warning("Skipping only the first instruction"); - assemblyInstruction(instruction); - checkNoExtraCharacters(); - column = savedColumn; - wereManyInstructions = false; - assemblyInstruction(instruction2); - wereManyInstructions = true; - } else { - pairing = false; - assemblyInstruction(instruction); - wereManyInstructions = false; - } -} - -void assemblyLine() { - debug { - writeln(line); - } - lineNo++; - totalLines++; - column = 0; - listingColumn = 6; - if (origin >= 0) { - listWord(cast(ushort) origin); - listingLine[listingColumn++] = ' '; - } - string label = readLabel(); - currentLabel = null; - if (label !is null) { - if (!inFalseCondition()) { - if (!pass2) { - if (label in labelTable) - throw new AssemblyError("Label declared twice"); - currentLabel = new Label(origin); - labelTable[label] = currentLabel; - } else { - assert(label in labelTable); - currentLabel = labelTable[label]; - currentLabel.passed = true; - if (currentLabel.unused && getOption('u') && optionUnusedLabels) - warning("Unused label: " ~ label); - } - } - if (eol()) { - listCommentLine(); - return; - } - readSpaces(); - } - commentOrRep: for (;;) { - if (eol()) { - listCommentLine(); - return; - } - switch (line[column]) { - case '\t': - case ' ': - column++; - continue; - case '*': - case ';': - case '|': - listCommentLine(); - return; - case ':': - if (inFalseCondition()) { - listCommentLine(); - return; - } - column++; - readUnsignedWord(); - mustBeKnownInPass1(); - int repeatLimit = value; - if (repeatLimit == 0) { - listCommentLine(); - return; - } - readSpaces(); - repeating = true; - if (repeatLimit == 1) - break; - if (willSkip) - warning("Skipping only the first instruction"); - int savedColumn = column; - for (repeatCounter = 0; repeatCounter < repeatLimit; repeatCounter++) { - column = savedColumn; - assemblyPair(); - } - checkNoExtraCharacters(); - listLine(); - wereManyInstructions = true; - return; - default: - repeating = false; - break commentOrRep; - } - } - if (inFalseCondition()) { - switch (readInstruction()) { - case "END": - assemblyEnd(); - break; - case "IFT": - assemblyIft(); - break; - case "ELI": - assemblyEli(); - break; - case "ELS": - assemblyEls(); - break; - case "EIF": - assemblyEif(); - break; - default: - listCommentLine(); - return; - } - checkNoExtraCharacters(); - listCommentLine(); - return; - } - assemblyPair(); - checkNoExtraCharacters(); - listLine(); -} - -void assemblyFile(string filename) { - filename = filename.defaultExtension("asx"); - if (getOption('p')) - filename = absolutePath(filename); - File stream = openInputFile(filename); - scope (exit) stream.close(); - string oldFilename = currentFilename; - int oldLineNo = lineNo; - currentFilename = filename; - lineNo = 0; - foundEnd = false; - line = ""; - readChar: while (!foundEnd) { - int b = readByte(&stream); - switch (b) { - case -1: - break readChar; - case '\r': - assemblyLine(); - line = ""; - b = readByte(&stream); - if (b < 0) - break readChar; - if (b != '\n') - line ~= cast(char) b; - break; - case '\n': - case 0x9b: - assemblyLine(); - line = ""; - break; - default: - line ~= cast(char) b; - break; - } - } - if (!foundEnd) - assemblyLine(); - foundEnd = false; - currentFilename = oldFilename; - lineNo = oldLineNo; -} - -void assemblyPass() { - origin = -1; - loadOrigin = -1; - loadingOrigin = -1; - blockIndex = -1; - optionFill = false; - option5200 = false; - optionHeaders = true; - optionListing = pass2; - optionObject = true; - optionUnusedLabels = true; - willSkip = false; - skipping = false; - repeatOffset = 0; - wereManyInstructions = false; - currentFilename = "command line"; - lineNo = 0; - foreach (definition; commandLineDefinitions) { - line = replaceFirst(definition, "=", " equ "); - assemblyLine(); - } - line = null; - totalLines = 0; - assemblyFile(sourceFilename); - if (ifContexts.length != 0) - throw new AssemblyError("Missing EIF"); - if (willSkip) - throw new AssemblyError("Can't skip over this"); -} - -pure bool isOption(string arg) { - if (arg.length < 2) return false; - if (arg[0] == '-') return true; - if (arg[0] != '/') return false; - if (arg.length == 2) return true; - if (arg[2] == ':') return true; - return false; -} - -void setOption(char letter) { - assert(letter >= 'a' && letter <= 'z'); - if (options[letter - 'a']) { - exitCode = 3; - return; - } - options[letter - 'a'] = true; -} - -int main(string[] args) { - for (int i = 1; i < args.length; i++) { - string arg = args[i]; - if (isOption(arg)) { - char letter = arg[1]; - if (letter >= 'A' && letter <= 'Z') - letter += 'a' - 'A'; - switch (letter) { - case 'c': - case 'i': - case 'm': - case 'p': - case 'q': - case 'u': - if (arg.length != 2) - exitCode = 3; - setOption(letter); - break; - case 'd': - string definition = null; - if (arg[0] == '/') { - if (arg.length >= 3 && arg[2] == ':') - definition = arg[3 .. $]; - } else if (i + 1 < args.length && !isOption(args[i + 1])) - definition = args[++i]; - if (definition is null || find(definition, '=').empty) - exitCode = 3; - commandLineDefinitions ~= definition; - break; - case 'l': - case 't': - case 'o': - setOption(letter); - string filename = null; - if (arg[0] == '/') { - if (arg.length >= 3 && arg[2] == ':') - filename = arg[3 .. $]; - } else if (i + 1 < args.length && !isOption(args[i + 1])) - filename = args[++i]; - if (filename is null && (letter == 'o' || arg.length != 2)) - exitCode = 3; - optionParameters[letter - 'a'] = filename; - break; - default: - exitCode = 3; - break; - } - continue; - } - if (sourceFilename !is null) - exitCode = 3; - sourceFilename = arg; - } - if (sourceFilename is null) - exitCode = 3; - if (!getOption('q')) - writeln(TITLE); - if (exitCode != 0) { - write( -`Syntax: xasm source [options] -/c Include false conditionals in listing -/d:label=value Define a label -/i Don't list included files -/l[:filename] Generate listing -/o:filename Set object file name -/M Print Makefile rule -/p Print absolute paths in listing and error messages -/q Suppress info messages -/t[:filename] List label table -/u Warn of unused labels -`); - return exitCode; - } - try { - assemblyPass(); - pass2 = true; - assemblyPass(); - if (getOption('t') && labelTable.length > 0) - listLabelTable(); - } catch (AssemblyError e) { - warning(e.msg, true); - exitCode = 2; - } - listingStream.close(); - objectStream.close(); - if (exitCode <= 1) { - if (!getOption('q')) { - writefln("%d lines of source assembled", totalLines); - if (objectBytes > 0) - writefln("%d bytes written to the object file", objectBytes); - } - if (getOption('m')) { - writef("%s:", makeTarget); - foreach (filename; makeSources) - writef(" %s", makeEscape(filename)); - write("\n\txasm"); - for (int i = 1; i < args.length; i++) { - string arg = args[i]; - if (isOption(arg)) { - char letter = arg[1]; - if (letter >= 'A' && letter <= 'Z') - letter += 'a' - 'A'; - switch (letter) { - case 'm': - break; - case 'o': - if (arg[0] == '/') - writef(" /%c:$@", arg[1]); - else { - writef(" -%c $@", arg[1]); - ++i; - } - break; - default: - if (arg[0] == '-' - && (letter == 'd' || letter == 'l' || letter == 't') - && i + 1 < args.length && !isOption(args[i + 1])) { - writef(" %s %s", arg, makeEscape(args[++i])); - } - else { - writef(" %s", makeEscape(arg)); - } - break; - } - continue; - } - write(" $<"); - } - writeln(); - } - } - return exitCode; -} +// xasm 3.1.0 by Piotr Fusik +// http://xasm.atari.org +// Can be compiled with DMD v2.087.1. + +// Poetic License: +// +// This work 'as-is' we provide. +// No warranty express or implied. +// We've done our best, +// to debug and test. +// Liability for damages denied. +// +// Permission is granted hereby, +// to copy, share, and modify. +// Use as is fit, +// free or for profit. +// These rights, on this notice, rely. + +import std.algorithm; +import std.array; +import std.conv; +import std.math; +import std.path; +import std.stdio; + +version (Windows) { + import core.sys.windows.windows; +} + +int readByte(File *file) { + char c; + if (file.readf("%c", &c) != 1) + return -1; + return c; +} + +const string TITLE = "xasm 3.1.0"; + +string sourceFilename = null; +bool[26] options; +string[26] optionParameters; +string[] commandLineDefinitions = null; +string makeTarget; +string[] makeSources = null; + +int exitCode = 0; + +int totalLines; + +bool pass2 = false; + +bool optionFill; // opt f +bool option5200; // opt g +bool optionHeaders; // opt h +bool optionListing; // opt l +bool optionObject; // opt o +bool optionUnusedLabels; // opt u + +string currentFilename; +int lineNo; +int includeLevel = 0; +string line; +int column; + +bool foundEnd; + +class AssemblyError : Exception { + this(string msg) { + super(msg); + } +} + +class Label { + int value; + bool unused = true; + bool unknownInPass1 = false; + bool passed = false; + + this(int value) { + this.value = value; + } +} + +Label[string] labelTable; +Label currentLabel; + +alias int function(int a, int b) OperatorFunction; + +bool inOpcode = false; + +struct ValOp { + int value; + OperatorFunction func; + int priority; +} + +ValOp[] valOpStack; + +int value; +bool unknownInPass1; + +enum AddrMode { + ACCUMULATOR = 0, + IMMEDIATE = 1, + ABSOLUTE = 2, + ZEROPAGE = 3, + ABSOLUTE_X = 4, + ZEROPAGE_X = 5, + ABSOLUTE_Y = 6, + ZEROPAGE_Y = 7, + INDIRECT_X = 8, + INDIRECT_Y = 9, + INDIRECT = 10, + ABS_OR_ZP = 11, // temporarily used in readAddrMode + STANDARD_MASK = 15, + INCREMENT = 0x20, + DECREMENT = 0x30, + ZERO = 0x40 +} + +AddrMode addrMode; + +enum OrgModifier { + NONE, + FORCE_HEADER, + FORCE_FFFF, + RELOCATE +} + +int origin = -1; +int loadOrigin; +int loadingOrigin; +ushort[] blockEnds; +int blockIndex; + +bool repeating; // line +int repeatCounter; // line + +bool instructionBegin; +bool pairing; + +bool willSkip; +bool skipping; +ushort[] skipOffsets; +int skipOffsetsIndex = 0; + +int repeatOffset; // instruction repeat + +bool wereManyInstructions; + +alias void function(int move) MoveFunction; + +int value1; +AddrMode addrMode1; +int value2; +AddrMode addrMode2; + +struct IfContext { + bool condition; + bool wasElse; + bool aConditionMatched; +} + +IfContext[] ifContexts; + +File listingStream; +char[32] listingLine; +int listingColumn; +string lastListedFilename = null; + +File objectStream; + +int objectBytes = 0; + +bool getOption(char letter) { + assert(letter >= 'a' && letter <= 'z'); + return options[letter - 'a']; +} + +void warning(string msg, bool error = false) { + stdout.flush(); + version (Windows) { + HANDLE stderrHandle = GetStdHandle(STD_ERROR_HANDLE); + CONSOLE_SCREEN_BUFFER_INFO csbi; + GetConsoleScreenBufferInfo(stderrHandle, &csbi); + SetConsoleTextAttribute(stderrHandle, (csbi.wAttributes & ~0xf) | (error ? 12 : 14)); + scope (exit) SetConsoleTextAttribute(stderrHandle, csbi.wAttributes); + } + if (line !is null) + stderr.writeln(line); + stderr.writefln("%s (%d) %s: %s", currentFilename, lineNo, error ? "ERROR" : "WARNING", msg); + exitCode = 1; +} + +void illegalCharacter() { + throw new AssemblyError("Illegal character"); +} + +bool eol() { + return column >= line.length; +} + +char readChar() { + if (eol()) + throw new AssemblyError("Unexpected end of line"); + return line[column++]; +} + +int readDigit(int base) { + if (eol()) return -1; + int r = line[column]; + if (r >= '0' && r <= '9') { + r -= '0'; + } else { + r &= 0xdf; + if (r >= 'A' && r <= 'Z') + r -= 'A' - 10; + else + return -1; + } + if (r < base) { + column++; + return r; + } + return -1; +} + +int readNumber(int base) { + long r = readDigit(base); + if (r < 0) + illegalCharacter(); + do { + int d = readDigit(base); + if (d < 0) + return cast(int) r; + r = r * base + d; + } while (r <= 0x7fffffff); + throw new AssemblyError("Number too big"); +} + +void readSpaces() { + switch (readChar()) { + case '\t': + case ' ': + break; + default: + throw new AssemblyError("Space expected"); + } + while (!eol()) { + switch (line[column]) { + case '\t': + case ' ': + column++; + break; + default: + return; + } + } +} + +string readLabel() { + string label; + while (!eol()) { + char c = line[column++]; + if (c >= '0' && c <= '9' || c == '_') { + label ~= c; + continue; + } + c &= 0xdf; + if (c >= 'A' && c <= 'Z') { + label ~= c; + continue; + } + column--; + break; + } + return label >= "A" ? label : null; +} + +void readComma() { + if (readChar() != ',') + throw new AssemblyError("Bad or missing function parameter"); +} + +string readInstruction() { + string r; + for (int i = 0; i < 3; i++) { + char c = readChar() & 0xdf; + if (c < 'A' || c > 'Z') + throw new AssemblyError("Illegal instruction"); + r ~= c; + } + return r; +} + +string readFunction() { + if (column + 5 >= line.length) return null; + if (line[column + 3] != '(') return null; + string r; + for (int i = 0; i < 3; i++) { + char c = line[column + i] & 0xdf; + if (c < 'A' || c > 'Z') return null; + r ~= c; + } + column += 4; + return r; +} + +string readFilename() { + readSpaces(); + char delimiter = readChar(); + switch (delimiter) { + case '"': + case '\'': + string filename; + char c; + while ((c = readChar()) != delimiter) + filename ~= c; + return filename; + default: + illegalCharacter(); + } + assert(0); +} + +void readStringChar(char c) { + if (readChar() != c) + throw new AssemblyError("String error"); +} + +ubyte[] readString() { + if (eol()) return null; + char delimiter = readChar(); + switch (delimiter) { + case '"': + case '\'': + ubyte[] r; + for (;;) { + char c = readChar(); + if (c == delimiter) { + if (eol()) return r; + if (line[column] != delimiter) { + if (line[column] == '*') { + column++; + foreach (ref ubyte b; r) + b ^= 0x80; + } + return r; + } + column++; + } + r ~= cast(ubyte) c; + } + default: + column--; + return null; + } +} + +void checkNoExtraCharacters() { + if (eol()) return; + switch (line[column]) { + case '\t': + case ' ': + return; + default: + throw new AssemblyError("Extra characters on line"); + } +} + +void checkOriginDefined() { + if (origin < 0) + throw new AssemblyError("No ORG specified"); +} + +int operatorPlus(int a, int b) { + return b; +} + +int operatorMinus(int a, int b) { + return -b; +} + +int operatorLow(int a, int b) { + return b & 0xff; +} + +int operatorHigh(int a, int b) { + return (b >> 8) & 0xff; +} + +int operatorLogicalNot(int a, int b) { + return !b; +} + +int operatorBitwiseNot(int a, int b) { + return ~b; +} + +int operatorAdd(int a, int b) { + long r = cast(long) a + b; + if (r < -0x80000000L || r > 0x7fffffffL) + throw new AssemblyError("Arithmetic overflow"); + return a + b; +} + +int operatorSubtract(int a, int b) { + long r = cast(long) a - b; + if (r < -0x80000000L || r > 0x7fffffffL) + throw new AssemblyError("Arithmetic overflow"); + return a - b; +} + +int operatorMultiply(int a, int b) { + long r = cast(long) a * b; + if (r < -0x80000000L || r > 0x7fffffffL) + throw new AssemblyError("Arithmetic overflow"); + return a * b; +} + +int operatorDivide(int a, int b) { + if (b == 0) + throw new AssemblyError("Divide by zero"); + return a / b; +} + +int operatorModulus(int a, int b) { + if (b == 0) + throw new AssemblyError("Divide by zero"); + return a % b; +} + +int operatorAnd(int a, int b) { + return a & b; +} + +int operatorOr(int a, int b) { + return a | b; +} + +int operatorXor(int a, int b) { + return a ^ b; +} + +int operatorEqual(int a, int b) { + return a == b; +} + +int operatorNotEqual(int a, int b) { + return a != b; +} + +int operatorLess(int a, int b) { + return a < b; +} + +int operatorGreater(int a, int b) { + return a > b; +} + +int operatorLessEqual(int a, int b) { + return a <= b; +} + +int operatorGreaterEqual(int a, int b) { + return a >= b; +} + +int operatorShiftLeft(int a, int b) { + if (b < 0) + return operatorShiftRight(a, -b); + if (a != 0 && b >= 32) + throw new AssemblyError("Arithmetic overflow"); + long r = cast(long) a << b; + if (r & 0xffffffff00000000L) + throw new AssemblyError("Arithmetic overflow"); + return a << b; +} + +int operatorShiftRight(int a, int b) { + if (b < 0) + return operatorShiftLeft(a, -b); + if (b >= 32) + b = 31; + return a >> b; +} + +int operatorLogicalAnd(int a, int b) { + return a && b; +} + +int operatorLogicalOr(int a, int b) { + return a || b; +} + +void pushValOp(int value, OperatorFunction func, int priority) { + ValOp valOp; + valOp.value = value; + valOp.func = func; + valOp.priority = priority; + valOpStack ~= valOp; +} + +void readValue() { + assert(valOpStack.length == 0); + unknownInPass1 = false; + int priority = 0; + do { + int operand; + char c = readChar(); + switch (c) { + case '[': + priority += 10; + continue; + case '+': + pushValOp(0, &operatorPlus, priority + 8); + continue; + case '-': + pushValOp(0, &operatorMinus, priority + 8); + continue; + case '<': + pushValOp(0, &operatorLow, priority + 8); + continue; + case '>': + pushValOp(0, &operatorHigh, priority + 8); + continue; + case '!': + pushValOp(0, &operatorLogicalNot, priority + 4); + continue; + case '~': + pushValOp(0, &operatorBitwiseNot, priority + 8); + continue; + case '(': + throw new AssemblyError("Use square brackets instead"); + case '*': + checkOriginDefined(); + operand = origin; + break; + case '#': + if (!repeating) + throw new AssemblyError("'#' is allowed only in repeated lines"); + operand = repeatCounter; + break; + case '\'': + case '"': + operand = readChar(); + if (operand == c) + readStringChar(c); + readStringChar(c); + if (!eol() && line[column] == '*') { + column++; + operand ^= 0x80; + } + break; + case '^': + switch (readChar()) { + case '0': + operand = option5200 ? 0xc000 : 0xd000; + break; + case '1': + operand = option5200 ? 0xc010 : 0xd010; + break; + case '2': + operand = option5200 ? 0xe800 : 0xd200; + break; + case '3': + if (option5200) + throw new AssemblyError("There's no PIA chip in Atari 5200"); + operand = 0xd300; + break; + case '4': + operand = 0xd400; + break; + default: + illegalCharacter(); + } + int d = readDigit(16); + if (d < 0) + illegalCharacter(); + operand += d; + break; + case '{': + if (inOpcode) + throw new AssemblyError("Nested opcodes not supported"); + ValOp[] savedValOpStack = valOpStack; + AddrMode savedAddrMode = addrMode; + bool savedUnknownInPass1 = unknownInPass1; + bool savedInstructionBegin = instructionBegin; + valOpStack.length = 0; + inOpcode = true; + assemblyInstruction(readInstruction()); + if (readChar() != '}') + throw new AssemblyError("Missing '}'"); + assert(!instructionBegin); + inOpcode = false; + valOpStack = savedValOpStack; + addrMode = savedAddrMode; + unknownInPass1 = savedUnknownInPass1; + instructionBegin = savedInstructionBegin; + operand = value; + break; + case '$': + operand = readNumber(16); + break; + case '%': + operand = readNumber(2); + break; + default: + column--; + if (c >= '0' && c <= '9') { + operand = readNumber(10); + break; + } + string label = readLabel(); + if (label is null) + illegalCharacter(); + if (label in labelTable) { + Label l = labelTable[label]; + operand = l.value; + l.unused = false; + if (pass2) { + if (l.passed) { + if (l.unknownInPass1) + unknownInPass1 = true; + } else { + if (l.unknownInPass1) + throw new AssemblyError("Illegal forward reference"); + unknownInPass1 = true; + } + } else { + if (l.unknownInPass1) + unknownInPass1 = true; + } + } else { + if (pass2) + throw new AssemblyError("Undeclared label: " ~ label); + unknownInPass1 = true; + } + break; + } + while (!eol() && line[column] == ']') { + column++; + priority -= 10; + if (priority < 0) + throw new AssemblyError("Unmatched bracket"); + } + if (eol()) { + if (priority != 0) + throw new AssemblyError("Unmatched bracket"); + pushValOp(operand, &operatorPlus, 1); + } else { + switch (line[column++]) { + case '+': + pushValOp(operand, &operatorAdd, priority + 6); + break; + case '-': + pushValOp(operand, &operatorSubtract, priority + 6); + break; + case '*': + pushValOp(operand, &operatorMultiply, priority + 7); + break; + case '/': + pushValOp(operand, &operatorDivide, priority + 7); + break; + case '%': + pushValOp(operand, &operatorModulus, priority + 7); + break; + case '<': + switch (readChar()) { + case '<': + pushValOp(operand, &operatorShiftLeft, priority + 7); + break; + case '=': + pushValOp(operand, &operatorLessEqual, priority + 5); + break; + case '>': + pushValOp(operand, &operatorNotEqual, priority + 5); + break; + default: + column--; + pushValOp(operand, &operatorLess, priority + 5); + break; + } + break; + case '=': + switch (readChar()) { + default: + column--; + goto case '='; + case '=': + pushValOp(operand, &operatorEqual, priority + 5); + break; + } + break; + case '>': + switch (readChar()) { + case '>': + pushValOp(operand, &operatorShiftRight, priority + 7); + break; + case '=': + pushValOp(operand, &operatorGreaterEqual, priority + 5); + break; + default: + column--; + pushValOp(operand, &operatorGreater, priority + 5); + break; + } + break; + case '!': + switch (readChar()) { + case '=': + pushValOp(operand, &operatorNotEqual, priority + 5); + break; + default: + illegalCharacter(); + } + break; + case '&': + switch (readChar()) { + case '&': + pushValOp(operand, &operatorLogicalAnd, priority + 3); + break; + default: + column--; + pushValOp(operand, &operatorAnd, priority + 7); + break; + } + break; + case '|': + switch (readChar()) { + case '|': + pushValOp(operand, &operatorLogicalOr, priority + 2); + break; + default: + column--; + pushValOp(operand, &operatorOr, priority + 6); + break; + } + break; + case '^': + pushValOp(operand, &operatorXor, priority + 6); + break; + default: + column--; + if (priority != 0) + throw new AssemblyError("Unmatched bracket"); + pushValOp(operand, &operatorPlus, 1); + break; + } + } + for (;;) { + immutable sp = valOpStack.length - 1; + if (sp <= 0 || valOpStack[sp].priority > valOpStack[sp - 1].priority) + break; + int operand1 = valOpStack[sp - 1].value; + OperatorFunction func1 = valOpStack[sp - 1].func; + valOpStack[sp - 1] = valOpStack[sp]; + valOpStack.length = sp; + if (pass2 || !unknownInPass1) { // skip operations if unknown operands + valOpStack[sp - 1].value = func1(operand1, valOpStack[sp - 1].value); + } + } + } while (valOpStack.length != 1 || valOpStack[0].priority != 1); + value = valOpStack[0].value; + valOpStack.length = 0; +} + +debug int testValue(string l) { + line = l; + column = 0; + readValue(); + writefln("Value of %s is %x", l, value); + return value; +} + +unittest { + assert(testValue("123") == 123); + assert(testValue("$1234abCd") == 0x1234abcd); + assert(testValue("%101") == 5); + assert(testValue("^07") == 0xd007); + assert(testValue("^1f") == 0xd01f); + assert(testValue("^23") == 0xd203); + assert(testValue("^31") == 0xd301); + assert(testValue("^49") == 0xd409); + assert(testValue("!0") == 1); + assert(testValue("<$1234") == 0x34); + assert(testValue(">$1234567") == 0x45); + assert(testValue("1+2") == 3); + assert(testValue("1+2*3") == 7); + assert(testValue("[1+2]*3") == 9); + assert(testValue("{nop}") == 0xea); + assert(testValue("{CLC}+{sec}") == 0x50); + assert(testValue("{Jsr}") == 0x20); + assert(testValue("{bit a:}") == 0x2c); + assert(testValue("{bIt $7d}") == 0x24); +} + +void mustBeKnownInPass1() { + if (unknownInPass1) + throw new AssemblyError("Label not defined before"); +} + +void readWord() { + readValue(); + if ((!unknownInPass1 || pass2) && (value < -0xffff || value > 0xffff)) + throw new AssemblyError("Value out of range"); +} + +void readUnsignedWord() { + readWord(); + if ((!unknownInPass1 || pass2) && value < 0) + throw new AssemblyError("Value out of range"); +} + +void readKnownPositive() { + readValue(); + mustBeKnownInPass1(); + if (value <= 0) + throw new AssemblyError("Value out of range"); +} + +void optionalIncDec() { + if (eol()) return; + switch (line[column]) { + case '+': + column++; + addrMode += AddrMode.INCREMENT; + return; + case '-': + column++; + addrMode += AddrMode.DECREMENT; + return; + default: + return; + } +} + +void readAddrMode() { + readSpaces(); + char c = readChar(); + switch (c) { + case '@': + addrMode = AddrMode.ACCUMULATOR; + return; + case '#': + case '<': + case '>': + addrMode = AddrMode.IMMEDIATE; + if (inOpcode && line[column] == '}') + return; + readWord(); + final switch (c) { + case '#': + break; + case '<': + value &= 0xff; + break; + case '>': + value = (value >> 8) & 0xff; + break; + } + return; + case '(': + if (inOpcode) { + switch (readChar()) { + case ',': + switch (readChar()) { + case 'X': + case 'x': + break; + default: + illegalCharacter(); + } + if (readChar() != ')') + throw new AssemblyError("Need parenthesis"); + addrMode = AddrMode.INDIRECT_X; + return; + case ')': + if (readChar() == ',') { + switch (readChar()) { + case 'Y': + case 'y': + break; + default: + illegalCharacter(); + } + addrMode = AddrMode.INDIRECT_Y; + return; + } else { + column--; + addrMode = AddrMode.INDIRECT; + return; + } + default: + column--; + break; + } + } + readUnsignedWord(); + switch (readChar()) { + case ',': + switch (readChar()) { + case 'X': + case 'x': + addrMode = AddrMode.INDIRECT_X; + break; + case '0': + addrMode = cast(AddrMode) (AddrMode.INDIRECT_X + AddrMode.ZERO); + break; + default: + illegalCharacter(); + } + if (readChar() != ')') + throw new AssemblyError("Need parenthesis"); + return; + case ')': + if (eol()) { + addrMode = AddrMode.INDIRECT; + return; + } + if (line[column] == ',') { + column++; + switch (readChar()) { + case 'Y': + case 'y': + addrMode = AddrMode.INDIRECT_Y; + break; + case '0': + addrMode = cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.ZERO); + break; + default: + illegalCharacter(); + } + optionalIncDec(); + return; + } + addrMode = AddrMode.INDIRECT; + return; + default: + illegalCharacter(); + } + break; + case 'A': + case 'a': + if (!eol() && line[column] == ':') { + column++; + addrMode = AddrMode.ABSOLUTE; + } else { + addrMode = AddrMode.ABS_OR_ZP; + column--; + } + break; + case 'Z': + case 'z': + if (!eol() && line[column] == ':') { + column++; + addrMode = AddrMode.ZEROPAGE; + } else { + addrMode = AddrMode.ABS_OR_ZP; + column--; + } + break; + default: + addrMode = AddrMode.ABS_OR_ZP; + column--; + break; + } + // absolute or zeropage addressing, optionally indexed + if (inOpcode && (addrMode == AddrMode.ABSOLUTE || addrMode == AddrMode.ZEROPAGE)) { + switch (readChar()) { + case '}': + column--; + return; + case ',': + switch (readChar()) { + case 'X': + case 'x': + addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_X - AddrMode.ABSOLUTE); + return; + case 'Y': + case 'y': + addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_Y - AddrMode.ABSOLUTE); + return; + default: + illegalCharacter(); + } + return; + default: + column--; + break; + } + } + readUnsignedWord(); + if (addrMode == AddrMode.ABS_OR_ZP) { + if (unknownInPass1 || value > 0xff) + addrMode = AddrMode.ABSOLUTE; + else + addrMode = AddrMode.ZEROPAGE; + } + if (eol()) return; + if (line[column] == ',') { + column++; + switch (readChar()) { + case 'X': + case 'x': + addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_X - AddrMode.ABSOLUTE); + optionalIncDec(); + return; + case 'Y': + case 'y': + addrMode += cast(AddrMode) (AddrMode.ABSOLUTE_Y - AddrMode.ABSOLUTE); + optionalIncDec(); + return; + default: + illegalCharacter(); + } + } +} + +void readAbsoluteAddrMode() { + if (inOpcode && readChar() == '}') { + column--; + } else { + readAddrMode(); + switch (addrMode) { + case AddrMode.ABSOLUTE: + case AddrMode.ZEROPAGE: + break; + default: + illegalAddrMode(); + } + } + addrMode = AddrMode.ABSOLUTE; +} + +debug AddrMode testAddrMode(string l) { + line = l; + column = 0; + readAddrMode(); + writefln("Addressing mode of \"%s\" is %x", l, addrMode); + return addrMode; +} + +unittest { + assert(testAddrMode(" @") == AddrMode.ACCUMULATOR); + assert(testAddrMode(" #0") == AddrMode.IMMEDIATE); + assert(testAddrMode(" $abc,x-") == cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.DECREMENT)); + assert(testAddrMode(" $ab,Y+") == cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.INCREMENT)); + assert(testAddrMode(" (0,x)") == AddrMode.INDIRECT_X); + assert(testAddrMode(" ($ff),Y+") == cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT)); + assert(testAddrMode(" ($abcd)") == AddrMode.INDIRECT); + inOpcode = true; + assert(testAddrMode(" a:}") == AddrMode.ABSOLUTE); + assert(testAddrMode(" z:}") == AddrMode.ZEROPAGE); + assert(testAddrMode(" a:,x}") == AddrMode.ABSOLUTE_X); + assert(testAddrMode(" z:,y}") == AddrMode.ZEROPAGE_Y); + assert(testAddrMode(" (,X)}") == AddrMode.INDIRECT_X); + assert(testAddrMode(" (),y}") == AddrMode.INDIRECT_Y); + assert(testAddrMode(" ()}") == AddrMode.INDIRECT); + inOpcode = false; +} + +bool inFalseCondition() { + foreach (IfContext ic; ifContexts) { + if (!ic.condition) return true; + } + return false; +} + +string makeEscape(string s) { + return s.replace("$", "$$"); +} + +File openInputFile(string filename) { + if (find(makeSources, filename).empty) + makeSources ~= filename; + try { + return File(filename); + } catch (Exception e) { + throw new AssemblyError(e.msg); + } +} + +File openOutputFile(char letter, string defaultExt) { + string filename = optionParameters[letter - 'a']; + if (filename is null) + filename = sourceFilename.setExtension(defaultExt); + if (letter == 'o') + makeTarget = makeEscape(filename); + try { + return File(filename, "wb"); + } catch (Exception e) { + throw new AssemblyError(e.msg); + } +} + +void ensureListingFileOpen(char letter, string msg) { + if (!listingStream.isOpen) { + listingStream = openOutputFile(letter, "lst"); + if (!getOption('q')) + write(msg); + listingStream.writeln(TITLE); + } +} + +void listNibble(int x) { + listingLine[listingColumn++] = cast(char) (x <= 9 ? x + '0' : x + ('A' - 10)); +} + +void listByte(ubyte x) { + listNibble(x >> 4); + listNibble(x & 0xf); +} + +void listWord(ushort x) { + listByte(cast(ubyte) (x >> 8)); + listByte(cast(ubyte) x); +} + +void listLine() { + if (!optionListing || !getOption('l') || line is null) + return; + assert(pass2); + if (inFalseCondition() && !getOption('c')) + return; + if (getOption('i') && includeLevel > 0) + return; + ensureListingFileOpen('l', "Writing listing file...\n"); + if (currentFilename != lastListedFilename) { + listingStream.writeln("Source: ", currentFilename); + lastListedFilename = currentFilename; + } + int i = 4; + int x = lineNo; + while (x > 0 && i >= 0) { + listingLine[i--] = '0' + x % 10; + x /= 10; + } + while (i >= 0) + listingLine[i--] = ' '; + listingLine[5] = ' '; + while (listingColumn < 32) + listingLine[listingColumn++] = ' '; + listingStream.writefln("%.32s%s", listingLine, line); +} + +void listCommentLine() { + if (currentLabel is null) + listingColumn = 6; + else { + assert(!inFalseCondition()); + checkOriginDefined(); + } + listLine(); +} + +void listLabelTable() { + if (optionParameters['t' - 'a'] !is null && listingStream.isOpen) + listingStream.close(); + ensureListingFileOpen('t', "Writing label table...\n"); + listingStream.writeln("Label table:"); + foreach (string name; sort(labelTable.keys)) { + Label l = labelTable[name]; + listingStream.write(l.unused ? 'n' : ' '); + listingStream.write(l.unknownInPass1 ? '2' : ' '); + int value = l.value; + char sign = ' '; + if (value < 0) { + sign = '-'; + value = -value; + } + listingStream.writefln( + (l.value & 0xffff0000) != 0 ? " %s%08X %s" : " %s%04X %s", + sign, value, name); + } +} + +debug ubyte[] objectBuffer; + +void objectByte(ubyte b) { + debug { + objectBuffer ~= b; + } else { + assert(pass2); + if (!optionObject) return; + if (!objectStream.isOpen) { + objectStream = openOutputFile('o', "obx"); + if (!getOption('q')) + writeln("Writing object file..."); + } + try { + objectStream.write(cast(char) b); + } catch (Exception e) { + throw new AssemblyError("Error writing object file"); + } + } + objectBytes++; +} + +void objectWord(ushort w) { + objectByte(cast(ubyte) w); + objectByte(cast(ubyte) (w >> 8)); +} + +void putByte(ubyte b) { + if (inOpcode) { + if (instructionBegin) { + value = b; + instructionBegin = false; + } + return; + } + if (willSkip) { + assert(!pass2); + willSkip = false; + skipping = true; + } + if (skipping) { + assert(!pass2); + skipOffsets[$ - 1]++; + } + if (instructionBegin) { + repeatOffset = -2; + instructionBegin = false; + } + repeatOffset--; + if (optionFill && loadingOrigin >= 0 && loadingOrigin != loadOrigin) { + if (loadingOrigin > loadOrigin) + throw new AssemblyError("Can't fill from higher to lower memory location"); + if (pass2) { + while (loadingOrigin < loadOrigin) { + objectByte(0xff); + loadingOrigin++; + } + } + } + debug { + objectByte(b); + } + if (pass2) { + debug { + } else { + objectByte(b); + } + if (listingColumn < 29) { + listByte(b); + listingLine[listingColumn++] = ' '; + } else { + listingLine[29] = '+'; + listingColumn = 30; + } + } + if (optionHeaders) { + if (origin < 0) + throw new AssemblyError("No ORG specified"); + assert(blockIndex >= 0); + if (!pass2) { + blockEnds[blockIndex] = cast(ushort) loadOrigin; + } + } + if (origin >= 0) { + origin++; + loadingOrigin = ++loadOrigin; + } +} + +void putWord(ushort w) { + putByte(cast(ubyte) w); + putByte(cast(ubyte) (w >> 8)); +} + +void putCommand(ubyte b) { + putByte(b); + if (inOpcode) return; + switch (addrMode & AddrMode.STANDARD_MASK) { + case AddrMode.ACCUMULATOR: + break; + case AddrMode.IMMEDIATE: + case AddrMode.ZEROPAGE: + case AddrMode.ZEROPAGE_X: + case AddrMode.ZEROPAGE_Y: + case AddrMode.INDIRECT_X: + case AddrMode.INDIRECT_Y: + if (pass2 && (value < -0xff || value > 0xff)) + throw new AssemblyError("Value out of range"); + putByte(cast(ubyte) value); + break; + case AddrMode.ABSOLUTE: + case AddrMode.ABSOLUTE_X: + case AddrMode.ABSOLUTE_Y: + case AddrMode.INDIRECT: + putWord(cast(ushort) value); + break; + default: + assert(0); + } + switch (addrMode) { + case cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.INCREMENT): + case cast(AddrMode) (AddrMode.ZEROPAGE_X + AddrMode.INCREMENT): + putByte(0xe8); + break; + case cast(AddrMode) (AddrMode.ABSOLUTE_X + AddrMode.DECREMENT): + case cast(AddrMode) (AddrMode.ZEROPAGE_X + AddrMode.DECREMENT): + putByte(0xca); + break; + case cast(AddrMode) (AddrMode.ABSOLUTE_Y + AddrMode.INCREMENT): + case cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.INCREMENT): + case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT): + case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.INCREMENT + AddrMode.ZERO): + putByte(0xc8); + break; + case cast(AddrMode) (AddrMode.ABSOLUTE_Y + AddrMode.DECREMENT): + case cast(AddrMode) (AddrMode.ZEROPAGE_Y + AddrMode.DECREMENT): + case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.DECREMENT): + case cast(AddrMode) (AddrMode.INDIRECT_Y + AddrMode.DECREMENT + AddrMode.ZERO): + putByte(0x88); + break; + default: + break; + } +} + +void noOpcode() { + if (inOpcode) + throw new AssemblyError("Can't get opcode of this"); +} + +void directive() { + noOpcode(); + if (repeating) + throw new AssemblyError("Can't repeat this directive"); + if (pairing) + throw new AssemblyError("Can't pair this directive"); +} + +void noRepeatSkipDirective() { + directive(); + if (willSkip) + throw new AssemblyError("Can't skip over this"); + repeatOffset = 0; +} + +void illegalAddrMode() { + throw new AssemblyError("Illegal addressing mode"); +} + +void addrModeForMove(int move) { + final switch (move) { + case 0: + readAddrMode(); + break; + case 1: + value = value1; + addrMode = addrMode1; + break; + case 2: + value = value2; + addrMode = addrMode2; + break; + } +} + +void assemblyAccumulator(ubyte b, ubyte prefix, int move) { + addrModeForMove(move); + if (prefix != 0) + putByte(prefix); + switch (addrMode & AddrMode.STANDARD_MASK) { + case AddrMode.ACCUMULATOR: + case AddrMode.INDIRECT: + illegalAddrMode(); + break; + case AddrMode.IMMEDIATE: + if (b == 0x80) { + // STA # + illegalAddrMode(); + } + putCommand(cast(ubyte) (b + 9)); + break; + case AddrMode.ABSOLUTE: + putCommand(cast(ubyte) (b + 0xd)); + break; + case AddrMode.ZEROPAGE: + putCommand(cast(ubyte) (b + 5)); + break; + case AddrMode.ABSOLUTE_X: + putCommand(cast(ubyte) (b + 0x1d)); + break; + case AddrMode.ZEROPAGE_X: + putCommand(cast(ubyte) (b + 0x15)); + break; + case AddrMode.ZEROPAGE_Y: + addrMode -= 1; + goto case AddrMode.ABSOLUTE_Y; + case AddrMode.ABSOLUTE_Y: + putCommand(cast(ubyte) (b + 0x19)); + break; + case AddrMode.INDIRECT_X: + if ((addrMode & AddrMode.ZERO) != 0) + putWord(0x00a2); + putCommand(cast(ubyte) (b + 1)); + break; + case AddrMode.INDIRECT_Y: + if ((addrMode & AddrMode.ZERO) != 0) + putWord(0x00a0); + putCommand(cast(ubyte) (b + 0x11)); + break; + default: + assert(0); + } +} + +void assemblyShift(ubyte b) { + readAddrMode(); + switch (addrMode & AddrMode.STANDARD_MASK) { + case AddrMode.ACCUMULATOR: + if (b == 0xc0 || b == 0xe0) { + // INC @, DEC @ + illegalAddrMode(); + } + putByte(cast(ubyte) (b + 0xa)); + break; + case AddrMode.ABSOLUTE: + putCommand(cast(ubyte) (b + 0xe)); + break; + case AddrMode.ZEROPAGE: + putCommand(cast(ubyte) (b + 6)); + break; + case AddrMode.ABSOLUTE_X: + putCommand(cast(ubyte) (b + 0x1e)); + break; + case AddrMode.ZEROPAGE_X: + putCommand(cast(ubyte) (b + 0x16)); + break; + default: + illegalAddrMode(); + } +} + +void assemblyCompareIndex(ubyte b) { + readAddrMode(); + switch (addrMode) { + case AddrMode.IMMEDIATE: + putCommand(b); + break; + case AddrMode.ABSOLUTE: + putCommand(cast(ubyte) (b + 0xc)); + break; + case AddrMode.ZEROPAGE: + putCommand(cast(ubyte) (b + 4)); + break; + default: + illegalAddrMode(); + } +} + +void assemblyLda(int move) { + assemblyAccumulator(0xa0, 0, move); +} + +void assemblyLdx(int move) { + addrModeForMove(move); + switch (addrMode & AddrMode.STANDARD_MASK) { + case AddrMode.IMMEDIATE: + putCommand(0xa2); + break; + case AddrMode.ABSOLUTE: + putCommand(0xae); + break; + case AddrMode.ZEROPAGE: + putCommand(0xa6); + break; + case AddrMode.ABSOLUTE_Y: + putCommand(0xbe); + break; + case AddrMode.ZEROPAGE_Y: + putCommand(0xb6); + break; + default: + illegalAddrMode(); + } +} + +void assemblyLdy(int move) { + addrModeForMove(move); + switch (addrMode & AddrMode.STANDARD_MASK) { + case AddrMode.IMMEDIATE: + putCommand(0xa0); + break; + case AddrMode.ABSOLUTE: + putCommand(0xac); + break; + case AddrMode.ZEROPAGE: + putCommand(0xa4); + break; + case AddrMode.ABSOLUTE_X: + putCommand(0xbc); + break; + case AddrMode.ZEROPAGE_X: + putCommand(0xb4); + break; + default: + illegalAddrMode(); + } +} + +void assemblySta(int move) { + assemblyAccumulator(0x80, 0, move); +} + +void assemblyStx(int move) { + addrModeForMove(move); + switch (addrMode & AddrMode.STANDARD_MASK) { + case AddrMode.ABSOLUTE: + putCommand(0x8e); + break; + case AddrMode.ZEROPAGE: + putCommand(0x86); + break; + case AddrMode.ABSOLUTE_Y: + addrMode += 1; + goto case AddrMode.ZEROPAGE_Y; + case AddrMode.ZEROPAGE_Y: + putCommand(0x96); + break; + default: + illegalAddrMode(); + } +} + +void assemblySty(int move) { + addrModeForMove(move); + switch (addrMode & AddrMode.STANDARD_MASK) { + case AddrMode.ABSOLUTE: + putCommand(0x8c); + break; + case AddrMode.ZEROPAGE: + putCommand(0x84); + break; + case AddrMode.ABSOLUTE_X: + addrMode += 1; + goto case AddrMode.ZEROPAGE_X; + case AddrMode.ZEROPAGE_X: + putCommand(0x94); + break; + default: + illegalAddrMode(); + } +} + +void assemblyBit() { + readAddrMode(); + switch (addrMode) { + case AddrMode.ABSOLUTE: + putCommand(0x2c); + break; + case AddrMode.ZEROPAGE: + putCommand(0x24); + break; + default: + illegalAddrMode(); + } +} + +void putJump() { + switch (addrMode) { + case AddrMode.ZEROPAGE: + addrMode = AddrMode.ABSOLUTE; + goto case AddrMode.ABSOLUTE; + case AddrMode.ABSOLUTE: + putCommand(0x4c); + break; + case AddrMode.INDIRECT: + if (pass2 && (value & 0xff) == 0xff) + warning("Buggy indirect jump"); + putCommand(0x6c); + break; + default: + illegalAddrMode(); + } +} + +void assemblyJmp() { + readAddrMode(); + putJump(); +} + +void assemblyConditionalJump(ubyte b) { + noOpcode(); + readAddrMode(); + if ((addrMode == AddrMode.ABSOLUTE || addrMode == AddrMode.ZEROPAGE) + && pass2 && origin >= 0 && value - origin - 2 >= -0x80 && value - origin - 2 <= 0x7f) { + warning("Plain branch instruction would be sufficient"); + } + putByte(b); + putByte(3); + putJump(); +} + +void assemblyJsr() { + readAbsoluteAddrMode(); + putCommand(0x20); +} + +ubyte calculateBranch(int offset) { + if (offset < -0x80 || offset > 0x7f) { + int dist = offset < 0 ? -offset - 0x80 : offset - 0x7f; + throw new AssemblyError("Branch out of range by " ~ to!string(dist) ~ " bytes"); + } + return cast(ubyte) offset; +} + +void assemblyBranch(ubyte b) { + readAbsoluteAddrMode(); + if (inOpcode) { + putByte(b); + return; + } + checkOriginDefined(); + putByte(b); + putByte(pass2 ? calculateBranch(value - origin - 1) : 0); +} + +void assemblyRepeat(ubyte b) { + noOpcode(); + int offset = repeatOffset; + if (offset >= 0) + throw new AssemblyError("No instruction to repeat"); + if (pass2 && wereManyInstructions) + warning("Repeating only the last instruction"); + putByte(b); + putByte(calculateBranch(offset)); +} + +void assemblySkip(ubyte b) { + noOpcode(); + if (willSkip) { + skipOffsets[$ - 1] = 2; + willSkip = false; + } + putByte(b); + if (pass2) + putByte(calculateBranch(skipOffsets[skipOffsetsIndex++])); + else { + putByte(0); + skipOffsets ~= 0; + willSkip = true; + } +} + +void assemblyInw() { + noOpcode(); + readAddrMode(); + switch (addrMode) { + case AddrMode.ABSOLUTE: + putCommand(0xee); + putWord(0x03d0); + value++; + putCommand(0xee); + break; + case AddrMode.ZEROPAGE: + putCommand(0xe6); + putWord(0x02d0); + value++; + putCommand(0xe6); + break; + case AddrMode.ABSOLUTE_X: + putCommand(0xfe); + putWord(0x03d0); + value++; + putCommand(0xfe); + break; + case AddrMode.ZEROPAGE_X: + putCommand(0xf6); + putWord(0x02d0); + value++; + putCommand(0xf6); + break; + default: + illegalAddrMode(); + } +} + +void assemblyMove() { + noOpcode(); + readAddrMode(); + value1 = value; + addrMode1 = addrMode; + bool unknown1 = unknownInPass1; + readAddrMode(); + value2 = value; + addrMode2 = addrMode; + unknownInPass1 = unknown1; +} + +void assemblyMoveByte(MoveFunction load, MoveFunction store) { + assemblyMove(); + load(1); + store(2); +} + +void assemblyMoveWord(MoveFunction load, MoveFunction store, ubyte inc, ubyte dec) { + assemblyMove(); + switch (addrMode2) { + case AddrMode.ABSOLUTE: + case AddrMode.ZEROPAGE: + case AddrMode.ABSOLUTE_X: + case AddrMode.ZEROPAGE_X: + case AddrMode.ABSOLUTE_Y: + case AddrMode.ZEROPAGE_Y: + break; + default: + illegalAddrMode(); + } + switch (addrMode1) { + case AddrMode.IMMEDIATE: + int high = value1 >> 8; + value1 &= 0xff; + load(1); + store(2); + value2++; + if (unknownInPass1) { + value1 = high; + load(1); + } else { + if (inc != 0 && cast(ubyte) (value1 + 1) == high) + putByte(inc); + else if (dec != 0 && cast(ubyte) (value1 - 1) == high) + putByte(dec); + else if (value1 != high) { + value1 = high; + load(1); + } + } + store(2); + break; + case AddrMode.ABSOLUTE: + case AddrMode.ZEROPAGE: + case AddrMode.ABSOLUTE_X: + case AddrMode.ZEROPAGE_X: + case AddrMode.ABSOLUTE_Y: + case AddrMode.ZEROPAGE_Y: + load(1); + store(2); + value1++; + value2++; + load(1); + store(2); + break; + default: + illegalAddrMode(); + } +} + +void storeDtaNumber(int val, char letter) { + int limit = 0xffff; + if (letter == 'b') limit = 0xff; + if ((!unknownInPass1 || pass2) && (val < -limit || val > limit)) + throw new AssemblyError("Value out of range"); + final switch (letter) { + case 'a': + putWord(cast(ushort) val); + break; + case 'b': + case 'l': + putByte(cast(ubyte) val); + break; + case 'h': + putByte(cast(ubyte) (val >> 8)); + break; + } +} + +void assemblyDtaInteger(char letter) { + if (readFunction() == "SIN") { + readWord(); + int sinCenter = value; + readComma(); + readWord(); + int sinAmp = value; + readComma(); + readKnownPositive(); + int sinPeriod = value; + int sinMin = 0; + int sinMax = sinPeriod - 1; + switch (readChar()) { + case ')': + break; + case ',': + readUnsignedWord(); + mustBeKnownInPass1(); + sinMin = value; + readComma(); + readUnsignedWord(); + mustBeKnownInPass1(); + sinMax = value; + if (readChar() != ')') { + illegalCharacter(); + } + break; + default: + illegalCharacter(); + } + while (sinMin <= sinMax) { + int val = sinCenter + cast(int) rint(sinAmp * sin(sinMin * 2 * PI / sinPeriod)); + storeDtaNumber(val, letter); + sinMin++; + } + return; + } + readWord(); + storeDtaNumber(value, letter); +} + +bool realSign; + +int realExponent; + +long realMantissa; + +void putReal() { + if (realMantissa == 0) { + putWord(0); + putWord(0); + putWord(0); + return; + } + while (realMantissa < 0x1000000000L) { + realMantissa <<= 4; + realExponent--; + } + if ((realExponent & 1) != 0) { + if (realMantissa & 0xf) + throw new AssemblyError("Out of precision"); + realMantissa >>= 4; + } + realExponent = (realExponent + 0x89) >> 1; + if (realExponent < 64 - 49) + throw new AssemblyError("Out of precision"); + if (realExponent > 64 + 49) + throw new AssemblyError("Number too big"); + putByte(cast(ubyte) (realSign ? realExponent + 0x80 : realExponent)); + putByte(cast(ubyte) (realMantissa >> 32)); + putByte(cast(ubyte) (realMantissa >> 24)); + putByte(cast(ubyte) (realMantissa >> 16)); + putByte(cast(ubyte) (realMantissa >> 8)); + putByte(cast(ubyte) realMantissa); +} + +bool readSign() { + switch (readChar()) { + case '+': + return false; + case '-': + return true; + default: + column--; + return false; + } +} + +void readExponent() { + bool sign = readSign(); + char c = readChar(); + if (c < '0' || c > '9') + illegalCharacter(); + int e = c - '0'; + c = readChar(); + if (c >= '0' && c <= '9') + e = 10 * e + c - '0'; + else + column--; + realExponent += sign ? -e : e; + putReal(); +} + +void readFraction() { + for (;;) { + char c = readChar(); + if (c >= '0' && c <= '9') { + if (c != '0' && realMantissa >= 0x1000000000L) + throw new AssemblyError("Out of precision"); + realMantissa <<= 4; + realMantissa += c - '0'; + realExponent--; + continue; + } + if (c == 'E' || c == 'e') { + readExponent(); + return; + } + column--; + putReal(); + return; + } +} + +void assemblyDtaReal() { + realSign = readSign(); + realExponent = 0; + realMantissa = 0; + char c = readChar(); + if (c == '.') { + readFraction(); + return; + } + if (c < '0' || c > '9') + illegalCharacter(); + do { + if (realMantissa < 0x1000000000L) { + realMantissa <<= 4; + realMantissa += c - '0'; + } else { + if (c != '0') + throw new AssemblyError("Out of precision"); + realExponent++; + } + c = readChar(); + } while (c >= '0' && c <= '9'); + switch (c) { + case '.': + readFraction(); + break; + case 'E': + case 'e': + readExponent(); + break; + default: + column--; + putReal(); + break; + } +} + +void assemblyDtaNumbers(char letter) { + if (eol() || line[column] != '(') { + column--; + assemblyDtaInteger('b'); + return; + } + column++; + for (;;) { + final switch (letter) { + case 'a': + case 'b': + case 'h': + case 'l': + assemblyDtaInteger(letter); + break; + case 'r': + assemblyDtaReal(); + break; + } + switch (readChar()) { + case ')': + return; + case ',': + break; + default: + illegalCharacter(); + } + } +} + +void assemblyDta() { + noOpcode(); + readSpaces(); + for (;;) { + switch (readChar()) { + case 'A': + case 'a': + assemblyDtaNumbers('a'); + break; + case 'B': + case 'b': + assemblyDtaNumbers('b'); + break; + case 'C': + case 'c': + ubyte[] s = readString(); + if (s is null) { + column--; + assemblyDtaInteger('b'); + break; + } + foreach (ubyte b; s) { + putByte(b); + } + break; + case 'D': + case 'd': + ubyte[] s = readString(); + if (s is null) { + column--; + assemblyDtaInteger('b'); + break; + } + foreach (ubyte b; s) { + final switch (b & 0x60) { + case 0x00: + putByte(cast(ubyte) (b + 0x40)); + break; + case 0x20: + case 0x40: + putByte(cast(ubyte) (b - 0x20)); + break; + case 0x60: + putByte(b); + break; + } + } + break; + case 'H': + case 'h': + assemblyDtaNumbers('h'); + break; + case 'L': + case 'l': + assemblyDtaNumbers('l'); + break; + case 'R': + case 'r': + assemblyDtaNumbers('r'); + break; + default: + column--; + assemblyDtaInteger('b'); + break; + } + if (eol() || line[column] != ',') + break; + column++; + } +} + +void assemblyEqu() { + directive(); + if (currentLabel is null) + throw new AssemblyError("Label name required"); + currentLabel.value = 0; + readSpaces(); + readValue(); + currentLabel.value = value; + currentLabel.unknownInPass1 = unknownInPass1; + if (optionListing) { + listingLine[6] = '='; + int val = value; + listingLine[7] = ' '; + if (val < 0) { + listingLine[7] = '-'; + val = -val; + } + listingColumn = 8; + if ((val & 0xffff0000) != 0) + listWord(cast(ushort) (val >> 16)); + else { + while (listingColumn < 12) + listingLine[listingColumn++] = ' '; + } + listWord(cast(ushort) val); + } +} + +void assemblyEnd() { + directive(); + assert(!foundEnd); + foundEnd = true; +} + +void assemblyIftEli() { + ifContexts[$ - 1].condition = true; + if (!inFalseCondition()) { + readSpaces(); + readValue(); + mustBeKnownInPass1(); + if (value != 0) + ifContexts[$ - 1].aConditionMatched = true; + else + listLine(); + ifContexts[$ - 1].condition = value != 0; + } +} + +void checkMissingIft() { + if (ifContexts.length == 0) + throw new AssemblyError("Missing IFT"); +} + +void assemblyIft() { + directive(); + ifContexts.length++; + assemblyIftEli(); +} + +void assemblyEliEls() { + directive(); + checkMissingIft(); + if (ifContexts[$ - 1].wasElse) + throw new AssemblyError("EIF expected"); +} + +void assemblyEli() { + assemblyEliEls(); + if (ifContexts[$ - 1].aConditionMatched) { + ifContexts[$ - 1].condition = false; + return; + } + assemblyIftEli(); +} + +void assemblyEls() { + assemblyEliEls(); + with (ifContexts[$ - 1]) { + if (condition && aConditionMatched) + listLine(); + wasElse = true; + condition = !aConditionMatched; + } +} + +void assemblyEif() { + directive(); + checkMissingIft(); + ifContexts.length--; +} + +void assemblyErt() { + directive(); + readSpaces(); + readValue(); + if (pass2 && value != 0) + throw new AssemblyError("User-defined error"); +} + +bool readOption() { + switch (readChar()) { + case '-': + return false; + case '+': + return true; + default: + illegalCharacter(); + } + assert(0); +} + +void assemblyOpt() { + directive(); + readSpaces(); + while (!eol()) { + switch (line[column++]) { + case 'F': + case 'f': + optionFill = readOption(); + break; + case 'G': + case 'g': + option5200 = readOption(); + break; + case 'H': + case 'h': + optionHeaders = readOption(); + break; + case 'L': + case 'l': + optionListing = readOption() && !pass2; + break; + case 'O': + case 'o': + optionObject = readOption(); + break; + case 'U': + case 'u': + optionUnusedLabels = readOption(); + break; + default: + column--; + return; + } + } +} + +void originWord(ushort value, char listingChar) { + objectWord(value); + listWord(value); + listingLine[listingColumn++] = listingChar; +} + +OrgModifier readOrgModifier() { + readSpaces(); + if (column + 2 < line.length && line[column + 1] == ':') { + switch (line[column]) { + case 'F': + case 'f': + checkHeadersOn(); + column += 2; + return OrgModifier.FORCE_FFFF; + case 'A': + case 'a': + checkHeadersOn(); + column += 2; + return OrgModifier.FORCE_HEADER; + case 'R': + case 'r': + column += 2; + return OrgModifier.RELOCATE; + default: + break; + } + } + return OrgModifier.NONE; +} + +void setOrigin(int addr, OrgModifier modifier) { + origin = loadOrigin = addr; + bool requestedHeader = modifier != OrgModifier.NONE; + if (requestedHeader || loadingOrigin < 0 || (addr != loadingOrigin && !optionFill)) { + blockIndex++; + if (!pass2) { + assert(blockIndex == blockEnds.length); + blockEnds ~= cast(ushort) (addr - 1); + } + if (pass2 && optionHeaders) { + if (addr - 1 == blockEnds[blockIndex]) { + if (requestedHeader) + throw new AssemblyError("Cannot generate an empty block"); + return; + } + if (modifier == OrgModifier.FORCE_FFFF || objectBytes == 0) { + assert(requestedHeader || addr != loadingOrigin); + originWord(0xffff, '>'); + listingLine[listingColumn++] = ' '; + } + if (requestedHeader || addr != loadingOrigin) { + originWord(cast(ushort) addr, '-'); + originWord(blockEnds[blockIndex], '>'); + listingLine[listingColumn++] = ' '; + loadingOrigin = -1; + } + } + } +} + +void checkHeadersOn() { + if (!optionHeaders) + throw new AssemblyError("Illegal when Atari file headers disabled"); +} + +void assemblyOrg() { + noRepeatSkipDirective(); + OrgModifier modifier = readOrgModifier(); + readUnsignedWord(); + mustBeKnownInPass1(); + if (modifier == OrgModifier.RELOCATE) { + checkOriginDefined(); + origin = value; + } + else { + setOrigin(value, modifier); + } +} + +void assemblyRunIni(ushort addr) { + noRepeatSkipDirective(); + checkHeadersOn(); + loadingOrigin = -1; // don't fill + OrgModifier modifier = readOrgModifier(); + if (modifier == OrgModifier.RELOCATE) + throw new AssemblyError("r: invalid here"); + setOrigin(addr, modifier); + readUnsignedWord(); + putWord(cast(ushort) (value)); + loadingOrigin = -1; // don't fill +} + +void assemblyIcl() { + directive(); + string filename = readFilename(); + checkNoExtraCharacters(); + listLine(); + includeLevel++; + assemblyFile(filename); + includeLevel--; + line = null; +} + +void assemblyIns() { + string filename = readFilename(); + int offset = 0; + int length = -1; + if (!eol() && line[column] == ',') { + column++; + readValue(); + mustBeKnownInPass1(); + offset = value; + if (!eol() && line[column] == ',') { + column++; + readKnownPositive(); + length = value; + } + } + File stream = openInputFile(filename); + scope (exit) stream.close(); + try { + stream.seek(offset, offset >= 0 ? SEEK_SET : SEEK_END); + } catch (Exception e) { + throw new AssemblyError("Error seeking file"); + } + if (inOpcode) + length = 1; + while (length != 0) { + int b = readByte(&stream); + if (b < 0) { + if (length > 0) + throw new AssemblyError("File is too short"); + break; + } + putByte(cast(ubyte) b); + if (length > 0) length--; + } +} + +void assemblyInstruction(string instruction) { + if (!inOpcode && origin < 0 && currentLabel !is null && instruction != "EQU") + throw new AssemblyError("No ORG specified"); + instructionBegin = true; + switch (instruction) { + case "ADC": + assemblyAccumulator(0x60, 0, 0); + break; + case "ADD": + assemblyAccumulator(0x60, 0x18, 0); + break; + case "AND": + assemblyAccumulator(0x20, 0, 0); + break; + case "ASL": + assemblyShift(0x00); + break; + case "BCC": + assemblyBranch(0x90); + break; + case "BCS": + assemblyBranch(0xb0); + break; + case "BEQ": + assemblyBranch(0xf0); + break; + case "BIT": + assemblyBit(); + break; + case "BMI": + assemblyBranch(0x30); + break; + case "BNE": + assemblyBranch(0xd0); + break; + case "BPL": + assemblyBranch(0x10); + break; + case "BRK": + putByte(0x00); + break; + case "BVC": + assemblyBranch(0x50); + break; + case "BVS": + assemblyBranch(0x70); + break; + case "CLC": + putByte(0x18); + break; + case "CLD": + putByte(0xd8); + break; + case "CLI": + putByte(0x58); + break; + case "CLV": + putByte(0xb8); + break; + case "CMP": + assemblyAccumulator(0xc0, 0, 0); + break; + case "CPX": + assemblyCompareIndex(0xe0); + break; + case "CPY": + assemblyCompareIndex(0xc0); + break; + case "DEC": + assemblyShift(0xc0); + break; + case "DEX": + putByte(0xca); + break; + case "DEY": + putByte(0x88); + break; + case "DTA": + assemblyDta(); + break; + case "EIF": + assemblyEif(); + break; + case "ELI": + assemblyEli(); + break; + case "ELS": + assemblyEls(); + break; + case "END": + assemblyEnd(); + break; + case "EOR": + assemblyAccumulator(0x40, 0, 0); + break; + case "EQU": + assemblyEqu(); + break; + case "ERT": + assemblyErt(); + break; + case "ICL": + assemblyIcl(); + break; + case "IFT": + assemblyIft(); + break; + case "INC": + assemblyShift(0xe0); + break; + case "INI": + assemblyRunIni(0x2e2); + break; + case "INS": + assemblyIns(); + break; + case "INX": + putByte(0xe8); + break; + case "INY": + putByte(0xc8); + break; + case "INW": + assemblyInw(); + break; + case "JCC": + assemblyConditionalJump(0xb0); + break; + case "JCS": + assemblyConditionalJump(0x90); + break; + case "JEQ": + assemblyConditionalJump(0xd0); + break; + case "JMI": + assemblyConditionalJump(0x10); + break; + case "JMP": + assemblyJmp(); + break; + case "JNE": + assemblyConditionalJump(0xf0); + break; + case "JPL": + assemblyConditionalJump(0x30); + break; + case "JSR": + assemblyJsr(); + break; + case "JVC": + assemblyConditionalJump(0x70); + break; + case "JVS": + assemblyConditionalJump(0x50); + break; + case "LDA": + assemblyAccumulator(0xa0, 0, 0); + break; + case "LDX": + assemblyLdx(0); + break; + case "LDY": + assemblyLdy(0); + break; + case "LSR": + assemblyShift(0x40); + break; + case "MVA": + assemblyMoveByte(&assemblyLda, &assemblySta); + break; + case "MVX": + assemblyMoveByte(&assemblyLdx, &assemblyStx); + break; + case "MVY": + assemblyMoveByte(&assemblyLdy, &assemblySty); + break; + case "MWA": + assemblyMoveWord(&assemblyLda, &assemblySta, 0, 0); + break; + case "MWX": + assemblyMoveWord(&assemblyLdx, &assemblyStx, 0xe8, 0xca); + break; + case "MWY": + assemblyMoveWord(&assemblyLdy, &assemblySty, 0xc8, 0x88); + break; + case "NOP": + putByte(0xea); + break; + case "OPT": + assemblyOpt(); + break; + case "ORA": + assemblyAccumulator(0x00, 0, 0); + break; + case "ORG": + assemblyOrg(); + break; + case "PHA": + putByte(0x48); + break; + case "PHP": + putByte(0x08); + break; + case "PLA": + putByte(0x68); + break; + case "PLP": + putByte(0x28); + break; + case "RCC": + assemblyRepeat(0x90); + break; + case "RCS": + assemblyRepeat(0xb0); + break; + case "REQ": + assemblyRepeat(0xf0); + break; + case "RMI": + assemblyRepeat(0x30); + break; + case "RNE": + assemblyRepeat(0xd0); + break; + case "ROL": + assemblyShift(0x20); + break; + case "ROR": + assemblyShift(0x60); + break; + case "RPL": + assemblyRepeat(0x10); + break; + case "RTI": + putByte(0x40); + break; + case "RTS": + putByte(0x60); + break; + case "RUN": + assemblyRunIni(0x2e0); + break; + case "RVC": + assemblyRepeat(0x50); + break; + case "RVS": + assemblyRepeat(0x70); + break; + case "SBC": + assemblyAccumulator(0xe0, 0, 0); + break; + case "SCC": + assemblySkip(0x90); + break; + case "SCS": + assemblySkip(0xb0); + break; + case "SEC": + putByte(0x38); + break; + case "SED": + putByte(0xf8); + break; + case "SEI": + putByte(0x78); + break; + case "SEQ": + assemblySkip(0xf0); + break; + case "SMI": + assemblySkip(0x30); + break; + case "SNE": + assemblySkip(0xd0); + break; + case "SPL": + assemblySkip(0x10); + break; + case "STA": + assemblyAccumulator(0x80, 0, 0); + break; + case "STX": + assemblyStx(0); + break; + case "STY": + assemblySty(0); + break; + case "SUB": + assemblyAccumulator(0xe0, 0x38, 0); + break; + case "SVC": + assemblySkip(0x50); + break; + case "SVS": + assemblySkip(0x70); + break; + case "TAX": + putByte(0xaa); + break; + case "TAY": + putByte(0xa8); + break; + case "TSX": + putByte(0xba); + break; + case "TXA": + putByte(0x8a); + break; + case "TXS": + putByte(0x9a); + break; + case "TYA": + putByte(0x98); + break; + default: + throw new AssemblyError("Illegal instruction"); + } + skipping = false; +} + +debug ubyte[] testInstruction(string l) { + objectBuffer.length = 0; + line = l; + column = 0; + assemblyInstruction(readInstruction()); + write(line, " assembles to"); + foreach (ubyte b; objectBuffer) + writef(" %02x", b); + writeln(); + return objectBuffer; +} + +unittest { + assert(testInstruction("nop") == cast(ubyte[]) hexString!"ea"); + assert(testInstruction("add (5,0)") == cast(ubyte[]) hexString!"18a2006105"); + assert(testInstruction("mwa #$abcd $1234") == cast(ubyte[]) hexString!"a9cd8d3412a9ab8d3512"); + assert(testInstruction("dta 5,d'Foo'*,a($4589)") == cast(ubyte[]) hexString!"05a6efef8945"); + assert(testInstruction("dta r(1,12,123,1234567890,12345678900000,.5,.03,000.1664534589,1e97)") + == cast(ubyte[]) hexString!"400100000000 401200000000 410123000000 441234567890 461234567890 3f5000000000 3f0300000000 3f1664534589 701000000000"); +} + +void assemblyPair() { + assert(!inOpcode); + string instruction = readInstruction(); + if (!eol() && line[column] == ':') { + pairing = true; + column++; + string instruction2 = readInstruction(); + int savedColumn = column; + if (willSkip) + warning("Skipping only the first instruction"); + assemblyInstruction(instruction); + checkNoExtraCharacters(); + column = savedColumn; + wereManyInstructions = false; + assemblyInstruction(instruction2); + wereManyInstructions = true; + } else { + pairing = false; + assemblyInstruction(instruction); + wereManyInstructions = false; + } +} + +void assemblyLine() { + debug { + writeln(line); + } + lineNo++; + totalLines++; + column = 0; + listingColumn = 6; + if (origin >= 0) { + listWord(cast(ushort) origin); + listingLine[listingColumn++] = ' '; + } + string label = readLabel(); + currentLabel = null; + if (label !is null) { + if (!inFalseCondition()) { + if (!pass2) { + if (label in labelTable) + throw new AssemblyError("Label declared twice"); + currentLabel = new Label(origin); + labelTable[label] = currentLabel; + } else { + assert(label in labelTable); + currentLabel = labelTable[label]; + currentLabel.passed = true; + if (currentLabel.unused && getOption('u') && optionUnusedLabels) + warning("Unused label: " ~ label); + } + } + if (eol()) { + listCommentLine(); + return; + } + readSpaces(); + } + commentOrRep: for (;;) { + if (eol()) { + listCommentLine(); + return; + } + switch (line[column]) { + case '\t': + case ' ': + column++; + continue; + case '*': + case ';': + case '|': + listCommentLine(); + return; + case ':': + if (inFalseCondition()) { + listCommentLine(); + return; + } + column++; + readUnsignedWord(); + mustBeKnownInPass1(); + int repeatLimit = value; + if (repeatLimit == 0) { + listCommentLine(); + return; + } + readSpaces(); + repeating = true; + if (repeatLimit == 1) + break; + if (willSkip) + warning("Skipping only the first instruction"); + int savedColumn = column; + for (repeatCounter = 0; repeatCounter < repeatLimit; repeatCounter++) { + column = savedColumn; + assemblyPair(); + } + checkNoExtraCharacters(); + listLine(); + wereManyInstructions = true; + return; + default: + repeating = false; + break commentOrRep; + } + } + if (inFalseCondition()) { + switch (readInstruction()) { + case "END": + assemblyEnd(); + break; + case "IFT": + assemblyIft(); + break; + case "ELI": + assemblyEli(); + break; + case "ELS": + assemblyEls(); + break; + case "EIF": + assemblyEif(); + break; + default: + listCommentLine(); + return; + } + checkNoExtraCharacters(); + listCommentLine(); + return; + } + assemblyPair(); + checkNoExtraCharacters(); + listLine(); +} + +void assemblyFile(string filename) { + filename = filename.defaultExtension("asx"); + if (getOption('p')) + filename = absolutePath(filename); + File stream = openInputFile(filename); + scope (exit) stream.close(); + string oldFilename = currentFilename; + int oldLineNo = lineNo; + currentFilename = filename; + lineNo = 0; + foundEnd = false; + line = ""; + readChar: while (!foundEnd) { + int b = readByte(&stream); + switch (b) { + case -1: + break readChar; + case '\r': + assemblyLine(); + line = ""; + b = readByte(&stream); + if (b < 0) + break readChar; + if (b != '\n') + line ~= cast(char) b; + break; + case '\n': + case 0x9b: + assemblyLine(); + line = ""; + break; + default: + line ~= cast(char) b; + break; + } + } + if (!foundEnd) + assemblyLine(); + foundEnd = false; + currentFilename = oldFilename; + lineNo = oldLineNo; +} + +void assemblyPass() { + origin = -1; + loadOrigin = -1; + loadingOrigin = -1; + blockIndex = -1; + optionFill = false; + option5200 = false; + optionHeaders = true; + optionListing = pass2; + optionObject = true; + optionUnusedLabels = true; + willSkip = false; + skipping = false; + repeatOffset = 0; + wereManyInstructions = false; + currentFilename = "command line"; + lineNo = 0; + foreach (definition; commandLineDefinitions) { + line = replaceFirst(definition, "=", " equ "); + assemblyLine(); + } + line = null; + totalLines = 0; + assemblyFile(sourceFilename); + if (ifContexts.length != 0) + throw new AssemblyError("Missing EIF"); + if (willSkip) + throw new AssemblyError("Can't skip over this"); +} + +pure bool isOption(string arg) { + if (arg.length < 2) return false; + if (arg[0] == '-') return true; + if (arg[0] != '/') return false; + if (arg.length == 2) return true; + if (arg[2] == ':') return true; + return false; +} + +void setOption(char letter) { + assert(letter >= 'a' && letter <= 'z'); + if (options[letter - 'a']) { + exitCode = 3; + return; + } + options[letter - 'a'] = true; +} + +int main(string[] args) { + for (int i = 1; i < args.length; i++) { + string arg = args[i]; + if (isOption(arg)) { + char letter = arg[1]; + if (letter >= 'A' && letter <= 'Z') + letter += 'a' - 'A'; + switch (letter) { + case 'c': + case 'i': + case 'm': + case 'p': + case 'q': + case 'u': + if (arg.length != 2) + exitCode = 3; + setOption(letter); + break; + case 'd': + string definition = null; + if (arg[0] == '/') { + if (arg.length >= 3 && arg[2] == ':') + definition = arg[3 .. $]; + } else if (i + 1 < args.length && !isOption(args[i + 1])) + definition = args[++i]; + if (definition is null || find(definition, '=').empty) + exitCode = 3; + commandLineDefinitions ~= definition; + break; + case 'l': + case 't': + case 'o': + setOption(letter); + string filename = null; + if (arg[0] == '/') { + if (arg.length >= 3 && arg[2] == ':') + filename = arg[3 .. $]; + } else if (i + 1 < args.length && !isOption(args[i + 1])) + filename = args[++i]; + if (filename is null && (letter == 'o' || arg.length != 2)) + exitCode = 3; + optionParameters[letter - 'a'] = filename; + break; + default: + exitCode = 3; + break; + } + continue; + } + if (sourceFilename !is null) + exitCode = 3; + sourceFilename = arg; + } + if (sourceFilename is null) + exitCode = 3; + if (!getOption('q')) + writeln(TITLE); + if (exitCode != 0) { + write( +`Syntax: xasm source [options] +/c Include false conditionals in listing +/d:label=value Define a label +/i Don't list included files +/l[:filename] Generate listing +/o:filename Set object file name +/M Print Makefile rule +/p Print absolute paths in listing and error messages +/q Suppress info messages +/t[:filename] List label table +/u Warn of unused labels +`); + return exitCode; + } + try { + assemblyPass(); + pass2 = true; + assemblyPass(); + if (getOption('t') && labelTable.length > 0) + listLabelTable(); + } catch (AssemblyError e) { + warning(e.msg, true); + exitCode = 2; + } + listingStream.close(); + objectStream.close(); + if (exitCode <= 1) { + if (!getOption('q')) { + writefln("%d lines of source assembled", totalLines); + if (objectBytes > 0) + writefln("%d bytes written to the object file", objectBytes); + } + if (getOption('m')) { + writef("%s:", makeTarget); + foreach (filename; makeSources) + writef(" %s", makeEscape(filename)); + write("\n\txasm"); + for (int i = 1; i < args.length; i++) { + string arg = args[i]; + if (isOption(arg)) { + char letter = arg[1]; + if (letter >= 'A' && letter <= 'Z') + letter += 'a' - 'A'; + switch (letter) { + case 'm': + break; + case 'o': + if (arg[0] == '/') + writef(" /%c:$@", arg[1]); + else { + writef(" -%c $@", arg[1]); + ++i; + } + break; + default: + if (arg[0] == '-' + && (letter == 'd' || letter == 'l' || letter == 't') + && i + 1 < args.length && !isOption(args[i + 1])) { + writef(" %s %s", arg, makeEscape(args[++i])); + } + else { + writef(" %s", makeEscape(arg)); + } + break; + } + continue; + } + write(" $<"); + } + writeln(); + } + } + return exitCode; +} diff --git a/xasm.properties b/xasm.properties index a1aa219..51e2c0d 100644 --- a/xasm.properties +++ b/xasm.properties @@ -1,42 +1,42 @@ -# xasm settings for SciTE (http://www.scintilla.org/SciTE.html) - -file.patterns.xasm=*.asx -filter.xasm=xasm (asx)|$(file.patterns.xasm)| -*filter.xasm=$(filter.xasm) -lexer.$(file.patterns.xasm)=asm -*language.xasm=xasm|asx|| - -keywords.$(file.patterns.xasm)= \ - adc add and asl bcc bcs beq bit \ - bmi bne bpl brk bvc bvs clc cld \ - cli clv cmp cpx cpy dec dex dey \ - dta eif eli els end eor equ ert \ - icl ift inc ini ins inw inx iny \ - jcc jcs jeq jmi jmp jne jpl jsr \ - jvc jvs lda ldx ldy lsr mva mvx \ - mvy mwa mwx mwy nop opt ora org \ - pha php pla plp rcc rcs req rmi \ - rne rol ror rpl rti rts run rvc \ - rvs sbc scc scs sec sed sei seq \ - smi sne spl sta stx sty sub svc \ - svs tax tay tsx txa txs tya - -comment.block.asm=; - -style.asm.32= -style.asm.1=$(colour.code.comment.line) -style.asm.2= -style.asm.3=$(colour.string) -style.asm.4=$(colour.operator) -style.asm.5= -style.asm.6=$(colour.keyword) -style.asm.7= -style.asm.12=$(colour.char) -style.asm.13=$(colour.error) - -command.compile.$(file.patterns.xasm)=xasm /o:$(FileName).xex $(FilePath) -command.build.$(file.patterns.xasm)=make -if PLAT_WIN - command.go.$(file.patterns.xasm)=start $(FileName).xex -if PLAT_GTK - command.go.$(file.patterns.xasm)=atari800 -run $(FileName).xex +# xasm settings for SciTE (http://www.scintilla.org/SciTE.html) + +file.patterns.xasm=*.asx +filter.xasm=xasm (asx)|$(file.patterns.xasm)| +*filter.xasm=$(filter.xasm) +lexer.$(file.patterns.xasm)=asm +*language.xasm=xasm|asx|| + +keywords.$(file.patterns.xasm)= \ + adc add and asl bcc bcs beq bit \ + bmi bne bpl brk bvc bvs clc cld \ + cli clv cmp cpx cpy dec dex dey \ + dta eif eli els end eor equ ert \ + icl ift inc ini ins inw inx iny \ + jcc jcs jeq jmi jmp jne jpl jsr \ + jvc jvs lda ldx ldy lsr mva mvx \ + mvy mwa mwx mwy nop opt ora org \ + pha php pla plp rcc rcs req rmi \ + rne rol ror rpl rti rts run rvc \ + rvs sbc scc scs sec sed sei seq \ + smi sne spl sta stx sty sub svc \ + svs tax tay tsx txa txs tya + +comment.block.asm=; + +style.asm.32= +style.asm.1=$(colour.code.comment.line) +style.asm.2= +style.asm.3=$(colour.string) +style.asm.4=$(colour.operator) +style.asm.5= +style.asm.6=$(colour.keyword) +style.asm.7= +style.asm.12=$(colour.char) +style.asm.13=$(colour.error) + +command.compile.$(file.patterns.xasm)=xasm /o:$(FileName).xex $(FilePath) +command.build.$(file.patterns.xasm)=make +if PLAT_WIN + command.go.$(file.patterns.xasm)=start $(FileName).xex +if PLAT_GTK + command.go.$(file.patterns.xasm)=atari800 -run $(FileName).xex