aboutsummaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorIan C <ianc@noddybox.co.uk>2016-03-07 15:00:21 +0000
committerIan C <ianc@noddybox.co.uk>2016-03-07 15:00:21 +0000
commit77e8708934c5c792b1435fa11dfe3c0a6f636a8c (patch)
tree8c68ecddaf2c2c0730ba310b8d1b9e0f1bd16132
parent6e9c9c9205d6eec1ff1cfb3fa407c6714854145a (diff)
Updated README and copied latest version in.
-rw-r--r--INSTALL38
-rw-r--r--README.md39
-rw-r--r--doc/Makefile32
-rw-r--r--doc/README22
-rw-r--r--doc/manual.asciidoc546
-rw-r--r--doc/manual.html1577
-rw-r--r--doc/manual.pdfbin0 -> 145493 bytes
-rw-r--r--src/6502.c1585
-rw-r--r--src/6502.h43
-rw-r--r--src/Makefile89
-rw-r--r--src/alias.c146
-rw-r--r--src/alias.h52
-rw-r--r--src/basetype.h51
-rw-r--r--src/casm.c925
-rw-r--r--src/cmd.h155
-rw-r--r--src/codepage.c259
-rw-r--r--src/codepage.h55
-rw-r--r--src/expr.c700
-rw-r--r--src/expr.h53
-rw-r--r--src/global.h51
-rw-r--r--src/label.c470
-rw-r--r--src/label.h123
-rw-r--r--src/listing.c418
-rw-r--r--src/listing.h77
-rw-r--r--src/macro.c509
-rw-r--r--src/macro.h101
-rw-r--r--src/output.c228
-rw-r--r--src/output.h58
-rw-r--r--src/parse.c332
-rw-r--r--src/parse.h123
-rw-r--r--src/stack.c143
-rw-r--r--src/stack.h73
-rw-r--r--src/state.c221
-rw-r--r--src/state.h130
-rw-r--r--src/test/175
-rw-r--r--src/test/268
-rw-r--r--src/test/320
-rw-r--r--src/test/6502.1272
-rw-r--r--src/test/inc6
-rw-r--r--src/test/z80.1447
-rw-r--r--src/util.c201
-rw-r--r--src/util.h94
-rw-r--r--src/varchar.c158
-rw-r--r--src/varchar.h79
-rw-r--r--src/z80.c2458
-rw-r--r--src/z80.h46
46 files changed, 13347 insertions, 1 deletions
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 <http://www.gnu.org/licenses/>.
+#
+# -------------------------------------------------------------------------
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+ 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 <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
+
+
+
+// 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 @@
+<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.1//EN"
+ "http://www.w3.org/TR/xhtml11/DTD/xhtml11.dtd">
+<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en">
+<head>
+<meta http-equiv="Content-Type" content="application/xhtml+xml; charset=UTF-8" />
+<meta name="generator" content="AsciiDoc 8.6.6" />
+<title>CASM</title>
+<style type="text/css">
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+ font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+ font-family: Arial,Helvetica,sans-serif;
+}
+
+body {
+ margin: 1em 5% 1em 5%;
+}
+
+a {
+ color: blue;
+ text-decoration: underline;
+}
+a:visited {
+ color: fuchsia;
+}
+
+em {
+ font-style: italic;
+ color: navy;
+}
+
+strong {
+ font-weight: bold;
+ color: #083194;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #527bbd;
+ margin-top: 1.2em;
+ margin-bottom: 0.5em;
+ line-height: 1.3;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+h2 {
+ padding-top: 0.5em;
+}
+h3 {
+ float: left;
+}
+h3 + * {
+ clear: left;
+}
+h5 {
+ font-size: 1.0em;
+}
+
+div.sectionbody {
+ margin-left: 0;
+}
+
+hr {
+ border: 1px solid silver;
+}
+
+p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+ margin-top: 0;
+}
+ul > li { color: #aaa; }
+ul > li > * { color: black; }
+
+pre {
+ padding: 0;
+ margin: 0;
+}
+
+#author {
+ color: #527bbd;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+ font-size: small;
+ border-top: 2px solid silver;
+ padding-top: 0.5em;
+ margin-top: 4.0em;
+}
+#footer-text {
+ float: left;
+ padding-bottom: 0.5em;
+}
+#footer-badges {
+ float: right;
+ padding-bottom: 0.5em;
+}
+
+#preamble {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.admonitionblock {
+ margin-top: 2.0em;
+ margin-bottom: 2.0em;
+ margin-right: 10%;
+ color: #606060;
+}
+
+div.content { /* Block element content. */
+ padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+ color: #527bbd;
+ font-weight: bold;
+ text-align: left;
+ margin-top: 1.0em;
+ margin-bottom: 0.5em;
+}
+div.title + * {
+ margin-top: 0;
+}
+
+td div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content + div.title {
+ margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+ background: #ffffee;
+ border: 1px solid #dddddd;
+ border-left: 4px solid #f0f0f0;
+ padding: 0.5em;
+}
+
+div.listingblock > div.content {
+ border: 1px solid #dddddd;
+ border-left: 5px solid #f0f0f0;
+ background: #f8f8f8;
+ padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+ padding-left: 1.0em;
+ margin-left: 1.0em;
+ margin-right: 10%;
+ border-left: 5px solid #f0f0f0;
+ color: #888;
+}
+
+div.quoteblock > div.attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock > pre.content {
+ font-family: inherit;
+ font-size: inherit;
+}
+div.verseblock > div.attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+ text-align: left;
+}
+
+div.admonitionblock .icon {
+ vertical-align: top;
+ font-size: 1.1em;
+ font-weight: bold;
+ text-decoration: underline;
+ color: #527bbd;
+ padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+ padding-left: 0.5em;
+ border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+ border-left: 3px solid #dddddd;
+ padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; }
+a.image:visited { color: white; }
+
+dl {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+dt {
+ margin-top: 0.5em;
+ margin-bottom: 0;
+ font-style: normal;
+ color: navy;
+}
+dd > *:first-child {
+ margin-top: 0.1em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+ol.arabic {
+ list-style-type: decimal;
+}
+ol.loweralpha {
+ list-style-type: lower-alpha;
+}
+ol.upperalpha {
+ list-style-type: upper-alpha;
+}
+ol.lowerroman {
+ list-style-type: lower-roman;
+}
+ol.upperroman {
+ list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+tfoot {
+ font-weight: bold;
+}
+td > div.verse {
+ white-space: pre;
+}
+
+div.hdlist {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+div.hdlist tr {
+ padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+ font-weight: bold;
+}
+td.hdlist1 {
+ vertical-align: top;
+ font-style: normal;
+ padding-right: 0.8em;
+ color: navy;
+}
+td.hdlist2 {
+ vertical-align: top;
+}
+div.hdlist.compact tr {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.comment {
+ background: yellow;
+}
+
+.footnote, .footnoteref {
+ font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+ vertical-align: super;
+}
+
+#footnotes {
+ margin: 20px 0 20px 0;
+ padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+ margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+ border: none;
+ border-top: 1px solid silver;
+ height: 1px;
+ text-align: left;
+ margin-left: 0;
+ width: 20%;
+ min-width: 100px;
+}
+
+div.colist td {
+ padding-right: 0.5em;
+ padding-bottom: 0.3em;
+ vertical-align: top;
+}
+div.colist td img {
+ margin-top: 0.3em;
+}
+
+@media print {
+ #footer-badges { display: none; }
+}
+
+#toc {
+ margin-bottom: 2.5em;
+}
+
+#toctitle {
+ color: #527bbd;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 1.0em;
+ margin-bottom: 0.1em;
+}
+
+div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+div.toclevel2 {
+ margin-left: 2em;
+ font-size: 0.9em;
+}
+div.toclevel3 {
+ margin-left: 4em;
+ font-size: 0.9em;
+}
+div.toclevel4 {
+ margin-left: 6em;
+ font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+tt {
+ font-family: monospace;
+ font-size: inherit;
+ color: navy;
+}
+
+div.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.tableblock > table {
+ border: 3px solid #527bbd;
+}
+thead, p.table.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.table {
+ margin-top: 0;
+}
+/* Because the table frame attribute is overriden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+ border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+ border-left-style: none;
+ border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+ border-top-style: none;
+ border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+.monospaced {
+ font-family: monospace;
+ font-size: inherit;
+ color: navy;
+}
+
+table.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.tableblock {
+ margin-top: 0;
+}
+table.tableblock {
+ border-width: 3px;
+ border-spacing: 0px;
+ border-style: solid;
+ border-color: #527bbd;
+ border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+ border-width: 1px;
+ padding: 4px;
+ border-style: solid;
+ border-color: #527bbd;
+}
+
+table.tableblock.frame-topbot {
+ border-left-style: hidden;
+ border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+ border-top-style: hidden;
+ border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+ border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+ text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+ text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+ text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+ vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+ vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+ vertical-align: bottom;
+}
+
+
+/*
+ * manpage specific
+ *
+ * */
+
+body.manpage h1 {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-top: 2px solid silver;
+ border-bottom: 2px solid silver;
+}
+body.manpage h2 {
+ border-style: none;
+}
+body.manpage div.sectionbody {
+ margin-left: 3em;
+}
+
+@media print {
+ body.manpage div#toc { display: none; }
+}
+</style>
+<script type="text/javascript">
+/*<![CDATA[*/
+var asciidoc = { // Namespace.
+
+/////////////////////////////////////////////////////////////////////
+// Table Of Contents generator
+/////////////////////////////////////////////////////////////////////
+
+/* Author: Mihai Bazon, September 2002
+ * http://students.infoiasi.ro/~mishoo
+ *
+ * Table Of Content generator
+ * Version: 0.4
+ *
+ * Feel free to use this script under the terms of the GNU General Public
+ * License, as long as you do not remove or alter this notice.
+ */
+
+ /* modified by Troy D. Hanson, September 2006. License: GPL */
+ /* modified by Stuart Rackham, 2006, 2009. License: GPL */
+
+// toclevels = 1..4.
+toc: function (toclevels) {
+
+ function getText(el) {
+ var text = "";
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
+ text += i.data;
+ else if (i.firstChild != null)
+ text += getText(i);
+ }
+ return text;
+ }
+
+ function TocEntry(el, text, toclevel) {
+ this.element = el;
+ this.text = text;
+ this.toclevel = toclevel;
+ }
+
+ function tocEntries(el, toclevels) {
+ var result = new Array;
+ var re = new RegExp('[hH]([2-'+(toclevels+1)+'])');
+ // Function that scans the DOM tree for header elements (the DOM2
+ // nodeIterator API would be a better technique but not supported by all
+ // browsers).
+ var iterate = function (el) {
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
+ var mo = re.exec(i.tagName);
+ if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
+ result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
+ }
+ iterate(i);
+ }
+ }
+ }
+ iterate(el);
+ return result;
+ }
+
+ var toc = document.getElementById("toc");
+ if (!toc) {
+ return;
+ }
+
+ // Delete existing TOC entries in case we're reloading the TOC.
+ var tocEntriesToRemove = [];
+ var i;
+ for (i = 0; i < toc.childNodes.length; i++) {
+ var entry = toc.childNodes[i];
+ if (entry.nodeName == 'div'
+ && entry.getAttribute("class")
+ && entry.getAttribute("class").match(/^toclevel/))
+ tocEntriesToRemove.push(entry);
+ }
+ for (i = 0; i < tocEntriesToRemove.length; i++) {
+ toc.removeChild(tocEntriesToRemove[i]);
+ }
+
+ // Rebuild TOC entries.
+ var entries = tocEntries(document.getElementById("content"), toclevels);
+ for (var i = 0; i < entries.length; ++i) {
+ var entry = entries[i];
+ if (entry.element.id == "")
+ entry.element.id = "_toc_" + i;
+ var a = document.createElement("a");
+ a.href = "#" + entry.element.id;
+ a.appendChild(document.createTextNode(entry.text));
+ var div = document.createElement("div");
+ div.appendChild(a);
+ div.className = "toclevel" + entry.toclevel;
+ toc.appendChild(div);
+ }
+ if (entries.length == 0)
+ toc.parentNode.removeChild(toc);
+},
+
+
+/////////////////////////////////////////////////////////////////////
+// Footnotes generator
+/////////////////////////////////////////////////////////////////////
+
+/* Based on footnote generation code from:
+ * http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
+ */
+
+footnotes: function () {
+ // Delete existing footnote entries in case we're reloading the footnodes.
+ var i;
+ var noteholder = document.getElementById("footnotes");
+ if (!noteholder) {
+ return;
+ }
+ var entriesToRemove = [];
+ for (i = 0; i < noteholder.childNodes.length; i++) {
+ var entry = noteholder.childNodes[i];
+ if (entry.nodeName == 'div' && entry.getAttribute("class") == "footnote")
+ entriesToRemove.push(entry);
+ }
+ for (i = 0; i < entriesToRemove.length; i++) {
+ noteholder.removeChild(entriesToRemove[i]);
+ }
+
+ // Rebuild footnote entries.
+ var cont = document.getElementById("content");
+ var spans = cont.getElementsByTagName("span");
+ var refs = {};
+ var n = 0;
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnote") {
+ n++;
+ var note = spans[i].getAttribute("data-note");
+ if (!note) {
+ // Use [\s\S] in place of . so multi-line matches work.
+ // Because JavaScript has no s (dotall) regex flag.
+ note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
+ spans[i].innerHTML =
+ "[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ spans[i].setAttribute("data-note", note);
+ }
+ noteholder.innerHTML +=
+ "<div class='footnote' id='_footnote_" + n + "'>" +
+ "<a href='#_footnoteref_" + n + "' title='Return to text'>" +
+ n + "</a>. " + note + "</div>";
+ var id =spans[i].getAttribute("id");
+ if (id != null) refs["#"+id] = n;
+ }
+ }
+ if (n == 0)
+ noteholder.parentNode.removeChild(noteholder);
+ else {
+ // Process footnoterefs.
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnoteref") {
+ var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
+ href = href.match(/#.*/)[0]; // Because IE return full URL.
+ n = refs[href];
+ spans[i].innerHTML =
+ "[<a href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ }
+ }
+ }
+},
+
+install: function(toclevels) {
+ var timerId;
+
+ function reinstall() {
+ asciidoc.footnotes();
+ if (toclevels) {
+ asciidoc.toc(toclevels);
+ }
+ }
+
+ function reinstallAndRemoveTimer() {
+ clearInterval(timerId);
+ reinstall();
+ }
+
+ timerId = setInterval(reinstall, 500);
+ if (document.addEventListener)
+ document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false);
+ else
+ window.onload = reinstallAndRemoveTimer;
+}
+
+}
+asciidoc.install();
+/*]]>*/
+</script>
+</head>
+<body class="article">
+<div id="header">
+<h1>CASM</h1>
+</div>
+<div id="content">
+<div id="preamble">
+<div class="sectionbody">
+<div class="paragraph"><p>A simple, portable multi-pass assembler</p></div>
+<div class="paragraph"><p>Copyright &#169; 2003-2015 Ian Cowburn &lt;<a href="mailto:ianc@noddybox.co.uk">ianc@noddybox.co.uk</a>&gt;</p></div>
+<div class="paragraph"><p>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.</p></div>
+<div class="paragraph"><p>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.</p></div>
+<div class="paragraph"><p>You should have received a copy of the GNU General Public License
+along with this program. If not, see <a href="http://www.gnu.org/licenses/gpl-3.0.html">http://www.gnu.org/licenses/gpl-3.0.html</a></p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_usage">Usage</h2>
+<div class="sectionbody">
+<div class="listingblock">
+<div class="content">
+<pre><tt>casm file</tt></pre>
+</div></div>
+<div class="paragraph"><p>Assembles file, and places the output in <em>output</em> by default.</p></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_source_format_example">Source Format Example</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>The source files follow this basic format:</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt>; 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</tt></pre>
+</div></div>
+<div class="paragraph"><p>The source files follow the following rules:</p></div>
+<div class="ulist"><ul>
+<li>
+<p>
+Any text past a semicolon (;) is discarded as a comment (except when part
+ of a string constant).
+</p>
+</li>
+<li>
+<p>
+Labels must start in column zero (the left hand most column).
+</p>
+<div class="ulist"><ul>
+<li>
+<p>
+If the label ends with a colon (:) then the colon is removed.
+</p>
+</li>
+<li>
+<p>
+If the label doesn&#8217;t start with a period (.) then it is assumed a global
+ label.
+</p>
+</li>
+<li>
+<p>
+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.
+</p>
+</li>
+<li>
+<p>
+Any label can be followed by an <em>equ</em> directive, in which case the label
+ is set to that value rather than the current program counter.
+</p>
+</li>
+<li>
+<p>
+Labels are case-insensitive.
+</p>
+</li>
+</ul></div>
+</li>
+<li>
+<p>
+Directives and opcodes must appear further along the line (anywhere else
+ other than the left hand column where labels live basically).
+</p>
+</li>
+<li>
+<p>
+Strings can either be quoted with single or double quotes; this allows you to
+ put the other quote type inside the string.
+</p>
+</li>
+</ul></div>
+<div class="sect2">
+<h3 id="_recognised_directives">Recognised directives</h3>
+<div class="paragraph"><p>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.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+processor <em>CPU</em>
+</dt>
+<dd>
+<p>
+ Sets the processor type to <em>CPU</em>. If omitted then Z80 is the default.
+ Note that this can appear multiple times in the same file. Currently
+ supported <em>CPU</em> values are <tt>Z80</tt> and <tt>6502</tt>.
+</p>
+</dd>
+<dt class="hdlist1">
+option <em>setting</em>, <em>value</em>
+</dt>
+<dd>
+<p>
+ 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 <em>setting</em> can be prefixed with a plus or minus character to switch it
+ on or off respectively.
+</p>
+</dd>
+<dt class="hdlist1">
+equ <em>value</em>
+</dt>
+<dd>
+<p>
+ Sets the top level label to <em>value</em>. Note this requires a label on the
+ same line.
+</p>
+</dd>
+<dt class="hdlist1">
+org <em>value</em>
+</dt>
+<dd>
+<p>
+ Sets the program counter (PC) to <em>value</em>. The PC defaults to zero.
+</p>
+</dd>
+<dt class="hdlist1">
+ds <em>value</em>[, <em>fill</em>]
+</dt>
+<dd>
+<p>
+ Skips on the program counter <em>value</em> bytes. If the optional <em>fill</em> is
+ provided then the bytes are filled with <em>fill</em>, otherwise they are filled
+ with zero.
+</p>
+</dd>
+<dt class="hdlist1">
+db <em>value</em>[, <em>value</em>]
+</dt>
+<dd>
+<p>
+ Writes bytes to the current PC. The values can be constants, expressions,
+ labels or strings. Built-in aliases are <tt>byte</tt> and <tt>text</tt>.
+</p>
+</dd>
+<dt class="hdlist1">
+dw &lt;value&gt;[, &lt;value&gt;]
+</dt>
+<dd>
+<p>
+ Writes words (16-bit values) to the current PC. The values can be
+ constants, expressions or labels. Note that <tt>word</tt> is a built-in alias for
+ this directive.
+</p>
+</dd>
+<dt class="hdlist1">
+align <em>value</em>[, <em>fill</em>]
+</dt>
+<dd>
+<p>
+ Align the PC so that (PC modulus <em>value</em>) is zero. Will error if <em>value</em>
+ is less than 2 or greater that 32768. No values are written to the skipped
+ bytes unless the optional <em>fill</em> is supplied.
+</p>
+</dd>
+<dt class="hdlist1">
+include <em>filename</em>
+</dt>
+<dd>
+<p>
+ Includes the source file <em>filename</em> as if it was text entered at the
+ current location.
+</p>
+</dd>
+<dt class="hdlist1">
+incbin <em>filename</em>
+</dt>
+<dd>
+<p>
+ Includes the binary data in <em>filename</em> at the current PC, as if it was a
+ sequence of <tt>db</tt> directives with all the bytes from the file.
+</p>
+</dd>
+<dt class="hdlist1">
+alias <em>command</em>, <em>replacement</em>
+</dt>
+<dd>
+<p>
+ Creates an alias so that whenever the command <em>command</em> is found in the
+ source it is replaced with <em>replacement</em>. The idea of this is to make it
+ easier to import sources that use unknown directives, e.g.
+</p>
+<div class="literalblock">
+<div class="content">
+<pre><tt>alias setaddr,org
+alias ldreg,ld</tt></pre>
+</div></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt>cpu z80</tt></pre>
+</div></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt>setaddr $8000 ; These two are
+org $8000 ; equivalent.</tt></pre>
+</div></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt>ld a,(hl) ; These two are
+ldreg a,(hl) ; equivalent.</tt></pre>
+</div></div>
+</dd>
+<dt class="hdlist1">
+nullcmd
+</dt>
+<dd>
+<p>
+ Simply does nothing. It&#8217;s only real use is as an alias if you wished to
+ strip a directive from a foreign source file.
+</p>
+</dd>
+<dt class="hdlist1">
+end
+</dt>
+<dd>
+<p>
+ Terminates the input processing. Anything past the directive will be
+ ignored.
+</p>
+</dd>
+</dl></div>
+</div>
+<div class="sect2">
+<h3 id="_expressions">Expressions</h3>
+<div class="paragraph"><p>In any of the directives above, where a value is defined, an expression can be
+entered.</p></div>
+<div class="paragraph"><p>The following formats for constant numbers are supported (note these are
+illustrated as a regular expression):</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+"x" or <em>x</em>
+</dt>
+<dd>
+<p>
+ A single quoted character will be converted into the appropriate character
+ code.
+</p>
+</dd>
+<dt class="hdlist1">
+[1-9][0-9]*
+</dt>
+<dd>
+<p>
+ A decimal number, e.g. 42.
+</p>
+</dd>
+<dt class="hdlist1">
+0[0-7]*
+</dt>
+<dd>
+<p>
+ An octal number, e.g. 052.
+</p>
+</dd>
+<dt class="hdlist1">
+0x[0-9a-fA-f]+
+</dt>
+<dd>
+<p>
+ A hex number, e.g. 0x2a.
+</p>
+</dd>
+<dt class="hdlist1">
+[0-9a-fA-f]+h
+</dt>
+<dd>
+<p>
+ A hex number, e.g. 2ah.
+</p>
+</dd>
+<dt class="hdlist1">
+$[0-9a-fA-f]+
+</dt>
+<dd>
+<p>
+ A hex number, e.g. $2a.
+</p>
+</dd>
+<dt class="hdlist1">
+[01]+b
+</dt>
+<dd>
+<p>
+ A binary number, e.g. 00101010b
+</p>
+</dd>
+<dt class="hdlist1">
+[a-zA-Z_0-9]+
+</dt>
+<dd>
+<p>
+ A label, e.g. <tt>main_loop</tt>.
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p>The following operators are understood. The order here is the order of
+precedence.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+{ }
+</dt>
+<dd>
+<p>
+ Brackets used to alter the order of precedence. Note normal parenthesis
+ aren&#8217;t used as the assembly language may make use of them.
+</p>
+</dd>
+<dt class="hdlist1">
+~ + -
+</dt>
+<dd>
+<p>
+ Bitwise NOT/unary plus/unary minus.
+</p>
+</dd>
+<dt class="hdlist1">
+&lt;&lt; &gt;&gt;
+</dt>
+<dd>
+<p>
+ Shift left/shift right.
+</p>
+</dd>
+<dt class="hdlist1">
+/ * %
+</dt>
+<dd>
+<p>
+ Division/multiplication/modulus.
+</p>
+</dd>
+<dt class="hdlist1">
++ -
+</dt>
+<dd>
+<p>
+ Addition/subtraction.
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p>All the following have the same precedence, and so will be done left to right.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+==
+</dt>
+<dd>
+<p>
+ Equality. Returns 1 if the arguments are equal, otherwise zero.
+</p>
+</dd>
+<dt class="hdlist1">
+!=
+</dt>
+<dd>
+<p>
+ Inequality. Returns 1 if the arguments are unequal, otherwise zero.
+</p>
+</dd>
+<dt class="hdlist1">
+&lt; &lt;= &gt; &gt;=
+</dt>
+<dd>
+<p>
+ Less than/less than or equal/greater than/greater than or equal. Returns 1
+ if the arguments are equal, otherwise zero.
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p>All the following have the same precedence, and so will be done left to right.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+&amp;&amp; &amp;
+</dt>
+<dd>
+<p>
+ Boolean/bitwise AND. For boolean operation arguments, zero is FALSE,
+ otherwise TRUE.
+</p>
+</dd>
+<dt class="hdlist1">
+|| |
+</dt>
+<dd>
+<p>
+ Boolean/bitwise OR.
+</p>
+</dd>
+<dt class="hdlist1">
+^
+</dt>
+<dd>
+<p>
+ Bitwise XOR.
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p>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.</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> 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</tt></pre>
+</div></div>
+<div class="paragraph"><p>Note that the expression is evaluated using a standard C int, and then cast
+to the appropriate size.</p></div>
+</div>
+<div class="sect2">
+<h3 id="_character_sets">Character Sets</h3>
+<div class="paragraph"><p>The assembler has built-in support for a few different character sets.
+These can be set by using the options <em>charset</em> or <em>codepage</em>, i.e.</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> option codepage, &lt;format&gt;
+ option charset, &lt;format&gt;</tt></pre>
+</div></div>
+<div class="paragraph"><p>The following values can be used for <em>format</em>.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+ascii
+</dt>
+<dd>
+<p>
+ 7-bit ASCII. This is the default.
+</p>
+</dd>
+<dt class="hdlist1">
+spectrum
+</dt>
+<dd>
+<p>
+ The character codes as used on the Sinclair ZX Spectrum.
+</p>
+</dd>
+<dt class="hdlist1">
+zx81
+</dt>
+<dd>
+<p>
+ 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.
+</p>
+</dd>
+<dt class="hdlist1">
+cbm
+</dt>
+<dd>
+<p>
+ PETSCII as used on the Commodore Business Machine&#8217;s range from the
+ PET to the C128. See <a href="https://en.wikipedia.org/wiki/PETSCII">https://en.wikipedia.org/wiki/PETSCII</a> for
+ more details.
+</p>
+</dd>
+</dl></div>
+<div class="paragraph"><p>e.g.</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> 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</tt></pre>
+</div></div>
+</div>
+<div class="sect2">
+<h3 id="_macros">Macros</h3>
+<div class="paragraph"><p>Macros can be defined in one of two ways; either parameterless or with named
+parameters. Macro names are case-insensitive.</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt>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</tt></pre>
+</div></div>
+<div class="paragraph"><p>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.</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt>mac: macro char,junk,interface
+
+ ld a,@char
+ ld b,\2
+ call @interface
+
+ endm</tt></pre>
+</div></div>
+<div class="paragraph"><p>The at symbol (@) used for parameter expansion in named argument macros can
+be replaced by using the following option, e.g.</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> option macro-arg-char,&amp;</tt></pre>
+</div></div>
+<div class="paragraph"><p>Note that this is enforced when the macro is <strong>used</strong>, not when it is <strong>defined</strong>.
+Also the character must not be quoted, as that will be parsed as a string
+holding the character code of the character.</p></div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_output_format">Output Format</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>By default the assembled code is written to a file called <strong>output</strong> as raw
+binary covering the block of memory that the assembly touched.</p></div>
+<div class="paragraph"><p>This can be controlled with the following options.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+option output-file, <em>file</em>
+</dt>
+<dd>
+<p>
+ Send the output to <em>file</em>.
+</p>
+</dd>
+<dt class="hdlist1">
+option output-type, <em>format</em>
+</dt>
+<dd>
+<p>
+ Controls the output format with the following settings
+</p>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+raw
+</dt>
+<dd>
+<p>
+ The default raw binary.
+</p>
+</dd>
+<dt class="hdlist1">
+spectrum
+</dt>
+<dd>
+<p>
+ 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.
+</p>
+</dd>
+</dl></div>
+</dd>
+</dl></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_listing">Listing</h2>
+<div class="sectionbody">
+<div class="paragraph"><p>By default no output listing is generated. This can be controlled by the
+the following options.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+option list, &lt;on|off&gt;
+</dt>
+<dd>
+<p>
+ Enables/disables listing. The listing will go to stdout.
+</p>
+</dd>
+<dt class="hdlist1">
+option list-file, <em>file</em>
+</dt>
+<dd>
+<p>
+ Sends the listing to <em>file</em>. Note this should appear before enabling the
+ listing.
+</p>
+</dd>
+<dt class="hdlist1">
+option list-pc, &lt;on|off&gt;
+</dt>
+<dd>
+<p>
+ 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 <strong>off</strong>.
+</p>
+</dd>
+<dt class="hdlist1">
+option list-hex, &lt;on|off&gt;
+</dt>
+<dd>
+<p>
+ Control the output of the bytes generated by the source line in hex.
+ Defaults to <strong>off</strong>. If <strong>on</strong> 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.
+</p>
+</dd>
+<dt class="hdlist1">
+option list-labels, &lt;on|off|all&gt;
+</dt>
+<dd>
+<p>
+ Controls the listing of labels, either <strong>off</strong> (the default), <strong>on</strong> to dump
+ label values at the end of the listing and <strong>all</strong> to dump all labels,
+ including internally generated private labels for macros.
+</p>
+</dd>
+<dt class="hdlist1">
+option list-macros, &lt;off|exec|dump|all&gt;
+</dt>
+<dd>
+<p>
+ Controls the listing of macro invocations, either
+</p>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+off
+</dt>
+<dd>
+<p>
+ The default; don&#8217;t list anything.
+</p>
+</dd>
+<dt class="hdlist1">
+exec
+</dt>
+<dd>
+<p>
+ List invocations of macros.
+</p>
+</dd>
+<dt class="hdlist1">
+dump
+</dt>
+<dd>
+<p>
+ Produce a list of macro definitions at the end of the listing.
+</p>
+</dd>
+<dt class="hdlist1">
+all
+</dt>
+<dd>
+<p>
+ Combine "exec" and "dump"
+</p>
+</dd>
+</dl></div>
+</dd>
+<dt class="hdlist1">
+option list-rm-blanks, &lt;on|off&gt;
+</dt>
+<dd>
+<p>
+ Defaults to <strong>on</strong>. This option causes multiple blank lines to be collapsed
+ down to a single line.
+</p>
+</dd>
+</dl></div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_z80_cpu">Z80 CPU</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_opcodes">Opcodes</h3>
+<div class="paragraph"><p>The Z80 assembler uses the standard Zilog opcodes, and supports
+undocumented instructions.</p></div>
+<div class="paragraph"><p>For instructions were the Accumulator can be assumed it can be omitted, and
+EOR can be used the same as XOR:</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> xor a,a ; These are equivalent
+ xor a
+ eor a,a
+
+ and a,b ; These are equivalent
+ and b</tt></pre>
+</div></div>
+<div class="paragraph"><p>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:</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> 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.</tt></pre>
+</div></div>
+<div class="paragraph"><p>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.</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> srl (iy-1),d
+ set 3,(iy-1),a
+ res 4,(iy-1),b</tt></pre>
+</div></div>
+<div class="paragraph"><p>For the hidden IN instruction using the flag register the following are all
+equivalent:</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> in (c)
+ in f,(c)</tt></pre>
+</div></div>
+<div class="paragraph"><p>For the hidden OUT instruction using the flag register, $00 or $ff depending
+on where you&#8217;re reading, the following are all equivalent, where <em>value</em> can
+be any value at all:</p></div>
+<div class="listingblock">
+<div class="content">
+<pre><tt> out (c)
+ out (c),f
+ out (c),&lt;value&gt;</tt></pre>
+</div></div>
+</div>
+<div class="sect2">
+<h3 id="_options">Options</h3>
+<div class="paragraph"><p>The Z80 assembler has no options.</p></div>
+</div>
+</div>
+</div>
+<div class="sect1">
+<h2 id="_6502_cpu">6502 CPU</h2>
+<div class="sectionbody">
+<div class="sect2">
+<h3 id="_opcodes_2">Opcodes</h3>
+<div class="paragraph"><p>The 6502 assembler uses the standard Motorola opcodes.</p></div>
+</div>
+<div class="sect2">
+<h3 id="_options_2">Options</h3>
+<div class="paragraph"><p>The 6502 assembler has the following options.</p></div>
+<div class="dlist"><dl>
+<dt class="hdlist1">
+option zero-page, &lt;on|off|auto&gt;
+</dt>
+<dd>
+<p>
+ Use Zero-Page addressing for <em>absolute</em> and <em>absolute</em>,X address modes.
+ If mode is set to <strong>auto</strong> then tries to calculate the mode based on the
+ value in the last pass.
+ Defaults to <strong>off</strong>. e.g.
+</p>
+<div class="literalblock">
+<div class="content">
+<pre><tt>cpu 6502
+org $8000</tt></pre>
+</div></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt>lda $0000,x ; Produces $bd $00 $00
+option +zero-page
+lda $0000,x ; Produces $b5 $00
+lda $1234,x ; Produces an error</tt></pre>
+</div></div>
+<div class="literalblock">
+<div class="content">
+<pre><tt>option zero-page,auto
+lda $00,x ; Produces $b5 $00
+lda $8000,x ; Produces $bd $00 $80</tt></pre>
+</div></div>
+</dd>
+</dl></div>
+</div>
+</div>
+</div>
+</div>
+<div id="footnotes"><hr /></div>
+<div id="footer">
+<div id="footer-text">
+Last updated 2016-01-27 16:12:35 GMT
+</div>
+</div>
+</body>
+</html>
diff --git a/doc/manual.pdf b/doc/manual.pdf
new file mode 100644
index 0000000..d39db3e
--- /dev/null
+++ b/doc/manual.pdf
Binary files 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 6502 Assembler
+
+*/
+#include <stdlib.h>
+#include <string.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+#
+# -------------------------------------------------------------------------
+#
+# 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for aliases.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for aliases.
+
+*/
+
+#ifndef CASM_ALIAS_H
+#define CASM_ALIAS_H
+
+#include <stdio.h>
+
+/* ---------------------------------------- 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Main
+
+*/
+#include <stdlib.h>
+#include <limits.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+#include <time.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Code page handling.
+
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Expands an expression.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+
+#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_SHIFTR:
+ if (right<0)
+ {
+ sprintf(error,"Cannot shift right 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=left<right;
+ break;
+
+ case TYPE_GT:
+ *result=left>right;
+ 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<<no_bits)-1);
+}
+
+
+int ExprEval(const char *expr, int *result)
+{
+ Stack *output=ToPostfix(expr);
+ int ret;
+
+ if (!output)
+ return FALSE;
+
+ ret=EvalPostfix(&output,result);
+
+ ClearStack(output);
+
+ return ret;
+}
+
+
+/* Gets a readable reason for an error from ExprEval() or ExprParse.
+*/
+const char *ExprError(void)
+{
+ return error;
+}
+
+/*
+vim: ai sw=4 ts=8 expandtab
+*/
diff --git a/src/expr.h b/src/expr.h
new file mode 100644
index 0000000..5c4545c
--- /dev/null
+++ b/src/expr.h
@@ -0,0 +1,53 @@
+/*
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for labels.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for labels.
+
+*/
+
+#ifndef CASM_LABEL_H
+#define CASM_LABEL_H
+
+#include <stdio.h>
+
+/* ---------------------------------------- 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Common utilities
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+#include <stdarg.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for macros.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for macros.
+
+*/
+
+#ifndef CASM_MACRO_H
+#define CASM_MACRO_H
+
+#include <stdlib.h>
+#include <stdio.h>
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Various output type handlers.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Tokeniser.
+
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for macros.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Stores assembly state.
+
+*/
+#include <stdlib.h>
+#include <string.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Common utilities
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <ctype.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Common utilities
+
+*/
+
+#ifndef CASM_UTIL_H
+#define CASM_UTIL_H
+
+#include <stdlib.h>
+
+
+/* ---------------------------------------- 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Collection for macros.
+
+*/
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <stdarg.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ Z80 Assembler
+
+*/
+#include <stdlib.h>
+#include <string.h>
+#include <stdarg.h>
+
+#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 <http://www.gnu.org/licenses/>.
+
+ -------------------------------------------------------------------------
+
+ 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
+*/