diff options
author | Ian C <ianc@noddybox.co.uk> | 2016-03-07 15:00:21 +0000 |
---|---|---|
committer | Ian C <ianc@noddybox.co.uk> | 2016-03-07 15:00:21 +0000 |
commit | 77e8708934c5c792b1435fa11dfe3c0a6f636a8c (patch) | |
tree | 8c68ecddaf2c2c0730ba310b8d1b9e0f1bd16132 /src/casm.c | |
parent | 6e9c9c9205d6eec1ff1cfb3fa407c6714854145a (diff) |
Updated README and copied latest version in.
Diffstat (limited to 'src/casm.c')
-rw-r--r-- | src/casm.c | 925 |
1 files changed, 925 insertions, 0 deletions
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 +*/ |