From 77e8708934c5c792b1435fa11dfe3c0a6f636a8c Mon Sep 17 00:00:00 2001 From: Ian C Date: Mon, 7 Mar 2016 15:00:21 +0000 Subject: Updated README and copied latest version in. --- INSTALL | 38 + README.md | 39 +- doc/Makefile | 32 + doc/README | 22 + doc/manual.asciidoc | 546 ++++++++++++ doc/manual.html | 1577 +++++++++++++++++++++++++++++++++ doc/manual.pdf | Bin 0 -> 145493 bytes src/6502.c | 1585 +++++++++++++++++++++++++++++++++ src/6502.h | 43 + src/Makefile | 89 ++ src/alias.c | 146 +++ src/alias.h | 52 ++ src/basetype.h | 51 ++ src/casm.c | 925 +++++++++++++++++++ src/cmd.h | 155 ++++ src/codepage.c | 259 ++++++ src/codepage.h | 55 ++ src/expr.c | 700 +++++++++++++++ src/expr.h | 53 ++ src/global.h | 51 ++ src/label.c | 470 ++++++++++ src/label.h | 123 +++ src/listing.c | 418 +++++++++ src/listing.h | 77 ++ src/macro.c | 509 +++++++++++ src/macro.h | 101 +++ src/output.c | 228 +++++ src/output.h | 58 ++ src/parse.c | 332 +++++++ src/parse.h | 123 +++ src/stack.c | 143 +++ src/stack.h | 73 ++ src/state.c | 221 +++++ src/state.h | 130 +++ src/test/1 | 75 ++ src/test/2 | 68 ++ src/test/3 | 20 + src/test/6502.1 | 272 ++++++ src/test/inc | 6 + src/test/z80.1 | 447 ++++++++++ src/util.c | 201 +++++ src/util.h | 94 ++ src/varchar.c | 158 ++++ src/varchar.h | 79 ++ src/z80.c | 2458 +++++++++++++++++++++++++++++++++++++++++++++++++++ src/z80.h | 46 + 46 files changed, 13347 insertions(+), 1 deletion(-) create mode 100644 INSTALL create mode 100644 doc/Makefile create mode 100644 doc/README create mode 100644 doc/manual.asciidoc create mode 100644 doc/manual.html create mode 100644 doc/manual.pdf create mode 100644 src/6502.c create mode 100644 src/6502.h create mode 100644 src/Makefile create mode 100644 src/alias.c create mode 100644 src/alias.h create mode 100644 src/basetype.h create mode 100644 src/casm.c create mode 100644 src/cmd.h create mode 100644 src/codepage.c create mode 100644 src/codepage.h create mode 100644 src/expr.c create mode 100644 src/expr.h create mode 100644 src/global.h create mode 100644 src/label.c create mode 100644 src/label.h create mode 100644 src/listing.c create mode 100644 src/listing.h create mode 100644 src/macro.c create mode 100644 src/macro.h create mode 100644 src/output.c create mode 100644 src/output.h create mode 100644 src/parse.c create mode 100644 src/parse.h create mode 100644 src/stack.c create mode 100644 src/stack.h create mode 100644 src/state.c create mode 100644 src/state.h create mode 100644 src/test/1 create mode 100644 src/test/2 create mode 100644 src/test/3 create mode 100644 src/test/6502.1 create mode 100644 src/test/inc create mode 100644 src/test/z80.1 create mode 100644 src/util.c create mode 100644 src/util.h create mode 100644 src/varchar.c create mode 100644 src/varchar.h create mode 100644 src/z80.c create mode 100644 src/z80.h diff --git a/INSTALL b/INSTALL new file mode 100644 index 0000000..614920f --- /dev/null +++ b/INSTALL @@ -0,0 +1,38 @@ + +This program does not unfortunately use the configure script, but I have been +careful to write is as portably as possible -- it should only be using ISO C. + +In fact the only non-portable part is perhaps the Makefile for non UNIX-like +systems. + + +UNIX-like systems +================= + +To build the software on unix, or a compatible build system (eg. cygwin), +type the following: + + cd src + make + +Then copy the casm executable wherever you want. The documentation in the doc +directory can also be copied wherever. + +The documentation is supplied pre-built, but if local changes are made it can +be regenerated by: + + cd doc + make + +Note that the documentation relies on asciidoc. + + +Other or broken systems +======================= + +If the Makefile is not usable on your system, you simply need to compile all +the .c files and produce a single casm object. E.g. (using the cc command +as an example): + + cd src + cc -o casm *.c diff --git a/README.md b/README.md index 644a907..5b9b454 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,39 @@ # casm -Cross assembler + +Portable cross assembler. + +## Usage + +Simply pass it the file to assemble, i.e. + +`casm source.txt` + +Full documentation can be found in either [PDF](docs/manual.pdf) or +[HTML](docs/manual.html) format. + +## Processors + +Currently **casm** supports: + + * Z80 (the default) + * 6502 + +Plans for: + + * Gameboy Z80 derarative + * Ricoh 5A22 (SNES) + * SPC700 (SNES sound chip) + +## Output Formats + +Currently **casm** supports: + + * Raw binary output + * ZX Spectrum TAP file + +Plans for: + + * D64 Commodore 1571 disk image + * T64 Commodore 64 tape image + * SNES ROM + * NES ROM diff --git a/doc/Makefile b/doc/Makefile new file mode 100644 index 0000000..0791d55 --- /dev/null +++ b/doc/Makefile @@ -0,0 +1,32 @@ +# casm - Simple, portable assembler +# +# Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ------------------------------------------------------------------------- +# +# Makefile for documentation +# + +all: manual.html manual.pdf + +manual.html: manual.asciidoc + asciidoc manual.asciidoc + +manual.pdf: manual.asciidoc + a2x -fpdf -dbook manual.asciidoc + +clean: + rm -f manual.html manual.pdf diff --git a/doc/README b/doc/README new file mode 100644 index 0000000..66dbb8c --- /dev/null +++ b/doc/README @@ -0,0 +1,22 @@ + casm - A portable cross assembler + ================================= + + Copyright 2003-2015 Ian Cowburn + + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + See manual.pdf or manual.html for instructions on use. + + See LICENSE for full license text. diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc new file mode 100644 index 0000000..888a9f0 --- /dev/null +++ b/doc/manual.asciidoc @@ -0,0 +1,546 @@ + +CASM +==== + +A simple, portable multi-pass assembler + +Copyright (C) 2003-2015 Ian Cowburn + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.html + +Usage +----- + + +---- +casm file +---- + +Assembles file, and places the output in _output_ by default. + + +Source Format Example +--------------------- + +The source files follow this basic format: + +---- +; Comments +; +label1: equ 0xffff + + org $4000; + + db "Hello, World\n",0 + +main jp local_label ; Comments + +.local_label + inc a + +another: + inc b + jp local_label ; Actually jumps to the following local_label. + +.local_label + ret +---- + + +The source files follow the following rules: + +* Any text past a semicolon (;) is discarded as a comment (except when part + of a string constant). + +* Labels must start in column zero (the left hand most column). + + ** If the label ends with a colon (:) then the colon is removed. + + ** If the label doesn't start with a period (.) then it is assumed a global + label. + + ** If the label starts with a period (.) then it is assumed to be a local + label. Local labels are associated with the preceding global label. If a + global label and related local label have the same name, the local label + will be used on expansion. + + ** Any label can be followed by an 'equ' directive, in which case the label + is set to that value rather than the current program counter. + + ** Labels are case-insensitive. + +* Directives and opcodes must appear further along the line (anywhere else + other than the left hand column where labels live basically). + +* Strings can either be quoted with single or double quotes; this allows you to + put the other quote type inside the string. + + +Recognised directives +~~~~~~~~~~~~~~~~~~~~~ + +All directives are also recognised with an optional period (.) in front of +them, and are case insensitive. Directives can also be used to control the +output of a program listing, and the output of the assembly itself. These are +documented in further sections. + + +processor _CPU_:: + Sets the processor type to _CPU_. If omitted then Z80 is the default. + Note that this can appear multiple times in the same file. Currently + supported _CPU_ values are +Z80+ and +6502+. + +option _setting_, _value_:: + Set options. Options are defined later on, and each CPU can also have its + own options. For options that support booleans (on/off/true/false), + the _setting_ can be prefixed with a plus or minus character to switch it + on or off respectively. + +equ _value_:: + Sets the top level label to _value_. Note this requires a label on the + same line. + +org _value_:: + Sets the program counter (PC) to _value_. The PC defaults to zero. + +ds _value_[, _fill_]:: + Skips on the program counter _value_ bytes. If the optional _fill_ is + provided then the bytes are filled with _fill_, otherwise they are filled + with zero. + +db _value_[, _value_]:: + Writes bytes to the current PC. The values can be constants, expressions, + labels or strings. Built-in aliases are +byte+ and +text+. + +dw [, ]:: + Writes words (16-bit values) to the current PC. The values can be + constants, expressions or labels. Note that +word+ is a built-in alias for + this directive. + +align _value_[, _fill_]:: + Align the PC so that (PC modulus _value_) is zero. Will error if _value_ + is less than 2 or greater that 32768. No values are written to the skipped + bytes unless the optional _fill_ is supplied. + +include _filename_:: + Includes the source file _filename_ as if it was text entered at the + current location. + +incbin _filename_:: + Includes the binary data in _filename_ at the current PC, as if it was a + sequence of +db+ directives with all the bytes from the file. + +alias _command_, _replacement_:: + Creates an alias so that whenever the command _command_ is found in the + source it is replaced with _replacement_. The idea of this is to make it + easier to import sources that use unknown directives, e.g. + + alias setaddr,org + alias ldreg,ld + + cpu z80 + + setaddr $8000 ; These two are + org $8000 ; equivalent. + + ld a,(hl) ; These two are + ldreg a,(hl) ; equivalent. + +nullcmd:: + Simply does nothing. It's only real use is as an alias if you wished to + strip a directive from a foreign source file. + +end:: + Terminates the input processing. Anything past the directive will be + ignored. + + +Expressions +~~~~~~~~~~~ + +In any of the directives above, where a value is defined, an expression can be +entered. + +The following formats for constant numbers are supported (note these are +illustrated as a regular expression): + +"x" or 'x':: + A single quoted character will be converted into the appropriate character + code. + +[1-9][0-9]*:: + A decimal number, e.g. 42. + +0[0-7]*:: + An octal number, e.g. 052. + +0x[0-9a-fA-f]+:: + A hex number, e.g. 0x2a. + +[0-9a-fA-f]+h:: + A hex number, e.g. 2ah. + +$[0-9a-fA-f]+:: + A hex number, e.g. $2a. + +[01]+b:: + A binary number, e.g. 00101010b + +[a-zA-Z_0-9]+:: + A label, e.g. +main_loop+. + +The following operators are understood. The order here is the order of +precedence. + +{ }:: + Brackets used to alter the order of precedence. Note normal parenthesis + aren't used as the assembly language may make use of them. + +~ + -:: + Bitwise NOT/unary plus/unary minus. + +<< >>:: + Shift left/shift right. + +/ * %:: + Division/multiplication/modulus. + ++ -:: + Addition/subtraction. + +All the following have the same precedence, and so will be done left to right. + +==:: + Equality. Returns 1 if the arguments are equal, otherwise zero. + +!=:: + Inequality. Returns 1 if the arguments are unequal, otherwise zero. + +< \<= > >=:: + Less than/less than or equal/greater than/greater than or equal. Returns 1 + if the arguments are equal, otherwise zero. + + +All the following have the same precedence, and so will be done left to right. + +&& &:: + Boolean/bitwise AND. For boolean operation arguments, zero is FALSE, + otherwise TRUE. + +|| |:: + Boolean/bitwise OR. + +^:: + Bitwise XOR. + + +Assembly instructions will also permit these expressions to be used where +applicable. As many opcodes use parenthesis to indicate addressing modes, +remember that {} brackets can be used to alter expression precedence. + +---- + ld a,{8+2}*2 ; On the Z80 loads A with the value 20 + ld a,({8+2}*2) ; On the Z80 loads A with the value stored at + ; address 20 +---- + +Note that the expression is evaluated using a standard C int, and then cast +to the appropriate size. + + +Character Sets +~~~~~~~~~~~~~~ + +The assembler has built-in support for a few different character sets. +These can be set by using the options _charset_ or _codepage_, i.e. + +---- + option codepage, + option charset, +---- + +The following values can be used for _format_. + +ascii:: + 7-bit ASCII. This is the default. + +spectrum:: + The character codes as used on the Sinclair ZX Spectrum. + +zx81:: + The character codes as used on the Sinclair ZX-81. Lower case + letters are encoded as normal upper case letters and upper case + letter will be encoded as inverse upper case letters. + +cbm:: + PETSCII as used on the Commodore Business Machine's range from the + PET to the C128. See https://en.wikipedia.org/wiki/PETSCII for + more details. + +e.g. + +---- + option +list + option +list-hex + + option charset,ascii + db "Hello",'A' +; $48 $65 $6C $6C $6F $41 + + option charset,zx81 + db "Hello",'A' +; $AD $2A $31 $31 $34 $A6 + + option codepage,cbm + db "Hello",'A' +; $48 $45 $4C $4C $4F $41 + + option codepage,spectrum + db "Hello",'A' +; $48 $65 $6C $6C $6F $41 + +---- + + +Macros +~~~~~~ + +Macros can be defined in one of two ways; either parameterless or with named +parameters. Macro names are case-insensitive. + +---- +macro1: macro + + ld a,\1 + ld b,\2 + call \3 + + endm + +macro2: macro char,junk,interface + + ld a,@char + ld b,@junk + call @interface + + endm +---- + +Note that trying to expand and unknown/missing argument will be replaced with +an empty string. Also the two argument reference styles can be mixed, though +obviously the @ form only makes sense in a parameterised macro, e.g. + +---- + +mac: macro char,junk,interface + + ld a,@char + ld b,\2 + call @interface + + endm +---- + +The at symbol (@) used for parameter expansion in named argument macros can +be replaced by using the following option, e.g. + +---- + option macro-arg-char,& +---- + +Note that this is enforced when the macro is *used*, not when it is *defined*. +Also the character must not be quoted, as that will be parsed as a string +holding the character code of the character. + + +Output Format +------------- + +By default the assembled code is written to a file called *output* as raw +binary covering the block of memory that the assembly touched. + +This can be controlled with the following options. + +option output-file, _file_:: + Send the output to _file_. + +option output-type, _format_:: + Controls the output format with the following settings + + raw;; + The default raw binary. + + spectrum;; + Generates a Spectrum TAP file for an emulator. The TAP file will + be given the same name as the output filename, and its load address + will be set to the start of the created memory. Remember that TAP + files can be concatenated, so the output could be appended to + another TAP file containing a BASIC loader for example. + + +Listing +------- + +By default no output listing is generated. This can be controlled by the +the following options. + +option list, :: + Enables/disables listing. The listing will go to stdout. + +option list-file, _file_:: + Sends the listing to _file_. Note this should appear before enabling the + listing. + +option list-pc, :: + Control the output of the current PC in the as a comment preceding the + line (so that a listing could be reassembled with no editing). Defaults + to *off*. + +option list-hex, :: + Control the output of the bytes generated by the source line in hex. + Defaults to *off*. If *on* then the hex is output in a comment preceding + the line (possibly with the PC above), so that a listing is still valid to + be assembled. + +option list-labels, :: + Controls the listing of labels, either *off* (the default), *on* to dump + label values at the end of the listing and *all* to dump all labels, + including internally generated private labels for macros. + +option list-macros, :: + Controls the listing of macro invocations, either + + off;; + The default; don't list anything. + exec;; + List invocations of macros. + dump;; + Produce a list of macro definitions at the end of the listing. + all;; + Combine "exec" and "dump" + +option list-rm-blanks, :: + Defaults to *on*. This option causes multiple blank lines to be collapsed + down to a single line. + + +Z80 CPU +------- + +Opcodes +~~~~~~~ + +The Z80 assembler uses the standard Zilog opcodes, and supports +undocumented instructions. + +For instructions were the Accumulator can be assumed it can be omitted, and +EOR can be used the same as XOR: + +---- + xor a,a ; These are equivalent + xor a + eor a,a + + and a,b ; These are equivalent + and b +---- + +Where the high/low register parts of the IX and IY registers are to be used, +simply use ixl, iyl, ixh and iyh. Note that the assembler will accept +illegal pairings involving H and L, but these will be warned about: + +---- + + ld ixh,$e5 + ld iyl,iyl + + ld ixh,l ; This will be turned into "ld ixh,ixl" and a + ; warning will be issued. + + ld iyh,ixl ; This will generate an error as the index registers + ; have been mixed. + +---- + +For bit manipulations that also can copied to a register, these can be +represented by adding the destination register as an extra parameter, e.g. + +---- + + srl (iy-1),d + set 3,(iy-1),a + res 4,(iy-1),b + +---- + +For the hidden IN instruction using the flag register the following are all +equivalent: + +---- + in (c) + in f,(c) +---- + +For the hidden OUT instruction using the flag register, $00 or $ff depending +on where you're reading, the following are all equivalent, where _value_ can +be any value at all: + +---- + out (c) + out (c),f + out (c), +---- + + +Options +~~~~~~~ + +The Z80 assembler has no options. + + +6502 CPU +-------- + +Opcodes +~~~~~~~ + +The 6502 assembler uses the standard Motorola opcodes. + + +Options +~~~~~~~ + +The 6502 assembler has the following options. + +option zero-page, :: + Use Zero-Page addressing for _absolute_ and _absolute_,X address modes. + If mode is set to *auto* then tries to calculate the mode based on the + value in the last pass. + Defaults to *off*. e.g. + + cpu 6502 + org $8000 + + lda $0000,x ; Produces $bd $00 $00 + option +zero-page + lda $0000,x ; Produces $b5 $00 + lda $1234,x ; Produces an error + + option zero-page,auto + lda $00,x ; Produces $b5 $00 + lda $8000,x ; Produces $bd $00 $80 + + + +// vim: ai sw=4 ts=8 expandtab spell diff --git a/doc/manual.html b/doc/manual.html new file mode 100644 index 0000000..4aa250c --- /dev/null +++ b/doc/manual.html @@ -0,0 +1,1577 @@ + + + + + +CASM + + + + + +
+
+
+

A simple, portable multi-pass assembler

+

Copyright © 2003-2015 Ian Cowburn <ianc@noddybox.co.uk>

+

This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 3 of the License, or +(at your option) any later version.

+

This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details.

+

You should have received a copy of the GNU General Public License +along with this program. If not, see http://www.gnu.org/licenses/gpl-3.0.html

+
+
+
+

Usage

+
+
+
+
casm file
+
+

Assembles file, and places the output in output by default.

+
+
+
+

Source Format Example

+
+

The source files follow this basic format:

+
+
+
; Comments
+;
+label1: equ     0xffff
+
+        org     $4000;
+
+        db      "Hello, World\n",0
+
+main    jp      local_label     ; Comments
+
+.local_label
+        inc     a
+
+another:
+        inc     b
+        jp      local_label     ; Actually jumps to the following local_label.
+
+.local_label
+        ret
+
+

The source files follow the following rules:

+
    +
  • +

    +Any text past a semicolon (;) is discarded as a comment (except when part + of a string constant). +

    +
  • +
  • +

    +Labels must start in column zero (the left hand most column). +

    +
      +
    • +

      +If the label ends with a colon (:) then the colon is removed. +

      +
    • +
    • +

      +If the label doesn’t start with a period (.) then it is assumed a global + label. +

      +
    • +
    • +

      +If the label starts with a period (.) then it is assumed to be a local + label. Local labels are associated with the preceding global label. If a + global label and related local label have the same name, the local label + will be used on expansion. +

      +
    • +
    • +

      +Any label can be followed by an equ directive, in which case the label + is set to that value rather than the current program counter. +

      +
    • +
    • +

      +Labels are case-insensitive. +

      +
    • +
    +
  • +
  • +

    +Directives and opcodes must appear further along the line (anywhere else + other than the left hand column where labels live basically). +

    +
  • +
  • +

    +Strings can either be quoted with single or double quotes; this allows you to + put the other quote type inside the string. +

    +
  • +
+
+

Recognised directives

+

All directives are also recognised with an optional period (.) in front of +them, and are case insensitive. Directives can also be used to control the +output of a program listing, and the output of the assembly itself. These are +documented in further sections.

+
+
+processor CPU +
+
+

+ Sets the processor type to CPU. If omitted then Z80 is the default. + Note that this can appear multiple times in the same file. Currently + supported CPU values are Z80 and 6502. +

+
+
+option setting, value +
+
+

+ Set options. Options are defined later on, and each CPU can also have its + own options. For options that support booleans (on/off/true/false), + the setting can be prefixed with a plus or minus character to switch it + on or off respectively. +

+
+
+equ value +
+
+

+ Sets the top level label to value. Note this requires a label on the + same line. +

+
+
+org value +
+
+

+ Sets the program counter (PC) to value. The PC defaults to zero. +

+
+
+ds value[, fill] +
+
+

+ Skips on the program counter value bytes. If the optional fill is + provided then the bytes are filled with fill, otherwise they are filled + with zero. +

+
+
+db value[, value] +
+
+

+ Writes bytes to the current PC. The values can be constants, expressions, + labels or strings. Built-in aliases are byte and text. +

+
+
+dw <value>[, <value>] +
+
+

+ Writes words (16-bit values) to the current PC. The values can be + constants, expressions or labels. Note that word is a built-in alias for + this directive. +

+
+
+align value[, fill] +
+
+

+ Align the PC so that (PC modulus value) is zero. Will error if value + is less than 2 or greater that 32768. No values are written to the skipped + bytes unless the optional fill is supplied. +

+
+
+include filename +
+
+

+ Includes the source file filename as if it was text entered at the + current location. +

+
+
+incbin filename +
+
+

+ Includes the binary data in filename at the current PC, as if it was a + sequence of db directives with all the bytes from the file. +

+
+
+alias command, replacement +
+
+

+ Creates an alias so that whenever the command command is found in the + source it is replaced with replacement. The idea of this is to make it + easier to import sources that use unknown directives, e.g. +

+
+
+
alias setaddr,org
+alias ldreg,ld
+
+
+
+
cpu         z80
+
+
+
+
setaddr     $8000   ; These two are
+org         $8000   ; equivalent.
+
+
+
+
ld          a,(hl)  ; These two are
+ldreg       a,(hl)  ; equivalent.
+
+
+
+nullcmd +
+
+

+ Simply does nothing. It’s only real use is as an alias if you wished to + strip a directive from a foreign source file. +

+
+
+end +
+
+

+ Terminates the input processing. Anything past the directive will be + ignored. +

+
+
+
+
+

Expressions

+

In any of the directives above, where a value is defined, an expression can be +entered.

+

The following formats for constant numbers are supported (note these are +illustrated as a regular expression):

+
+
+"x" or x +
+
+

+ A single quoted character will be converted into the appropriate character + code. +

+
+
+[1-9][0-9]* +
+
+

+ A decimal number, e.g. 42. +

+
+
+0[0-7]* +
+
+

+ An octal number, e.g. 052. +

+
+
+0x[0-9a-fA-f]+ +
+
+

+ A hex number, e.g. 0x2a. +

+
+
+[0-9a-fA-f]+h +
+
+

+ A hex number, e.g. 2ah. +

+
+
+$[0-9a-fA-f]+ +
+
+

+ A hex number, e.g. $2a. +

+
+
+[01]+b +
+
+

+ A binary number, e.g. 00101010b +

+
+
+[a-zA-Z_0-9]+ +
+
+

+ A label, e.g. main_loop. +

+
+
+

The following operators are understood. The order here is the order of +precedence.

+
+
+{ } +
+
+

+ Brackets used to alter the order of precedence. Note normal parenthesis + aren’t used as the assembly language may make use of them. +

+
+
+~ + - +
+
+

+ Bitwise NOT/unary plus/unary minus. +

+
+
+<< >> +
+
+

+ Shift left/shift right. +

+
+
+/ * % +
+
+

+ Division/multiplication/modulus. +

+
+
++ - +
+
+

+ Addition/subtraction. +

+
+
+

All the following have the same precedence, and so will be done left to right.

+
+
+== +
+
+

+ Equality. Returns 1 if the arguments are equal, otherwise zero. +

+
+
+!= +
+
+

+ Inequality. Returns 1 if the arguments are unequal, otherwise zero. +

+
+
+< <= > >= +
+
+

+ Less than/less than or equal/greater than/greater than or equal. Returns 1 + if the arguments are equal, otherwise zero. +

+
+
+

All the following have the same precedence, and so will be done left to right.

+
+
+&& & +
+
+

+ Boolean/bitwise AND. For boolean operation arguments, zero is FALSE, + otherwise TRUE. +

+
+
+|| | +
+
+

+ Boolean/bitwise OR. +

+
+
+^ +
+
+

+ Bitwise XOR. +

+
+
+

Assembly instructions will also permit these expressions to be used where +applicable. As many opcodes use parenthesis to indicate addressing modes, +remember that {} brackets can be used to alter expression precedence.

+
+
+
    ld  a,{8+2}*2               ; On the Z80 loads A with the value 20
+    ld  a,({8+2}*2)             ; On the Z80 loads A with the value stored at
+                                ; address 20
+
+

Note that the expression is evaluated using a standard C int, and then cast +to the appropriate size.

+
+
+

Character Sets

+

The assembler has built-in support for a few different character sets. +These can be set by using the options charset or codepage, i.e.

+
+
+
    option codepage, <format>
+    option charset, <format>
+
+

The following values can be used for format.

+
+
+ascii +
+
+

+ 7-bit ASCII. This is the default. +

+
+
+spectrum +
+
+

+ The character codes as used on the Sinclair ZX Spectrum. +

+
+
+zx81 +
+
+

+ The character codes as used on the Sinclair ZX-81. Lower case + letters are encoded as normal upper case letters and upper case + letter will be encoded as inverse upper case letters. +

+
+
+cbm +
+
+

+ PETSCII as used on the Commodore Business Machine’s range from the + PET to the C128. See https://en.wikipedia.org/wiki/PETSCII for + more details. +

+
+
+

e.g.

+
+
+
    option  +list
+    option  +list-hex
+
+    option  charset,ascii
+    db      "Hello",'A'
+; $48 $65 $6C $6C $6F $41
+
+    option  charset,zx81
+    db      "Hello",'A'
+; $AD $2A $31 $31 $34 $A6
+
+    option  codepage,cbm
+    db      "Hello",'A'
+; $48 $45 $4C $4C $4F $41
+
+    option  codepage,spectrum
+    db      "Hello",'A'
+; $48 $65 $6C $6C $6F $41
+
+
+
+

Macros

+

Macros can be defined in one of two ways; either parameterless or with named +parameters. Macro names are case-insensitive.

+
+
+
macro1: macro
+
+        ld a,\1
+        ld b,\2
+        call \3
+
+        endm
+
+macro2: macro char,junk,interface
+
+        ld a,@char
+        ld b,@junk
+        call @interface
+
+        endm
+
+

Note that trying to expand and unknown/missing argument will be replaced with +an empty string. Also the two argument reference styles can be mixed, though +obviously the @ form only makes sense in a parameterised macro, e.g.

+
+
+
mac:    macro char,junk,interface
+
+        ld a,@char
+        ld b,\2
+        call @interface
+
+        endm
+
+

The at symbol (@) used for parameter expansion in named argument macros can +be replaced by using the following option, e.g.

+
+
+
        option  macro-arg-char,&
+
+

Note that this is enforced when the macro is used, not when it is defined. +Also the character must not be quoted, as that will be parsed as a string +holding the character code of the character.

+
+
+
+
+

Output Format

+
+

By default the assembled code is written to a file called output as raw +binary covering the block of memory that the assembly touched.

+

This can be controlled with the following options.

+
+
+option output-file, file +
+
+

+ Send the output to file. +

+
+
+option output-type, format +
+
+

+ Controls the output format with the following settings +

+
+
+raw +
+
+

+ The default raw binary. +

+
+
+spectrum +
+
+

+ Generates a Spectrum TAP file for an emulator. The TAP file will + be given the same name as the output filename, and its load address + will be set to the start of the created memory. Remember that TAP + files can be concatenated, so the output could be appended to + another TAP file containing a BASIC loader for example. +

+
+
+
+
+
+
+
+

Listing

+
+

By default no output listing is generated. This can be controlled by the +the following options.

+
+
+option list, <on|off> +
+
+

+ Enables/disables listing. The listing will go to stdout. +

+
+
+option list-file, file +
+
+

+ Sends the listing to file. Note this should appear before enabling the + listing. +

+
+
+option list-pc, <on|off> +
+
+

+ Control the output of the current PC in the as a comment preceding the + line (so that a listing could be reassembled with no editing). Defaults + to off. +

+
+
+option list-hex, <on|off> +
+
+

+ Control the output of the bytes generated by the source line in hex. + Defaults to off. If on then the hex is output in a comment preceding + the line (possibly with the PC above), so that a listing is still valid to + be assembled. +

+
+
+option list-labels, <on|off|all> +
+
+

+ Controls the listing of labels, either off (the default), on to dump + label values at the end of the listing and all to dump all labels, + including internally generated private labels for macros. +

+
+
+option list-macros, <off|exec|dump|all> +
+
+

+ Controls the listing of macro invocations, either +

+
+
+off +
+
+

+ The default; don’t list anything. +

+
+
+exec +
+
+

+ List invocations of macros. +

+
+
+dump +
+
+

+ Produce a list of macro definitions at the end of the listing. +

+
+
+all +
+
+

+ Combine "exec" and "dump" +

+
+
+
+
+option list-rm-blanks, <on|off> +
+
+

+ Defaults to on. This option causes multiple blank lines to be collapsed + down to a single line. +

+
+
+
+
+
+

Z80 CPU

+
+
+

Opcodes

+

The Z80 assembler uses the standard Zilog opcodes, and supports +undocumented instructions.

+

For instructions were the Accumulator can be assumed it can be omitted, and +EOR can be used the same as XOR:

+
+
+
    xor     a,a         ; These are equivalent
+    xor     a
+    eor     a,a
+
+    and     a,b         ; These are equivalent
+    and     b
+
+

Where the high/low register parts of the IX and IY registers are to be used, +simply use ixl, iyl, ixh and iyh. Note that the assembler will accept +illegal pairings involving H and L, but these will be warned about:

+
+
+
    ld  ixh,$e5
+    ld  iyl,iyl
+
+    ld  ixh,l           ; This will be turned into "ld ixh,ixl" and a
+                        ; warning will be issued.
+
+    ld  iyh,ixl         ; This will generate an error as the index registers
+                        ; have been mixed.
+
+

For bit manipulations that also can copied to a register, these can be +represented by adding the destination register as an extra parameter, e.g.

+
+
+
    srl (iy-1),d
+    set 3,(iy-1),a
+    res 4,(iy-1),b
+
+

For the hidden IN instruction using the flag register the following are all +equivalent:

+
+
+
    in  (c)
+    in  f,(c)
+
+

For the hidden OUT instruction using the flag register, $00 or $ff depending +on where you’re reading, the following are all equivalent, where value can +be any value at all:

+
+
+
    out (c)
+    out (c),f
+    out (c),<value>
+
+
+
+

Options

+

The Z80 assembler has no options.

+
+
+
+
+

6502 CPU

+
+
+

Opcodes

+

The 6502 assembler uses the standard Motorola opcodes.

+
+
+

Options

+

The 6502 assembler has the following options.

+
+
+option zero-page, <on|off|auto> +
+
+

+ Use Zero-Page addressing for absolute and absolute,X address modes. + If mode is set to auto then tries to calculate the mode based on the + value in the last pass. + Defaults to off. e.g. +

+
+
+
cpu     6502
+org     $8000
+
+
+
+
lda     $0000,x     ; Produces $bd $00 $00
+option  +zero-page
+lda     $0000,x     ; Produces $b5 $00
+lda     $1234,x     ; Produces an error
+
+
+
+
option  zero-page,auto
+lda     $00,x       ; Produces $b5 $00
+lda     $8000,x     ; Produces $bd $00 $80
+
+
+
+
+
+
+
+

+ + + diff --git a/doc/manual.pdf b/doc/manual.pdf new file mode 100644 index 0000000..d39db3e Binary files /dev/null and b/doc/manual.pdf differ diff --git a/src/6502.c b/src/6502.c new file mode 100644 index 0000000..ddaf482 --- /dev/null +++ b/src/6502.c @@ -0,0 +1,1585 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + 6502 Assembler + +*/ +#include +#include + +#include "global.h" +#include "expr.h" +#include "label.h" +#include "parse.h" +#include "cmd.h" +#include "codepage.h" + +#include "6502.h" + + +/* ---------------------------------------- TYPES AND GLOBALS +*/ +enum option_t +{ + OPT_ZP +}; + +enum zp_mode_t +{ + ZP_OFF, + ZP_ON, + ZP_AUTO +}; + +static const ValueTable options[] = +{ + {"zero-page", OPT_ZP}, + {NULL} +}; + +static const ValueTable zp_table[] = +{ + YES_NO_ENTRIES(ZP_ON, ZP_OFF), + {"auto", ZP_AUTO}, + {NULL} +}; + +static struct +{ + enum zp_mode_t zp_mode; +} option; + + +typedef enum +{ + ACCUMULATOR, + IMPLIED, + IMMEDIATE, + ABSOLUTE, + ZERO_PAGE, + ABSOLUTE_INDEX_X, + ABSOLUTE_INDEX_Y, + ZERO_PAGE_INDEX_X, + ZERO_PAGE_INDEX_Y, + ZERO_PAGE_INDIRECT_X, + ZERO_PAGE_INDIRECT_Y, + INDIRECT, + ADDR_MODE_ERROR, + ADDR_MODE_UNKNOWN +} address_mode_t; + +static const char *address_mode_name[] = +{ + "Accumulator", + "Implied", + "Immediate", + "Absolute", + "Zero Page", + "Absolute, index X", + "Absolute, index Y", + "Zero Page, index X", + "Zero Page, index Y", + "Zero Page, indirect X", + "Zero Page, indirect Y", + "Indirect", + "Address Mode Error", + "Address Mode Unknown" +}; + + +/* ---------------------------------------- MACROS +*/ +#define CMD_ADDRESS_MODE(mode, address) \ +do \ +{ \ + CalcAddressMode(argc, argv, quoted, err, errsize, &mode, &address); \ + \ + if (mode == ADDR_MODE_UNKNOWN) \ + { \ + snprintf(err, errsize, "%s: couldn't work out " \ + "addressing mode", argv[0]); \ + return CMD_FAILED; \ + } \ + \ + if (mode == ADDR_MODE_ERROR) return CMD_FAILED; \ +} while(0) + + +#define CMD_ZP_MODE(ZP_mode, non_ZP_mode) \ +do \ +{ \ + switch(option.zp_mode) \ + { \ + case ZP_ON: \ + if (*address < 0 || *address > 255) \ + { \ + snprintf(err, errsize, "value %d outside of " \ + "zero page", *address); \ + *mode = ADDR_MODE_ERROR; \ + return; \ + } \ + \ + *mode = ZP_mode; \ + break; \ + \ + case ZP_OFF: \ + *mode = non_ZP_mode; \ + break; \ + \ + case ZP_AUTO: \ + if (IsFinalPass() && *address >= 0 && *address <= 255) \ + { \ + *mode = ZP_mode; \ + } \ + else \ + { \ + *mode = non_ZP_mode; \ + } \ + break; \ + } \ +} while (0) + + +/* ---------------------------------------- PRIVATE FUNCTIONS +*/ +static void CalcAddressMode(int argc, char *argv[], int quoted[], + char *err, size_t errsize, + address_mode_t *mode, int *address) +{ + *mode = ADDR_MODE_UNKNOWN; + *address = 0; + + /* Implied + */ + if (argc == 1) + { + *mode = IMPLIED; + return; + } + + /* Accumulator + */ + if (argc == 2 && CompareString(argv[1], "A")) + { + *mode = ACCUMULATOR; + return; + } + + /* Immediate + */ + if (argc == 2 && argv[1][0] == '#') + { + *mode = IMMEDIATE; + + if (!ExprEval(argv[1] + 1, address)) + { + snprintf(err, errsize, "%s: expression error: %s", + argv[1], ExprError()); + *mode = ADDR_MODE_ERROR; + } + + return; + } + + + /* Absolute + */ + if (argc == 2 && !quoted[1]) + { + if (!ExprEval(argv[1], address)) + { + snprintf(err, errsize, "%s: expression error: %s", + argv[1], ExprError()); + *mode = ADDR_MODE_ERROR; + return; + } + + CMD_ZP_MODE(ZERO_PAGE, ABSOLUTE); + + return; + } + + + /* Absolute,[XY] + */ + if (argc == 3 && !quoted[1]) + { + if (!ExprEval(argv[1], address)) + { + snprintf(err, errsize, "%s: expression error: %s", + argv[1], ExprError()); + *mode = ADDR_MODE_ERROR; + return; + } + + if (CompareString(argv[2], "X")) + { + CMD_ZP_MODE(ZERO_PAGE_INDEX_X, ABSOLUTE_INDEX_X); + } + else if (CompareString(argv[2], "Y")) + { + CMD_ZP_MODE(ZERO_PAGE_INDEX_Y, ABSOLUTE_INDEX_Y); + } + else + { + snprintf(err, errsize, "unknown index register '%s'", argv[2]); + *mode = ADDR_MODE_ERROR; + return; + } + + return; + } + + + /* (zp,x) or (ind) + */ + if (argc == 2 && quoted[1] == '(') + { + char *addr; + + if (!CompareEnd(argv[1], ",x")) + { + if (!ExprEval(argv[1], address)) + { + snprintf(err, errsize, "%s: expression error: %s", + argv[1], ExprError()); + *mode = ADDR_MODE_ERROR; + return; + } + + *mode = INDIRECT; + return; + } + + *mode = ZERO_PAGE_INDIRECT_X; + + addr = DupStr(argv[1]); + *strchr(addr, ',') = 0; + + if (!ExprEval(addr, address)) + { + snprintf(err, errsize, "%s: expression error: %s", + addr, ExprError()); + *mode = ADDR_MODE_ERROR; + free(addr); + return; + } + + free(addr); + + if (*address < 0 || *address > 255) + { + snprintf(err, errsize, "value %d outside of zero page", *address); + *mode = ADDR_MODE_ERROR; + return; + } + + return; + } + + + /* (zp),y + */ + if (argc == 3 && quoted[1] == '(') + { + *mode = ZERO_PAGE_INDIRECT_Y; + + if (!CompareString(argv[2], "y")) + { + snprintf(err, errsize, "illegal index register '%s' used for " + "zero-page indirect", argv[2]); + *mode = ADDR_MODE_ERROR; + return; + } + + if (!ExprEval(argv[1], address)) + { + snprintf(err, errsize, "%s: expression error: %s", + argv[1], ExprError()); + *mode = ADDR_MODE_ERROR; + return; + } + + if (*address < 0 || *address > 255) + { + snprintf(err, errsize, "value %d outside of zero page", *address); + *mode = ADDR_MODE_ERROR; + return; + } + + return; + } +} + + + +/* ---------------------------------------- COMMAND HANDLERS +*/ + +static CommandStatus DUMMY(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ACCUMULATOR: + return CMD_OK; + + case IMPLIED: + return CMD_OK; + + case IMMEDIATE: + return CMD_OK; + + case ABSOLUTE: + return CMD_OK; + + case ZERO_PAGE: + return CMD_OK; + + case ABSOLUTE_INDEX_X: + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + return CMD_OK; + + case ZERO_PAGE_INDEX_Y: + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus ADC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0x69); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x6d); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x65); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x7d); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0x79); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x75); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0x61); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0x71); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus AND(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0x29); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x2d); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x25); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x3d); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0x39); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x35); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0x21); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0x31); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus ASL(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMPLIED: + case ACCUMULATOR: + PCWrite(0x0a); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x0e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x06); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x1e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x16); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus BIT(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0x2c); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x24); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus CMP(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0xc9); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0xcd); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xc5); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0xdd); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0xd9); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0xd5); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0xc1); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0xd1); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus CPX(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0xe0); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0xec); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xe4); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus CPY(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0xc0); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0xcc); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xc4); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus DEC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0xce); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xc6); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0xde); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0xd6); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus EOR(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0x49); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x4d); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x45); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x5d); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0x59); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x55); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0x41); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0x51); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus INC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0xee); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xe6); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0xfe); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0xf6); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus JMP(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0x4c); + PCWriteWord(address); + return CMD_OK; + + case INDIRECT: + PCWrite(0x6c); + PCWriteWord(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus JSR(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0x20); + PCWriteWord(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus LDA(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0xa9); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0xad); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xa5); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0xbd); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0xb9); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0xb5); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0xa1); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0xb1); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus LDX(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0xa2); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0xae); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xa6); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0xbe); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_Y: + PCWrite(0xb6); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus LDY(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0xa0); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0xac); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xa4); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0xbc); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0xb4); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus LSR(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMPLIED: + case ACCUMULATOR: + PCWrite(0x4a); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x4e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x46); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x5e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x56); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus ORA(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0x09); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x0d); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x05); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x1d); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0x19); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x15); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0x01); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0x11); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus ROL(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMPLIED: + case ACCUMULATOR: + PCWrite(0x2a); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x2e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x26); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x3e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x36); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus ROR(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMPLIED: + case ACCUMULATOR: + PCWrite(0x6a); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0x6e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x66); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x7e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x76); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus SBC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case IMMEDIATE: + PCWrite(0xe9); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE: + PCWrite(0xed); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0xe5); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0xfd); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0xf9); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0xf5); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0xe1); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0xf1); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus STA(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0x8d); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x85); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + PCWrite(0x9d); + PCWriteWord(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + PCWrite(0x99); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x95); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_X: + PCWrite(0x81); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDIRECT_Y: + PCWrite(0x91); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus STX(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0x8e); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x86); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_Y: + if (address < 0 || address > 255) + { + snprintf(err, errsize, "%s: value %d outside of zero page", + argv[0], address); + return CMD_FAILED; + } + + PCWrite(0x96); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_Y: + PCWrite(0x96); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +static CommandStatus STY(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + address_mode_t mode; + int address; + + CMD_ADDRESS_MODE(mode, address); + + switch(mode) + { + case ABSOLUTE: + PCWrite(0x8c); + PCWriteWord(address); + return CMD_OK; + + case ZERO_PAGE: + PCWrite(0x84); + PCWrite(address); + return CMD_OK; + + case ABSOLUTE_INDEX_X: + if (address < 0 || address > 255) + { + snprintf(err, errsize, "%s: value %d outside of zero page", + argv[0], address); + return CMD_FAILED; + } + + PCWrite(0x94); + PCWrite(address); + return CMD_OK; + + case ZERO_PAGE_INDEX_X: + PCWrite(0x94); + PCWrite(address); + return CMD_OK; + + default: + snprintf(err, errsize, "%s: unsupported addressing mode %s", + argv[0], address_mode_name[mode]); + return CMD_FAILED; + } +} + +/* ---------------------------------------- OPCODE TABLES +*/ +typedef struct +{ + const char *op; + int code; +} OpcodeTable; + +typedef struct +{ + const char *op; + Command cmd; +} HandlerTable; + +static const OpcodeTable implied_opcodes[] = +{ + {"NOP", 0xea}, + {"TXS", 0x9a}, + {"TSX", 0xba}, + {"PHA", 0x48}, + {"PLA", 0x68}, + {"PHP", 0x08}, + {"PLP", 0x28}, + {"CLC", 0x18}, + {"SEC", 0x38}, + {"CLI", 0x58}, + {"SEI", 0x78}, + {"CLV", 0xb8}, + {"CLD", 0xd8}, + {"SED", 0xf8}, + {"BRK", 0x00}, + {"TAX", 0xaa}, + {"TXA", 0x8a}, + {"DEX", 0xca}, + {"INX", 0xe8}, + {"TAY", 0xa8}, + {"TYA", 0x98}, + {"DEY", 0x88}, + {"INY", 0xc8}, + {"RTI", 0x40}, + {"RTS", 0x60}, + {NULL} +}; + + +static const OpcodeTable branch_opcodes[] = +{ + {"BPL", 0x10}, + {"BMI", 0x30}, + {"BVC", 0x50}, + {"BVS", 0x70}, + {"BCC", 0x90}, + {"BCS", 0xB0}, + {"BNE", 0xD0}, + {"BEQ", 0xF0}, + {NULL} +}; + + +static const HandlerTable handler_table[] = +{ + {"ADC", ADC}, + {"AND", AND}, + {"ASL", ASL}, + {"BIT", BIT}, + {"CMP", CMP}, + {"CPX", CPX}, + {"CPY", CPY}, + {"DEC", DEC}, + {"EOR", EOR}, + {"INC", INC}, + {"JMP", JMP}, + {"JSR", JSR}, + {"LDA", LDA}, + {"LDX", LDX}, + {"LDY", LDY}, + {"LSR", LSR}, + {"ORA", ORA}, + {"ROL", ROL}, + {"ROR", ROR}, + {"SBC", SBC}, + {"STA", STA}, + {"STX", STX}, + {"STY", STY}, + {NULL} +}; + + + + +/* ---------------------------------------- PUBLIC FUNCTIONS +*/ + +void Init_6502(void) +{ + option.zp_mode = ZP_OFF; +} + + +const ValueTable *Options_6502(void) +{ + return options; +} + +CommandStatus SetOption_6502(int opt, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + const ValueTable *val; + + switch(opt) + { + case OPT_ZP: + CMD_ARGC_CHECK(1); + CMD_TABLE(argv[0], zp_table, val); + + option.zp_mode = val->value; + + if (option.zp_mode == ZP_AUTO) + { + SetNeededPasses(3); + } + break; + + default: + break; + } + + return CMD_OK; +} + +CommandStatus Handler_6502(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int f; + + /* Check for simple (implied addressing) opcodes + */ + for(f = 0; implied_opcodes[f].op; f++) + { + if (CompareString(argv[0], implied_opcodes[f].op)) + { + PCWrite(implied_opcodes[f].code); + return CMD_OK; + } + } + + /* Check for branch opcodes + */ + for(f = 0; branch_opcodes[f].op; f++) + { + if (CompareString(argv[0], branch_opcodes[f].op)) + { + int offset; + + CMD_ARGC_CHECK(2); + + CMD_EXPR(argv[1], offset); + + offset = offset - (PC() + 2); + + if (IsFinalPass() && (offset < -128 || offset > 127)) + { + snprintf(err, errsize, "%s: Branch offset (%d) too big", + argv[1], offset); + return CMD_FAILED; + } + + PCWrite(branch_opcodes[f].code); + PCWrite(offset); + + return CMD_OK; + } + } + + + /* Check for other opcodes + */ + for(f = 0; handler_table[f].op; f++) + { + if (CompareString(argv[0], handler_table[f].op)) + { + return handler_table[f].cmd(label, argc, argv, + quoted, err, errsize);; + } + } + + + return CMD_NOT_KNOWN; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/6502.h b/src/6502.h new file mode 100644 index 0000000..df974b4 --- /dev/null +++ b/src/6502.h @@ -0,0 +1,43 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + 6502 Assembler + +*/ + +#ifndef CASM_6502_H +#define CASM_6502_H + +void Init_6502(void); + +const ValueTable *Options_6502(void); + +CommandStatus SetOption_6502(int opt, int argc, char *argv[], int quoted[], + char *error, size_t error_size); + +CommandStatus Handler_6502(const char *label, int argc, char *argv[], + int quoted[], char *error, size_t error_size); + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/Makefile b/src/Makefile new file mode 100644 index 0000000..d862ea0 --- /dev/null +++ b/src/Makefile @@ -0,0 +1,89 @@ +# casm - Simple, portable assembler +# +# Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) +# +# This program is free software: you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 3 of the License, or +# (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# ------------------------------------------------------------------------- +# +# Makefile +# + +CFLAGS += -g + +TARGET = casm + +SOURCE = casm.c \ + expr.c \ + label.c \ + macro.c \ + parse.c \ + state.c \ + codepage.c \ + util.c \ + output.c \ + varchar.c \ + stack.c \ + listing.c \ + alias.c \ + 6502.c \ + z80.c + +OBJECTS = casm.o \ + expr.o \ + label.o \ + macro.o \ + parse.o \ + state.o \ + codepage.o \ + util.o \ + output.o \ + varchar.o \ + stack.o \ + listing.o \ + alias.o \ + 6502.o \ + z80.o + +$(TARGET): $(OBJECTS) + $(CC) $(CLAGS) -o $(TARGET) $(OBJECTS) + +clean: + rm -f $(TARGET) $(TARGET).exe $(OBJECTS) core *.core + +6502.o: 6502.c global.h basetype.h util.h state.h expr.h label.h parse.h \ + cmd.h codepage.h 6502.h +alias.o: alias.c global.h basetype.h util.h state.h alias.h +casm.o: casm.c global.h basetype.h util.h state.h expr.h label.h macro.h \ + cmd.h parse.h codepage.h output.h stack.h listing.h alias.h z80.h 6502.h +codepage.o: codepage.c global.h basetype.h util.h state.h codepage.h \ + parse.h cmd.h +expr.o: expr.c global.h basetype.h util.h state.h expr.h label.h +label.o: label.c global.h basetype.h util.h state.h codepage.h parse.h \ + cmd.h stack.h label.h +listing.o: listing.c global.h basetype.h util.h state.h label.h macro.h \ + cmd.h parse.h expr.h varchar.h listing.h +macro.o: macro.c global.h basetype.h util.h state.h codepage.h parse.h \ + cmd.h varchar.h macro.h +output.o: output.c global.h basetype.h util.h state.h output.h parse.h \ + cmd.h +parse.o: parse.c global.h basetype.h util.h state.h codepage.h parse.h \ + cmd.h +stack.o: stack.c global.h basetype.h util.h state.h stack.h +state.o: state.c global.h basetype.h util.h state.h expr.h +util.o: util.c global.h basetype.h util.h state.h +varchar.o: varchar.c global.h basetype.h util.h state.h codepage.h \ + parse.h cmd.h varchar.h +z80.o: z80.c global.h basetype.h util.h state.h expr.h label.h parse.h \ + cmd.h codepage.h varchar.h z80.h diff --git a/src/alias.c b/src/alias.c new file mode 100644 index 0000000..01017bd --- /dev/null +++ b/src/alias.c @@ -0,0 +1,146 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for aliases. + +*/ +#include +#include +#include +#include + +#include "global.h" +#include "alias.h" + + +/* ---------------------------------------- TYPES +*/ + +typedef struct alias +{ + char *command; + char *alias; + struct alias *next; +} Alias; + + +/* ---------------------------------------- GLOBALS +*/ +static Alias *head; +static Alias *tail; + + +/* ---------------------------------------- PRIVATE FUNCTIONS INTERFACES +*/ +static Alias *FindAlias(const char *p) +{ + Alias *a = head; + + while(a) + { + if (CompareString(a->command, p)) + { + return a; + } + + a = a->next; + } + + return NULL; +} + + +static void AddAlias(const char *p, const char *r) +{ + Alias *a = FindAlias(p); + + if (!a) + { + a = Malloc(sizeof *a); + + a->command = DupStr(p); + a->alias = DupStr(r); + a->next = NULL; + + if (tail) + { + tail->next = a; + } + + tail = a; + + if (!head) + { + head = a; + } + } + else + { + free(a->alias); + a->alias = DupStr(r); + } +} + + +/* ---------------------------------------- INTERFACES +*/ + +void AliasClear() +{ + while(head) + { + Alias *a; + + free(head->command); + free(head->alias); + a = head; + head = head->next; + free(a); + } + + head = NULL; + tail = NULL; +} + + +void AliasCreate(const char *command, const char *alias) +{ + AddAlias(command, alias); +} + + +char *AliasExpand(char *command) +{ + Alias *a; + + if ((a = FindAlias(command))) + { + free(command); + command = DupStr(a->alias); + } + + return command; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/alias.h b/src/alias.h new file mode 100644 index 0000000..7f55d7c --- /dev/null +++ b/src/alias.h @@ -0,0 +1,52 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for aliases. + +*/ + +#ifndef CASM_ALIAS_H +#define CASM_ALIAS_H + +#include + +/* ---------------------------------------- INTERFACES +*/ + +/* Clear all aliases +*/ +void AliasClear(); + +/* Create an alias +*/ +void AliasCreate(const char *command, const char *alias); + +/* Expand an alias. The passed pointer is returned if there is no alias for + the command, otherwise if there is an alias the passed pointer is freed + and a newly allocated version of the replacement is returned. +*/ +char *AliasExpand(char *command); + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/basetype.h b/src/basetype.h new file mode 100644 index 0000000..1cf4892 --- /dev/null +++ b/src/basetype.h @@ -0,0 +1,51 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Basic, global integral types + +*/ + +#ifndef CASM_BASETYPE_H +#define CASM_BASETYPE_H + +/* Used to represent a byte +*/ +typedef unsigned char Byte; + +/* Used to represent a word +*/ +typedef unsigned int Word; + +/* Get byte of word +*/ +#define LOBYTE(w) ((w)&0xff) +#define HIBYTE(w) (((w)>>8)&0xff) + +/* Set byte of word +*/ +#define SET_LOBYTE(w,v) w=(w&0xff00)|(v) +#define SET_HIBYTE(w,v) w=(w&0x00ff)|((v)<<8) + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/casm.c b/src/casm.c new file mode 100644 index 0000000..35cd205 --- /dev/null +++ b/src/casm.c @@ -0,0 +1,925 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Main + +*/ +#include +#include +#include +#include +#include +#include + +#include "global.h" +#include "expr.h" +#include "label.h" +#include "macro.h" +#include "parse.h" +#include "cmd.h" +#include "state.h" +#include "codepage.h" +#include "output.h" +#include "stack.h" +#include "listing.h" +#include "alias.h" + +/* ---------------------------------------- PROCESSORS +*/ +#include "z80.h" +#include "6502.h" + + +/* ---------------------------------------- MACROS +*/ + + +/* ---------------------------------------- TYPES +*/ + +/* Defines a CPU +*/ +typedef struct +{ + const char *name; + int mem_size; + WordMode word_mode; + void (*init)(void); + const ValueTable *(*options)(void); + CommandStatus (*set_option)(int o, int c, char *a[], + int q[], char *e, size_t s); + Command handler; +} CPU; + + +/* ---------------------------------------- GLOBALS +*/ +static const CPU cpu_table[]= +{ + { + "Z80", + 0x10000, + LSB_Word, + Init_Z80, Options_Z80, SetOption_Z80, Handler_Z80 + }, + { + "6502", + 0x10000, + LSB_Word, + Init_6502, Options_6502, SetOption_6502, Handler_6502 + }, + + {NULL} +}; + +#define NO_CPU (sizeof(cpu)/sizeof(cpu[0])) + +static const CPU *cpu = cpu_table; + + +/* ---------------------------------------- PROTOS +*/ +static void CheckLimits(void); + +static void InitProcessors(void); + +static void RunPass(const char *name, FILE *, int depth); +static void ProduceOutput(void); + + +/* ---------------------------------------- INTERNAL COMMAND HANDLING +*/ + +static CommandStatus EQU(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int result; + + CMD_LABEL_CHECK; + CMD_ARGC_CHECK(2); + CMD_EXPR(argv[1], result); + + LabelSet(label, result, ANY_LABEL); + + return CMD_OK; +} + +static CommandStatus ORG(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int result; + + CMD_ARGC_CHECK(2); + CMD_EXPR(argv[1], result); + + SetPC(result); + + /* If there was a label, set that as well + */ + if (label) + { + LabelSet(label, result, ANY_LABEL); + } + + return CMD_OK; +} + +static CommandStatus DS(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int count; + int value = 0; + int f; + + CMD_ARGC_CHECK(2); + CMD_EXPR(argv[1], count); + + for(f = 0; f < count; f++) + { + if (argc > 2) + { + CMD_EXPR(argv[2], value); + } + + PCWrite(value); + } + + return CMD_OK; +} + +static CommandStatus DefineMem(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize, + int bitsize) +{ + int f; + + CMD_ARGC_CHECK(2); + + for(f = 1; f < argc; f++) + { + if (quoted[f]) + { + const char *p = argv[f]; + + while(*p) + { + int val = CodepageConvert(*p++); + + if (bitsize == 8) + { + PCWrite(val); + } + else + { + PCWriteWord(val); + } + } + } + else + { + int val; + + CMD_EXPR(argv[f], val); + + if (bitsize == 8) + { + PCWrite(val); + } + else + { + PCWriteWord(val); + } + } + } + + return CMD_OK; +} + + +static CommandStatus DB(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + return DefineMem(label, argc, argv, quoted, err, errsize, 8); +} + + +static CommandStatus DW(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + return DefineMem(label, argc, argv, quoted, err, errsize, 16); +} + + +static CommandStatus ALIGN(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int count; + int value = 0; + int f; + + CMD_ARGC_CHECK(2); + CMD_EXPR(argv[1], count); + + if (count < 2 || count > 32768) + { + snprintf(err, errsize, "%s: Illegal align size %d", argv[0], count); + return CMD_FAILED; + } + + while ((PC() % count)) + { + if (argc > 2) + { + CMD_EXPR(argv[2], value); + PCWrite(value); + } + else + { + PCAdd(1); + } + } + + return CMD_OK; +} + + +static CommandStatus INCBIN(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + FILE *fp; + int num; + unsigned char buff[4096]; + + CMD_ARGC_CHECK(2); + + if (!(fp = fopen(argv[1], "rb"))) + { + snprintf(err, errsize, "Failed to open '%s'", argv[1]); + return CMD_FAILED; + } + + while((num = fread(buff, 1, sizeof buff, fp)) > 0) + { + int f; + + for(f = 0; f < num; f++) + { + PCWrite(buff[f]); + } + } + + fclose(fp); + + return CMD_OK; +} + + +static CommandStatus ARCH(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int f; + + CMD_ARGC_CHECK(2); + + for(f = 0; cpu_table[f].name; f++) + { + if (CompareString(argv[1], cpu_table[f].name)) + { + cpu = cpu_table + f; + SetWordMode(cpu->word_mode); + return CMD_OK; + } + } + + snprintf(err, errsize, "%s: unknown CPU '%s'\n", argv[0], argv[1]); + + return CMD_FAILED; +} + + +static CommandStatus OPTION(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + const ValueTable *entry; + char *argv_yes[2] = {"yes", NULL}; + char *argv_no[2] = {"no", NULL}; + int qu[1] = {0}; + int *q; + char **args; + int ac; + char *opt; + + if (*argv[1] == '+' || *argv[1] == '-') + { + CMD_ARGC_CHECK(2); + + opt = argv[1] + 1; + + q = qu; + ac = 1; + + if (*argv[1] == '+') + { + args = argv_yes; + } + else + { + args = argv_no; + } + } + else + { + CMD_ARGC_CHECK(3); + args = argv + 2; + ac = argc - 2; + q = quoted + 2; + opt = argv[1]; + } + + if ((entry = ParseTable(opt, ListOptions()))) + { + return ListSetOption(entry->value, ac, args, q, err, errsize); + } + else if ((entry = ParseTable(opt, MacroOptions()))) + { + return MacroSetOption(entry->value, ac, args, q, err, errsize); + } + else if ((entry = ParseTable(opt, CodepageOptions()))) + { + return CodepageSetOption(entry->value, ac, args, q, err, errsize); + } + else if ((entry = ParseTable(opt, OutputOptions()))) + { + return OutputSetOption(entry->value, ac, args, q, err, errsize); + } + else if ((entry = ParseTable(opt, cpu->options()))) + { + return cpu->set_option(entry->value, ac, args, q, err, errsize); + } + + snprintf(err, errsize, "%s: unknown option %s", argv[0], opt); + return CMD_FAILED; +} + + +static CommandStatus ALIAS(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + CMD_ARGC_CHECK(3); + + AliasCreate(argv[1], argv[2]); + + return CMD_OK; +} + + +static CommandStatus NULLCMD(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + return CMD_OK; +} + + +static struct +{ + const char *cmd; + Command handler; +} command_table[] = +{ + {"equ", EQU}, + {".equ", EQU}, + {"org", ORG}, + {".org", ORG}, + {"ds", DS}, + {".ds", DS}, + {"defs", DS}, + {".defs", DS}, + {"defb", DB}, + {".defb", DB}, + {"db", DB}, + {".db", DB}, + {"byte", DB}, + {".byte", DB}, + {"text", DB}, + {".text", DB}, + {"dw", DW}, + {".dw", DW}, + {"defw", DW}, + {".defw", DW}, + {"word", DW}, + {".word", DW}, + {"align", ALIGN}, + {".align", ALIGN}, + {"incbin", INCBIN}, + {".incbin", INCBIN}, + {"cpu", ARCH}, + {".cpu", ARCH}, + {"arch", ARCH}, + {".arch", ARCH}, + {"option", OPTION}, + {".option", OPTION}, + {"opt", OPTION}, + {".opt", OPTION}, + {"alias", ALIAS}, + {".alias", ALIAS}, + {"nullcmd", NULLCMD}, + {".nullcmd", NULLCMD}, + {NULL} +}; + + +static CommandStatus RunInternal(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int f; + + for(f = 0; command_table[f].cmd; f++) + { + if (CompareString(command_table[f].cmd, argv[0])) + { + return command_table[f].handler(label, argc, argv, + quoted, err, errsize); + } + } + + return CMD_NOT_KNOWN; +} + + +/* ---------------------------------------- MAIN +*/ +int main(int argc, char *argv[]) +{ + FILE *fp = NULL; + int done = FALSE; + int f; + + CheckLimits(); + + if (!argv[1]) + { + fprintf(stderr,"usage: casm file\n"); + exit(EXIT_FAILURE); + } + + fp = fopen(argv[1], "r"); + + if (!fp) + { + fprintf(stderr,"Failed to read from %s\n", argv[1]); + return EXIT_FAILURE; + } + + ClearState(); + + SetPC(0); + InitProcessors(); + + while(!done) + { + RunPass(argv[1], fp, 0); + rewind(fp); + + SetPC(0); + MacroSetDefaults(); + AliasClear(); + InitProcessors(); + LabelResetNamespace(); + + if (IsFinalPass()) + { + done = TRUE; + } + else + { + NextPass(); + } + } + + ProduceOutput(); + + return EXIT_SUCCESS; +} + + +/* ---------------------------------------- UTIL +*/ + +/* Checks compiler limits +*/ +static void CheckLimits(void) +{ + if (CHAR_BIT != 8) + { + fprintf(stderr,"Sorry - char must be 8 bits for correct operation\n\n"); + fprintf(stderr,"Actually, it may work - recompile with this\n"); + fprintf(stderr,"test disabled and let the author know please!\n"); + exit(EXIT_FAILURE); + } + +#if (INT_MAX < 0x10000) + fprintf(stderr,"sorry - maximum int must be bigger than 0xffff\n"); + exit(EXIT_FAILURE); +#endif + +#if (INT_MIN > -0x10000) + fprintf(stderr,"sorry - minimum int must be bigger than -0xffff\n"); + exit(EXIT_FAILURE); +#endif +} + +static void InitProcessors(void) +{ + int f; + + for(f = 0; cpu_table[f].name; f++) + { + cpu_table[f].init(); + } + + SetWordMode(cpu->word_mode); +} + + +/* ---------------------------------------- ASSEMBLY PASS +*/ +static CommandStatus RunLine(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + CommandStatus cmdstat = CMD_OK; + + /* Run internal then CPU commands + */ + cmdstat = RunInternal(label, argc, argv, quoted, err, errsize); + + if (cmdstat == CMD_NOT_KNOWN) + { + cmdstat = cpu->handler(label, argc, argv, quoted, err, errsize); + } + + return cmdstat; +} + + +static void RunPass(const char *name, FILE *fp, int depth) +{ + char src[4096]; + char err[4096]; + int line_no = 1; + MacroDef *macro_def = NULL; + Stack *macro_stack; + Macro *macro = NULL; + int skip_macro = FALSE; + + if (depth == 1024) + { + fprintf(stderr, "Include files too deep\n"); + exit(EXIT_FAILURE); + } + + macro_stack = StackCreate(); + + while(TRUE) + { + Line line; + char *label = NULL; + LabelType type; + int cmd_offset = 0; + CommandStatus cmdstat; + char **argv; + int argc; + int *quoted; + + ListStartLine(); + + if (macro) + { + char *next; + + next = MacroPlay(macro); + + if (next) + { + CopyStr(src, next, sizeof src); + free(next); + } + else + { + ListMacroInvokeEnd(MacroName(macro)); + MacroFree(macro); + macro = StackPop(macro_stack); + LabelScopePop(); + goto next_line; + } + } + + if (!macro) + { + if (!fgets(src, sizeof src, fp)) + { + if (macro_def) + { + snprintf(err, sizeof err,"Unterminated macro"); + cmdstat = CMD_FAILED; + goto error_handling; + } + + StackFree(macro_stack); + return; + } + } + + RemoveNL(src); + + if (!ParseLine(&line, src)) + { + snprintf(err, sizeof err,"%s\n%s", src, ParseError()); + cmdstat = CMD_FAILED; + goto error_handling; + } + + /* Check for labels + */ + if (line.first_column) + { + label = line.token[0]; + cmd_offset = 1; + + if (!LabelSanatise(label, &type)) + { + snprintf(err, sizeof err, "Invalid label '%s'", label); + cmdstat = CMD_FAILED; + goto error_handling; + } + + /* Check for setting global labels in macros. This could have + unexpected consequences. + */ + if (macro && type == GLOBAL_LABEL) + { + snprintf(err, sizeof err, "Don't set global labels in macros"); + cmdstat = CMD_FAILED; + goto error_handling; + } + + /* This may well be updated by a command, but easier to set anyway + */ + LabelSet(label, PC(), type); + } + + /* Check for no command/label only. Still record for macro though. + */ + if (line.no_tokens == cmd_offset) + { + if (macro_def) + { + MacroRecord(macro_def, src); + } + + ListLine(src); + + goto next_line; + } + + argv = line.token + cmd_offset; + argc = line.no_tokens - cmd_offset; + quoted = line.quoted + cmd_offset; + + /* Expand aliases + */ + argv[0] = AliasExpand(argv[0]); + + /* Check for END + */ + if (CompareString(argv[0], "end") || CompareString(argv[0], ".end")) + { + ListLine(src); + return; + } + + /* Check for include + */ + if (CompareString(argv[0], "include") || + CompareString(argv[0], ".include")) + { + FILE *fp; + + if (argc < 2) + { + snprintf(err, sizeof err, "%s: missing argument", argv[0]); + cmdstat = CMD_FAILED; + goto error_handling; + } + + if (!(fp = fopen(argv[1], "r"))) + { + snprintf(err, sizeof err, "%s: failed to open '%s'", + argv[0], argv[1]); + cmdstat = CMD_FAILED; + goto error_handling; + } + + RunPass(argv[1], fp, depth + 1); + + goto next_line; + } + + /* Check for macro definition + */ + if (CompareString(argv[0], "macro")) + { + /* Only define macros on the first pass + */ + if (IsFirstPass()) + { + if (macro_def) + { + snprintf(err, sizeof err, + "macro: can't nest macro definitions"); + cmdstat = CMD_FAILED; + goto error_handling; + } + + if (!label) + { + snprintf(err, sizeof err, "macro: missing name"); + cmdstat = CMD_FAILED; + goto error_handling; + } + + macro_def = MacroCreate(label, argc - 1, + argc > 1 ? argv + 1 : NULL, + err, sizeof err); + + if (!macro_def) + { + cmdstat = CMD_FAILED; + goto error_handling; + } + } + else + { + skip_macro = TRUE; + } + + goto next_line; + } + + if (CompareString(argv[0], "endm")) + { + if (!macro_def && IsFirstPass()) + { + snprintf(err, sizeof err, "endm: No macro started"); + cmdstat = CMD_FAILED; + goto error_handling; + } + + macro_def = NULL; + skip_macro = FALSE; + goto next_line; + } + + if (macro_def) + { + MacroRecord(macro_def, src); + goto next_line; + } + + if (skip_macro) + { + goto next_line; + } + + /* Run internal then CPU commands. Then if that fails try a macro. + */ + cmdstat = RunInternal(label, argc, argv, quoted, err, sizeof err); + + if (cmdstat == CMD_NOT_KNOWN) + { + cmdstat = cpu->handler(label, argc, argv, quoted, err, sizeof err); + } + + ListLine(src); + + if (cmdstat == CMD_NOT_KNOWN) + { + Macro *m; + + cmdstat = MacroFind(&m, argc, argv, quoted, err, sizeof err); + + /* If we get a macro then create a new top-level label for it + */ + if (m) + { + if (macro) + { + StackPush(macro_stack, macro); + + if (StackSize(macro_stack) > 1023) + { + snprintf(err, sizeof err, "Macro invocation too deep"); + cmdstat = CMD_FAILED; + goto error_handling; + } + } + + macro = m; + + ListMacroInvokeStart(argc, argv, quoted); + + LabelScopePush(LabelCreateNamespace(), PC()); + cmdstat = CMD_OK; + } + } + +error_handling: + switch(cmdstat) + { + case CMD_OK: + break; + + case CMD_OK_WARNING: + ListError("%s(%d): WARNING %s", name, line_no, err); + ListError("%s(%d): %s", name, line_no, src); + + if (macro) + { + ListError("%s(%d): In macro '%s'", + name, line_no, MacroName(macro)); + } + + break; + + case CMD_NOT_KNOWN: + ListError("%s(%d): Unknown command/opcode '%s'", + name, line_no, line.token[cmd_offset]); + + if (macro) + { + ListError("%s(%d): In macro '%s'", + name, line_no, MacroName(macro)); + } + + exit(EXIT_FAILURE); + + break; + + case CMD_FAILED: + ListError("%s(%d): ERROR %s", name, line_no, err); + ListError("%s(%d): %s", name, line_no, src); + + if (macro) + { + ListError("%s(%d): In macro '%s'", + name, line_no, MacroName(macro)); + } + + exit(EXIT_FAILURE); + + break; + } + +next_line: + if (!macro) + { + line_no++; + } + + ParseFree(&line); + } +} + + +/* ---------------------------------------- OUTPUT +*/ +static void ProduceOutput(void) +{ + ListFinish(); + + if (!OutputCode()) + { + fprintf(stderr, "%s\n", OutputError()); + } +} + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/cmd.h b/src/cmd.h new file mode 100644 index 0000000..b47b1b2 --- /dev/null +++ b/src/cmd.h @@ -0,0 +1,155 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Command callback definitions + +*/ + +#ifndef CASM_CMD_H +#define CASM_CMD_H + +/* ---------------------------------------- TYPES +*/ + + +typedef enum +{ + CMD_OK, + CMD_OK_WARNING, + CMD_FAILED, + CMD_NOT_KNOWN +} CommandStatus; + +/* Used to implement a command. +*/ +typedef CommandStatus (*Command)(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize); + + +/* ---------------------------------------- HELPER MACROS +*/ + + +/* Check for the precesence of a label. Expects a variable called 'label' + + Args: + NONE + + Expects: + + argv - The argv passed to the command handler + label - The label passed to the command handler + err - The error location passed to the command handler + errsize - The size of the error location passed to the command handler +*/ +#define CMD_LABEL_CHECK \ +do \ +{ \ + if (!label) \ + { \ + snprintf(err, errsize, "%s: missing label", argv[0]); \ + return CMD_FAILED; \ + } \ +} while(0) + + + +/* Check for the arg count being at least a specified number. + + Args: + min - The minimum number of arguments to support + + Expects: + + argc - The argc passed to the command handler + argv - The argv passed to the command handler + err - The error location passed to the command handler + errsize - The size of the error location passed to the command handler +*/ +#define CMD_ARGC_CHECK(min) \ +do \ +{ \ + if (argc < min) \ + { \ + snprintf(err, errsize, "%s: missing argument", argv[0]); \ + return CMD_FAILED; \ + } \ +} while(0) + + + + +/* Evaluate an expression. + + Args: + expr - The expression to evaluate + result - The variable to hold the result + + Expects: + + argc - The argc passed to the command handler + argv - The argv passed to the command handler + err - The error location passed to the command handler + errsize - The size of the error location passed to the command handler +*/ +#define CMD_EXPR(expr, result) \ +do \ +{ \ + if (!ExprEval(expr, &result)) \ + { \ + snprintf(err, errsize, "%s: expression error: %s", \ + argv[0], ExprError()); \ + return CMD_FAILED; \ + } \ +} while(0) + + + +/* Parse a table using ParseTable() + + Args: + str - The string to look for + table - The table to parse + result - Variable to hold the return from ParseTable() + + Expects: + + argv - The argv passed to the command handler + err - The error location passed to the command handler + errsize - The size of the error location passed to the command handler +*/ +#define CMD_TABLE(str, table, result) \ +do \ +{ \ + if (!(result = ParseTable(str, table))) \ + { \ + snprintf(err, errsize, "unknown value: \"%s\"", str); \ + return CMD_FAILED; \ + } \ +} while(0) + + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/codepage.c b/src/codepage.c new file mode 100644 index 0000000..0083947 --- /dev/null +++ b/src/codepage.c @@ -0,0 +1,259 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Code page handling. + +*/ +#include +#include +#include +#include + +#include "global.h" +#include "codepage.h" + +/* ---------------------------------------- TYPES +*/ + +typedef enum +{ + CP_ASCII, + CP_ZX81, + CP_SPECTRUM, + CP_CBM +} Codepage; + + +typedef struct +{ + int code; + int result; +} CodepageDef; + + +enum option_t +{ + OPT_CODEPAGE +}; + + +static const ValueTable option_set[] = +{ + {"codepage", OPT_CODEPAGE}, + {"charset", OPT_CODEPAGE}, + {NULL} +}; + + +static const ValueTable codepage_table[] = +{ + {"ascii", CP_ASCII}, + {"zx81", CP_ZX81}, + {"spectrum", CP_SPECTRUM}, + {"cbm", CP_CBM}, + {NULL} +}; + + + +/* ---------------------------------------- GLOBALS +*/ +static Codepage cp = CP_ASCII; + +static CodepageDef cp_ascii[] = +{ + {' ', 0x20}, {'!', 0x21}, {'"', 0x22}, {'#', 0x23}, + {'$', 0x24}, {'%', 0x25}, {'&', 0x26}, {'\'', 0x27}, + {'(', 0x28}, {')', 0x29}, {'*', 0x2a}, {'+', 0x2b}, + {',', 0x2c}, {'-', 0x2d}, {'.', 0x2e}, {'/', 0x2f}, + {'0', 0x30}, {'1', 0x31}, {'2', 0x32}, {'3', 0x33}, + {'4', 0x34}, {'5', 0x35}, {'6', 0x36}, {'7', 0x37}, + {'8', 0x38}, {'9', 0x39}, {':', 0x3a}, {';', 0x3b}, + {'<', 0x3c}, {'=', 0x3d}, {'>', 0x3e}, {'?', 0x3f}, + {'@', 0x40}, {'A', 0x41}, {'B', 0x42}, {'C', 0x43}, + {'D', 0x44}, {'E', 0x45}, {'F', 0x46}, {'G', 0x47}, + {'H', 0x48}, {'I', 0x49}, {'J', 0x4a}, {'K', 0x4b}, + {'L', 0x4c}, {'M', 0x4d}, {'N', 0x4e}, {'O', 0x4f}, + {'P', 0x50}, {'Q', 0x51}, {'R', 0x52}, {'S', 0x53}, + {'T', 0x54}, {'U', 0x55}, {'V', 0x56}, {'W', 0x57}, + {'X', 0x58}, {'Y', 0x59}, {'Z', 0x5a}, {'[', 0x5b}, + {'\\', 0x5c}, {']', 0x5d}, {'^', 0x5e}, {'_', 0x5f}, + {'`', 0x60}, {'a', 0x61}, {'b', 0x62}, {'c', 0x63}, + {'d', 0x64}, {'e', 0x65}, {'f', 0x66}, {'g', 0x67}, + {'h', 0x68}, {'i', 0x69}, {'j', 0x6a}, {'k', 0x6b}, + {'l', 0x6c}, {'m', 0x6d}, {'n', 0x6e}, {'o', 0x6f}, + {'p', 0x70}, {'q', 0x71}, {'r', 0x72}, {'s', 0x73}, + {'t', 0x74}, {'u', 0x75}, {'v', 0x76}, {'w', 0x77}, + {'x', 0x78}, {'y', 0x79}, {'z', 0x7a}, {'{', 0x7b}, + {'|', 0x7c}, {'}', 0x7d}, {'~', 0x7e}, + {0, 0} +}; + + +static CodepageDef cp_zx81[] = +{ + {' ', 0x00}, {'!', 0x00}, {'"', 0x0b}, {'#', 0x0c}, + {'$', 0x0d}, {'%', 0x00}, {'&', 0x00}, {'\'', 0x0b}, + {'(', 0x10}, {')', 0x11}, {'*', 0x17}, {'+', 0x15}, + {',', 0x1a}, {'-', 0x16}, {'.', 0x1b}, {'/', 0x24}, + {'0', 0x1c}, {'1', 0x1d}, {'2', 0x1e}, {'3', 0x1f}, + {'4', 0x20}, {'5', 0x21}, {'6', 0x22}, {'7', 0x23}, + {'8', 0x24}, {'9', 0x25}, {':', 0x0e}, {';', 0x19}, + {'<', 0x12}, {'=', 0x14}, {'>', 0x13}, {'?', 0x0f}, + {'@', 0x97}, {'A', 0xa6}, {'B', 0xa7}, {'C', 0xa8}, + {'D', 0xa9}, {'E', 0xaa}, {'F', 0xab}, {'G', 0xac}, + {'H', 0xad}, {'I', 0xae}, {'J', 0xaf}, {'K', 0xb0}, + {'L', 0xb1}, {'M', 0xb2}, {'N', 0xb3}, {'O', 0xb4}, + {'P', 0xb5}, {'Q', 0xb6}, {'R', 0xb7}, {'S', 0xb8}, + {'T', 0xb9}, {'U', 0xba}, {'V', 0xbb}, {'W', 0xbc}, + {'X', 0xbd}, {'Y', 0xbe}, {'Z', 0xbf}, {'[', 0x10}, + {'\\', 0x24}, {']', 0x11}, {'^', 0xde}, {'_', 0x80}, + {'`', 0x60}, {'a', 0x26}, {'b', 0x27}, {'c', 0x28}, + {'d', 0x29}, {'e', 0x2a}, {'f', 0x2b}, {'g', 0x2c}, + {'h', 0x2d}, {'i', 0x2e}, {'j', 0x2f}, {'k', 0x30}, + {'l', 0x31}, {'m', 0x32}, {'n', 0x33}, {'o', 0x34}, + {'p', 0x35}, {'q', 0x36}, {'r', 0x37}, {'s', 0x38}, + {'t', 0x39}, {'u', 0x3a}, {'v', 0x3b}, {'w', 0x3c}, + {'x', 0x3d}, {'y', 0x3e}, {'z', 0x3f}, {'{', 0x90}, + {'|', 0x00}, {'}', 0x91}, {'~', 0x96}, + {0, 0} +}; + + +static CodepageDef cp_spectrum[] = +{ + {' ', 0x20}, {'!', 0x21}, {'"', 0x22}, {'#', 0x23}, + {'$', 0x24}, {'%', 0x25}, {'&', 0x26}, {'\'', 0x27}, + {'(', 0x28}, {')', 0x29}, {'*', 0x2a}, {'+', 0x2b}, + {',', 0x2c}, {'-', 0x2d}, {'.', 0x2e}, {'/', 0x2f}, + {'0', 0x30}, {'1', 0x31}, {'2', 0x32}, {'3', 0x33}, + {'4', 0x34}, {'5', 0x35}, {'6', 0x36}, {'7', 0x37}, + {'8', 0x38}, {'9', 0x39}, {':', 0x3a}, {';', 0x3b}, + {'<', 0x3c}, {'=', 0x3d}, {'>', 0x3e}, {'?', 0x3f}, + {'@', 0x40}, {'A', 0x41}, {'B', 0x42}, {'C', 0x43}, + {'D', 0x44}, {'E', 0x45}, {'F', 0x46}, {'G', 0x47}, + {'H', 0x48}, {'I', 0x49}, {'J', 0x4a}, {'K', 0x4b}, + {'L', 0x4c}, {'M', 0x4d}, {'N', 0x4e}, {'O', 0x4f}, + {'P', 0x50}, {'Q', 0x51}, {'R', 0x52}, {'S', 0x53}, + {'T', 0x54}, {'U', 0x55}, {'V', 0x56}, {'W', 0x57}, + {'X', 0x58}, {'Y', 0x59}, {'Z', 0x5a}, {'[', 0x5b}, + {'\\', 0x5c}, {']', 0x5d}, {'^', 0x5e}, {'_', 0x5f}, + {'`', 0x27}, {'a', 0x61}, {'b', 0x62}, {'c', 0x63}, + {'d', 0x64}, {'e', 0x65}, {'f', 0x66}, {'g', 0x67}, + {'h', 0x68}, {'i', 0x69}, {'j', 0x6a}, {'k', 0x6b}, + {'l', 0x6c}, {'m', 0x6d}, {'n', 0x6e}, {'o', 0x6f}, + {'p', 0x70}, {'q', 0x71}, {'r', 0x72}, {'s', 0x73}, + {'t', 0x74}, {'u', 0x75}, {'v', 0x76}, {'w', 0x77}, + {'x', 0x78}, {'y', 0x79}, {'z', 0x7a}, {'{', 0x7b}, + {'|', 0x7c}, {'}', 0x7d}, {'~', 0x7e}, + {0, 0} +}; + + +static CodepageDef cp_cbm[] = +{ + {' ', 0x20}, {'!', 0x21}, {'"', 0x22}, {'#', 0x23}, + {'$', 0x24}, {'%', 0x25}, {'&', 0x26}, {'\'', 0x27}, + {'(', 0x28}, {')', 0x29}, {'*', 0x2a}, {'+', 0x2b}, + {',', 0x2c}, {'-', 0x2d}, {'.', 0x2e}, {'/', 0x2f}, + {'0', 0x30}, {'1', 0x31}, {'2', 0x32}, {'3', 0x33}, + {'4', 0x34}, {'5', 0x35}, {'6', 0x36}, {'7', 0x37}, + {'8', 0x38}, {'9', 0x39}, {':', 0x3a}, {';', 0x3b}, + {'<', 0x3c}, {'=', 0x3d}, {'>', 0x3e}, {'?', 0x3f}, + {'@', 0x40}, {'A', 0x41}, {'B', 0x42}, {'C', 0x43}, + {'D', 0x44}, {'E', 0x45}, {'F', 0x46}, {'G', 0x47}, + {'H', 0x48}, {'I', 0x49}, {'J', 0x4a}, {'K', 0x4b}, + {'L', 0x4c}, {'M', 0x4d}, {'N', 0x4e}, {'O', 0x4f}, + {'P', 0x50}, {'Q', 0x51}, {'R', 0x52}, {'S', 0x53}, + {'T', 0x54}, {'U', 0x55}, {'V', 0x56}, {'W', 0x57}, + {'X', 0x58}, {'Y', 0x59}, {'Z', 0x5a}, {'[', 0x5b}, + {'\\', 0x2f}, {']', 0x5d}, {'^', 0x5e}, {'_', 0x60}, + {'`', 0x27}, {'a', 0x41}, {'b', 0x42}, {'c', 0x43}, + {'d', 0x44}, {'e', 0x45}, {'f', 0x46}, {'g', 0x47}, + {'h', 0x48}, {'i', 0x49}, {'j', 0x4a}, {'k', 0x4b}, + {'l', 0x4c}, {'m', 0x4d}, {'n', 0x4e}, {'o', 0x4f}, + {'p', 0x50}, {'q', 0x51}, {'r', 0x52}, {'s', 0x53}, + {'t', 0x54}, {'u', 0x55}, {'v', 0x56}, {'w', 0x57}, + {'x', 0x58}, {'y', 0x59}, {'z', 0x5a}, {'{', 0x5b}, + {'|', 0x7d}, {'}', 0x5d}, {'~', 0x7e}, + {0, 0} +}; + +static CodepageDef *cp_table[] = +{ + cp_ascii, + cp_zx81, + cp_spectrum, + cp_cbm +}; + + +/* ---------------------------------------- INTERFACES +*/ + +const ValueTable *CodepageOptions(void) +{ + return option_set; +} + + +CommandStatus CodepageSetOption(int opt, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + const ValueTable *val; + + CMD_ARGC_CHECK(1); + + switch(opt) + { + case OPT_CODEPAGE: + CMD_TABLE(argv[0], codepage_table, val); + cp = val->value; + break; + + default: + break; + } + + return CMD_OK; +} + + + +int CodepageConvert(int code) +{ + int f; + + for(f = 0; cp_table[cp][f].code; f++) + { + if (cp_table[cp][f].code == code) + { + return cp_table[cp][f].result; + } + } + + return 0; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/codepage.h b/src/codepage.h new file mode 100644 index 0000000..64f5868 --- /dev/null +++ b/src/codepage.h @@ -0,0 +1,55 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Code page handling + +*/ + +#ifndef CASM_CODEPAGE_H +#define CASM_CODEPAGE_H + +#include "parse.h" +#include "cmd.h" + +/* ---------------------------------------- INTERFACES +*/ + +/* Codepage options +*/ +const ValueTable *CodepageOptions(void); + +CommandStatus CodepageSetOption(int opt, int argc, char *argv[], + int quoted[], char *error, size_t error_size); + + + + +/* Converts the passed character into the appropriate codepage value. + Returns zero for unknown/unconvertable characters. +*/ +int CodepageConvert(int code); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/expr.c b/src/expr.c new file mode 100644 index 0000000..02222d7 --- /dev/null +++ b/src/expr.c @@ -0,0 +1,700 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Expands an expression. + +*/ +#include +#include +#include +#include + +#include "global.h" +#include "expr.h" +#include "label.h" +#include "state.h" +#include "util.h" + +/* ---------------------------------------- MACROS +*/ +#define TYPE_OPERAND 0 +#define TYPE_LABEL 1 +#define TYPE_OPERATOR 2 /* This acts as a base for operator tokens */ +#define TYPE_LPAREN 3 +#define TYPE_RPAREN 4 +#define TYPE_DIVIDE 5 +#define TYPE_MULTIPLY 6 +#define TYPE_ADD 7 +#define TYPE_SUBTRACT 8 +#define TYPE_NOT 9 +#define TYPE_AND 10 +#define TYPE_OR 11 +#define TYPE_XOR 12 +#define TYPE_UNARY_PLUS 13 +#define TYPE_UNARY_NEG 14 +#define TYPE_SHIFTL 15 +#define TYPE_SHIFTR 16 +#define TYPE_EQUALITY 17 +#define TYPE_BOOL_AND 18 +#define TYPE_BOOL_OR 19 +#define TYPE_MODULUS 20 +#define TYPE_LT 21 +#define TYPE_GT 22 +#define TYPE_LTEQ 23 +#define TYPE_GTEQ 24 +#define TYPE_INEQUALITY 25 + +#define SYM_LPAREN "{" +#define SYM_RPAREN "}" +#define SYM_DIVIDE "/" +#define SYM_MULTIPLY "*" +#define SYM_ADD "+" +#define SYM_SUBTRACT "-" +#define SYM_NOT "~" +#define SYM_AND "&" +#define SYM_OR "|" +#define SYM_XOR "^" +#define SYM_UNARY_PLUS "+" +#define SYM_UNARY_NEG "-" +#define SYM_SHIFTL "<<" +#define SYM_SHIFTR ">>" +#define SYM_EQUALITY "==" +#define SYM_BOOL_AND "&&" +#define SYM_BOOL_OR "||" +#define SYM_MODULUS "%" +#define SYM_LT "<" +#define SYM_GT ">" +#define SYM_LTEQ "<=" +#define SYM_GTEQ ">=" +#define SYM_INEQUALITY "!=" + +#define OPERATOR_CHARS "{}/*+-~&|^<>=%!" + +#define IS_OPERATOR_TYPE(a) ((a)>=TYPE_OPERATOR) + +#define IS_PAREN(c) ((c)==SYM_LPAREN[0] || (c)==SYM_RPAREN[0]) + +#define IS_OPERATOR(c) (strchr(OPERATOR_CHARS,(c))) + +#define IS_OPERAND_END(c) ((c)==' ' || (c)=='\t' || IS_OPERATOR(c)) + +/* ---------------------------------------- TYPES +*/ + +typedef struct stk + { + int token; + int priority; + int is_unary; + char *text; + struct stk *next; + } Stack; + + +typedef enum {OpBinary, OpPretendUnary, OpUnary} OpType; + +typedef struct + { + const char *symbol; + int priority; + int token; + OpType type; + int allow_unary; + } Operator; + + +typedef enum {OpOk, OpSyntaxError, OpUnknown} OpError; + + +/* ---------------------------------------- GLOBALS +*/ +static char error[1024]; + +static const Operator op_info[]= + { + /* Unary ops - must be first in list. Note that LPAREN is treated + as a unary as it must be preceeded by another operator, rather + than an operand. + */ + {SYM_NOT, 9, TYPE_NOT, OpUnary, TRUE}, + {SYM_UNARY_PLUS,9, TYPE_UNARY_PLUS,OpUnary, TRUE}, + {SYM_UNARY_NEG, 9, TYPE_UNARY_NEG, OpUnary, TRUE}, + {SYM_LPAREN, 99, TYPE_LPAREN, OpPretendUnary, TRUE}, + + /* Binary ops and don't care ops + */ + {SYM_RPAREN, 99, TYPE_RPAREN, OpBinary, FALSE}, + {SYM_SHIFTL, 6, TYPE_SHIFTL, OpBinary, TRUE}, + {SYM_SHIFTR, 6, TYPE_SHIFTR, OpBinary, TRUE}, + {SYM_MULTIPLY, 5, TYPE_MULTIPLY, OpBinary, TRUE}, + {SYM_DIVIDE, 5, TYPE_DIVIDE, OpBinary, TRUE}, + {SYM_MODULUS, 5, TYPE_MODULUS, OpBinary, TRUE}, + {SYM_ADD, 4, TYPE_ADD, OpBinary, TRUE}, + {SYM_SUBTRACT, 4, TYPE_SUBTRACT, OpBinary, TRUE}, + {SYM_EQUALITY, 1, TYPE_EQUALITY, OpBinary, TRUE}, + {SYM_INEQUALITY,1, TYPE_INEQUALITY,OpBinary, TRUE}, + {SYM_LTEQ, 1, TYPE_LTEQ, OpBinary, TRUE}, + {SYM_GTEQ, 1, TYPE_GTEQ, OpBinary, TRUE}, + {SYM_LT, 1, TYPE_LT, OpBinary, TRUE}, + {SYM_GT, 1, TYPE_GT, OpBinary, TRUE}, + {SYM_BOOL_AND, 0, TYPE_BOOL_AND, OpBinary, TRUE}, + {SYM_AND, 0, TYPE_AND, OpBinary, TRUE}, + {SYM_BOOL_OR, 0, TYPE_BOOL_OR, OpBinary, TRUE}, + {SYM_OR, 0, TYPE_OR, OpBinary, TRUE}, + {SYM_XOR, 0, TYPE_XOR, OpBinary, TRUE}, + + {0,0,0} + }; + + +/* ---------------------------------------- PRIVATE FUNCTIONS +*/ + +/* Empty a stack +*/ +static Stack *ClearStack(Stack *stack) +{ + while(stack) + { + Stack *tmp=stack; + + stack=stack->next; + free(tmp->text); + free(tmp); + } + + return stack; +} + + +/* Push onto the stack +*/ +static Stack *Push(Stack *stack, int token, int priority, + int is_unary, const char *text) +{ + Stack *e=Malloc(sizeof *e); + + e->text=DupStr(text); + e->priority=priority; + e->token=token; + e->is_unary=is_unary; + e->next=stack; + stack=e; + + return stack; +} + + +/* Destroy the top element on the stack +*/ +static Stack *Pop(Stack *stack) +{ + if (stack) + { + Stack *tmp=stack; + + stack=stack->next; + + free(tmp->text); + free(tmp); + } + + return stack; +} + + +/* Debug to dump a stack non-destructively +*/ +static void Dump(const char *name, Stack *stack) +{ + while (stack) + { + printf("%-10s: Type: %-2d is_unary: %d text: %s\n", + name,stack->token,stack->is_unary,stack->text); + + stack=stack->next; + } +} + + +/* Find the symbol for an operator +*/ +static const char *ToString(int token) +{ + static char buff[32]; + int f; + + strcpy(buff,"UNKNOWN"); + + for(f=0;op_info[f].symbol;f++) + { + if (op_info[f].token==token) + { + strcpy(buff,op_info[f].symbol); + return buff; + } + } + + return "UNKNOWN"; +} + + +/* Search the operator info +*/ +static const OpError FindOp(const char *p, int prev_was_op, const Operator **op) +{ + int f; + int found=FALSE; + + *op=NULL; + + for(f=0;op_info[f].symbol;f++) + { + if (strncmp(op_info[f].symbol,p,strlen(op_info[f].symbol))==0) + { + found=TRUE; + + if ((prev_was_op && op_info[f].type!=OpBinary) || + (!prev_was_op && op_info[f].type==OpBinary)) + + { + *op=op_info+f; + return OpOk; + } + } + } + + if (found) + return OpSyntaxError; + else + return OpUnknown; +} + + +static Stack *ToPostfix(const char *expr) +{ + Stack *stack=NULL; + Stack *output=NULL; + char buff[1024]; + char *p; + int prev_was_op=TRUE; + + /* Parse the infix expression into postfix + */ + while(*expr) + { + if (IS_OPERATOR(*expr)) + { + /* Found an operator - parse it + */ + const Operator *op; + OpError op_err; + + op_err=FindOp(expr,prev_was_op,&op); + + /* Unknown operator in expression + */ + if (!op) + { + if (op_err==OpSyntaxError) + sprintf(error,"Syntax error with operator %c",*expr); + else + sprintf(error,"Unknown operator %c",*expr); + + stack=ClearStack(stack); + output=ClearStack(output); + + return NULL; + } + + /* Special handling for closing parenthesis + */ + if (op->token==TYPE_RPAREN) + { + while(stack && stack->token!=TYPE_LPAREN) + { + output=Push(output, + stack->token, + stack->priority, + stack->is_unary, + stack->text); + stack=Pop(stack); + } + + /* Syntax error in expression + */ + if (!stack) + { + sprintf(error,"Missing %s", SYM_LPAREN); + + stack=ClearStack(stack); + output=ClearStack(output); + + return NULL; + } + + /* Remove the opening bracket and continue + */ + stack=Pop(stack); + } + else + { + /* Output the stack until an operator of lower precedence is + found + */ + if (op->type==OpUnary) + { + while(stack && !IS_PAREN(stack->text[0]) && + !stack->is_unary && + (stack->priority >= op->priority)) + { + output=Push(output, + stack->token, + stack->priority, + stack->is_unary, + stack->text); + + stack=Pop(stack); + } + } + else + { + while(stack && !IS_PAREN(stack->text[0]) && + (stack->priority >= op->priority)) + { + output=Push(output, + stack->token, + stack->priority, + stack->is_unary, + stack->text); + + stack=Pop(stack); + } + } + + stack=Push(stack, + op->token, + op->priority, + op->type==OpUnary, + op->symbol); + } + + /* Move past token + */ + expr+=strlen(op->symbol); + + prev_was_op=op->allow_unary; + } + else + { + /* Found an operand - parse and store the operand + */ + p=buff; + + while(*expr && !IS_OPERAND_END(*expr)) + { + *p++=*expr++; + } + + *p=0; + + /* Note that operands are marked as unary just to chose one over + the other branch in EvalPostfix() - no other reason + */ + output=Push(output,TYPE_OPERAND,0,TRUE,buff); + + prev_was_op=FALSE; + } + + while(isspace((unsigned char)*expr)) + { + expr++; + } + } + + while(stack) + { + output=Push(output, + stack->token, + stack->priority, + stack->is_unary, + stack->text); + + stack=Pop(stack); + } + + /* Return the stack + */ + return output; +} + + +static int EvalPostfix(Stack **stack, int *result) +{ + Stack *expr; + int token; + + expr=*stack; + + if (!expr) + { + sprintf(error,"Called with empty postfix stack"); + return FALSE; + } + + token=expr->token; + + if (expr->is_unary) + { + if (token==TYPE_OPERAND) + { + if (!LabelExpand(expr->text, result) && IsFinalPass()) + { + sprintf(error,"Invalid value '%s'",expr->text); + return FALSE; + } + + expr = Pop(expr); + } + else if (token==TYPE_LABEL) + { + const Label *label = NULL; + + if (IsFinalPass()) + { + if (!(label = LabelFind(expr->text, ANY_LABEL))) + { + sprintf(error,"Undefined label '%s'",expr->text); + return FALSE; + } + } + + *result = (label == NULL ? 0 : label->value); + expr = Pop(expr); + } + else + { + int val; + + expr=Pop(expr); + + if (!expr || !EvalPostfix(&expr,&val)) + { + sprintf(error,"Operator '%s' expects an argument", + ToString(token)); + *stack=expr; + return FALSE; + } + + switch(token) + { + case TYPE_NOT: + *result=~val; + break; + + case TYPE_UNARY_PLUS: + *result=+val; + break; + + case TYPE_UNARY_NEG: + *result=-val; + break; + + default: + sprintf(error,"Execpected unary token '%s' processed", + ToString(token)); + *stack=expr; + return FALSE; + break; + } + } + } + else + { + int left,right; + + expr=Pop(expr); + + if (!expr || !EvalPostfix(&expr,&right)) + { + sprintf(error,"Operator '%s' expects two " + "arguments (unknown label?)", + ToString(token)); + *stack=expr; + return FALSE; + } + + if (!expr || !EvalPostfix(&expr,&left)) + { + sprintf(error,"Operator '%s' expects two " + "arguments (unknown label?)", + ToString(token)); + *stack=expr; + return FALSE; + } + + switch(token) + { + case TYPE_DIVIDE: + *result=left/right; + break; + + case TYPE_MULTIPLY: + *result=left*right; + break; + + case TYPE_MODULUS: + *result=left%right; + break; + + case TYPE_ADD: + *result=left+right; + break; + + case TYPE_SUBTRACT: + *result=left-right; + break; + + case TYPE_AND: + *result=left&right; + break; + + case TYPE_OR: + *result=left|right; + break; + + case TYPE_BOOL_AND: + *result=left&&right; + break; + + case TYPE_BOOL_OR: + *result=left||right; + break; + + case TYPE_XOR: + *result=left^right; + break; + + case TYPE_SHIFTL: + if (right<0) + { + sprintf(error,"Cannot shift left by a " + "negative number (%d)",right); + *stack=expr; + return FALSE; + } + + *result=left<>right; + break; + + case TYPE_EQUALITY: + *result=left==right; + break; + + case TYPE_INEQUALITY: + *result=left!=right; + break; + + case TYPE_LT: + *result=leftright; + break; + + case TYPE_LTEQ: + *result=left<=right; + break; + + case TYPE_GTEQ: + *result=left>=right; + break; + + default: + sprintf(error,"Unexpected binary token '%s' processed", + ToString(token)); + *stack=expr; + return FALSE; + break; + } + } + + *stack=expr; + + return TRUE; +} + + +/* ---------------------------------------- INTERFACES +*/ + +int ExprConvert(int no_bits, int value) +{ + if (value<0) + { + value=-value; + value--; + value=~value; + } + + return value & ((1<. + + ------------------------------------------------------------------------- + + Expands an expression. + +*/ + +#ifndef CASM_EXPR_H +#define CASM_EXPR_H + +/* ---------------------------------------- INTERFACES +*/ + +/* Converts a number to a sized value. If the number is negative then the + result is returned in twos complement form. +*/ +int ExprConvert(int no_bits, int value); + + +/* Returns the result of expr and stores the answer in result. + Returns FALSE on error. +*/ +int ExprEval(const char *expr, int *result); + + +/* Gets a readable reason for an error from ExprEval() or ExprParse. +*/ +const char *ExprError(void); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/global.h b/src/global.h new file mode 100644 index 0000000..7591719 --- /dev/null +++ b/src/global.h @@ -0,0 +1,51 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Global include to get basic shared interfaces and types + +*/ + +#ifndef CASM_GLOBAL_H +#define CASM_GLOBAL_H + + +/* ---------------------------------------- GLOBALLY NEEDED INCLUDES +*/ +#include "basetype.h" +#include "util.h" +#include "state.h" + + +/* ---------------------------------------- MACROS +*/ +#ifndef TRUE +#define TRUE 1 +#endif + +#ifndef FALSE +#define FALSE 0 +#endif + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/label.c b/src/label.c new file mode 100644 index 0000000..c15627f --- /dev/null +++ b/src/label.c @@ -0,0 +1,470 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for labels. + +*/ +#include +#include +#include + +#include "global.h" +#include "codepage.h" +#include "stack.h" +#include "label.h" + + +/* ---------------------------------------- TYPES +*/ + +typedef struct global +{ + Label label; + Label *locals; + int no_locals; + int local_size; + struct global *next; + struct global *next_scope; +} GlobalLabel; + + +/* ---------------------------------------- GLOBALS +*/ +static GlobalLabel *head; +static GlobalLabel *tail; +static GlobalLabel *scope; + +static char namespace[MAX_LABEL_SIZE + 1]; + +static Stack *stack; + + +/* ---------------------------------------- PRIVATE FUNCTIONS INTERFACES +*/ +static Label *FindLocal(const char *p, GlobalLabel *in) +{ + int f; + + for(f = 0; f < in->no_locals; f++) + { + if (CompareString(in->locals[f].name, p)) + { + return in->locals + f; + } + } + + return NULL; +} + + +static GlobalLabel *FindGlobal(const char *p) +{ + GlobalLabel *g = head; + + while(g) + { + if (CompareString(g->label.name, p)) + { + return g; + } + + g = g->next; + } + + return NULL; +} + + +static Label *Find(const char *p, LabelType type) +{ + Label *l = NULL; + + if ((type & LOCAL_LABEL) && scope) + { + l = FindLocal(p, scope); + } + + if (l == NULL && (type & GLOBAL_LABEL)) + { + GlobalLabel *g = FindGlobal(p); + + if (g) + { + l = &g->label; + } + } + + return l; +} + + +static void AddGlobal(const char *p, int value) +{ + GlobalLabel *l = FindGlobal(p); + + if (!l) + { + l = Malloc(sizeof *l); + + CopyStr(l->label.name, p, sizeof l->label.name); + + l->label.value = value; + l->label.type = GLOBAL_LABEL; + + l->no_locals = 0; + l->locals = NULL; + l->local_size = 0; + l->next = NULL; + l->next_scope = NULL; + + if (tail) + { + tail->next = l; + } + + tail = l; + + if (!head) + { + head = l; + } + } + else + { + l->label.value = value; + } + + scope = l; +} + +static void AddLocal(const char *p, int value) +{ + int i; + Label *l; + + if (scope == NULL) + { + fprintf(stderr, "BUG: Tried to add local %p " + "with no current scope\n", p); + exit(EXIT_FAILURE); + } + + l = FindLocal(p, scope); + + if (!l) + { + if (scope->no_locals >= scope->local_size) + { + scope->local_size += 32; + + scope->locals = + Realloc(scope->locals, + (sizeof *scope->locals) * scope->local_size); + } + + i = scope->no_locals++; + + CopyStr(scope->locals[i].name, p, sizeof scope->locals[i].name); + scope->locals[i].value = value; + scope->locals[i].type = LOCAL_LABEL; + } + else + { + l->value = value; + } +} + + +static int ParseBase(const char *str, int base, int *result, char last) +{ + char *p; + int i; + + *result = (int)strtol(str, &p, base); + + return *p == last; +} + + +/* ---------------------------------------- INTERFACES +*/ + +void LabelClear(void) +{ + GlobalLabel *l; + + l = head; + + while(l) + { + GlobalLabel *tmp; + + tmp = l; + + l = l->next; + + if (tmp->no_locals) + { + free(tmp->locals); + } + + free(tmp); + } + + head = NULL; + tail = NULL; +} + + +int LabelExpand(const char *expr, int *result) +{ + Label *label; + int found = FALSE; + + /* Check for special single-characters + */ + if (expr[0] && !expr[1]) + { + switch(expr[0]) + { + /* Current PC + */ + case '$': + *result = PC(); + return TRUE; + + default: + break; + } + } + + /* Find the label, or evaluate the constant if not found + */ + if ((label = Find(expr, ANY_LABEL))) + { + *result = label->value; + found = TRUE; + } + + if (!found) + { + size_t len; + char first, last; + + len = strlen(expr); + first = expr[0]; + last = expr[len - 1]; + + if (first == '$') + { + found = ParseBase(expr + 1, 16, result, '\0'); + } + else if (last == 'h') + { + found = ParseBase(expr, 16, result, 'h'); + } + else if (first == '%') + { + found = ParseBase(expr, 2, result, '\0'); + } + else if (last == 'b') + { + found = ParseBase(expr, 2, result, 'b'); + } + else if (first == '\'' && last == '\'' && len == 3) + { + *result = CodepageConvert(expr[1]); + found = TRUE; + } + else + { + found = ParseBase(expr, 0, result, '\0'); + } + } + + return found; +} + + +int LabelSanatise(char *label, LabelType *type) +{ + int status = TRUE; + size_t len; + + *type = GLOBAL_LABEL; + + len = strlen(label); + + if (len && label[0] == '.') + { + *type = LOCAL_LABEL; + memmove(label, label + 1, len--); + } + + if (len && label[len - 1] == ':') + { + label[--len] = 0; + } + + if (len == 0) + { + status = FALSE; + } + + return status; +} + + +const Label *LabelFind(const char *label, LabelType type) +{ + return Find(label, type); +} + + +void LabelSet(const char *label, int value, LabelType type) +{ + /* ANY_LABEL indicates that a label is being updated + */ + switch(type) + { + case ANY_LABEL: + if (!scope || CompareString(scope->label.name, label)) + { + scope->label.value = value; + } + else + { + AddLocal(label,value); + } + break; + + case GLOBAL_LABEL: + AddGlobal(label, value); + StackClear(stack); + break; + + case LOCAL_LABEL: + AddLocal(label,value); + break; + } +} + + +void LabelScopePush(const char *name, int value) +{ + if (!stack) + { + stack = StackCreate(); + } + + StackPush(stack, scope); + AddGlobal(name, value); +} + + +void LabelScopePop(void) +{ + scope = StackPop(stack); + + if (!scope) + { + fprintf(stderr, "ERROR: Popping the global scope left it empty"); + exit(EXIT_FAILURE); + } +} + + +void LabelSetScope(const char *label) +{ + scope = FindGlobal(label); +} + + +const char *LabelCreateNamespace(void) +{ + int f; + + if (namespace[0] == 0) + { + LabelResetNamespace(); + } + + f = 1; + + while(f < MAX_LABEL_SIZE && namespace[f] == '9') + { + f++; + } + + if (f < MAX_LABEL_SIZE) + { + namespace[f]++; + } + + return namespace; +} + +void LabelResetNamespace(void) +{ + int f; + + namespace[0] = '_'; + + for(f = 1; f < MAX_LABEL_SIZE; f++) + { + namespace[f] = '0'; + } + + namespace[MAX_LABEL_SIZE] = 0; +} + + +void LabelDump(FILE *fp, int dump_private) +{ + GlobalLabel *g = head; + + while(g) + { + int f; + + if (g->label.name[0] != '_' || dump_private) + { + fprintf(fp, "; %-*s = $%4.4x (%d)\n", MAX_LABEL_SIZE, + g->label.name, + (unsigned)g->label.value, + g->label.value); + + for(f = 0; f < g->no_locals; f++) + { + fprintf(fp, "; .%-*s = $%4.4x (%d)\n", MAX_LABEL_SIZE, + g->locals[f].name, + (unsigned)g->locals[f].value, + g->locals[f].value); + } + } + + g = g->next; + } +} + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/label.h b/src/label.h new file mode 100644 index 0000000..faf841b --- /dev/null +++ b/src/label.h @@ -0,0 +1,123 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for labels. + +*/ + +#ifndef CASM_LABEL_H +#define CASM_LABEL_H + +#include + +/* ---------------------------------------- INTERFACES +*/ + +/* Maximum size of a label. All characters past this point will be ignored. +*/ +#define MAX_LABEL_SIZE 32 + +/* Label types +*/ +typedef enum +{ + GLOBAL_LABEL = 1, + LOCAL_LABEL = 2, + ANY_LABEL = 3 +} LabelType; + + +/* A label +*/ +typedef struct +{ + char name[MAX_LABEL_SIZE+1]; + int value; + LabelType type; +} Label; + + +/* Clear labels +*/ +void LabelClear(void); + + +/* Expand a label or constant value. Returns TRUE if parsed OK. +*/ +int LabelExpand(const char *expr, int *result); + + +/* Utility to sanatise a label name, returning whether it's a global or local + label. Returns FALSE if the label is invalid, otherwise returns TRUE. +*/ +int LabelSanatise(char *label, LabelType *type); + + +/* Get a label. Returns NULL for an undefined label. +*/ +const Label *LabelFind(const char *label, LabelType type); + + +/* Set a label. If the label already exists, overwrites the value. + If the label being set is a GLOBAL_LABEL also sets the scope for local + variables and the stacked global scope is cleared. +*/ +void LabelSet(const char *label, int value, LabelType type); + + +/* Set a global label, pushing the current global namespace onto a stack. +*/ +void LabelScopePush(const char *label, int value); + + +/* Pops the last namespace that was saved with LabelScopePush(). +*/ +void LabelScopePop(void); + + +/* Sets the current global scope for local variables to the named global + label. +*/ +void LabelSetScope(const char *label); + + +/* Create a label name to be used as a namespace. The return is static and + must be saved to be used. Namespaces are simple identifer created in a set + order, so LabelResetNamespace() can be used to replay them. +*/ +const char *LabelCreateNamespace(void); + + +/* Reset namespace generation back to its initial point. +*/ +void LabelResetNamespace(void); + + +/* Dump a formatted report of labels to the passed file pointer +*/ +void LabelDump(FILE *fp, int dump_private); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/listing.c b/src/listing.c new file mode 100644 index 0000000..2168309 --- /dev/null +++ b/src/listing.c @@ -0,0 +1,418 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Common utilities + +*/ +#include +#include +#include +#include +#include + +#include "global.h" +#include "state.h" +#include "label.h" +#include "macro.h" +#include "expr.h" +#include "varchar.h" +#include "listing.h" + + +/* ---------------------------------------- PRIVATE TYPES +*/ +enum option_t +{ + OPT_LIST, + OPT_LISTFILE, + OPT_LISTPC, + OPT_LISTHEX, + OPT_LISTMACROS, + OPT_LISTLABELS, + OPT_LISTRMBLANK +}; + +static const ValueTable option_set[] = +{ + {"list", OPT_LIST}, + {"list-file", OPT_LISTFILE}, + {"list-pc", OPT_LISTPC}, + {"list-hex", OPT_LISTHEX}, + {"list-macros", OPT_LISTMACROS}, + {"list-labels", OPT_LISTLABELS}, + {"list-rm-blank", OPT_LISTRMBLANK}, + {NULL} +}; + + +typedef enum +{ + LabelsOff = 0, + LabelsDump = 1, + LabelsDumpPrivate = 2 +} LabelMode; + + +typedef enum +{ + MacrosOff = 0, + MacrosInvoke = 1, + MacrosDump = 2 +} MacroMode; + + +typedef struct +{ + int enabled; + int dump_PC; + int dump_bytes; + int rm_blank; + LabelMode labels; + MacroMode macros; +} Options; + + +/* ---------------------------------------- PRIVATE DATA +*/ +static int line_PC; +static int last_line_blank; + +static FILE *output; + +static Options options = +{ + FALSE, + FALSE, + FALSE, + TRUE, + LabelsOff, + MacrosOff, +}; + + +static ValueTable labels_table[] = +{ + {"off", LabelsOff}, + {"no", LabelsOff}, + {"on", LabelsDump}, + {"yes", LabelsDump}, + {"all", LabelsDump | LabelsDumpPrivate}, + {NULL} +}; + + +static ValueTable macros_table[] = +{ + {"off", MacrosOff}, + {"no", MacrosOff}, + {"yes", MacrosInvoke}, + {"exec", MacrosInvoke}, + {"dump", MacrosDump}, + {"all", MacrosInvoke | MacrosDump}, + {NULL} +}; + + +/* ---------------------------------------- PRIVATE FUNCTIONS +*/ +static void BuildArgs(Varchar *str, int argc, char *argv[], int quoted[]) +{ + int f; + + for(f = 0; f < argc; f++) + { + if (f > 0) + { + VarcharAdd(str, ", "); + } + + if (quoted[f] && quoted[f] == '(') + { + VarcharPrintf(str, "(%s)", argv[f]); + } + else if (quoted[f]) + { + VarcharPrintf(str, "%c%s%c", quoted[f], argv[f], quoted[f]); + } + else + { + VarcharAdd(str, argv[f]); + } + } +} + + +static void Output(const char *fmt, ...) +{ + if (IsFirstPass() && options.enabled) + { + va_list va; + + va_start(va, fmt); + + if (output) + { + vfprintf(output, fmt, va); + } + else + { + vfprintf(stdout, fmt, va); + } + + va_end(va); + } +} + + + +static FILE *GetOutput(void) +{ + return output ? output : stdout; +} + + +static int IsBlankLine(const char *p) +{ + while(*p) + { + if (!isspace((unsigned char)*p++)) + { + return FALSE; + } + } + + return TRUE; +} + + +/* ---------------------------------------- INTERFACES +*/ + +const ValueTable *ListOptions(void) +{ + return option_set; +} + +CommandStatus ListSetOption(int opt, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + const ValueTable *val; + CommandStatus stat = CMD_OK; + + if (!IsFinalPass()) + { + return CMD_OK; + } + + CMD_ARGC_CHECK(1); + + switch(opt) + { + case OPT_LIST: + options.enabled = ParseTrueFalse(argv[0], FALSE); + break; + + case OPT_LISTFILE: + if (output) + { + snprintf(err, errsize, "output file already set"); + stat = CMD_FAILED; + } + else + { + output = fopen(argv[0], "w"); + + if (!output) + { + snprintf(err, errsize, "couldn't open \"%s\"", argv[0]); + stat = CMD_FAILED; + } + } + break; + + case OPT_LISTPC: + options.dump_PC = ParseTrueFalse(argv[0], FALSE); + break; + + case OPT_LISTHEX: + options.dump_bytes = ParseTrueFalse(argv[0], FALSE); + break; + + case OPT_LISTMACROS: + CMD_TABLE(argv[0], macros_table, val); + options.macros = val->value; + break; + + case OPT_LISTLABELS: + CMD_TABLE(argv[0], labels_table, val); + options.labels = val->value; + break; + + case OPT_LISTRMBLANK: + options.rm_blank = ParseTrueFalse(argv[0], FALSE); + break; + + default: + break; + } + + return stat; +} + + +void ListStartLine(void) +{ + line_PC = PC(); +} + + +void ListLine(const char *line) +{ + if (IsFinalPass() && options.enabled) + { + if (options.rm_blank && last_line_blank && IsBlankLine(line)) + { + return; + } + + last_line_blank = IsBlankLine(line); + + Output("%s\n", line); + + /* Generate PC and hex dump and add to comment + */ + if ((options.dump_PC || options.dump_bytes) && (PC() != line_PC)) + { + Varchar *hex; + int f; + + hex = VarcharCreate(NULL); + + VarcharAdd(hex, ";"); + + if (options.dump_PC) + { + VarcharPrintf(hex, " $%4.4X:", (unsigned)line_PC); + } + + if (options.dump_bytes && (PC() - line_PC < 256)) + { + for(f = line_PC; f < PC(); f++) + { + VarcharPrintf(hex, " $%2.2X", (unsigned)ReadByte(f)); + } + } + + Output("%s\n", VarcharContents(hex)); + + VarcharFree(hex); + } + } +} + + +void ListMacroInvokeStart(int argc, char *argv[], int quoted[]) +{ + if (IsFinalPass() && options.enabled && options.macros & MacrosInvoke) + { + Varchar *s; + + s = VarcharCreate(NULL); + + VarcharPrintf(s, "; START MACRO %s ", argv[0]); + + BuildArgs(s, argc - 1, argv + 1, quoted + 1); + + Output("%s\n", VarcharContents(s)); + VarcharFree(s); + } +} + + +void ListMacroInvokeEnd(const char *name) +{ + if (IsFinalPass() && options.enabled && options.macros & MacrosInvoke) + { + Varchar *s; + + s = VarcharCreate(NULL); + + VarcharAdd(s, "; END MACRO "); + VarcharAdd(s, name); + + Output("%s\n", VarcharContents(s)); + VarcharFree(s); + } +} + + +void ListError(const char *fmt, ...) +{ + char buff[4096]; + va_list va; + + va_start(va, fmt); + vsnprintf(buff, sizeof buff, fmt, va); + va_end(va); + + fprintf(stderr, "%s\n", buff); + + if (IsFinalPass() && options.enabled && output) + { + Output("%s\n", buff); + } +} + + +void ListFinish(void) +{ + if (IsFinalPass() && options.enabled) + { + if (options.labels) + { + Output("\n;\n; LABELS:\n;\n"); + LabelDump(GetOutput(), options.labels & LabelsDumpPrivate); + } + + if (options.macros & MacrosDump) + { + Output("\n;\n; MACROS:\n;\n"); + MacroDump(GetOutput()); + } + } + + if (output) + { + if (output != stdout) + { + fclose(output); + } + + output = NULL; + } + + options.enabled = FALSE; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/listing.h b/src/listing.h new file mode 100644 index 0000000..38588f5 --- /dev/null +++ b/src/listing.h @@ -0,0 +1,77 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Listing handler + +*/ + +#ifndef CASM_LISTING_H +#define CASM_LISTING_H + +#include "cmd.h" +#include "parse.h" +#include "label.h" + + +/* List options +*/ +const ValueTable *ListOptions(void); + +CommandStatus ListSetOption(int opt, int argc, char *argv[], + int quoted[], char *error, size_t error_size); + + +/* Call before start of line processing +*/ +void ListStartLine(void); + +/* Output a read line to the listing. +*/ +void ListLine(const char *line); + + +/* Output a macro invocation to the listing +*/ +void ListMacroInvokeStart(int argc, char *argv[], int quoted[]); +void ListMacroInvokeEnd(const char *name); + + +/* Output arbitary string. A terminating new line will NOT be added. +*/ +void ListPrintf(const char *fmt, ...); + + +/* Output error/warning message. The error will also be reported to stderr. + A terminating newline will be added. +*/ +void ListError(const char *fmt, ...); + + +/* Finish the listing +*/ +void ListFinish(void); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/macro.c b/src/macro.c new file mode 100644 index 0000000..03891c2 --- /dev/null +++ b/src/macro.c @@ -0,0 +1,509 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for macros. + +*/ +#include +#include +#include +#include + +#include "global.h" +#include "codepage.h" +#include "varchar.h" +#include "macro.h" + + +/* ---------------------------------------- TYPES +*/ + +enum option_t +{ + OPT_MACROARGCHAR +}; + +static const ValueTable option_set[] = +{ + {"macro-arg-char", OPT_MACROARGCHAR}, + {NULL} +}; + + +struct mdef +{ + char *name; + int no_args; + char **args; + int no_lines; + char **lines; + struct mdef *next; +}; + + +struct macro +{ + MacroDef *def; + int line; + int argc; + char **argv; + int *quoted; +}; + + +/* ---------------------------------------- GLOBALS +*/ +static MacroDef *head; +static MacroDef *tail; + +static const char *arg_chars = "ABCDEFGHIJKLMNOPQRSTUVXYZ" + "abcdefghijklmnopqrstuvxyz" + "0123456789_"; + +static struct +{ + int arg_char; +} options = +{ + '@' +}; + + +/* ---------------------------------------- PRIVATE FUNCTIONS INTERFACES +*/ +static MacroDef *FindMacro(const char *p) +{ + MacroDef *m = head; + + while(m) + { + if (CompareString(m->name, p)) + { + return m; + } + + m = m->next; + } + + return NULL; +} + + +static int CheckArgName(const char *p) +{ + while(*p) + { + if (!strchr(arg_chars, *p++)) + { + return FALSE; + } + } + + return TRUE; +} + + +static MacroDef *AddMacro(const char *p, int argc, char *argv[], + char *err, size_t errsize) +{ + MacroDef *m = FindMacro(p); + + if (!m) + { + m = Malloc(sizeof *m); + + m->name = DupStr(p); + m->no_lines = 0; + m->lines = NULL; + m->next = NULL; + + m->no_args = argc; + + if (m->no_args == 0) + { + m->args = NULL; + } + else + { + int f; + + m->args = Malloc((sizeof *m->args) * m->no_args); + + for(f = 0; f < argc; f++) + { + if (!argv[f][0] || !CheckArgName(argv[f])) + { + snprintf(err, errsize, "illegal argument name '%s'", + argv[f]); + return NULL; + } + + m->args[f] = DupStr(argv[f]); + } + } + + if (tail) + { + tail->next = m; + } + + tail = m; + + if (!head) + { + head = m; + } + } + else + { + snprintf(err, errsize, "macro %s already exists", p); + m = NULL; + } + + return m; +} + + +static void AddArg(Varchar *str, Macro *macro, int arg_no) +{ + if (arg_no < macro->argc) + { + int quote = macro->quoted[arg_no]; + + if (quote) + { + VarcharAddChar(str, quote); + } + + VarcharAdd(str, macro->argv[arg_no]); + + if (quote) + { + if (quote == '(') + { + VarcharAddChar(str, ')'); + } + else + { + VarcharAddChar(str, quote); + } + } + } +} + + +static int FindArg(MacroDef *def, const char *name) +{ + int f; + + for(f = 0; f < def->no_args; f++) + { + if (CompareString(def->args[f], name)) + { + return f; + } + } + + return def->no_args; +} + +/* ---------------------------------------- INTERFACES +*/ + +void MacroSetDefaults(void) +{ + options.arg_char = '@'; +} + +const ValueTable *MacroOptions(void) +{ + return option_set; +} + +CommandStatus MacroSetOption(int opt, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + CommandStatus stat = CMD_OK; + + CMD_ARGC_CHECK(1); + + switch(opt) + { + case OPT_MACROARGCHAR: + options.arg_char = argv[0][0]; + + if (options.arg_char == 0) + { + snprintf(err, errsize, "illegal character '\\0'"); + stat = CMD_FAILED; + } else if (isalnum((unsigned char)options.arg_char)) + { + snprintf(err, errsize, "illegal character '%c'", + options.arg_char); + stat = CMD_FAILED; + } + break; + + default: + break; + } + + return stat; +} + + +MacroDef *MacroCreate(const char *name, int argc, char *argv[], + char *err, size_t errsize) +{ + MacroDef *macro = NULL; + + macro = AddMacro(name, argc, argv, err, errsize); + + return macro; +} + +void MacroRecord(MacroDef *macro, const char *line) +{ + if (macro) + { + macro->no_lines++; + + macro->lines = Realloc(macro->lines, macro->no_lines * + sizeof *macro->lines); + macro->lines[macro->no_lines - 1] = DupStr(line); + } +} + +CommandStatus MacroFind(Macro **ret, int argc, char *argv[], int quoted[], + char *err, size_t errsize) +{ + MacroDef *def = NULL; + Macro *macro = NULL; + CommandStatus status = CMD_NOT_KNOWN; + + if (argc > 0) + { + def = FindMacro(argv[0]); + } + + if (def) + { + if (def->no_args && def->no_args != argc - 1) + { + snprintf(err, errsize, "%s: expected %d argument%s, got %d", + argv[0], def->no_args, + def->no_args == 1 ? "" : "s", argc - 1); + + status = CMD_FAILED; + } + else + { + int f; + + macro = Malloc(sizeof *macro); + macro->line = 0; + macro->def = def; + macro->argc = argc; + macro->argv = Malloc(argc * sizeof *macro->argv); + macro->quoted = Malloc(argc * sizeof *macro->quoted); + + for(f = 0; f < argc; f++) + { + macro->argv[f] = DupStr(argv[f]); + macro->quoted[f] = quoted[f]; + } + + status = CMD_OK; + } + } + + *ret = macro; + + return status; +} + + +char *MacroPlay(Macro *macro) +{ + if (macro && macro->line < macro->def->no_lines) + { + int size = 1000; + int rd; + char num[64]; + char arg[128]; + const char *line; + int in_num = -1; + int in_arg = -1; + Varchar *str; + + line = macro->def->lines[macro->line++]; + + /* If no arguments, simply duplicate + */ + if (!strchr(line, '\\') && !strchr(line, options.arg_char)) + { + return DupStr(line); + } + + /* Expand the arguments + */ + str = VarcharCreate(NULL); + rd = 0; + + while(line[rd]) + { + if (line[rd] == '\\') + { + in_num = 0; + rd++; + } + else if (line[rd] == options.arg_char) + { + in_arg = 0; + rd++; + } + else + { + if (in_num != -1) + { + if (isdigit((unsigned char)line[rd]) && in_num < 60) + { + num[in_num++] = line[rd++]; + } + else + { + num[in_num] = 0; + AddArg(str, macro, atoi(num)); + in_num = -1; + } + } + else if (in_arg != -1) + { + if (strchr(arg_chars, line[rd]) && in_arg < 125) + { + arg[in_arg++] = line[rd++]; + } + else + { + arg[in_arg] = 0; + AddArg(str, macro, FindArg(macro->def, arg) + 1); + in_arg = -1; + } + } + else + { + VarcharAddChar(str, line[rd++]); + } + } + } + + /* Check for arguments at the end of the line + */ + if (in_num != -1) + { + num[in_num] = 0; + AddArg(str, macro, atoi(num)); + in_num = -1; + } + + if (in_arg != -1) + { + arg[in_arg] = 0; + AddArg(str, macro, FindArg(macro->def, arg) + 1); + in_arg = -1; + } + + return VarcharTransfer(str); + } + + return NULL; +} + + +void MacroFree(Macro *macro) +{ + if (macro) + { + int f; + + for(f = 0; f < macro->argc; f++) + { + free(macro->argv[f]); + } + + free(macro->quoted); + free(macro->argv); + free(macro); + } +} + + +const char *MacroName(Macro *macro) +{ + if (macro) + { + return macro->def->name; + } + + return "(unknown)"; +} + + +void MacroDump(FILE *fp) +{ + MacroDef *m = head; + + while(m) + { + int f; + + fprintf(fp, "; %s: MACRO", m->name); + + if (m->no_args) + { + for(f = 0; f < m->no_args; f++) + { + fprintf(fp, "%s%s", f == 0 ? " " : ", ", m->args[f]); + } + } + + putc('\n', fp); + + for(f = 0; f < m->no_lines; f++) + { + fprintf(fp, "; %s\n", m->lines[f]); + } + + fprintf(fp, "; ENDM\n"); + + m = m->next; + + if (m) + { + fprintf(fp, ";\n"); + } + } +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/macro.h b/src/macro.h new file mode 100644 index 0000000..ed4c688 --- /dev/null +++ b/src/macro.h @@ -0,0 +1,101 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for macros. + +*/ + +#ifndef CASM_MACRO_H +#define CASM_MACRO_H + +#include +#include +#include "cmd.h" +#include "parse.h" + +typedef struct mdef MacroDef; +typedef struct macro Macro; + +/* ---------------------------------------- INTERFACES +*/ + +/* Macro options +*/ +void MacroSetDefaults(void); + +const ValueTable *MacroOptions(void); + +CommandStatus MacroSetOption(int opt, int argc, char *argv[], + int quoted[], char *error, size_t error_size); + + + +/* Create a new macro definition. Returns NULL if the macro already exists + or cannot be created and updates the passed error string. + argc/argv is a list of the parameters. argv can be NULL if argc is zero. +*/ +MacroDef *MacroCreate(const char *name, int argc, char *argv[], + char *err, size_t errsize); + + +/* Record a macro's contents. +*/ +void MacroRecord(MacroDef *macro, const char *line); + + +/* Find a macro, placing a pointer to it in the passed macro or NULL if not + known, or an error occurred. Records the passed arguments for playing back + the macro. argv[0] is the macro name. + + Will return CMD_NOT_KNOWN if the macro is unknown. Returns CMD_FAILED if + there is a problem with the macro, e.g. insufficient arguments. Returns + CMD_OK when the macro if found and OK. +*/ +CommandStatus MacroFind(Macro **macro, int argc, char *argv[], int quoted[], + char *err, size_t errsize); + + +/* Playback a found macro. Returns the next line, or NULL if the macro has + finished. The returned line is argument expanded and must be free()ed + after use. +*/ +char *MacroPlay(Macro *macro); + + +/* Free a macro once it's finished playing. +*/ +void MacroFree(Macro *macro); + + +/* Get the name of a running macro +*/ +const char *MacroName(Macro *macro); + + +/* Dump out the macro definitions, commented +*/ +void MacroDump(FILE *fp); + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/output.c b/src/output.c new file mode 100644 index 0000000..3b1809f --- /dev/null +++ b/src/output.c @@ -0,0 +1,228 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Various output type handlers. + +*/ +#include +#include + +#include "global.h" +#include "output.h" + + +/* ---------------------------------------- GLOBALS +*/ + +enum option_t +{ + OPT_OUTPUTFILE, + OPT_OUTPUTFORMAT +}; + +static const ValueTable option_set[] = +{ + {"output-file", OPT_OUTPUTFILE}, + {"output-format", OPT_OUTPUTFORMAT}, + {NULL} +}; + +typedef enum +{ + Raw, + SpectrumTap +} Format; + +static char output[4096] = "output"; +static char error[1024]; +static Format format = Raw; + +static ValueTable format_table[] = +{ + {"raw", Raw}, + {"spectrum", SpectrumTap}, + {NULL} +}; + + +/* ---------------------------------------- PRIVATE FUNCTIONS +*/ +static int OutputRawBinary(const Byte *mem, int min, int max) +{ + FILE *fp = fopen(output, "wb"); + + if (!fp) + { + snprintf(error, sizeof error,"Failed to open %s\n", output); + return FALSE; + } + + fwrite(mem + min, 1, max - min + 1, fp); + + fclose(fp); + + return TRUE; +} + + +static Byte TapByte(FILE *fp, Byte b, Byte chk) +{ + chk ^= b; + putc(b, fp); + return chk; +} + + +static Byte TapWord(FILE *fp, int w, Byte chk) +{ + chk = TapByte(fp, w & 0xff, chk); + chk = TapByte(fp, (w & 0xff00) >> 8, chk); + return chk; +} + + +static Byte TapString(FILE *fp, const char *p, int len, Byte chk) +{ + while(len--) + { + chk = TapByte(fp, *p ? *p++ : ' ', chk); + } + + return chk; +} + + +static int OutputSpectrumTap(const Byte *mem, int min, int max) +{ + FILE *fp = fopen(output, "wb"); + Byte chk = 0; + int len = max - min + 1; + + if (!fp) + { + snprintf(error, sizeof error,"Failed to open %s\n", output); + return FALSE; + } + + /* Output header + */ + TapWord(fp, 19, 0); + + chk = TapByte(fp, 0, chk); + chk = TapByte(fp, 3, chk); + chk = TapString(fp, output, 10, chk); + chk = TapWord(fp, len, chk); + chk = TapWord(fp, min, chk); + chk = TapWord(fp, 32768, chk); + + TapByte(fp, chk, 0); + + /* Output file data + */ + TapWord(fp, len + 2, 0); + + chk = 0; + + chk = TapByte(fp, 0xff, chk); + + while(min <= max) + { + chk = TapByte(fp, mem[min], chk); + min++; + } + + TapByte(fp, chk, 0); + + fclose(fp); + + return TRUE; +} + +/* ---------------------------------------- INTERFACES +*/ + +const ValueTable *OutputOptions(void) +{ + return option_set; +} + + +CommandStatus OutputSetOption(int opt, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + const ValueTable *val; + + CMD_ARGC_CHECK(1); + + switch(opt) + { + case OPT_OUTPUTFILE: + CopyStr(output, argv[0], sizeof output); + break; + + case OPT_OUTPUTFORMAT: + CMD_TABLE(argv[0], format_table, val); + format = val->value; + break; + + default: + break; + } + + return CMD_OK; +} + + +int OutputCode(void) +{ + int min = GetMinAddressWritten(); + int max = GetMaxAddressWritten(); + const Byte *mem = AddressSpace(); + + if (max == -1) + { + fprintf(stderr, "Skipping output; no written memory to write\n"); + return TRUE; + } + + switch(format) + { + case Raw: + return OutputRawBinary(mem, min, max); + + case SpectrumTap: + return OutputSpectrumTap(mem, min, max); + + default: + break; + } +} + + +const char *OutputError(void) +{ + return error; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/output.h b/src/output.h new file mode 100644 index 0000000..cb0e5ca --- /dev/null +++ b/src/output.h @@ -0,0 +1,58 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Various output type handlers + +*/ + +#ifndef CASM_OUTPUT_H +#define CASM_OUTPUT_H + +#include "parse.h" +#include "cmd.h" + +/* ---------------------------------------- INTERFACES +*/ + + +/* Output options +*/ +const ValueTable *OutputOptions(void); + +CommandStatus OutputSetOption(int opt, int argc, char *argv[], + int quoted[], char *error, size_t error_size); + + +/* Outputs the result of assembly. Returns TRUE if OK, FALSE for failure. +*/ +int OutputCode(void); + + +/* Returns a reason for the last failure. +*/ +const char *OutputError(void); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/parse.c b/src/parse.c new file mode 100644 index 0000000..abcd149 --- /dev/null +++ b/src/parse.c @@ -0,0 +1,332 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Tokeniser. + +*/ +#include +#include +#include +#include + +#include "global.h" +#include "codepage.h" +#include "parse.h" + +/* ---------------------------------------- MACROS/TYPES +*/ +typedef enum +{ + CONSUME_WS, + CONSUME_TOKEN, + FINISHED +} State; + + +/* ---------------------------------------- GLOBALS +*/ +static char error[1024]; + +static const ValueTable bool_table[] = +{ + YES_NO_ENTRIES(TRUE, FALSE), + {NULL} +}; + + + +/* ---------------------------------------- PRIVATE FUNCTIONS +*/ +static void AddToken(const char *start, const char *end, Line *line, int quoted) +{ + char *tok = Malloc(end - start + 2); + char *p; + + p = tok; + + while(start != end + 1) + { + *p++ = *start++; + } + + *p = 0; + + Trim(tok); + + /* Special case to convert single characters in quotes to their character + code. + */ + if (*tok && *(tok+1) == 0 && (quoted == '\'' || quoted == '"')) + { + char b[64]; + + snprintf(b, sizeof b, "%d", CodepageConvert(*tok)); + free(tok); + + tok = DupStr(b); + quoted = 0; + } + + line->no_tokens++; + + line->token = Realloc(line->token, (sizeof *line->token) * line->no_tokens); + + line->quoted = Realloc(line->quoted, + (sizeof *line->quoted) * line->no_tokens); + + line->token[line->no_tokens - 1] = tok; + line->quoted[line->no_tokens - 1] = quoted; +} + + +/* ---------------------------------------- INTERFACES +*/ + +int ParseLine(Line *line, const char *source) +{ + static const char *sep_chars[2] = + { + " \t:", + "," + }; + + static const char *quote_start_chars[2] = + { + "", + "\"'(" + }; + + static const char *quote_end_chars[2] = + { + "", + "\"')" + }; + + const char *p = NULL; + const char *start = NULL; + State state = CONSUME_WS; + int stage = 0; + char open_quote = 0; + char quote = 0; + int status = TRUE; + + p = source; + + line->first_column = FALSE; + line->token = NULL; + line->comment = NULL; + line->quoted = NULL; + line->no_tokens = 0; + + while(state != FINISHED) + { + switch(state) + { + case CONSUME_WS: + if (!*p) + { + state = FINISHED; + } + else if (*p == ';') + { + state = FINISHED; + line->comment = DupStr(p + 1); + Trim(line->comment); + } + else + { + const char *ptr; + + if ((ptr = strchr(quote_start_chars[stage], *p))) + { + open_quote = *p; + quote = quote_end_chars + [stage] + [ptr - quote_start_chars[stage]]; + + state = CONSUME_TOKEN; + start = ++p; + } + else + { + if (isspace((unsigned char)*p) || + strchr(sep_chars[stage], *p)) + { + p++; + } + else + { + state = CONSUME_TOKEN; + start = p; + open_quote = 0; + quote = 0; + } + } + + if (source == start && state == CONSUME_TOKEN) + { + line->first_column = TRUE; + } + } + break; + + case CONSUME_TOKEN: + if (!*p) + { + if (quote) + { + snprintf + (error, sizeof error, + "Unterminated quoted string; expected %c", quote); + + status = FALSE; + } + else if (start != p) + { + AddToken(start, p, line, FALSE); + } + + state = FINISHED; + } + else + { + if (quote) + { + if (*p == quote) + { + state = CONSUME_WS; + } + } + else if (strchr(sep_chars[stage], *p) || *p == ';') + { + state = CONSUME_WS; + } + + if (state == CONSUME_WS) + { + AddToken(start, p - 1, line, open_quote); + + if (quote) + { + open_quote = 0; + quote = 0; + p++; + } + + if ((line->no_tokens == 2 && line->first_column) || + (line->no_tokens == 1 && !line->first_column)) + { + stage = 1; + } + } + else + { + p++; + } + } + break; + + default: + break; + } + } + + return status; +} + + +void ParseFree(Line *line) +{ + int f; + + if (line->token) + { + for(f = 0; f < line->no_tokens; f++) + { + free(line->token[f]); + } + + free(line->token); + free(line->quoted); + } + + if (line->comment) + { + free(line->comment); + } + + line->token = NULL; + line->quoted = NULL; + line->comment = NULL; +} + + +const char *ParseError(void) +{ + return error; +} + +const ValueTable *ParseTable(const char *str, const ValueTable *table) +{ + while(table && table->str) + { + if (CompareString(str, table->str)) + { + return table; + } + + table++; + } + + return NULL; +} + + +int ParseTrueFalse(const char *str, int def) +{ + const ValueTable *val; + + val = ParseTable(str, bool_table); + + return val == NULL ? def : val->value; +} + + +int ParseInTable(const char *str, const char *names[]) +{ + int f; + + for(f = 0; names[f]; f++) + { + if (CompareString(str, names[f])) + { + return TRUE; + } + } + + return FALSE; +} + + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/parse.h b/src/parse.h new file mode 100644 index 0000000..ee1e37d --- /dev/null +++ b/src/parse.h @@ -0,0 +1,123 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Tokeniser and parser. + +*/ + +#ifndef CASM_PARSE_H +#define CASM_PARSE_H + +/* ---------------------------------------- TYPES +*/ + +/* Represents a tokenised line. + + no_tokens is the number of tokens that can be accessed via token[i]. + + If quoted[i] is non-zero it contains the opening character that was used to + quote argument [i]. Note that '(' counts as a quote in command arguments. + + If first_column is true then the first column held a parsable character. + +*/ +typedef struct Line +{ + int no_tokens; + int first_column; + int *quoted; + char **token; + char *comment; +} Line; + + +/* Used to represent a table of string/value pairs. The table is ended with + a NULL in str. +*/ +typedef struct ValueTable +{ + const char *str; + int value; +} ValueTable; + + +/* ---------------------------------------- MACROS +*/ + + +/* Defines the set of values allowed for boolean strings and generates table + entries using the passed 'tv' and 'fv' for true and false respictively. +*/ +#define YES_NO_ENTRIES(tv, fv) \ + {"on", tv}, \ + {"true", tv}, \ + {"yes", tv}, \ + {"off", fv}, \ + {"false", fv}, \ + {"no", fv} + +/* ---------------------------------------- INTERFACES +*/ + +/* Parses a line. Returns TRUE if line parsed OK, otherwise returns FALSE. + ParseError() will return the reason for any failures. + + Remember that it may be possible to get a line with no tokens. +*/ +int ParseLine(Line *line, const char *source); + + +/* Free up the dynamic parts of a tokenised line +*/ +void ParseFree(Line *line); + + +/* Returns a reason for the last failure. +*/ +const char *ParseError(void); + + +/* Return a value from a table. The check is done case insensitive. Returns + the value item, or NULL if not found. +*/ +const ValueTable *ParseTable(const char *str, const ValueTable *table); + + +/* Parse a true/false value from a built-in table. Returns def if the value is + invalid. + + The mappings generated from YES_NO_ENTRIES(TRUE, FALSE) are used. +}; + +*/ +int ParseTrueFalse(const char *str, int def); + + +/* Returns true if the passed text is in the passed NULL terminated array of + names. +*/ +int ParseInTable(const char *str, const char *names[]); + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/stack.c b/src/stack.c new file mode 100644 index 0000000..a59c2d5 --- /dev/null +++ b/src/stack.c @@ -0,0 +1,143 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for macros. + +*/ +#include +#include +#include + +#include "global.h" +#include "stack.h" + + +/* ---------------------------------------- TYPES +*/ + +typedef struct stackitem +{ + void *data; + struct stackitem *next; +} StackItem; + + +struct stack +{ + StackItem *items; + int size; +}; + + +/* ---------------------------------------- INTERFACES +*/ + +Stack *StackCreate(void) +{ + Stack *s; + + s = Malloc(sizeof *s); + + s->items = NULL; + s->size = 0; + + return s; +} + + +void StackPush(Stack *stack, void *item) +{ + if (stack) + { + StackItem *i; + + i = Malloc(sizeof *i); + + i->next = stack->items; + i->data = item; + stack->items = i; + stack->size++; + } +} + + +void *StackPop(Stack *stack) +{ + void *item = NULL; + + if (stack && stack->items) + { + StackItem *next = stack->items->next; + + item = stack->items->data; + + free(stack->items); + stack->items = next; + stack->size--; + } + + return item; +} + + +void *StackPeek(Stack *stack) +{ + void *item = NULL; + + if (stack && stack->items) + { + item = stack->items->data; + } + + return item; +} + + +int StackSize(Stack *stack) +{ + return stack ? stack->size : 0; +} + + +void StackFree(Stack *stack) +{ + if (stack) + { + StackItem *i = stack->items; + + while(i) + { + StackItem *t = i; + + i = i->next; + + free(t); + } + + free(stack); + } +} + + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/stack.h b/src/stack.h new file mode 100644 index 0000000..e84911f --- /dev/null +++ b/src/stack.h @@ -0,0 +1,73 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Simple stack + +*/ + +#ifndef CASM_STACK_H +#define CASM_STACK_H + +typedef struct stack Stack; + +/* ---------------------------------------- INTERFACES +*/ + +/* Create a new stack. +*/ +Stack *StackCreate(void); + + +/* Push a value to the stack. +*/ +void StackPush(Stack *stack, void *item); + + +/* Pop a value from the stack. Returns NULL if the stack is empty. +*/ +void *StackPop(Stack *stack); + + +/* Like a pop, but doesn't remove the entry. Returns NULL if the stack is + empty. +*/ +void *StackPeek(Stack *stack); + + +/* Remove all entries from the stack +*/ +#define StackClear(stack) do {} while(StackPop(stack)) + + +/* Number of items on stack +*/ +int StackSize(Stack *stack); + + +/* Free the stack. +*/ +void StackFree(Stack *stack); + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/state.c b/src/state.c new file mode 100644 index 0000000..da2f3c8 --- /dev/null +++ b/src/state.c @@ -0,0 +1,221 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Stores assembly state. + +*/ +#include +#include + +#include "global.h" +#include "state.h" +#include "util.h" +#include "expr.h" + + +/* ---------------------------------------- GLOBALS +*/ +static int pass = 1; +static int maxpass = 2; +static int pc = 0; +static int size = 0x10000; +static Byte *mem = NULL; +static int minw = 0; +static int maxw = 0; +static WordMode wmode = LSB_Word; + + +/* ---------------------------------------- INTERFACES +*/ + +void ClearState(void) +{ + pass = 1; + pc = 0; + minw = size; + maxw = -1; + wmode = LSB_Word; + SetAddressSpace(0x10000); +} + + +void NextPass(void) +{ + if (pass < maxpass) + { + minw = size; + maxw = -1; + pass++; + } +} + + +int IsFinalPass(void) +{ + return pass == maxpass; +} + + +int IsFirstPass(void) +{ + return pass == 1; +} + + +int IsIntermediatePass(void) +{ + return pass > 1 && pass < maxpass; +} + + +int SetNeededPasses(int n) +{ + if (!IsFinalPass()) + { + maxpass = n; + } +} + + +void SetAddressSpace(int s) +{ + size = s; + + mem = Malloc(s); + memset(mem, 0, size); +} + + +void SetWordMode(WordMode mode) +{ + wmode = mode; +} + + +void SetPC(int i) +{ + pc = i; +} + + +int PC(void) +{ + return pc; +} + + +void PCAdd(int i) +{ + pc += i; + + while(pc < 0) + { + pc += size; + } + + pc %= size; +} + + +void PCWrite(int i) +{ + if (pc < minw) + { + minw = pc; + } + + if (pc > maxw) + { + maxw = pc; + } + + mem[pc] = ExprConvert(8, i); + + pc = (pc + 1) % size; +} + + +void PCWriteWord(int i) +{ + PCWriteWordMode(i, wmode); +} + + +void PCWriteWordMode(int i, WordMode mode) +{ + int w; + int lsb; + int msb; + + w = ExprConvert(16, i); + + lsb = LOBYTE(w); + msb = HIBYTE(w); + + switch(mode) + { + case MSB_Word: + PCWrite(msb); + PCWrite(lsb); + break; + + case LSB_Word: + PCWrite(lsb); + PCWrite(msb); + break; + } +} + + +int GetMinAddressWritten(void) +{ + return minw; +} + + +int GetMaxAddressWritten(void) +{ + return maxw; +} + + +const Byte *AddressSpace(void) +{ + return mem; +} + + +Byte ReadByte(int addr) +{ + Byte b = 0; + + if (addr > -1 && addr < size) + { + b = mem[addr]; + } + + return b; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/state.h b/src/state.h new file mode 100644 index 0000000..6ce9da7 --- /dev/null +++ b/src/state.h @@ -0,0 +1,130 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Stores assembly state. + +*/ + +#ifndef CASM_STATE_H +#define CASM_STATE_H + +/* ---------------------------------------- TYPES +*/ +typedef enum +{ + MSB_Word, + LSB_Word +} WordMode; + +/* ---------------------------------------- INTERFACES +*/ + +/* Clear state to default. This creates 64K of RAM to write into. +*/ +void ClearState(void); + + +/* Sets the number of bytes in RAM. Addressing always starts from zero. +*/ +void SetAddressSpace(int size); + + +/* Move onto the next pass +*/ +void NextPass(void); + + +/* Is final pass? +*/ +int IsFinalPass(void); + + +/* Is first pass? +*/ +int IsFirstPass(void); + + +/* Is intermediate pass? +*/ +int IsIntermediatePass(void); + + +/* Set number of passes needed. This works while IsFinalPass() returns FALSE. +*/ +int SetNeededPasses(int n); + + +/* Set the current PC +*/ +void SetPC(int i); + + +/* Set the mode to write words in +*/ +void SetWordMode(WordMode mode); + + +/* Get the current PC +*/ +int PC(void); + + +/* Add a number to the PC +*/ +void PCAdd(int i); + + +/* Write a byte to the PC and increment it +*/ +void PCWrite(int i); + + +/* Write a word to the PC and increment it +*/ +void PCWriteWord(int i); +void PCWriteWordMode(int i, WordMode mode); + + +/* Get the minimum address written to +*/ +int GetMinAddressWritten(void); + + +/* Get the maximum address written to +*/ +int GetMaxAddressWritten(void); + + +/* Access the address space directly +*/ +const Byte *AddressSpace(void); + + +/* Read a byte from the address space +*/ +Byte ReadByte(int addr); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/test/1 b/src/test/1 new file mode 100644 index 0000000..304e4b8 --- /dev/null +++ b/src/test/1 @@ -0,0 +1,75 @@ +; +; Basic parsing and label handling +; +; Comments +; + list on + list labels,on + + alias fred,equ + +label0: ; First label - should default to zero + +label1 org $8000 +.local_1 org 8001h +.local_2 org 8002 + +label2: equ 11110101b +.local2_1 +.local_dup + + org 0x8100 +pc_test1: equ $ +pc_test_minus_100: equ $ - $100 + +label3: +.local3_1 +.local_dup + +forward: equ bin1 + bin2 + hex1 + +expr1_7 equ 1 + 2 * 3 +expr2_9 equ {1 + 2} * 3 + +bin1: equ 11110101b +bin2: equ 11110010b +hex1: equ $1234 +hex2 equ 1234h +hex3: equ 0x1234 +oct: equ 0177 +.dec equ 65535 +.dec2: equ 123456 + +align_test: org $9001 + + align $10 +should_end_10h: + + align $100 +should_end_100h: + + cpu z80 + option list + + cpu 6502 + option list + + option zp,true + option zp,on + option zp,yes + option zp,false + option zp,off + option zp,no + + option +zp + option -zp + +one equ 1 +two fred ONE * 2 + +true1 equ one == 1 +false1 equ one != 1 + +endaddr:end + +this is ignored diff --git a/src/test/2 b/src/test/2 new file mode 100644 index 0000000..53f272d --- /dev/null +++ b/src/test/2 @@ -0,0 +1,68 @@ + option +list + option list-macros,all + org $8000 + +macro1: macro + .byte "Hello World" + .text 0, $ff + endm + +hello: macro +.start + macro1 + dw start,end +.end + endm + + option charset, ascii +ascii: hello "ASC",$80 +.should_be_local_to_ascii + + option charset, zx81 +zx81: + hello "'81",$81 +.should_be_local_to_zx81 + + option charset, spectrum +speccy: hello "48K",$82 + + option charset, cbm +cbm: hello "CBM",$83 + + org $8100 + + ds 512,$ + .ds 512 + ds 512,$ff + +macro2: macro + db \1,0xe5,\2 + endm + +macro3: macro + macro2 \1,\2 + endm + +macro4: macro a + macro3 $ff,@a + endm + +macrotest: + macro4 "0x01" + macro4 "0x02" + macro4 "0x03" + MACRO4 "0x04" + macro4 "0x05" + macro4 "0x06" + + option macro-arg-char,& + +macro5: macro a + defb &a + endm + + macro5 "Hello" + macro5 "World" + +end: equ $ +end2: diff --git a/src/test/3 b/src/test/3 new file mode 100644 index 0000000..a3f68f1 --- /dev/null +++ b/src/test/3 @@ -0,0 +1,20 @@ + org $8000 + +start: + dw start, end +end: + +wmac: macro +.start + dw start,end +.end + endm + + align 8 + wmac + align 8 + wmac + align 8 + wmac + + text "EOL" diff --git a/src/test/6502.1 b/src/test/6502.1 new file mode 100644 index 0000000..f94bcc8 --- /dev/null +++ b/src/test/6502.1 @@ -0,0 +1,272 @@ + ;option list-file,list.txt + option +list + option +list-hex + option +list-pc + option list-labels,all + option list-macros,all + + ; + ; Test basically created by pasting in opcodes from 6502.org + ; + cpu 6502 + + org $8000 + +zero: equ 0 + + option zero-page,off + +adc_test + ADC #$44 + option +zero-page + ADC $44 + adc $44,x + option -zero-page + adc $4400 + adc $4400,x + adc $4400,y + ADC ($44,X) + ADC ($44),Y + +zp_test + option zero-page,auto + adc $80 ; Always ZP + adc $8080 ; Always !ZP + adc fwd_80 ; Always ZP - sure after 2nd pass onwards + adc fwd_101 ; Initially ZP - sure !ZP after 2nd pass onwards + option -zero-page + + +addr_mode_tests: ; Uncomment to test + option +zero-page + ; adc $4400 ; Outside of ZP + option -zero-page + ; adc $4400,z ; Unknown index reg + ; adc ($44,y) ; Wrong indirect reg + ; adc ($44),x ; Wrong indirect reg + +and_test + and #$44 + and $44 + and $44,x + and $4400 + and $4400,x + and $4400,y + and ($44,x) + and ($44),y + +asl_test + ASL A + ASL $44 + ASL $44,X + ASL $4400 + ASL $4400,X + +bit_test + BIT $44 + BIT $4400 + +branch_test + BPL branch_test + BMI branch_test + BVC branch_test + BVS branch_test + BCC branch_test + BCS branch_test + BNE branch_test + BEQ branch_test + + BPL brk_test + BMI brk_test + BVC brk_test + BVS brk_test + BCC brk_test + BCS brk_test + BNE brk_test + BEQ brk_test + + ; BEQ zero ; Would generate an error/warning + ; BEQ $f000 ; Would generate an error/warning + +brk_test + BRK + +cmp_test +.acc + CMP #$44 + CMP $44 + CMP $44,X + CMP $4400 + CMP $4400,X + CMP $4400,Y + CMP ($44,X) + CMP ($44),Y + +.xreg + CPX #$44 + CPX $44 + CPX $4400 + + +.yreg + CPY #$44 + CPY $44 + CPY $4400 + +dec_test + DEC $44 + DEC $44,X + DEC $4400 + DEC $4400,X + + + +eor_test + EOR #$44 + EOR $44 + EOR $44,X + EOR $4400 + EOR $4400,X + EOR $4400,Y + EOR ($44,X) + EOR ($44),Y + +flag_test + CLC + SEC + CLI + SEI + CLV + CLD + SED + +inc_test + INC $44 + INC $44,X + INC $4400 + INC $4400,X + + +jmp_test + JMP $5597 + JMP ($5597) + +jsr_test + JSR $5597 + +ld_test +.acc + LDA #$44 + LDA $44 + LDA $44,X + LDA $4400 + LDA $4400,X + LDA $4400,Y + LDA ($44,X) + LDA ($44),Y + +.xreg + LDX #$44 + LDX $44 + LDX $44,Y + LDX $4400 + LDX $4400,Y + +.yreg + LDY #$44 + LDY $44 + LDY $44,X + LDY $4400 + LDY $4400,X + +lst_test + + LSR A + LSR $44 + LSR $44,X + LSR $4400 + LSR $4400,X + +nop_test + + NOP + +ora_test + ORA #$44 + ORA $44 + ORA $44,X + ORA $4400 + ORA $4400,X + ORA $4400,Y + ORA ($44,X) + ORA ($44),Y + +xfer_test + TAX + TXA + DEX + INX + TAY + TYA + DEY + INY + + +rol_test + ROL A + ROL $44 + ROL $44,X + ROL $4400 + ROL $4400,X + +rot_test + ROR A + ROR $44 + ROR $44,X + ROR $4400 + ROR $4400,X + +ret_test + RTI + rts + +sbc_test + SBC #$44 + SBC $44 + SBC $44,X + SBC $4400 + SBC $4400,X + SBC $4400,Y + SBC ($44,X) + SBC ($44),Y + +store_test +.acc + STA $44 + STA $44,X + STA $4400 + STA $4400,X + STA $4400,Y + STA ($44,X) + STA ($44),Y + +.xreg + STX $44 + STX $44,Y + STX $4400 + +.yreg + STY $44 + STY $44,X + STY $4400 + +stack_test + TXS + TSX + PHA + PLA + PHP + PLP + +fwd_80: equ $80 +fwd_101 equ $101 diff --git a/src/test/inc b/src/test/inc new file mode 100644 index 0000000..4856cb3 --- /dev/null +++ b/src/test/inc @@ -0,0 +1,6 @@ + include test/1 +binstart: + incbin test/1 +binend: + + ;include test/inc diff --git a/src/test/z80.1 b/src/test/z80.1 new file mode 100644 index 0000000..d41828e --- /dev/null +++ b/src/test/z80.1 @@ -0,0 +1,447 @@ + option -list + option +list-hex + option +list-pc + option list-macros,off + option list-labels,off + + ;output file,a.tap + ;output format,spectrum + + cpu z80 + + org $8000 + + ; + ; Simple comments + ; + +ld8test:macro reg + LD @reg,A ; Comment? + LD @reg,B + LD @reg,C + LD @reg,D + LD @reg,E + LD @reg,H + LD @reg,L +.test + LD @reg,(HL) + LD @reg,(IX+127) + LD @reg,(IY-128) + LD (hl),@reg + LD (IX+127),@reg + LD (IY-128),@reg + ld @reg,$e5 + ld @reg,ixh + ld @reg,ixl + ld @reg,iyh + ld @reg,iyl + endm + +ld8test_undoc:macro + LD \1,A ; Comment? + LD \1,B + LD \1,C + LD \1,D + LD \1,E + LD \1,\1 + LD \1,\1 + ld \1,$e5 + endm + +ld_tests: +.value + ld (hl),$e5 + ld (ix+127),$e5 + ld (iy-128),$e5 +.a + ld8test a + LD a,(BC) + LD a,(DE) + LD a,(word) + ld a,i + ld a,r + ld r,a + ld i,a + +.b + ld8test b +.c + ld8test c +.d + ld8test d +.e + ld8test e +.h + ld8test h +.l + ld8test l +.ixl + ld8test_undoc ixl +.ixh + ld8test_undoc ixh +.iyl + ld8test_undoc iyl +.iyh + ld8test_undoc iyh + +.bit16 + ld bc,word + ld de,word + ld hl,word + ld sp,word + ld ix,word + ld iy,word + + ld bc,(word) + ld de,(word) + ld hl,(word) + ld sp,(word) + ld ix,(word) + ld iy,(word) + +alu: macro + \1 a,a + \1 a + \1 a,b + \1 b + \1 a,c + \1 a,d + \1 a,e + \1 a,h + \1 a,l + \1 a,ixh + \1 a,ixl + \1 a,iyh + \1 a,iyl + \1 a,(hl) + \1 a,(ix+100) + \1 a,(iy-100) + endm + +alutest: + alu add + alu adc + alu sub + alu sbc + alu and + alu or + alu xor + alu eor + alu cp + + inc a + inc b + inc c + inc d + inc e + inc h + inc l + inc ixh + inc ixl + inc iyh + inc iyl + inc (hl) + inc (ix-64) + inc (iy+64) + inc bc + inc de + inc hl + inc sp + inc ix + inc iy + + dec a + dec b + dec c + dec d + dec e + dec h + dec l + dec ixh + dec ixl + dec iyh + dec iyl + dec (hl) + dec (ix-64) + dec (iy+64) + dec bc + dec de + dec hl + dec sp + dec ix + dec iy + + im 0 + im 1 + im 2 + +alu16: + add hl,bc + add hl,de + add hl,hl + add hl,sp + + add ix,bc + add ix,de + add ix,ix + add ix,sp + + add iy,bc + add iy,de + add iy,iy + add iy,sp + + adc hl,bc + adc hl,de + adc hl,hl + adc hl,sp + + sbc hl,bc + sbc hl,de + sbc hl,hl + sbc hl,sp + +stack: + push af + push bc + push de + push hl + push ix + push iy + pop af + pop bc + pop de + pop hl + pop ix + pop iy + +exchange: + ex de,hl + ex af,af' + ex (sp),hl + ex (sp),ix + ex (sp),iy + ldi + ldir + ldd + lddr + cpi + cpir + cpdr + +bitm: macro + \1 a + \1 b + \1 c + \1 d + \1 e + \1 h + \1 l + \1 (hl) + \1 (ix+1) + \1 (iy-1) + \1 (ix+1),a + \1 (iy-1),a + \1 (ix+1),b + \1 (iy-1),b + \1 (ix+1),c + \1 (iy-1),c + \1 (ix+1),d + \1 (iy-1),d + \1 (ix+1),e + \1 (iy-1),e + \1 (ix+1),h + \1 (iy-1),h + \1 (ix+1),l + \1 (iy-1),l + endm + +set_res:macro + \1 0,a + \1 1,b + \1 2,c + \1 3,d + \1 4,e + \1 5,h + \1 6,l + \1 7, (hl) + \1 0, (ix+1) + \1 0, (iy-1) + \1 1, (ix+1),a + \1 1, (iy-1),a + \1 2, (ix+1),b + \1 2, (iy-1),b + \1 3, (ix+1),c + \1 3, (iy-1),c + \1 4, (ix+1),d + \1 4, (iy-1),d + \1 5, (ix+1),e + \1 5, (iy-1),e + \1 6, (ix+1),h + \1 6, (iy-1),h + \1 7, (ix+1),l + \1 7, (iy-1),l + endm + +bits: + RLCA + RRCA + RLA + RRA + + bitm rlc + bitm rl + bitm rrc + bitm rr + bitm sla + bitm SRA + bitm srl + bitm sll + + set_res set + set_res res + + bit 0,a + bit 1,b + bit 2,c + bit 3,d + bit 4,e + bit 5,h + bit 6,l + bit 7, (hl) + bit 0, (ix+1) + bit 0, (iy-1) + +jtest: macro + \1 endjmp + \1 nz,endjmp + \1 z,endjmp + \1 nc,endjmp + \1 c,endjmp + \1 po,endjmp + \1 pe,endjmp + \1 p,endjmp + \1 m,endjmp +.endjmp + endm + +jump: + jtest jp + jtest call + + jp (hl) + jp (ix) + jp (iy) + +.jrjump1 + jr jrjump1 + jr nz,jrjump1 + jr z,jrjump1 + jr nc,jrjump1 + jr c,jrjump1 + djnz jrjump1 + + jr jrjump2 + jr nz,jrjump2 + jr z,jrjump2 + jr nc,jrjump2 + jr c,jrjump2 + djnz jrjump2 +.jrjump2 + call 0 + + ret + ret nz + ret z + ret nc + ret c + ret po + ret pe + ret p + ret m + reti + retn + + rst 0 + rst 0h + rst 8 + rst 8h + rst 10 + rst 10h + rst 18 + rst 18h + rst 20 + rst 20h + rst 28 + rst 28h + rst 30 + rst 30h + rst 38 + rst 38h + + +io: + in (c) + in a,(255) + in a,(c) + in b,(c) + in c,(c) + in d,(c) + in e,(c) + in h,(c) + in l,(c) + in f,(c) + ini + inir + ind + indr + + out (c),0 + out (255),a + out (c),a + out (c),b + out (c),c + out (c),d + out (c),e + out (c),h + out (c),l + out (c),f + out (c),255 + out (c),123456789 + outi + otir + outd + otdr + +implied: + NOP + DI + EI + HALT + HLT + EXX + DAA + CPL + SCF + CCF + NEG + CPI + CPIR + CPD + CPDR + INI + INIR + IND + INDR + OUTI + OTIR + OUTD + OTDR + LDI + LDIR + LDD + LDDR + +word: defs 2 + +string: db "Hello World",0 diff --git a/src/util.c b/src/util.c new file mode 100644 index 0000000..fd4bd10 --- /dev/null +++ b/src/util.c @@ -0,0 +1,201 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Common utilities + +*/ +#include +#include +#include +#include + +#include "global.h" +#include "util.h" + + +/* ---------------------------------------- INTERFACES +*/ + +char *DupStr(const char *p) +{ + char *new; + + new = Malloc(strlen(p) + 1); + strcpy(new, p); + return new; +} + + +void *Malloc(size_t len) +{ + void *new; + + if (!(new = malloc(len))) + { + fprintf(stderr, "Unable to allocate %lu bytes\n", (unsigned long)len); + exit(EXIT_FAILURE); + } + + return new; +} + + +void *Realloc(void *p, size_t len) +{ + void *new; + + if (!(new = realloc(p, len))) + { + fprintf(stderr, "Unable to reallocate %lu bytes\n", (unsigned long)len); + exit(EXIT_FAILURE); + } + + return new; +} + + +char *RemoveNL(char *p) +{ + if (p) + { + size_t l = strlen(p); + + while (l > 0 && (p[l-1] == '\n')) + { + p[--l] = 0; + } + } + + return p; +} + + +char *Trim(char *p) +{ + if (p) + { + size_t l = strlen(p); + + while (l > 0 && isspace((unsigned char)p[0])) + { + memmove(p, p + 1, l--); + } + + while(l > 1 && isspace((unsigned char)p[l-1])) + { + p[--l] = 0; + } + } + + return p; +} + + +char *CopyStr(char *dest, const char *src, size_t size) +{ + strncpy(dest, src, size); + dest[size - 1] = 0; + return dest; +} + + +int CompareString(const char *a, const char *b) +{ + while(*a && *b) + { + char c,d; + + c = tolower((unsigned char)*a++); + d = tolower((unsigned char)*b++); + + if (c != d) + { + return FALSE; + } + } + + return *a == *b; +} + + +int CompareStart(const char *a, const char *b) +{ + while(*a && *b) + { + char c,d; + + c = tolower((unsigned char)*a++); + d = tolower((unsigned char)*b++); + + if (c != d) + { + return FALSE; + } + } + + return (*b == 0); +} + + +int CompareEnd(const char *a, const char *b) +{ + if (strlen(a) < strlen(b)) + { + return FALSE; + } + + a += strlen(a) - strlen(b); + + while(*a && *b) + { + char c,d; + + c = tolower((unsigned char)*a++); + d = tolower((unsigned char)*b++); + + if (c != d) + { + return FALSE; + } + } + + return (*b == 0); +} + + +int IsNullOrEmpty(const char *p) +{ + int empty = TRUE; + + while(p && *p && empty) + { + if (!isspace((unsigned char)*p++)) + { + empty = FALSE; + } + } + + return empty; +} + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/util.h b/src/util.h new file mode 100644 index 0000000..3af1663 --- /dev/null +++ b/src/util.h @@ -0,0 +1,94 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Common utilities + +*/ + +#ifndef CASM_UTIL_H +#define CASM_UTIL_H + +#include + + +/* ---------------------------------------- INTERFACES +*/ + + +/* Duplicate a string. Just use free() to free. +*/ +char *DupStr(const char *p); + + +/* Malloc wrapper. Just use free() to free. +*/ +void *Malloc(size_t len); + + +/* Realloc wrapper. Just use free() to free. +*/ +void *Realloc(void *p, size_t len); + + +/* Remove end-of-line characters. Returns the passed pointer. +*/ +char *RemoveNL(char *p); + + +/* Remove white space from the start and end of a string. +*/ +char *Trim(char *p); + + +/* Compare a string, but case insensitive. Returns TRUE for match, otherwise + FALSE. +*/ +int CompareString(const char *a, const char *b); + + +/* Compare the start of a string 'a' starts with string 'b', but case + insensitive. Returns TRUE for match, otherwise FALSE. +*/ +int CompareStart(const char *a, const char *b); + + +/* Compare the end of a string 'a' ends with string 'b', but case insensitive. + Returns TRUE for match, otherwise FALSE. +*/ +int CompareEnd(const char *a, const char *b); + + +/* Strncpy, with a safety nulling of the last character. Returns the 1st + parameter. +*/ +char *CopyStr(char *dest, const char *src, size_t size); + + +/* Returns TRUE if a string is either a NULL pointer or just full of white space +*/ +int IsNullOrEmpty(const char *p); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/varchar.c b/src/varchar.c new file mode 100644 index 0000000..592ff74 --- /dev/null +++ b/src/varchar.c @@ -0,0 +1,158 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Collection for macros. + +*/ +#include +#include +#include +#include + +#include "global.h" +#include "codepage.h" +#include "varchar.h" + + +/* ---------------------------------------- TYPES +*/ + +struct varchar +{ + size_t len; + size_t size; + char *data; +}; + + +/* ---------------------------------------- INTERFACES +*/ + +Varchar *VarcharCreate(const char *initial) +{ + Varchar *str; + + str = Malloc(sizeof *str); + str->len = 0; + str->size = 0; + str->data = NULL; + + if (initial) + { + VarcharAdd(str, initial); + } + + return str; +} + + +Varchar *VarcharAddChar(Varchar *str, int c) +{ + if ((str->size - str->len) < 2) + { + str->size += 1024; + str->data = Realloc(str->data, str->size); + } + + str->data[str->len++] = c; + str->data[str->len] = 0; + + return str; +} + + +Varchar *VarcharAdd(Varchar *str, const char *c) +{ + size_t l; + + l = strlen(c); + + if ((str->size - str->len) <= (l + 1)) + { + str->size += (l / 1024 + 1) * 1024; + str->data = Realloc(str->data, str->size); + } + + strcpy(str->data + str->len, c); + str->len += l; + str->data[str->len] = 0; + + return str; +} + + +Varchar *VarcharPrintf(Varchar *str, const char *fmt, ...) +{ + char buff[1025]; + va_list va; + size_t l; + + va_start(va, fmt); + vsnprintf(buff, sizeof buff, fmt, va); + va_end(va); + + buff[1024] = 0; + + return VarcharAdd(str, buff); +} + + +const char *VarcharContents(Varchar *str) +{ + return str->data; +} + + +char *VarcharTransfer(Varchar *str) +{ + char *p = str->data; + + free(str); + + return p; +} + +void VarcharClear(Varchar *str) +{ + if (str->data) + { + free(str->data); + } + + str->len = 0; + str->size = 0; + str->data = NULL; +} + +void VarcharFree(Varchar *str) +{ + if (str->data) + { + free(str->data); + } + + free(str); +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/varchar.h b/src/varchar.h new file mode 100644 index 0000000..7dd15e3 --- /dev/null +++ b/src/varchar.h @@ -0,0 +1,79 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Simple dynamic variable length string. + +*/ + +#ifndef CASM_VARCHAR_H +#define CASM_VARCHAR_H + +typedef struct varchar Varchar; + +/* ---------------------------------------- INTERFACES +*/ + +/* Create a new string. Initial contents set to pointer if not NULL. +*/ +Varchar *VarcharCreate(const char *initial); + + +/* Add a character to a string. Returns the passed Varchar. +*/ +Varchar *VarcharAddChar(Varchar *str, int c); + + +/* Add a string to the string. Returns the passed Varchar. +*/ +Varchar *VarcharAdd(Varchar *str, const char *c); + + +/* Adds a formatted string to the string. The formatted string must be less + than 1024 character or it will be truncated. Returns the passed Varchar. +*/ +Varchar *VarcharPrintf(Varchar *str, const char *fmt, ...); + + +/* Get the contents of a string, keeping ownership of it +*/ +const char *VarcharContents(Varchar *str); + + +/* Transfer the contents of a string, so that the returned string can be + freed. The Varchar container is destroyed and should not be used again. +*/ +char *VarcharTransfer(Varchar *str); + + +/* Clear a Varchar back to an empty string. +*/ +void VarcharClear(Varchar *str); + + +/* Release the string and the container. +*/ +void VarcharFree(Varchar *str); + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/z80.c b/src/z80.c new file mode 100644 index 0000000..ad42795 --- /dev/null +++ b/src/z80.c @@ -0,0 +1,2458 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Z80 Assembler + +*/ +#include +#include +#include + +#include "global.h" +#include "expr.h" +#include "label.h" +#include "parse.h" +#include "cmd.h" +#include "codepage.h" +#include "varchar.h" + +#include "z80.h" + + +/* ---------------------------------------- TYPES AND GLOBALS +*/ +typedef enum +{ + A8, + B8, + C8, + D8, + E8, + H8, + L8, + F8, + IXH8, + IXL8, + IYH8, + IYL8, + I8, + R8, + AF16, + AF16_ALT, + BC16, + DE16, + HL16, + SP16, + IX16, + IY16, + BC_ADDRESS, + DE_ADDRESS, + HL_ADDRESS, + SP_ADDRESS, + IX_ADDRESS, + IY_ADDRESS, + IX_OFFSET, + IY_OFFSET, + C_PORT, + ADDRESS, + VALUE, + INVALID_REG +} RegisterMode; + + +typedef enum +{ + NZ_FLAG, + Z_FLAG, + NC_FLAG, + C_FLAG, + PO_FLAG, + PE_FLAG, + P_FLAG, + M_FLAG +} ProcessorFlag; + + +typedef enum +{ + IS_NORMAL_8_BIT = 0x001, + IS_SPECIAL_8_BIT = 0x002, + IS_16_BIT = 0x004, + IS_MEMORY = 0x008, + IS_INDEX_X = 0x010, + IS_INDEX_Y = 0x020, + IS_SP = 0x040, + IS_VALUE = 0x080, + IS_SPECIAL_16_BIT = 0x100, + IS_ALTERNATE = 0x200, + IS_IO_PORT = 0x400 +} RegisterType; + +#define IsNormal8Bit(t) ((t) & IS_NORMAL_8_BIT) +#define IsSpecial8Bit(t) ((t) & IS_SPECIAL_8_BIT) +#define Is16Bit(t) ((t) & IS_16_BIT) +#define IsMemory(t) ((t) & IS_MEMORY) +#define IsIndexX(t) ((t) & IS_INDEX_X) +#define IsIndexY(t) ((t) & IS_INDEX_Y) +#define IsIndex(t) (IsIndexX(t) || IsIndexY(t)) +#define IsSP(t) ((t) & IS_SP) +#define IsAddress(t) (((t) & IS_VALUE) && ((t) & IS_MEMORY)) +#define IsValue(t) ((t) & IS_VALUE) +#define IsSimpleValue(t) ((t) == IS_VALUE) +#define IsAlternate(t) ((t) & IS_ALTERNATE) + +#define SHIFT_IX 0xdd +#define SHIFT_IY 0xfd + +#define WriteShift(r) \ +do \ +{ \ + switch(r) \ + { \ + case IXH8: \ + case IXL8: \ + case IX16: \ + case IX_OFFSET: \ + case IX_ADDRESS: \ + PCWrite(SHIFT_IX); \ + break; \ + case IYH8: \ + case IYL8: \ + case IY16: \ + case IY_OFFSET: \ + case IY_ADDRESS: \ + PCWrite(SHIFT_IY); \ + break; \ + default: \ + break; \ + } \ +} while(0) + +#define WriteEitherShift(r1,r2) \ +do \ +{ \ + int h_handled = FALSE; \ + switch(r1) \ + { \ + case IXH8: \ + case IXL8: \ + case IX16: \ + case IX_OFFSET: \ + case IX_ADDRESS: \ + PCWrite(SHIFT_IX); \ + h_handled = TRUE; \ + break; \ + case IYH8: \ + case IYL8: \ + case IY16: \ + case IY_OFFSET: \ + case IY_ADDRESS: \ + PCWrite(SHIFT_IY); \ + h_handled = TRUE; \ + break; \ + default: \ + break; \ + } \ + if (!h_handled) \ + switch(r2) \ + { \ + case IXH8: \ + case IXL8: \ + case IX16: \ + case IX_OFFSET: \ + case IX_ADDRESS: \ + PCWrite(SHIFT_IX); \ + h_handled = TRUE; \ + break; \ + case IYH8: \ + case IYL8: \ + case IY16: \ + case IY_OFFSET: \ + case IY_ADDRESS: \ + PCWrite(SHIFT_IY); \ + h_handled = TRUE; \ + break; \ + default: \ + break; \ + } \ +} while(0) + +#define WriteOffset(r,o) \ +do \ +{ \ + switch(r) \ + { \ + case IX_OFFSET: \ + case IY_OFFSET: \ + PCWrite(o); \ + break; \ + default: \ + break; \ + } \ +} while(0) + +#define CheckRange(arg,num,min,max) \ +do \ +{ \ + if (IsFinalPass() && (num < min || num > max)) \ + { \ + snprintf(err, errsize, "%s: outside valid " \ + "range of %d - %d", arg, min, max); \ + return CMD_FAILED; \ + } \ +} while(0) + +#define CheckOffset(a,o) CheckRange(a,0,-128,127) + +static const int flag_bitmask[] = +{ + 0x00, /* NZ_FLAG */ + 0x01, /* Z_FLAG */ + 0x02, /* NC_FLAG */ + 0x03, /* C_FLAG */ + 0x04, /* PO_FLAG */ + 0x05, /* PE_FLAG */ + 0x06, /* P_FLAG */ + 0x07 /* M_FLAG */ +}; + + +static const char *flag_text[] = +{ + "NZ", /* NZ_FLAG */ + "Z", /* Z_FLAG */ + "NC", /* NC_FLAG */ + "C", /* C_FLAG */ + "PO", /* PO_FLAG */ + "PE", /* PE_FLAG */ + "P", /* P_FLAG */ + "M", /* M_FLAG */ + NULL +}; + + +static const char *register_mode_name[] = +{ + "A", + "B", + "C", + "D", + "E", + "H", + "L", + "F", + "IXH", + "IXL", + "IYH", + "IYL", + "I", + "R", + "AF", + "AF'", + "BC", + "DE", + "HL", + "SP", + "IX", + "IY", + "(BC)", + "(DE)", + "(HL)", + "(SP)", + "(IX)", + "(IY)", + "(IX+offset)", + "(IY+offset)", + "(C)", + "(address)", + "value", + 0 +}; + + +static const int register_bitmask[] = +{ + 0x7, /* A8 */ + 0x0, /* B8 */ + 0x1, /* C8 */ + 0x2, /* D8 */ + 0x3, /* E8 */ + 0x4, /* H8 */ + 0x5, /* L8 */ + 0x6, /* F8 */ + 0x4, /* IXH8 - In effect H */ + 0x5, /* IXL8 - In effect L */ + 0x4, /* IYH8 - In effect H */ + 0x5, /* IYL8 - In effect L */ + -1, /* I8 */ + -1, /* R8 */ + 0x3, /* AF16 */ + -1, /* AF16_ALT */ + 0x0, /* BC16 */ + 0x1, /* DE16 */ + 0x2, /* HL16 */ + 0x3, /* SP16 */ + 0x2, /* IX16 - In effect HL */ + 0x2, /* IY16 - In effect HL */ + 0x0, /* BC_ADDRESS */ + 0x0, /* DE_ADDRESS */ + 0x0, /* HL_ADDRESS */ + 0x0, /* SP_ADDRESS */ + 0x0, /* IX_ADDRESS */ + 0x0, /* IY_ADDRESS */ + 0x0, /* IX_OFFSET */ + 0x0, /* IY_OFFSET */ + 0x0, /* C_PORT */ + 0x0, /* ADDRESS */ + 0x0, /* VALUE */ +}; + + +typedef struct +{ + RegisterMode mode; + int quote; + int starts_with; + int take_offset; + int take_value; + const char *ident; + RegisterType type; +} RegisterModeTable; + + +static RegisterModeTable register_mode_table[] = +{ + { + A8, + 0, + FALSE, + FALSE, + FALSE, + "A", + IS_NORMAL_8_BIT + }, + { + B8, + 0, + FALSE, + FALSE, + FALSE, + "B", + IS_NORMAL_8_BIT + }, + { + C8, + 0, + FALSE, + FALSE, + FALSE, + "C", + IS_NORMAL_8_BIT + }, + { + D8, + 0, + FALSE, + FALSE, + FALSE, + "D", + IS_NORMAL_8_BIT + }, + { + E8, + 0, + FALSE, + FALSE, + FALSE, + "E", + IS_NORMAL_8_BIT + }, + { + H8, + 0, + FALSE, + FALSE, + FALSE, + "H", + IS_NORMAL_8_BIT + }, + { + L8, + 0, + FALSE, + FALSE, + FALSE, + "L", + IS_NORMAL_8_BIT + }, + { + F8, + 0, + FALSE, + FALSE, + FALSE, + "F", + IS_SPECIAL_8_BIT + }, + { + IXL8, + 0, + FALSE, + FALSE, + FALSE, + "IXL", + IS_NORMAL_8_BIT|IS_INDEX_X + }, + { + IXH8, + 0, + FALSE, + FALSE, + FALSE, + "IXH", + IS_NORMAL_8_BIT|IS_INDEX_X + }, + { + IYL8, + 0, + FALSE, + FALSE, + FALSE, + "IYL", + IS_NORMAL_8_BIT|IS_INDEX_Y + }, + { + IYH8, + 0, + FALSE, + FALSE, + FALSE, + "IYH", + IS_NORMAL_8_BIT|IS_INDEX_Y + }, + { + I8, + 0, + FALSE, + FALSE, + FALSE, + "I", + IS_SPECIAL_8_BIT + }, + { + R8, + 0, + FALSE, + FALSE, + FALSE, + "R", + IS_SPECIAL_8_BIT + }, + + { + AF16, + 0, + FALSE, + FALSE, + FALSE, + "AF", + IS_SPECIAL_16_BIT + }, + { + AF16_ALT, + 0, + FALSE, + FALSE, + FALSE, + "AF'", + IS_SPECIAL_16_BIT|IS_ALTERNATE + }, + { + BC16, + 0, + FALSE, + FALSE, + FALSE, + "BC", + IS_16_BIT + }, + { + DE16, + 0, + FALSE, + FALSE, + FALSE, + "DE", + IS_16_BIT + }, + { + HL16, + 0, + FALSE, + FALSE, + FALSE, + "HL", + IS_16_BIT + }, + { + IX16, + 0, + FALSE, + FALSE, + FALSE, + "IX", + IS_16_BIT|IS_INDEX_X + }, + { + IY16, + 0, + FALSE, + FALSE, + FALSE, + "IY", + IS_16_BIT|IS_INDEX_Y + }, + { + SP16, + 0, + FALSE, + FALSE, + FALSE, + "SP", + IS_16_BIT|IS_SP + }, + + { + BC_ADDRESS, + '(', + FALSE, + FALSE, + FALSE, + "BC", + IS_16_BIT|IS_MEMORY + }, + { + DE_ADDRESS, + '(', + FALSE, + FALSE, + FALSE, + "DE", + IS_16_BIT|IS_MEMORY + }, + { + HL_ADDRESS, + '(', + FALSE, + FALSE, + FALSE, + "HL", + IS_16_BIT|IS_MEMORY + }, + { + IX_ADDRESS, + '(', + FALSE, + FALSE, + FALSE, + "IX", + IS_16_BIT|IS_MEMORY|IS_INDEX_X + }, + { + IY_ADDRESS, + '(', + FALSE, + FALSE, + FALSE, + "IY", + IS_16_BIT|IS_MEMORY|IS_INDEX_Y + }, + { + IX_OFFSET, + '(', + TRUE, + TRUE, + FALSE, + "IX", + IS_16_BIT|IS_MEMORY|IS_INDEX_X + }, + { + IY_OFFSET, + '(', + TRUE, + TRUE, + FALSE, + "IY", + IS_16_BIT|IS_MEMORY|IS_INDEX_Y + }, + { + SP_ADDRESS, + '(', + FALSE, + FALSE, + FALSE, + "SP", + IS_SPECIAL_16_BIT|IS_MEMORY|IS_SP + }, + { + C_PORT, + '(', + FALSE, + FALSE, + FALSE, + "C", + IS_IO_PORT + }, + + /* These cheat -- basically anything that doesn't match until here is either + an address or a value. No distinction is made between 8/16 bit values. + */ + { + ADDRESS, + '(', + TRUE, + FALSE, + TRUE, + "", + IS_VALUE|IS_MEMORY + }, + { + VALUE, + 0, + TRUE, + FALSE, + TRUE, + "", + IS_VALUE + }, + + {0} +}; + + +typedef enum +{ + WRITE_BYTE_LHS = -1, + WRITE_WORD_LHS = -2, + WRITE_BYTE_RHS = -3, + WRITE_WORD_RHS = -4 +} StreamCodes; + + +typedef struct +{ + RegisterMode lhs; + RegisterMode rhs; + int code[10]; +} RegisterPairCodes; + +#define NUM_REGISTER_CODES(a) ((sizeof a)/(sizeof a[1])) + +/* ---------------------------------------- PRIVATE FUNCTIONS +*/ +static int CalcRegisterMode(const char *arg, int quote, + RegisterMode *mode, + RegisterType *type, + int *offset, + char *err, size_t errsize) +{ + int f; + + if (IsNullOrEmpty(arg)) + { + snprintf(err, errsize, "empty argument supplied"); + return FALSE; + } + + for(f = 0; register_mode_table[f].ident; f++) + { + RegisterModeTable *t = register_mode_table + f; + + if (quote == t->quote) + { + int match; + + if (t->starts_with) + { + match = CompareStart(arg, t->ident); + } + else + { + match = CompareString(arg, t->ident); + } + + if (match) + { + *mode = t->mode; + *type = t->type; + *offset = 0; + + if (t->take_offset || t->take_value) + { + size_t l = strlen(t->ident); + + if (!ExprEval(arg + l, offset)) + { + snprintf(err, errsize, "%s: expression error: %s", + arg, ExprError()); + return FALSE; + } + } + + if (t->take_offset) + { + if (IsFinalPass() && (*offset < -128 || *offset > 127)) + { + snprintf(err, errsize, "%s: outside valid range " + "for offset", arg); + return FALSE; + } + } + + return TRUE; + } + } + } + + snprintf(err, errsize, "%s: couldn't calculate register/addressing mode", + arg); + + return FALSE; +} + + +static int CalcFlagMode(const char *arg, ProcessorFlag *flag, int *mask, + char *err, size_t errsize) +{ + int f; + + for(f = 0; flag_text[f]; f++) + { + if (CompareString(arg, flag_text[f])) + { + *flag = f; + *mask = flag_bitmask[f]; + return TRUE; + } + } + + snprintf(err, errsize, "%s: unknown flag", arg); + + return FALSE; +} + + +static int WriteRegisterPairModes(const RegisterPairCodes *codes, size_t count, + RegisterMode lhs, RegisterMode rhs, + int val_lhs, int val_rhs, + char *err, size_t errsize) +{ + size_t f; + + for(f = 0; f < count; f++) + { + if (codes[f].lhs == lhs && codes[f].rhs == rhs) + { + int r; + + for(r = 0; codes[f].code[r]; r++) + { + switch(codes[f].code[r]) + { + case WRITE_BYTE_LHS: + PCWrite(val_lhs); + break; + + case WRITE_WORD_LHS: + PCWriteWord(val_lhs); + break; + + case WRITE_BYTE_RHS: + PCWrite(val_rhs); + break; + + case WRITE_WORD_RHS: + PCWriteWord(val_rhs); + break; + + default: + PCWrite(codes[f].code[r]); + break; + } + } + return TRUE; + } + } + + snprintf(err, errsize, "LD: no code generation for register pair %s,%s", + register_mode_name[lhs], register_mode_name[rhs]); + + return FALSE; +} + + +/* Assume accumulator if only one argument +*/ +CommandStatus AssumeAccumulator(int argc, char *argv[], int quoted[], + char *err, size_t errsize, + RegisterMode *r1, RegisterMode *r2, + RegisterType *t1, RegisterType *t2, + int *off1, int *off2) +{ + CMD_ARGC_CHECK(2); + + if (argc == 2) + { + CalcRegisterMode("A", 0, r1, t1, off1, err, errsize); + + if (!CalcRegisterMode(argv[1], quoted[1], r2, t2, off2, + err, errsize)) + { + return CMD_FAILED; + } + } + else + { + if (!CalcRegisterMode(argv[1], quoted[1], r1, t1, off1, err, errsize)) + { + return CMD_FAILED; + } + + if (!CalcRegisterMode(argv[2], quoted[2], r2, t2, off2, err, errsize)) + { + return CMD_FAILED; + } + } + + return CMD_OK; +} + + +/* Returns true if the passed register is any of the passed ones. The list of + registers is terminated with INVALID_REG +*/ +static int IsAnyOf(RegisterMode reg, ...) +{ + va_list ap; + RegisterMode m; + + va_start(ap, reg); + + m = va_arg(ap, RegisterMode); + + while(m != INVALID_REG) + { + if (reg == m) + { + return TRUE; + } + + m = va_arg(ap, RegisterMode); + } + + return FALSE; +} + + +static CommandStatus IllegalArgs(int argc, char *argv[], int quoted[], + char *err, size_t errsize) +{ + Varchar *str; + int f; + + str = VarcharCreate(NULL); + + switch(argc) + { + case 0: + VarcharPrintf(str, "no command/arguments"); + break; + + case 1: + VarcharPrintf(str, "%s: no arguments", argv[0]); + break; + + case 2: + VarcharPrintf(str, "%s: illegal argument", argv[0]); + break; + + default: + VarcharPrintf(str, "%s: illegal arguments", argv[0]); + break; + } + + for(f = 1; f < argc; f++) + { + if (f == 1) + { + VarcharAdd(str, " "); + } + else + { + VarcharAdd(str, ", "); + } + + if (quoted[f] && quoted[f] == '(') + { + VarcharPrintf(str, "(%s)", argv[f]); + } + else if (quoted[f]) + { + VarcharPrintf(str, "%c%s%c", quoted[f], argv[f], quoted[f]); + } + else + { + VarcharAdd(str, argv[f]); + } + } + + snprintf(err, errsize, "%s", VarcharContents(str)); + VarcharFree(str); + + return CMD_FAILED; +} + + +/* ---------------------------------------- COMMAND HANDLERS +*/ +CommandStatus LD(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, BC_ADDRESS, {0x0a}, + A8, DE_ADDRESS, {0x1a}, + A8, ADDRESS, {0x3a, WRITE_WORD_RHS}, + BC_ADDRESS, A8, {0x02}, + DE_ADDRESS, A8, {0x12}, + ADDRESS, A8, {0x32, WRITE_WORD_RHS}, + A8, I8, {0xed, 0x57}, + A8, R8, {0xed, 0x5f}, + I8, A8, {0xed, 0x47}, + R8, A8, {0xed, 0x4f} + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + CMD_ARGC_CHECK(3); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) + { + return CMD_FAILED; + } + + if ((IsIndexX(t1) && IsIndexY(t2)) || + (IsIndexY(t1) && IsIndexX(t2))) + { + snprintf(err, errsize, + "%s: can't have mixed IX/IY registers", argv[0]); + + return CMD_FAILED; + } + + /* LD r,r' + */ + if (IsNormal8Bit(t1) && IsNormal8Bit(t2)) + { + CommandStatus s = CMD_OK; + + if ((IsIndex(t1) || IsIndex(t2)) && + (IsAnyOf(r1, H8, L8, INVALID_REG) || + IsAnyOf(r2, H8, L8, INVALID_REG))) + { + snprintf(err, errsize, "%s: H/L will actually be the index " + "register low/high register", argv[0]); + s = CMD_OK_WARNING; + } + + WriteEitherShift(r1,r2); + PCWrite(0x40 | register_bitmask[r1] << 3 | register_bitmask[r2]); + + return s; + } + + + /* LD r,n + */ + if (IsNormal8Bit(t1) && IsSimpleValue(t2)) + { + WriteShift(r1); + PCWrite(register_bitmask[r1] << 3 | 0x6); + PCWrite(off2); + + return CMD_OK; + } + + + /* LD r,(HL)/(IX+d)/(IY+d) + */ + if ((IsNormal8Bit(t1) && !IsIndex(t1)) && + (r2 == HL_ADDRESS || IsIndex(t2))) + { + WriteShift(r2); + PCWrite(0x46 | register_bitmask[r1] << 3); + WriteOffset(r2, off2); + + return CMD_OK; + } + + + /* LD (HL)/(IX+d)/(IY+d),r + */ + if ((IsNormal8Bit(t2) && !IsIndex(t2)) && + (r1 == HL_ADDRESS || IsIndex(t1))) + { + WriteShift(r1); + PCWrite(0x70 | register_bitmask[r2]); + WriteOffset(r1, off1); + + return CMD_OK; + } + + + /* LD (HL)/(IX+d)/(IY+d),n + */ + if ((r1 == HL_ADDRESS || r1 == IX_OFFSET || r1 == IY_OFFSET) && r2 == VALUE) + { + WriteShift(r1); + PCWrite(0x36); + WriteOffset(r1, off1); + PCWrite(off2); + return CMD_OK; + } + + + /* LD rr,nn + */ + if (Is16Bit(t1) && !IsMemory(t1) && r2 == VALUE) + { + WriteShift(r1); + PCWrite(register_bitmask[r1] << 4 | 0x01); + PCWriteWord(off2); + return CMD_OK; + } + + + /* LD HL/IX/IY,(nn) + */ + if ((r1 == HL16 || r1 == IX16 || r1 == IY16) && r2 == ADDRESS) + { + WriteShift(r1); + PCWrite(0x2a); + PCWriteWord(off2); + return CMD_OK; + } + + + /* LD rr,(nn) + */ + if (Is16Bit(t1) && !IsMemory(t1) && r2 == ADDRESS) + { + PCWrite(0xed); + PCWrite(0x4b | register_bitmask[r1] << 4); + PCWriteWord(off2); + return CMD_OK; + } + + + /* LD (nn),HL/IX/IY + */ + if ((r2 == HL16 || r2 == IX16 || r2 == IY16) && r1 == ADDRESS) + { + WriteShift(r2); + PCWrite(0x22); + PCWriteWord(off1); + return CMD_OK; + } + + + /* LD (nn),rr + */ + if (Is16Bit(t2) && !IsMemory(t2) && r1 == ADDRESS) + { + PCWrite(0xed); + PCWrite(0x43 | register_bitmask[r2] << 4); + PCWriteWord(off1); + return CMD_OK; + } + + + /* LD SP,HL/IX/IY + */ + if ((r2 == HL16 || r2 == IX16 || r2 == IY16) && r1 == SP16) + { + WriteShift(r2); + PCWrite(0xf9); + return CMD_OK; + } + + + /* Custom opcode generation using the codes table + */ + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus PUSH(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1; + RegisterType t1; + int off1; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + /* PUSH HL/IX/IY + */ + if (r1 == HL16 || r1 == IX16 || r1 == IY16) + { + WriteShift(r1); + PCWrite(0xe5); + return CMD_OK; + } + + + /* PUSH rr + */ + if (r1 == AF16 || r1 == BC16 || r1 == DE16) + { + PCWrite(0xc5 | register_bitmask[r1] << 4); + return CMD_OK; + } + + snprintf(err, errsize, "%s: invalid argument %s", argv[0], argv[1]); + + return CMD_OK; +} + + +CommandStatus POP(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1; + RegisterType t1; + int off1; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + /* POP HL/IX/IY + */ + if (r1 == HL16 || r1 == IX16 || r1 == IY16) + { + WriteShift(r1); + PCWrite(0xe1); + return CMD_OK; + } + + + /* POP rr + */ + if (r1 == AF16 || r1 == BC16 || r1 == DE16) + { + PCWrite(0xc1 | register_bitmask[r1] << 4); + return CMD_OK; + } + + snprintf(err, errsize, "%s: invalid argument %s", argv[0], argv[1]); + + return CMD_OK; +} + + +CommandStatus EX(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + DE16, HL16, {0xeb}, + AF16, AF16_ALT, {0x08}, + SP_ADDRESS, HL16, {0xe3}, + SP_ADDRESS, IX16, {SHIFT_IX, 0xe3}, + SP_ADDRESS, IY16, {SHIFT_IY, 0xe3} + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + CMD_ARGC_CHECK(3); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) + { + return CMD_FAILED; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus ADD(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xc6, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0x86}, + A8, IX_OFFSET, {SHIFT_IX, 0x86, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0x86, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* ADD A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0x80 | register_bitmask[r2]); + return CMD_OK; + } + + /* ADD HL,rr + */ + if (r1 == HL16 && IsAnyOf(r2, BC16, DE16, HL16, SP16, INVALID_REG)) + { + PCWrite(0x09 | register_bitmask[r2] << 4); + return CMD_OK; + } + + /* ADD IX,rr + */ + if (r1 == IX16 && IsAnyOf(r2, BC16, DE16, IX16, SP16, INVALID_REG)) + { + PCWrite(SHIFT_IX); + PCWrite(0x09 | register_bitmask[r2] << 4); + return CMD_OK; + } + + /* ADD IY,rr + */ + if (r1 == IY16 && IsAnyOf(r2, BC16, DE16, IY16, SP16, INVALID_REG)) + { + PCWrite(SHIFT_IY); + PCWrite(0x09 | register_bitmask[r2] << 4); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus ADC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xce, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0x8e}, + A8, IX_OFFSET, {SHIFT_IX, 0x8e, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0x8e, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* ADC A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0x88 | register_bitmask[r2]); + return CMD_OK; + } + + /* ADC HL,rr + */ + if (r1 == HL16 && IsAnyOf(r2, BC16, DE16, HL16, SP16, INVALID_REG)) + { + PCWrite(0xed); + PCWrite(0x4a | register_bitmask[r2] << 4); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus SUB(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xd6, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0x96}, + A8, IX_OFFSET, {SHIFT_IX, 0x96, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0x96, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* SUB A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0x90 | register_bitmask[r2]); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus SBC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xde, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0x9e}, + A8, IX_OFFSET, {SHIFT_IX, 0x9e, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0x9e, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* SBC A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0x98 | register_bitmask[r2]); + return CMD_OK; + } + + /* SBC HL,rr + */ + if (r1 == HL16 && IsAnyOf(r2, BC16, DE16, HL16, SP16, INVALID_REG)) + { + PCWrite(0xed); + PCWrite(0x42 | register_bitmask[r2] << 4); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus AND(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xe6, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0xa6}, + A8, IX_OFFSET, {SHIFT_IX, 0xa6, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0xa6, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* AND A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0xa0 | register_bitmask[r2]); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus OR(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xf6, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0xb6}, + A8, IX_OFFSET, {SHIFT_IX, 0xb6, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0xb6, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* OR A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0xb0 | register_bitmask[r2]); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus XOR(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xee, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0xae}, + A8, IX_OFFSET, {SHIFT_IX, 0xae, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0xae, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* XOR A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0xa8 | register_bitmask[r2]); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus CP(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static RegisterPairCodes codes[] = + { + A8, VALUE, {0xfe, WRITE_BYTE_RHS}, + A8, HL_ADDRESS, {0xbe}, + A8, IX_OFFSET, {SHIFT_IX, 0xbe, WRITE_BYTE_RHS}, + A8, IY_OFFSET, {SHIFT_IY, 0xbe, WRITE_BYTE_RHS}, + }; + + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + + if (AssumeAccumulator(argc, argv, quoted, err, errsize, + &r1, &r2, &t1, &t2, &off1, &off2) != CMD_OK) + { + return CMD_FAILED; + } + + /* CP A,r + */ + if (r1 == A8 && IsNormal8Bit(t2)) + { + WriteShift(r2); + PCWrite(0xb8 | register_bitmask[r2]); + return CMD_OK; + } + + if (!WriteRegisterPairModes(codes, NUM_REGISTER_CODES(codes), + r1, r2, off1, off2, err, errsize)) + { + return CMD_FAILED; + } + + return CMD_OK; +} + + +CommandStatus IM(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static int im[3] = {0x46, 0x56, 0x5e}; + RegisterMode r1; + RegisterType t1; + int off1; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + if (r1 != VALUE || (off1 < 0 || off1 > 2)) + { + snprintf(err, errsize, "%s: invalid argument %s", argv[0], argv[1]); + return CMD_FAILED; + } + + PCWrite(0xed); + PCWrite(im[off1]); + + return CMD_OK; +} + + +CommandStatus INC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1; + RegisterType t1; + int off1; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + /* INC r + */ + if (IsNormal8Bit(t1)) + { + WriteShift(r1); + PCWrite(0x04 | register_bitmask[r1] << 3); + return CMD_OK; + } + + /* INC (HL)/(IX+d)/(IY+d) + */ + if (r1 == HL_ADDRESS || r1 == IX_OFFSET || r1 == IY_OFFSET) + { + WriteShift(r1); + PCWrite(0x34); + WriteOffset(r1, off1); + return CMD_OK; + } + + /* INC rr + */ + if (Is16Bit(t1) && !IsMemory(t1)) + { + WriteShift(r1); + PCWrite(0x03 | register_bitmask[r1] << 4); + return CMD_OK; + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus DEC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1; + RegisterType t1; + int off1; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + /* DEC r + */ + if (IsNormal8Bit(t1)) + { + WriteShift(r1); + PCWrite(0x05 | register_bitmask[r1] << 3); + return CMD_OK; + } + + /* DEC (HL)/(IX+d)/(IY+d) + */ + if (r1 == HL_ADDRESS || r1 == IX_OFFSET || r1 == IY_OFFSET) + { + WriteShift(r1); + PCWrite(0x35); + WriteOffset(r1, off1); + return CMD_OK; + } + + /* DEC rr + */ + if (Is16Bit(t1) && !IsMemory(t1)) + { + WriteShift(r1); + PCWrite(0x0b | register_bitmask[r1] << 4); + return CMD_OK; + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus RLC_RL_RRC_RR_ETC(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1; + RegisterType t1; + int off1; + int opcode_mask; + + CMD_ARGC_CHECK(2); + + if (CompareString(argv[0], "RLC")) + { + opcode_mask = 0x00; + } + else if (CompareString(argv[0], "RL")) + { + opcode_mask = 0x10; + } + else if (CompareString(argv[0], "RRC")) + { + opcode_mask = 0x08; + } + else if (CompareString(argv[0], "RR")) + { + opcode_mask = 0x18; + } + else if (CompareString(argv[0], "SLA")) + { + opcode_mask = 0x20; + } + else if (CompareString(argv[0], "SRA")) + { + opcode_mask = 0x28; + } + else if (CompareString(argv[0], "SRL")) + { + opcode_mask = 0x38; + } + else if (CompareString(argv[0], "SLL")) + { + opcode_mask = 0x30; + } + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + /* Normal opcodes + */ + if (argc == 2) + { + /* OP r + */ + if (IsNormal8Bit(t1) && !IsIndex(t1)) + { + PCWrite(0xcb); + PCWrite(opcode_mask | register_bitmask[r1]); + return CMD_OK; + } + + /* OP (HL)/(IX+d)/(IY+d) + */ + if (r1 == HL_ADDRESS || r1 == IX_OFFSET || r1 == IY_OFFSET) + { + WriteShift(r1); + PCWrite(0xcb); + WriteOffset(r1, off1); + PCWrite(opcode_mask | 0x06); + return CMD_OK; + } + } + + /* Undocumented opcodes + */ + if (argc == 3) + { + RegisterMode r2; + RegisterType t2; + int off2; + + if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, + &off2, err, errsize)) + { + return CMD_FAILED; + } + + /* OP (IX+d)/(IY+d),r + */ + if ((r1 == IX_OFFSET || r1 == IY_OFFSET) && (r2 >= A8 && r2 <= L8)) + { + WriteShift(r1); + PCWrite(0xcb); + WriteOffset(r1, off1); + PCWrite(opcode_mask | register_bitmask[r2]); + return CMD_OK; + } + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus BIT_SET_RES(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + int opcode_mask; + + CMD_ARGC_CHECK(3); + + if (CompareString(argv[0], "BIT")) + { + opcode_mask = 0x40; + } + else if (CompareString(argv[0], "SET")) + { + opcode_mask = 0xc0; + } + else if (CompareString(argv[0], "RES")) + { + opcode_mask = 0x80; + } + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) + { + return CMD_FAILED; + } + + if (r1 != VALUE || (off1 < 0 || off1 > 7)) + { + snprintf(err, errsize, "%s: illegal value %s for bit number", + argv[0], argv[1]); + return CMD_FAILED; + } + + /* Normal opcodes + */ + if (argc == 3) + { + /* OP b,r + */ + if (IsNormal8Bit(t2) && !IsIndex(t2)) + { + PCWrite(0xcb); + PCWrite(opcode_mask | off1 << 3 | register_bitmask[r2]); + return CMD_OK; + } + + /* OP b,(HL)/(IX+d)/(IY+d) + */ + if (r2 == HL_ADDRESS || r2 == IX_OFFSET || r2 == IY_OFFSET) + { + WriteShift(r2); + PCWrite(0xcb); + WriteOffset(r2, off2); + PCWrite(opcode_mask | off1 << 3 | 0x06); + return CMD_OK; + } + } + + /* Undocumented opcodes + */ + if (argc > 3 && (CompareString(argv[0], "SET") || + CompareString(argv[0], "RES"))) + { + RegisterMode r3; + RegisterType t3; + int off3; + + if (!CalcRegisterMode(argv[3], quoted[3], &r3, &t3, + &off3, err, errsize)) + { + return CMD_FAILED; + } + + /* OP b,(IX+d)/(IY+d),r + */ + if ((r2 == IX_OFFSET || r2 == IY_OFFSET) && (r3 >= A8 && r3 <= L8)) + { + WriteShift(r2); + PCWrite(0xcb); + WriteOffset(r2, off2); + PCWrite(opcode_mask | off1 << 3 | register_bitmask[r3]); + return CMD_OK; + } + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus JP(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + if (argc == 2) + { + RegisterMode mode; + RegisterType type; + int val; + + if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE) + { + PCWrite(0xc3); + PCWriteWord(val); + return CMD_OK; + } + + if (mode == HL_ADDRESS || mode == IX_ADDRESS || mode == IY_ADDRESS) + { + WriteShift(mode); + PCWrite(0xe9); + return CMD_OK; + } + } + else if (argc == 3) + { + RegisterMode mode; + RegisterType type; + int val; + ProcessorFlag flag; + int mask; + + if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize) || + !CalcRegisterMode(argv[2], quoted[2], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE) + { + PCWrite(0xc2 | mask << 3); + PCWriteWord(val); + return CMD_OK; + } + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus JR(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + if (argc == 2) + { + RegisterMode mode; + RegisterType type; + int val; + + if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE) + { + int rel; + + rel = val - ((PC() + 2) % 0x10000); + + CheckOffset(argv[1], rel); + + PCWrite(0x18); + PCWrite(rel); + return CMD_OK; + } + } + else if (argc == 3) + { + RegisterMode mode; + RegisterType type; + int val; + ProcessorFlag flag; + int mask; + + if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize) || + !CalcRegisterMode(argv[2], quoted[2], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE && (flag >= NZ_FLAG && flag <= C_FLAG)) + { + int rel; + + rel = val - ((PC() + 2) % 0x10000); + + CheckOffset(argv[2], rel); + + PCWrite(0x20 | mask << 3); + PCWrite(rel); + return CMD_OK; + } + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus DJNZ(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode mode; + RegisterType type; + int val; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE) + { + int rel; + + rel = val - ((PC() + 2) % 0x10000); + + CheckOffset(argv[1], rel); + + PCWrite(0x10); + PCWrite(rel); + + return CMD_OK; + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus CALL(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + if (argc == 2) + { + RegisterMode mode; + RegisterType type; + int val; + + if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE) + { + PCWrite(0xcd); + PCWriteWord(val); + return CMD_OK; + } + } + else if (argc == 3) + { + RegisterMode mode; + RegisterType type; + int val; + ProcessorFlag flag; + int mask; + + if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize) || + !CalcRegisterMode(argv[2], quoted[2], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE) + { + PCWrite(0xc4 | mask << 3); + PCWriteWord(val); + return CMD_OK; + } + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus RET(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + if (argc == 1) + { + PCWrite(0xc9); + return CMD_OK; + } + else if (argc == 2) + { + ProcessorFlag flag; + int mask; + + if (!CalcFlagMode(argv[1], &flag, &mask, err, errsize)) + { + return CMD_FAILED; + } + + PCWrite(0xc0 | mask << 3); + return CMD_OK; + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus RST(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + static struct + { + int dec; + int hex; + } rst_arg[] = + { + {0, 0x00}, + {8, 0x08}, + {10, 0x10}, + {18, 0x18}, + {20, 0x20}, + {28, 0x28}, + {30, 0x30}, + {38, 0x38}, + {-1} + }; + + RegisterMode mode; + RegisterType type; + int val; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &mode, &type, &val, + err, errsize)) + { + return CMD_FAILED; + } + + if (mode == VALUE) + { + int f; + int mask = -1; + + for(f = 0; rst_arg[f].dec != -1; f++) + { + if (rst_arg[f].dec == val || rst_arg[f].hex == val) + { + mask = f << 3; + } + } + + if (mask != -1) + { + PCWrite(0xc7 | mask); + return CMD_OK; + } + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus IN(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + int opcode_mask; + + CMD_ARGC_CHECK(2); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + if (r1 == C_PORT && argc == 2) + { + PCWrite(0xed); + PCWrite(0x70); + return CMD_OK; + } + + CMD_ARGC_CHECK(3); + + if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) + { + return CMD_FAILED; + } + + if (r1 == A8 && r2 == ADDRESS) + { + CheckRange(argv[2], off2, 0, 255); + PCWrite(0xdb); + PCWrite(off2); + return CMD_OK; + } + + if (!IsIndex(t1) && (IsNormal8Bit(t1) || r1 == F8) && r2 == C_PORT) + { + PCWrite(0xed); + PCWrite(0x40 | register_bitmask[r1] << 3); + return CMD_OK; + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +CommandStatus OUT(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + RegisterMode r1, r2; + RegisterType t1, t2; + int off1, off2; + int opcode_mask; + + CMD_ARGC_CHECK(3); + + if (!CalcRegisterMode(argv[1], quoted[1], &r1, &t1, &off1, err, errsize)) + { + return CMD_FAILED; + } + + if (!CalcRegisterMode(argv[2], quoted[2], &r2, &t2, &off2, err, errsize)) + { + return CMD_FAILED; + } + + if (r1 == ADDRESS && r2 == A8) + { + CheckRange(argv[1], off1, 0, 255); + PCWrite(0xd3); + PCWrite(off1); + return CMD_OK; + } + + if (r1 == C_PORT && !IsIndex(t2) && (IsNormal8Bit(t2) || r2 == F8)) + { + PCWrite(0xed); + PCWrite(0x41 | register_bitmask[r2] << 3); + return CMD_OK; + } + + if (r1 == C_PORT && r2 == VALUE) + { + PCWrite(0xed); + PCWrite(0x71); + return CMD_OK; + } + + return IllegalArgs(argc, argv, quoted, err, errsize); +} + + +/* ---------------------------------------- OPCODE TABLES +*/ +typedef struct +{ + const char *op; + int code[3]; /* Zero ends, but code[0] always used. */ +} OpcodeTable; + +typedef struct +{ + const char *op; + Command cmd; +} HandlerTable; + + +static const HandlerTable handler_table[] = +{ + {"LD", LD}, + {"PUSH", PUSH}, + {"POP", POP}, + {"EX", EX}, + {"ADD", ADD}, + {"ADC", ADC}, + {"SUB", SUB}, + {"SBC", SBC}, + {"AND", AND}, + {"OR", OR}, + {"XOR", XOR}, + {"EOR", XOR}, + {"CP", CP}, + {"INC", INC}, + {"DEC", DEC}, + {"IM", IM}, + {"RLC", RLC_RL_RRC_RR_ETC}, + {"RL", RLC_RL_RRC_RR_ETC}, + {"RRC", RLC_RL_RRC_RR_ETC}, + {"RR", RLC_RL_RRC_RR_ETC}, + {"SLA", RLC_RL_RRC_RR_ETC}, + {"SRA", RLC_RL_RRC_RR_ETC}, + {"SRL", RLC_RL_RRC_RR_ETC}, + {"SLL", RLC_RL_RRC_RR_ETC}, + {"BIT", BIT_SET_RES}, + {"RES", BIT_SET_RES}, + {"SET", BIT_SET_RES}, + {"JP", JP}, + {"JR", JR}, + {"DJNZ", DJNZ}, + {"CALL", CALL}, + {"RET", RET}, + {"RST", RST}, + {"IN", IN}, + {"OUT", OUT}, + {NULL} +}; + + +static const OpcodeTable implied_opcodes[] = +{ + {"NOP", {0x00}}, + {"DI", {0xf3}}, + {"EI", {0xfb}}, + {"HALT", {0x76}}, + {"HLT", {0x76}}, + {"EXX", {0xd9}}, + {"DAA", {0x27}}, + {"CPL", {0x2f}}, + {"SCF", {0x37}}, + {"CCF", {0x3f}}, + {"NEG", {0xed, 0x44}}, + {"RLCA", {0x07}}, + {"RRCA", {0x0f}}, + {"RLA", {0x17}}, + {"RRA", {0x1f}}, + {"CPI", {0xed, 0xa1}}, + {"CPIR", {0xed, 0xb1}}, + {"CPD", {0xed, 0xa9}}, + {"CPDR", {0xed, 0xb9}}, + {"INI", {0xed, 0xa2}}, + {"INIR", {0xed, 0xb2}}, + {"IND", {0xed, 0xaa}}, + {"INDR", {0xed, 0xba}}, + {"OUTI", {0xed, 0xa3}}, + {"OTIR", {0xed, 0xb3}}, + {"OUTD", {0xed, 0xab}}, + {"OTDR", {0xed, 0xbb}}, + {"LDI", {0xed, 0xa0}}, + {"LDIR", {0xed, 0xb0}}, + {"LDD", {0xed, 0xa8}}, + {"LDDR", {0xed, 0xb8}}, + {"RRD", {0xed, 0x67}}, + {"RLD", {0xed, 0x6f}}, + {"RETI", {0xed, 0x4d}}, + {"RETN", {0xed, 0x45}}, + {NULL} +}; + + +/* ---------------------------------------- PUBLIC INTERFACES +*/ + +void Init_Z80(void) +{ +} + + +const ValueTable *Options_Z80(void) +{ + return NULL; +} + + +CommandStatus SetOption_Z80(int opt, int argc, char *argv[], int quoted[], + char *err, size_t errsize) +{ + return CMD_OK; +} + + +CommandStatus Handler_Z80(const char *label, int argc, char *argv[], + int quoted[], char *err, size_t errsize) +{ + int f; + + /* Check for simple (implied addressing) opcodes + */ + for(f = 0; implied_opcodes[f].op; f++) + { + if (CompareString(argv[0], implied_opcodes[f].op)) + { + int r; + + PCWrite(implied_opcodes[f].code[0]); + + for(r = 1; implied_opcodes[f].code[r]; r++) + { + PCWrite(implied_opcodes[f].code[r]); + } + + return CMD_OK; + } + } + + /* Check for other opcodes + */ + for(f = 0; handler_table[f].op; f++) + { + if (CompareString(argv[0], handler_table[f].op)) + { + return handler_table[f].cmd(label, argc, argv, + quoted, err, errsize);; + } + } + + return CMD_NOT_KNOWN; +} + + +/* +vim: ai sw=4 ts=8 expandtab +*/ diff --git a/src/z80.h b/src/z80.h new file mode 100644 index 0000000..5e99e95 --- /dev/null +++ b/src/z80.h @@ -0,0 +1,46 @@ +/* + + casm - Simple, portable assembler + + Copyright (C) 2003-2015 Ian Cowburn (ianc@noddybox.demon.co.uk) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + + ------------------------------------------------------------------------- + + Z80 Assembler + +*/ + +#ifndef CASM_Z80_H +#define CASM_Z80_H + +#include "parse.h" + +void Init_Z80(void); + +const ValueTable *Options_Z80(void); + +CommandStatus SetOption_Z80(int opt, int argc, char *argv[], int quoted[], + char *err, size_t errsize); + +CommandStatus Handler_Z80(const char *label, int argc, char *argv[], + int quoted[], char *error, size_t error_size); + + +#endif + +/* +vim: ai sw=4 ts=8 expandtab +*/ -- cgit v1.2.3