3917 lines
75 KiB
C
3917 lines
75 KiB
C
/* gasp.c - Gnu assembler preprocessor main program.
|
|
Copyright (C) 1994 Free Software Foundation, Inc.
|
|
|
|
Written by Steve and Judy Chamberlain of Cygnus Support,
|
|
sac@cygnus.com
|
|
|
|
This file is part of GASP, the GNU Assembler Preprocessor.
|
|
|
|
GASP is free software; you can redistribute it and/or modify
|
|
it under the terms of the GNU General Public License as published by
|
|
the Free Software Foundation; either version 2, or (at your option)
|
|
any later version.
|
|
|
|
GASP 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 GASP; see the file COPYING. If not, write to
|
|
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. */
|
|
|
|
/*
|
|
|
|
This program translates the input macros and stuff into a form
|
|
suitable for gas to consume.
|
|
|
|
|
|
gasp [-sdhau] [-c char] [-o <outfile>] <infile>*
|
|
|
|
-s copy source to output
|
|
-c <char> comments are started with <char> instead of !
|
|
-u allow unreasonable stuff
|
|
-p print line numbers
|
|
-d print debugging stats
|
|
-s semi colons start comments
|
|
-a use alternate syntax
|
|
Pseudo ops can start with or without a .
|
|
Labels have to be in first column.
|
|
Macro arg parameters subsituted by name, don't need the &.
|
|
String can start with ' too.
|
|
Strings can be surrounded by <..>
|
|
A %<exp> in a string evaluates the expression
|
|
Literal char in a string with !
|
|
|
|
|
|
*/
|
|
|
|
|
|
#include <stdio.h>
|
|
#include <getopt.h>
|
|
#include <ctype.h>
|
|
|
|
#include "host.h"
|
|
#include "libiberty.h"
|
|
|
|
char *program_version = "1.2";
|
|
|
|
#define MAX_INCLUDES 30 /* Maximum include depth */
|
|
#define MAX_REASONABLE 1000 /* Maximum number of expansions */
|
|
|
|
int unreasonable; /* -u on command line */
|
|
int stats; /* -d on command line */
|
|
int print_line_number; /* -p flag on command line */
|
|
int copysource; /* -c flag on command line */
|
|
int warnings; /* Number of WARNINGs generated so far. */
|
|
int errors; /* Number of ERRORs generated so far. */
|
|
int fatals; /* Number of fatal ERRORs generated so far (either 0 or 1). */
|
|
int alternate = 0; /* -a on command line */
|
|
char comment_char = '!';
|
|
int radix = 10; /* Default radix */
|
|
|
|
int had_end; /* Seen .END */
|
|
|
|
/* The output stream */
|
|
FILE *outfile;
|
|
|
|
|
|
/* Forward declarations. */
|
|
static int condass_lookup_name();
|
|
static int condass_on();
|
|
static int get();
|
|
static int get_and_process();
|
|
static int get_token();
|
|
static int getstring();
|
|
static int include_next_index();
|
|
static int macro_op();
|
|
static int linecount();
|
|
static int process_pseudo_op();
|
|
static void include_pop();
|
|
static void include_print_where_line();
|
|
/* string blocks
|
|
|
|
I had a couple of choices when deciding upon this data structure.
|
|
gas uses null terminated strings for all its internal work. This
|
|
often means that parts of the program that want to examine
|
|
substrings have to manipulate the data in the string to do the
|
|
right thing (a common operation is to single out a bit of text by
|
|
saving away the character after it, nulling it out, operating on
|
|
the substring and then replacing the character which was under the
|
|
null). This is a pain and I remember a load of problems that I had with
|
|
code in gas which almost got this right. Also, it's harder to grow and
|
|
allocate null terminated strings efficiently.
|
|
|
|
Obstacks provide all the functionality needed, but are too
|
|
complicated, hence the sb.
|
|
|
|
An sb is allocated by the caller, and is initialzed to point to an
|
|
sb_element. sb_elements are kept on a free lists, and used when
|
|
needed, replaced onto the free list when unused.
|
|
*/
|
|
|
|
#define max_power_two 30 /* don't allow strings more than
|
|
2^max_power_two long */
|
|
/* structure of an sb */
|
|
typedef struct sb
|
|
{
|
|
char *ptr; /* points to the current block. */
|
|
int len; /* how much is used. */
|
|
int pot; /* the maximum length is 1<<pot */
|
|
struct le *item;
|
|
}
|
|
sb;
|
|
|
|
/* Structure of the free list object of an sb */
|
|
typedef struct le
|
|
{
|
|
struct le *next;
|
|
int size;
|
|
char data[1];
|
|
}
|
|
sb_element;
|
|
|
|
/* The free list */
|
|
typedef struct
|
|
{
|
|
sb_element *size[max_power_two];
|
|
} sb_list_vector;
|
|
|
|
sb_list_vector free_list;
|
|
|
|
int string_count[max_power_two];
|
|
|
|
/* the attributes of each character are stored as a bit pattern
|
|
chartype, which gives us quick tests. */
|
|
|
|
|
|
#define FIRSTBIT 1
|
|
#define NEXTBIT 2
|
|
#define SEPBIT 4
|
|
#define WHITEBIT 8
|
|
#define COMMENTBIT 16
|
|
|
|
#define ISCOMMENTCHAR(x) (chartype[(unsigned)(x)] & COMMENTBIT)
|
|
#define ISFIRSTCHAR(x) (chartype[(unsigned)(x)] & FIRSTBIT)
|
|
#define ISNEXTCHAR(x) (chartype[(unsigned)(x)] & NEXTBIT)
|
|
#define ISSEP(x) (chartype[(unsigned)(x)] & SEPBIT)
|
|
#define ISWHITE(x) (chartype[(unsigned)(x)] & WHITEBIT)
|
|
|
|
static char chartype[256];
|
|
|
|
|
|
/* Conditional assembly uses the `ifstack'. Each aif pushes another
|
|
entry onto the stack, and sets the on flag if it should. The aelse
|
|
sets hadelse, and toggles on. An aend pops a level. We limit to
|
|
100 levels of nesting, not because we're facists pigs with read
|
|
only minds, but because more than 100 levels of nesting is probably
|
|
a bug in the user's macro structure. */
|
|
|
|
#define IFNESTING 100
|
|
struct
|
|
{
|
|
int on; /* is the level being output */
|
|
int hadelse; /* has an aelse been seen */
|
|
}
|
|
ifstack[IFNESTING];
|
|
int ifi;
|
|
|
|
/* The final and intermediate results of expression evaluation are kept in
|
|
exp_t's. Note that a symbol is not an sb, but a pointer into the input
|
|
line. It must be coped somewhere safe before the next line is read in. */
|
|
|
|
typedef struct
|
|
{
|
|
char *name;
|
|
int len;
|
|
}
|
|
symbol;
|
|
|
|
typedef struct
|
|
{
|
|
int value; /* constant part */
|
|
symbol add_symbol; /* name part */
|
|
symbol sub_symbol; /* name part */
|
|
}
|
|
exp_t;
|
|
|
|
|
|
/* Hashing is done in a pretty standard way. A hash_table has a
|
|
pointer to a vector of pointers to hash_entrys, and the size of the
|
|
vector. A hash_entry contains a union of all the info we like to
|
|
store in hash table. If there is a hash collision, hash_entries
|
|
with the same hash are kept in a chain. */
|
|
|
|
/* What the data in a hash_entry means */
|
|
typedef enum
|
|
{
|
|
hash_integer, /* name->integer mapping */
|
|
hash_string, /* name->string mapping */
|
|
hash_macro, /* name is a macro */
|
|
hash_formal /* name is a formal argument */
|
|
} hash_type;
|
|
|
|
typedef struct hs
|
|
{
|
|
sb key; /* symbol name */
|
|
hash_type type; /* symbol meaning */
|
|
union
|
|
{
|
|
sb s;
|
|
int i;
|
|
struct macro_struct *m;
|
|
struct formal_struct *f;
|
|
} value;
|
|
struct hs *next; /* next hash_entry with same hash key */
|
|
} hash_entry;
|
|
|
|
typedef struct
|
|
{
|
|
hash_entry **table;
|
|
int size;
|
|
} hash_table;
|
|
|
|
|
|
/* Structures used to store macros.
|
|
|
|
Each macro knows its name and included text. It gets built with a
|
|
list of formal arguments, and also keeps a hash table which points
|
|
into the list to speed up formal search. Each formal knows its
|
|
name and its default value. Each time the macro is expanded, the
|
|
formals get the actual values attatched to them. */
|
|
|
|
/* describe the formal arguments to a macro */
|
|
|
|
typedef struct formal_struct
|
|
{
|
|
struct formal_struct *next; /* next formal in list */
|
|
sb name; /* name of the formal */
|
|
sb def; /* the default value */
|
|
sb actual; /* the actual argument (changed on each expansion) */
|
|
int index; /* the index of the formal 0..formal_count-1 */
|
|
}
|
|
formal_entry;
|
|
|
|
/* describe the macro. */
|
|
|
|
typedef struct macro_struct
|
|
{
|
|
sb sub; /* substitution text. */
|
|
int formal_count; /* number of formal args. */
|
|
formal_entry *formals; /* pointer to list of formal_structs */
|
|
hash_table formal_hash; /* hash table of formals. */
|
|
}
|
|
macro_entry;
|
|
|
|
/* how we nest files and expand macros etc.
|
|
|
|
we keep a stack of of include_stack structs. each include file
|
|
pushes a new level onto the stack. we keep an sb with a pushback
|
|
too. unget chars are pushed onto the pushback sb, getchars first
|
|
checks the pushback sb before reading from the input stream.
|
|
|
|
small things are expanded by adding the text of the item onto the
|
|
pushback sb. larger items are grown by pushing a new level and
|
|
allocating the entire pushback buf for the item. each time
|
|
something like a macro is expanded, the stack index is changed. we
|
|
can then perform an exitm by popping all entries off the stack with
|
|
the same stack index. if we're being reasonable, we can detect
|
|
recusive expansion by checking the index is reasonably small.
|
|
*/
|
|
|
|
typedef enum
|
|
{
|
|
include_file, include_repeat, include_while, include_macro
|
|
} include_type;
|
|
|
|
struct include_stack
|
|
{
|
|
sb pushback; /* current pushback stream */
|
|
int pushback_index; /* next char to read from stream */
|
|
FILE *handle; /* open file */
|
|
sb name; /* name of file */
|
|
int linecount; /* number of lines read so far */
|
|
include_type type;
|
|
int index; /* index of this layer */
|
|
}
|
|
include_stack[MAX_INCLUDES];
|
|
|
|
struct include_stack *sp;
|
|
#define isp (sp - include_stack)
|
|
|
|
#define dsize 5
|
|
|
|
|
|
void include_print_where_line ();
|
|
|
|
|
|
#define FATAL(x) \
|
|
do { include_print_where_line (stderr); fprintf x ; fatals++; quit(); } while(0)
|
|
#define ERROR(x) \
|
|
do { include_print_where_line (stderr); fprintf x; errors++; } while(0)
|
|
#define WARNING(x) \
|
|
do { include_print_where_line (stderr); fprintf x; warnings++;} while(0)
|
|
|
|
|
|
|
|
/* exit the program and return the right ERROR code. */
|
|
void
|
|
quit ()
|
|
{
|
|
int exitcode;
|
|
if (fatals + errors)
|
|
exitcode = 1;
|
|
else
|
|
exitcode = 0;
|
|
|
|
if (stats)
|
|
{
|
|
int i;
|
|
for (i = 0; i < max_power_two; i++)
|
|
{
|
|
fprintf (stderr, "strings size %8d : %d\n", 1<<i, string_count[i]);
|
|
}
|
|
}
|
|
exit (exitcode);
|
|
}
|
|
|
|
|
|
/* this program is about manipulating strings.
|
|
they are managed in things called `sb's which is an abbreviation
|
|
for string buffers. an sb has to be created, things can be glued
|
|
on to it, and at the end of it's life it should be freed. the
|
|
contents should never be pointed at whilst it is still growing,
|
|
since it could be moved at any time
|
|
|
|
eg:
|
|
sb_new (&foo);
|
|
sb_grow... (&foo,...);
|
|
use foo->ptr[*];
|
|
sb_kill (&foo);
|
|
|
|
*/
|
|
|
|
/* initializes an sb. */
|
|
|
|
void
|
|
sb_build (ptr, size)
|
|
sb *ptr;
|
|
int size;
|
|
{
|
|
/* see if we can find one to allocate */
|
|
sb_element *e;
|
|
|
|
if (size > max_power_two)
|
|
{
|
|
FATAL ((stderr, "string longer than %d bytes requested.\n",
|
|
1 << max_power_two));
|
|
}
|
|
e = free_list.size[size];
|
|
if (!e)
|
|
{
|
|
/* nothing there, allocate one and stick into the free list */
|
|
e = (sb_element *) xmalloc (sizeof (sb_element) + (1 << size));
|
|
e->next = free_list.size[size];
|
|
e->size = 1 << size;
|
|
free_list.size[size] = e;
|
|
string_count[size]++;
|
|
}
|
|
|
|
/* remove from free list */
|
|
|
|
free_list.size[size] = e->next;
|
|
|
|
/* copy into callers world */
|
|
ptr->ptr = e->data;
|
|
ptr->pot = size;
|
|
ptr->len = 0;
|
|
ptr->item = e;
|
|
}
|
|
|
|
|
|
static void
|
|
sb_new (ptr)
|
|
sb *ptr;
|
|
{
|
|
sb_build (ptr, dsize);
|
|
}
|
|
|
|
/* deallocate the sb at ptr */
|
|
|
|
static
|
|
void
|
|
sb_kill (ptr)
|
|
sb *ptr;
|
|
{
|
|
/* return item to free list */
|
|
ptr->item->next = free_list.size[ptr->pot];
|
|
free_list.size[ptr->pot] = ptr->item;
|
|
}
|
|
|
|
/* add the sb at s to the end of the sb at ptr */
|
|
|
|
static void sb_check ();
|
|
|
|
static
|
|
void
|
|
sb_add_sb (ptr, s)
|
|
sb *ptr;
|
|
sb *s;
|
|
{
|
|
sb_check (ptr, s->len);
|
|
memcpy (ptr->ptr + ptr->len, s->ptr, s->len);
|
|
ptr->len += s->len;
|
|
}
|
|
|
|
/* make sure that the sb at ptr has room for another len characters,
|
|
and grow it if it doesn't. */
|
|
|
|
static void
|
|
sb_check (ptr, len)
|
|
sb *ptr;
|
|
int len;
|
|
{
|
|
if (ptr->len + len >= 1 << ptr->pot)
|
|
{
|
|
sb tmp;
|
|
int pot = ptr->pot;
|
|
while (ptr->len + len >= 1 << pot)
|
|
pot++;
|
|
sb_build (&tmp, pot);
|
|
sb_add_sb (&tmp, ptr);
|
|
sb_kill (ptr);
|
|
*ptr = tmp;
|
|
}
|
|
}
|
|
|
|
/* make the sb at ptr point back to the beginning. */
|
|
|
|
static void
|
|
sb_reset (ptr)
|
|
sb *ptr;
|
|
{
|
|
ptr->len = 0;
|
|
}
|
|
|
|
/* add character c to the end of the sb at ptr. */
|
|
|
|
void
|
|
sb_add_char (ptr, c)
|
|
sb *ptr;
|
|
char c;
|
|
{
|
|
sb_check (ptr, 1);
|
|
ptr->ptr[ptr->len++] = c;
|
|
}
|
|
|
|
/* add null terminated string s to the end of sb at ptr. */
|
|
|
|
static void
|
|
sb_add_string (ptr, s)
|
|
sb *ptr;
|
|
char *s;
|
|
{
|
|
int len = strlen (s);
|
|
sb_check (ptr, len);
|
|
memcpy (ptr->ptr + ptr->len, s, len);
|
|
ptr->len += len;
|
|
}
|
|
|
|
/* add string at s of length len to sb at ptr */
|
|
|
|
static void
|
|
sb_add_buffer (ptr, s, len)
|
|
sb *ptr;
|
|
char *s;
|
|
int len;
|
|
{
|
|
sb_check (ptr, len);
|
|
memcpy (ptr->ptr + ptr->len, s, len);
|
|
ptr->len += len;
|
|
}
|
|
|
|
|
|
/* print the sb at ptr to the output file */
|
|
|
|
static
|
|
void
|
|
sb_print (ptr)
|
|
sb *ptr;
|
|
{
|
|
int i;
|
|
int nc = 0;
|
|
|
|
for (i = 0; i < ptr->len; i++)
|
|
{
|
|
if (nc)
|
|
{
|
|
fprintf (outfile, ",");
|
|
}
|
|
fprintf (outfile, "%d", ptr->ptr[i]);
|
|
nc = 1;
|
|
}
|
|
}
|
|
|
|
static
|
|
void
|
|
sb_print_at (idx, ptr)
|
|
int idx;
|
|
sb *ptr;
|
|
{
|
|
int i;
|
|
for (i = idx; i < ptr->len; i++)
|
|
putc (ptr->ptr[i], outfile);
|
|
}
|
|
/* put a null at the end of the sb at in and return the start of the
|
|
string, so that it can be used as an arg to printf %s. */
|
|
|
|
static
|
|
char *
|
|
sb_name (in)
|
|
sb *in;
|
|
{
|
|
/* stick a null on the end of the string */
|
|
sb_add_char (in, 0);
|
|
return in->ptr;
|
|
}
|
|
|
|
/* start at the index idx into the string in sb at ptr and skip
|
|
whitespace. return the index of the first non whitespace character */
|
|
|
|
static int
|
|
sb_skip_white (idx, ptr)
|
|
int idx;
|
|
sb *ptr;
|
|
{
|
|
while (idx < ptr->len && ISWHITE (ptr->ptr[idx]))
|
|
idx++;
|
|
return idx;
|
|
}
|
|
|
|
/* start at the index idx into the sb at ptr. skips whitespace,
|
|
a comma and any following whitespace. returnes the index of the
|
|
next character. */
|
|
|
|
static int
|
|
sb_skip_comma (idx, ptr)
|
|
int idx;
|
|
sb *ptr;
|
|
{
|
|
while (idx < ptr->len && ISWHITE (ptr->ptr[idx]))
|
|
idx++;
|
|
|
|
if (idx < ptr->len
|
|
&& ptr->ptr[idx] == ',')
|
|
idx++;
|
|
|
|
while (idx < ptr->len && ISWHITE (ptr->ptr[idx]))
|
|
idx++;
|
|
|
|
return idx;
|
|
}
|
|
|
|
|
|
/* hash table maintenance. */
|
|
|
|
/* build a new hash table with size buckets, and fill in the info at ptr. */
|
|
|
|
static void
|
|
hash_new_table (size, ptr)
|
|
int size;
|
|
hash_table *ptr;
|
|
{
|
|
int i;
|
|
ptr->size = size;
|
|
ptr->table = (hash_entry **) xmalloc (size * (sizeof (hash_entry *)));
|
|
/* Fill with null-pointer, not zero-bit-pattern. */
|
|
for (i = 0; i < size; i++)
|
|
ptr->table[i] = 0;
|
|
}
|
|
|
|
/* calculate and return the hash value of the sb at key. */
|
|
|
|
static int
|
|
hash (key)
|
|
sb *key;
|
|
{
|
|
int k = 0x1234;
|
|
int i;
|
|
char *p = key->ptr;
|
|
for (i = 0; i < key->len; i++)
|
|
{
|
|
k ^= (k << 2) ^ *p;
|
|
p++;
|
|
}
|
|
return k & 0xf0fff;
|
|
}
|
|
|
|
/* lookup key in hash_table tab, if present, then return it, otherwise
|
|
build a new one and fill it with hash_integer. */
|
|
|
|
static
|
|
hash_entry *
|
|
hash_create (tab, key)
|
|
hash_table *tab;
|
|
sb *key;
|
|
{
|
|
int k = hash (key) % tab->size;
|
|
hash_entry *p;
|
|
hash_entry **table = tab->table;
|
|
|
|
p = table[k];
|
|
|
|
while (1)
|
|
{
|
|
if (!p)
|
|
{
|
|
hash_entry *n = (hash_entry *) xmalloc (sizeof (hash_entry));
|
|
n->next = table[k];
|
|
sb_new (&n->key);
|
|
sb_add_sb (&n->key, key);
|
|
table[k] = n;
|
|
n->type = hash_integer;
|
|
return n;
|
|
}
|
|
if (strncmp (table[k]->key.ptr, key->ptr, key->len) == 0)
|
|
{
|
|
return p;
|
|
}
|
|
p = p->next;
|
|
}
|
|
}
|
|
|
|
/* add sb name with key into hash_table tab. if replacing old value
|
|
and again, then ERROR. */
|
|
|
|
static
|
|
void
|
|
hash_add_to_string_table (tab, key, name, again)
|
|
hash_table *tab;
|
|
sb *key;
|
|
sb *name;
|
|
int again;
|
|
{
|
|
hash_entry *ptr = hash_create (tab, key);
|
|
if (ptr->type == hash_integer)
|
|
{
|
|
sb_new (&ptr->value.s);
|
|
}
|
|
if (ptr->value.s.len)
|
|
{
|
|
if (!again)
|
|
ERROR ((stderr, "redefintion not allowed"));
|
|
}
|
|
|
|
ptr->type = hash_string;
|
|
sb_reset (&ptr->value.s);
|
|
|
|
sb_add_sb (&ptr->value.s, name);
|
|
}
|
|
|
|
/* add integer name to hash_table tab with sb key. */
|
|
|
|
static
|
|
void
|
|
hash_add_to_int_table (tab, key, name)
|
|
hash_table *tab;
|
|
sb *key;
|
|
int name;
|
|
{
|
|
hash_entry *ptr = hash_create (tab, key);
|
|
ptr->value.i = name;
|
|
}
|
|
|
|
/* lookup sb key in hash_table tab. if found return hash_entry result,
|
|
else 0. */
|
|
|
|
static
|
|
hash_entry *
|
|
hash_lookup (tab, key)
|
|
hash_table *tab;
|
|
sb *key;
|
|
{
|
|
int k = hash (key) % tab->size;
|
|
hash_entry **table = tab->table;
|
|
hash_entry *p = table[k];
|
|
while (p)
|
|
{
|
|
if (p->key.len == key->len
|
|
&& strncmp (p->key.ptr, key->ptr, key->len) == 0)
|
|
return p;
|
|
p = p->next;
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
/* expressions
|
|
|
|
are handled in a really simple recursive decent way. each bit of
|
|
the machine takes an index into an sb and a pointer to an exp_t,
|
|
modifies the *exp_t and returns the index of the first character
|
|
past the part of the expression parsed.
|
|
|
|
expression precedence:
|
|
( )
|
|
unary + - ~
|
|
* /
|
|
+ -
|
|
&
|
|
| ~
|
|
|
|
*/
|
|
|
|
|
|
/* make sure that the exp_t at term is constant, if not the give the op ERROR. */
|
|
|
|
static
|
|
void
|
|
checkconst (op, term)
|
|
char op;
|
|
exp_t *term;
|
|
{
|
|
if (term->add_symbol.len
|
|
|| term->sub_symbol.len)
|
|
{
|
|
ERROR ((stderr, "the %c operator cannot take non-absolute arguments.\n", op));
|
|
}
|
|
}
|
|
|
|
/* turn the number in string at idx into a number of base,
|
|
fill in ptr and return the index of the first character not in the
|
|
number. */
|
|
|
|
static
|
|
int
|
|
sb_strtol (idx, string, base, ptr)
|
|
int idx;
|
|
sb *string;
|
|
int base;
|
|
int *ptr;
|
|
{
|
|
int value = 0;
|
|
idx = sb_skip_white (idx, string);
|
|
|
|
while (idx < string->len)
|
|
{
|
|
int ch = string->ptr[idx];
|
|
int dig = 0;
|
|
if (isdigit (ch))
|
|
dig = ch - '0';
|
|
else if (ch >= 'a' && ch <= 'f')
|
|
dig = ch - 'a' + 10;
|
|
else if (ch >= 'A' && ch <= 'F')
|
|
dig = ch - 'A' + 10;
|
|
else
|
|
break;
|
|
|
|
if (dig >= base)
|
|
break;
|
|
|
|
value = value * base + dig;
|
|
idx++;
|
|
}
|
|
*ptr = value;
|
|
return idx;
|
|
}
|
|
|
|
static int level_5 ();
|
|
|
|
int
|
|
level_0 (idx, string, lhs)
|
|
int idx;
|
|
sb *string;
|
|
exp_t *lhs;
|
|
{
|
|
lhs->add_symbol.len = 0;
|
|
lhs->add_symbol.name = 0;
|
|
|
|
lhs->sub_symbol.len = 0;
|
|
lhs->sub_symbol.name = 0;
|
|
|
|
idx = sb_skip_white (idx, string);
|
|
|
|
lhs->value = 0;
|
|
|
|
if (isdigit (string->ptr[idx]))
|
|
{
|
|
idx = sb_strtol (idx, string, 10, &lhs->value);
|
|
}
|
|
else if (ISFIRSTCHAR (string->ptr[idx]))
|
|
{
|
|
int len = 0;
|
|
lhs->add_symbol.name = string->ptr + idx;
|
|
while (idx < string->len && ISNEXTCHAR (string->ptr[idx]))
|
|
{
|
|
idx++;
|
|
len++;
|
|
}
|
|
lhs->add_symbol.len = len;
|
|
}
|
|
else if (string->ptr[idx] == '"')
|
|
{
|
|
sb acc;
|
|
sb_new (&acc);
|
|
ERROR ((stderr, "string where expression expected.\n"));
|
|
idx = getstring (idx, string, &acc);
|
|
sb_kill (&acc);
|
|
}
|
|
else
|
|
{
|
|
ERROR ((stderr, "can't find primary in expression.\n"));
|
|
idx++;
|
|
}
|
|
return sb_skip_white (idx, string);
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
level_1 (idx, string, lhs)
|
|
int idx;
|
|
sb *string;
|
|
exp_t *lhs;
|
|
{
|
|
idx = sb_skip_white (idx, string);
|
|
|
|
switch (string->ptr[idx])
|
|
{
|
|
case '+':
|
|
idx = level_1 (idx + 1, string, lhs);
|
|
break;
|
|
case '~':
|
|
idx = level_1 (idx + 1, string, lhs);
|
|
checkconst ('~', lhs);
|
|
lhs->value = ~lhs->value;
|
|
break;
|
|
case '-':
|
|
{
|
|
symbol t;
|
|
idx = level_1 (idx + 1, string, lhs);
|
|
lhs->value = -lhs->value;
|
|
t = lhs->add_symbol;
|
|
lhs->add_symbol = lhs->sub_symbol;
|
|
lhs->sub_symbol = t;
|
|
break;
|
|
}
|
|
case '(':
|
|
idx++;
|
|
idx = level_5 (sb_skip_white (idx, string), string, lhs);
|
|
if (string->ptr[idx] != ')')
|
|
ERROR ((stderr, "misplaced closing parens.\n"));
|
|
else
|
|
idx++;
|
|
break;
|
|
default:
|
|
idx = level_0 (idx, string, lhs);
|
|
break;
|
|
}
|
|
return sb_skip_white (idx, string);
|
|
}
|
|
|
|
static int
|
|
level_2 (idx, string, lhs)
|
|
int idx;
|
|
sb *string;
|
|
exp_t *lhs;
|
|
{
|
|
exp_t rhs;
|
|
|
|
idx = level_1 (idx, string, lhs);
|
|
|
|
while (idx < string->len && (string->ptr[idx] == '*'
|
|
|| string->ptr[idx] == '/'))
|
|
{
|
|
char op = string->ptr[idx++];
|
|
idx = level_1 (idx, string, &rhs);
|
|
switch (op)
|
|
{
|
|
case '*':
|
|
checkconst ('*', lhs);
|
|
checkconst ('*', &rhs);
|
|
lhs->value *= rhs.value;
|
|
break;
|
|
case '/':
|
|
checkconst ('/', lhs);
|
|
checkconst ('/', &rhs);
|
|
if (rhs.value == 0)
|
|
ERROR ((stderr, "attempt to divide by zero.\n"));
|
|
else
|
|
lhs->value /= rhs.value;
|
|
break;
|
|
}
|
|
}
|
|
return sb_skip_white (idx, string);
|
|
}
|
|
|
|
|
|
static int
|
|
level_3 (idx, string, lhs)
|
|
int idx;
|
|
sb *string;
|
|
exp_t *lhs;
|
|
{
|
|
exp_t rhs;
|
|
|
|
idx = level_2 (idx, string, lhs);
|
|
|
|
while (idx < string->len
|
|
&& (string->ptr[idx] == '+'
|
|
|| string->ptr[idx] == '-'))
|
|
{
|
|
char op = string->ptr[idx++];
|
|
idx = level_2 (idx, string, &rhs);
|
|
switch (op)
|
|
{
|
|
case '+':
|
|
lhs->value += rhs.value;
|
|
if (lhs->add_symbol.name && rhs.add_symbol.name)
|
|
{
|
|
ERROR ((stderr, "can't add two relocatable expressions\n"));
|
|
}
|
|
/* change nn+symbol to symbol + nn */
|
|
if (rhs.add_symbol.name)
|
|
{
|
|
lhs->add_symbol = rhs.add_symbol;
|
|
}
|
|
break;
|
|
case '-':
|
|
lhs->value -= rhs.value;
|
|
lhs->sub_symbol = rhs.add_symbol;
|
|
break;
|
|
}
|
|
}
|
|
return sb_skip_white (idx, string);
|
|
}
|
|
|
|
static int
|
|
level_4 (idx, string, lhs)
|
|
int idx;
|
|
sb *string;
|
|
exp_t *lhs;
|
|
{
|
|
exp_t rhs;
|
|
|
|
idx = level_3 (idx, string, lhs);
|
|
|
|
while (idx < string->len &&
|
|
string->ptr[idx] == '&')
|
|
{
|
|
char op = string->ptr[idx++];
|
|
idx = level_3 (idx, string, &rhs);
|
|
switch (op)
|
|
{
|
|
case '&':
|
|
checkconst ('&', lhs);
|
|
checkconst ('&', &rhs);
|
|
lhs->value &= rhs.value;
|
|
break;
|
|
}
|
|
}
|
|
return sb_skip_white (idx, string);
|
|
}
|
|
|
|
static int
|
|
level_5 (idx, string, lhs)
|
|
int idx;
|
|
sb *string;
|
|
exp_t *lhs;
|
|
{
|
|
exp_t rhs;
|
|
|
|
idx = level_4 (idx, string, lhs);
|
|
|
|
while (idx < string->len
|
|
&& (string->ptr[idx] == '|' || string->ptr[idx] == '~'))
|
|
{
|
|
char op = string->ptr[idx++];
|
|
idx = level_4 (idx, string, &rhs);
|
|
switch (op)
|
|
{
|
|
case '|':
|
|
checkconst ('|', lhs);
|
|
checkconst ('|', &rhs);
|
|
lhs->value |= rhs.value;
|
|
break;
|
|
case '~':
|
|
checkconst ('~', lhs);
|
|
checkconst ('~', &rhs);
|
|
lhs->value ^= rhs.value;
|
|
break;
|
|
}
|
|
}
|
|
return sb_skip_white (idx, string);
|
|
}
|
|
|
|
|
|
/* parse the expression at offset idx into string, fill up res with
|
|
the result. return the index of the first char past the expression.
|
|
*/
|
|
|
|
static int
|
|
exp_parse (idx, string, res)
|
|
int idx;
|
|
sb *string;
|
|
exp_t *res;
|
|
{
|
|
return level_5 (sb_skip_white (idx, string), string, res);
|
|
}
|
|
|
|
|
|
/* turn the expression at exp into text and glue it onto the end of
|
|
string. */
|
|
|
|
static void
|
|
exp_string (exp, string)
|
|
exp_t *exp;
|
|
sb *string;
|
|
{
|
|
int np = 0;
|
|
int ad = 0;
|
|
sb_reset (string);
|
|
|
|
if (exp->add_symbol.len)
|
|
{
|
|
sb_add_buffer (string, exp->add_symbol.name, exp->add_symbol.len);
|
|
np = 1;
|
|
ad = 1;
|
|
}
|
|
if (exp->value)
|
|
{
|
|
char buf[20];
|
|
if (np)
|
|
sb_add_char (string, '+');
|
|
sprintf (buf, "%d", exp->value);
|
|
sb_add_string (string, buf);
|
|
np = 1;
|
|
ad = 1;
|
|
}
|
|
if (exp->sub_symbol.len)
|
|
{
|
|
sb_add_char (string, '-');
|
|
sb_add_buffer (string, exp->add_symbol.name, exp->add_symbol.len);
|
|
np = 0;
|
|
ad = 1;
|
|
}
|
|
|
|
if (!ad)
|
|
sb_add_char (string, '0');
|
|
}
|
|
|
|
|
|
/* parse the expression at offset idx into sb in, return the value in val.
|
|
if the expression is not constant, give ERROR emsg. returns the index
|
|
of the first character past the end of the expression. */
|
|
|
|
static int
|
|
exp_get_abs (emsg, idx, in, val)
|
|
char *emsg;
|
|
int idx;
|
|
sb *in;
|
|
int *val;
|
|
{
|
|
exp_t res;
|
|
idx = exp_parse (idx, in, &res);
|
|
if (res.add_symbol.len || res.sub_symbol.len)
|
|
ERROR ((stderr, emsg));
|
|
*val = res.value;
|
|
return idx;
|
|
}
|
|
|
|
|
|
sb label; /* current label parsed from line */
|
|
hash_table assign_hash_table; /* hash table for all assigned variables */
|
|
hash_table keyword_hash_table; /* hash table for keyword */
|
|
hash_table vars; /* hash table for eq variables */
|
|
|
|
#define in_comment ';'
|
|
|
|
#if 1
|
|
void
|
|
strip_comments (out)
|
|
sb *out;
|
|
{
|
|
char *s = out->ptr;
|
|
int i = 0;
|
|
for (i = 0; i < out->len; i++)
|
|
{
|
|
if (ISCOMMENTCHAR(s[i]))
|
|
{
|
|
out->len = i;
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
#endif
|
|
|
|
/* push back character ch so that it can be read again. */
|
|
|
|
void
|
|
unget (ch)
|
|
int ch;
|
|
{
|
|
if (ch == '\n')
|
|
{
|
|
sp->linecount--;
|
|
}
|
|
if (sp->pushback_index)
|
|
sp->pushback_index--;
|
|
else
|
|
sb_add_char (&sp->pushback, ch);
|
|
}
|
|
|
|
/* push the sb ptr onto the include stack, with the given name, type and index. */
|
|
|
|
static
|
|
void
|
|
include_buf (name, ptr, type, index)
|
|
sb *name;
|
|
sb *ptr;
|
|
include_type type;
|
|
int index;
|
|
{
|
|
sp++;
|
|
if (sp - include_stack >= MAX_INCLUDES)
|
|
FATAL ((stderr, "unreasonable nesting.\n"));
|
|
sb_new (&sp->name);
|
|
sb_add_sb (&sp->name, name);
|
|
sp->handle = 0;
|
|
sp->linecount = 1;
|
|
sp->pushback_index = 0;
|
|
sp->type = type;
|
|
sp->index = index;
|
|
sb_new (&sp->pushback);
|
|
sb_add_sb (&sp->pushback, ptr);
|
|
}
|
|
|
|
|
|
/* used in ERROR messages, print info on where the include stack is onto file. */
|
|
static
|
|
void
|
|
include_print_where_line (file)
|
|
FILE *file;
|
|
{
|
|
struct include_stack *p = include_stack + 1;
|
|
|
|
while (p <= sp)
|
|
{
|
|
fprintf (file, "%s:%d ", sb_name (&p->name), p->linecount - ((p == sp) ? 1 : 0));
|
|
p++;
|
|
}
|
|
}
|
|
|
|
/* used in listings, print the line number onto file. */
|
|
static void
|
|
include_print_line (file)
|
|
FILE *file;
|
|
{
|
|
int n;
|
|
struct include_stack *p = include_stack + 1;
|
|
|
|
n = fprintf (file, "%4d", p->linecount);
|
|
p++;
|
|
while (p <= sp)
|
|
{
|
|
n += fprintf (file, ".%d", p->linecount);
|
|
p++;
|
|
}
|
|
while (n < 8 * 3)
|
|
{
|
|
fprintf (file, " ");
|
|
n++;
|
|
}
|
|
}
|
|
|
|
|
|
/* read a line from the top of the include stack into sb in. */
|
|
|
|
static int
|
|
get_line (in)
|
|
sb *in;
|
|
{
|
|
int online = 0;
|
|
int more = 1;
|
|
|
|
if (copysource)
|
|
{
|
|
putc (comment_char, outfile);
|
|
if (print_line_number)
|
|
include_print_line (outfile);
|
|
}
|
|
|
|
while (1)
|
|
{
|
|
int ch = get ();
|
|
|
|
while (ch == '\r')
|
|
ch = get ();
|
|
|
|
if (ch == EOF)
|
|
{
|
|
if (online)
|
|
{
|
|
WARNING ((stderr, "End of file not at start of line.\n"));
|
|
if (copysource)
|
|
putc ('\n', outfile);
|
|
ch = '\n';
|
|
}
|
|
else
|
|
more = 0;
|
|
break;
|
|
}
|
|
|
|
if (copysource)
|
|
{
|
|
putc (ch, outfile);
|
|
}
|
|
|
|
if (ch == '\n')
|
|
{
|
|
ch = get ();
|
|
online = 0;
|
|
if (ch == '+')
|
|
{
|
|
/* continued line */
|
|
if (copysource)
|
|
{
|
|
putc (comment_char, outfile);
|
|
putc ('+', outfile);
|
|
}
|
|
ch = get ();
|
|
}
|
|
else
|
|
{
|
|
if (ch != EOF)
|
|
unget (ch);
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
sb_add_char (in, ch);
|
|
}
|
|
online++;
|
|
}
|
|
|
|
return more;
|
|
}
|
|
|
|
/* find a label from sb in and put it in out. */
|
|
|
|
static int
|
|
grab_label (in, out)
|
|
sb *in;
|
|
sb *out;
|
|
{
|
|
int i = 0;
|
|
sb_reset (out);
|
|
if (ISFIRSTCHAR (in->ptr[i]))
|
|
{
|
|
sb_add_char (out, in->ptr[i]);
|
|
i++;
|
|
while ((ISNEXTCHAR (in->ptr[i])
|
|
|| in->ptr[i] == '\\'
|
|
|| in->ptr[i] == '&')
|
|
&& i < in->len)
|
|
{
|
|
sb_add_char (out, in->ptr[i]);
|
|
i++;
|
|
}
|
|
}
|
|
return i;
|
|
}
|
|
|
|
/* find all strange base stuff and turn into decimal. also
|
|
find all the other numbers and convert them from the default radix */
|
|
|
|
static void
|
|
change_base (idx, in, out)
|
|
int idx;
|
|
sb *in;
|
|
sb *out;
|
|
{
|
|
char buffer[20];
|
|
|
|
while (idx < in->len)
|
|
{
|
|
if (idx < in->len - 1 && in->ptr[idx + 1] == '\'')
|
|
{
|
|
int base;
|
|
int value;
|
|
switch (in->ptr[idx])
|
|
{
|
|
case 'b':
|
|
case 'B':
|
|
base = 2;
|
|
break;
|
|
case 'q':
|
|
case 'Q':
|
|
base = 8;
|
|
break;
|
|
case 'h':
|
|
case 'H':
|
|
base = 16;
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
base = 10;
|
|
break;
|
|
default:
|
|
ERROR ((stderr, "Illegal base character %c.\n", in->ptr[idx]));
|
|
base = 10;
|
|
break;
|
|
}
|
|
|
|
idx = sb_strtol (idx + 2, in, base, &value);
|
|
sprintf (buffer, "%d", value);
|
|
sb_add_string (out, buffer);
|
|
}
|
|
else if (ISFIRSTCHAR (in->ptr[idx]))
|
|
{
|
|
/* copy entire names through quickly */
|
|
sb_add_char (out, in->ptr[idx]);
|
|
idx++;
|
|
while (idx < in->len && ISNEXTCHAR (in->ptr[idx]))
|
|
{
|
|
sb_add_char (out, in->ptr[idx]);
|
|
idx++;
|
|
}
|
|
}
|
|
else if (isdigit (in->ptr[idx]))
|
|
{
|
|
int value;
|
|
/* all numbers must start with a digit, let's chew it and
|
|
spit out decimal */
|
|
idx = sb_strtol (idx, in, radix, &value);
|
|
sprintf (buffer, "%d", value);
|
|
sb_add_string (out, buffer);
|
|
|
|
/* skip all undigsested letters */
|
|
while (idx < in->len && ISNEXTCHAR (in->ptr[idx]))
|
|
{
|
|
sb_add_char (out, in->ptr[idx]);
|
|
idx++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* nothing special, just pass it through */
|
|
sb_add_char (out, in->ptr[idx]);
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
/* .end */
|
|
static void
|
|
do_end ()
|
|
{
|
|
had_end = 1;
|
|
}
|
|
|
|
/* .assign */
|
|
|
|
static void
|
|
do_assign (again, idx, in)
|
|
int again;
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
/* stick label in symbol table with following value */
|
|
exp_t e;
|
|
sb acc;
|
|
|
|
sb_new (&acc);
|
|
idx = exp_parse (idx, in, &e);
|
|
exp_string (&e, &acc);
|
|
hash_add_to_string_table (&assign_hash_table, &label, &acc, again);
|
|
sb_kill (&acc);
|
|
}
|
|
|
|
|
|
/* .radix [b|q|d|h] */
|
|
|
|
static
|
|
void
|
|
do_radix (ptr)
|
|
sb *ptr;
|
|
{
|
|
int idx = sb_skip_white (0, ptr);
|
|
switch (ptr->ptr[idx])
|
|
{
|
|
case 'B':
|
|
case 'b':
|
|
radix = 2;
|
|
break;
|
|
case 'q':
|
|
case 'Q':
|
|
radix = 8;
|
|
break;
|
|
case 'd':
|
|
case 'D':
|
|
radix = 10;
|
|
break;
|
|
case 'h':
|
|
case 'H':
|
|
radix = 16;
|
|
break;
|
|
default:
|
|
ERROR ((stderr, "radix is %c must be one of b, q, d or h", radix));
|
|
}
|
|
}
|
|
|
|
|
|
/* Parse off a .b, .w or .l */
|
|
|
|
static int
|
|
get_opsize (idx, in, size)
|
|
int idx;
|
|
sb *in;
|
|
int *size;
|
|
{
|
|
*size = 4;
|
|
if (in->ptr[idx] == '.')
|
|
{
|
|
idx++;
|
|
}
|
|
switch (in->ptr[idx])
|
|
{
|
|
case 'b':
|
|
case 'B':
|
|
*size = 1;
|
|
break;
|
|
case 'w':
|
|
case 'W':
|
|
*size = 2;
|
|
break;
|
|
case 'l':
|
|
case 'L':
|
|
*size = 4;
|
|
break;
|
|
case ' ':
|
|
case '\t':
|
|
break;
|
|
default:
|
|
ERROR ((stderr, "size must be one of b, w or l, is %c.\n", in->ptr[idx]));
|
|
break;
|
|
}
|
|
idx++;
|
|
|
|
return idx;
|
|
}
|
|
|
|
static
|
|
int eol(idx, line)
|
|
int idx;
|
|
sb *line;
|
|
{
|
|
idx = sb_skip_white (idx, line);
|
|
if (idx < line->len
|
|
&& ISCOMMENTCHAR(line->ptr[idx]))
|
|
return 1;
|
|
if (idx >= line->len)
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
/* .data [.b|.w|.l] <data>*
|
|
or d[bwl] <data>* */
|
|
|
|
static void
|
|
do_data (idx, in, size)
|
|
int idx;
|
|
sb *in;
|
|
int size;
|
|
{
|
|
int opsize = 4;
|
|
char *opname;
|
|
sb acc;
|
|
sb_new (&acc);
|
|
|
|
if (!size)
|
|
{
|
|
idx = get_opsize (idx, in, &opsize);
|
|
}
|
|
else {
|
|
opsize = size;
|
|
}
|
|
switch (opsize)
|
|
{
|
|
case 4:
|
|
opname = ".long";
|
|
break;
|
|
case 2:
|
|
opname = ".short";
|
|
break;
|
|
case 1:
|
|
opname = ".byte";
|
|
break;
|
|
}
|
|
|
|
|
|
fprintf (outfile, "%s\t", opname);
|
|
|
|
idx = sb_skip_white (idx, in);
|
|
|
|
if (alternate
|
|
&& idx < in->len
|
|
&& in->ptr[idx] == '"')
|
|
{
|
|
int i;
|
|
idx = getstring (idx, in, &acc);
|
|
for (i = 0; i < acc.len; i++)
|
|
{
|
|
if (i)
|
|
fprintf(outfile,",");
|
|
fprintf (outfile, "%d", acc.ptr[i]);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (!eol (idx, in))
|
|
{
|
|
exp_t e;
|
|
idx = exp_parse (idx, in, &e);
|
|
exp_string (&e, &acc);
|
|
sb_add_char (&acc, 0);
|
|
fprintf (outfile, acc.ptr);
|
|
if (idx < in->len && in->ptr[idx] == ',')
|
|
{
|
|
fprintf (outfile, ",");
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
sb_kill (&acc);
|
|
sb_print_at (idx, in);
|
|
fprintf (outfile, "\n");
|
|
}
|
|
|
|
/* .datab [.b|.w|.l] <repeat>,<fill> */
|
|
|
|
static void
|
|
do_datab (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
int opsize;
|
|
int repeat;
|
|
int fill;
|
|
|
|
idx = get_opsize (idx, in, &opsize);
|
|
|
|
idx = exp_get_abs ("datab repeat must be constant.\n", idx, in, &repeat);
|
|
idx = sb_skip_comma (idx, in);
|
|
idx = exp_get_abs ("datab data must be absolute.\n", idx, in, &fill);
|
|
|
|
fprintf (outfile, ".fill\t%d,%d,%d\n", repeat, opsize, fill);
|
|
}
|
|
|
|
/* .align <size> */
|
|
|
|
void
|
|
do_align (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
int al;
|
|
idx = exp_get_abs ("align needs absolute expression.\n", idx, in, &al);
|
|
|
|
if (al != 1
|
|
&& al != 2
|
|
&& al != 4)
|
|
WARNING ((stderr, "alignment must be one of 1, 2 or 4.\n"));
|
|
|
|
fprintf (outfile, ".align %d\n", al);
|
|
}
|
|
|
|
/* .res[.b|.w|.l] <size> */
|
|
|
|
void
|
|
do_res (idx, in, type)
|
|
int idx;
|
|
sb *in;
|
|
char type;
|
|
{
|
|
int size = 4;
|
|
int count = 0;
|
|
|
|
idx = get_opsize (idx, in, &size);
|
|
while (!eol(idx, in))
|
|
{
|
|
idx = sb_skip_white (idx, in);
|
|
if (in->ptr[idx] == ',')
|
|
idx++;
|
|
idx = exp_get_abs ("res needs absolute expression for fill count.\n", idx, in, &count);
|
|
|
|
if (type == 'c' || type == 'z')
|
|
count++;
|
|
|
|
fprintf (outfile, ".space %d\n", count * size);
|
|
}
|
|
}
|
|
|
|
|
|
/* .export */
|
|
|
|
void
|
|
do_export (in)
|
|
sb *in;
|
|
{
|
|
fprintf (outfile, ".global %s\n", sb_name (in));
|
|
}
|
|
|
|
/* .print [list] [nolist] */
|
|
|
|
void
|
|
do_print (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
idx = sb_skip_white (idx, in);
|
|
while (idx < in->len)
|
|
{
|
|
if (strncmp (in->ptr + idx, "LIST", 4) == 0)
|
|
{
|
|
fprintf (outfile, ".list\n");
|
|
idx += 4;
|
|
}
|
|
else if (strncmp (in->ptr + idx, "NOLIST", 6) == 0)
|
|
{
|
|
fprintf (outfile, ".nolist\n");
|
|
idx += 6;
|
|
}
|
|
idx++;
|
|
}
|
|
}
|
|
|
|
/* .head */
|
|
void
|
|
do_heading (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
sb head;
|
|
sb_new (&head);
|
|
idx = getstring (idx, in, &head);
|
|
fprintf (outfile, ".title \"%s\"\n", sb_name (&head));
|
|
sb_kill (&head);
|
|
}
|
|
|
|
/* .page */
|
|
|
|
void
|
|
do_page ()
|
|
{
|
|
fprintf (outfile, ".eject\n");
|
|
}
|
|
|
|
/* .form [lin=<value>] [col=<value>] */
|
|
void
|
|
do_form (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
int lines = 60;
|
|
int columns = 132;
|
|
idx = sb_skip_white (idx, in);
|
|
|
|
while (idx < in->len)
|
|
{
|
|
|
|
if (strncmp (in->ptr + idx, "LIN=", 4) == 0)
|
|
{
|
|
idx += 4;
|
|
idx = exp_get_abs ("form LIN= needs absolute expresssion.\n", idx, in, &lines);
|
|
}
|
|
|
|
if (strncmp (in->ptr + idx, "COL=", 4) == 0)
|
|
{
|
|
idx += 4;
|
|
idx = exp_get_abs ("form COL= needs absolute expresssion.\n", idx, in, &columns);
|
|
}
|
|
|
|
idx++;
|
|
}
|
|
fprintf (outfile, ".psize %d,%d\n", lines, columns);
|
|
|
|
}
|
|
|
|
int
|
|
get_any_string (idx, in, out)
|
|
int idx;
|
|
sb *in;
|
|
sb *out;
|
|
{
|
|
sb_reset (out);
|
|
idx = sb_skip_white (idx, in);
|
|
|
|
|
|
if (idx < in->len)
|
|
{
|
|
if (in->ptr[idx] == '%'
|
|
&& alternate)
|
|
{
|
|
int val;
|
|
char buf[20];
|
|
/* Turns the next expression into a string */
|
|
idx = exp_get_abs ("% operator needs absolute expression",
|
|
idx + 1,
|
|
in,
|
|
&val);
|
|
sprintf(buf, "\"%d\"", val);
|
|
sb_add_string (out, buf);
|
|
}
|
|
else if (in->ptr[idx] == '"'
|
|
|| in->ptr[idx] == '<'
|
|
|| (alternate && in->ptr[idx] == '\''))
|
|
{
|
|
if (alternate)
|
|
{
|
|
/* Keep the quotes */
|
|
sb_add_char (out, '\"');
|
|
idx = getstring (idx, in, out);
|
|
sb_add_char (out, '\"');
|
|
|
|
}
|
|
else {
|
|
idx = getstring (idx, in, out);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
while (idx < in->len
|
|
&& (in->ptr[idx] == '"'
|
|
|| in->ptr[idx] == '\''
|
|
|| !ISSEP (in->ptr[idx])))
|
|
{
|
|
if (in->ptr[idx] == '"'
|
|
|| in->ptr[idx] == '\'')
|
|
{
|
|
char tchar = in->ptr[idx];
|
|
sb_add_char (out, in->ptr[idx++]);
|
|
while (idx < in->len
|
|
&& in->ptr[idx] != tchar)
|
|
sb_add_char (out, in->ptr[idx++]);
|
|
}
|
|
sb_add_char (out, in->ptr[idx++]);
|
|
|
|
}
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
|
|
/* skip along sb in starting at idx, suck off whitespace a ( and more
|
|
whitespace. return the idx of the next char */
|
|
|
|
int
|
|
skip_openp (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
idx = sb_skip_white (idx, in);
|
|
if (in->ptr[idx] != '(')
|
|
ERROR ((stderr, "misplaced ( .\n"));
|
|
idx = sb_skip_white (idx + 1, in);
|
|
return idx;
|
|
}
|
|
|
|
/* skip along sb in starting at idx, suck off whitespace a ) and more
|
|
whitespace. return the idx of the next char */
|
|
|
|
int
|
|
skip_closep (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
idx = sb_skip_white (idx, in);
|
|
if (in->ptr[idx] != ')')
|
|
ERROR ((stderr, "misplaced ).\n"));
|
|
idx = sb_skip_white (idx + 1, in);
|
|
return idx;
|
|
}
|
|
|
|
/* .len */
|
|
|
|
int
|
|
dolen (idx, in, out)
|
|
int idx;
|
|
sb *in;
|
|
sb *out;
|
|
{
|
|
|
|
sb stringout;
|
|
char buffer[10];
|
|
|
|
sb_new (&stringout);
|
|
idx = skip_openp (idx, in);
|
|
idx = get_and_process (idx, in, &stringout);
|
|
idx = skip_closep (idx, in);
|
|
sprintf (buffer, "%d", stringout.len);
|
|
sb_add_string (out, buffer);
|
|
|
|
sb_kill (&stringout);
|
|
return idx;
|
|
}
|
|
|
|
|
|
/* .instr */
|
|
|
|
static
|
|
int
|
|
doinstr (idx, in, out)
|
|
int idx;
|
|
sb *in;
|
|
sb *out;
|
|
{
|
|
sb string;
|
|
sb search;
|
|
int i;
|
|
int start;
|
|
int res;
|
|
char buffer[10];
|
|
|
|
sb_new (&string);
|
|
sb_new (&search);
|
|
idx = skip_openp (idx, in);
|
|
idx = get_and_process (idx, in, &string);
|
|
idx = sb_skip_comma (idx, in);
|
|
idx = get_and_process (idx, in, &search);
|
|
idx = sb_skip_comma (idx, in);
|
|
if (isdigit (in->ptr[idx]))
|
|
{
|
|
idx = exp_get_abs (".instr needs absolute expresson.\n", idx, in, &start);
|
|
}
|
|
else
|
|
{
|
|
start = 0;
|
|
}
|
|
idx = skip_closep (idx, in);
|
|
res = -1;
|
|
for (i = start; i < string.len; i++)
|
|
{
|
|
if (strncmp (string.ptr + i, search.ptr, search.len) == 0)
|
|
{
|
|
res = i;
|
|
break;
|
|
}
|
|
}
|
|
sprintf (buffer, "%d", res);
|
|
sb_add_string (out, buffer);
|
|
sb_kill (&string);
|
|
sb_kill (&search);
|
|
return idx;
|
|
}
|
|
|
|
|
|
static int
|
|
dosubstr (idx, in, out)
|
|
int idx;
|
|
sb *in;
|
|
sb *out;
|
|
{
|
|
sb string;
|
|
int pos;
|
|
int len;
|
|
sb_new (&string);
|
|
|
|
idx = skip_openp (idx, in);
|
|
idx = get_and_process (idx, in, &string);
|
|
idx = sb_skip_comma (idx, in);
|
|
idx = exp_get_abs ("need absolute position.\n", idx, in, &pos);
|
|
idx = sb_skip_comma (idx, in);
|
|
idx = exp_get_abs ("need absolute length.\n", idx, in, &len);
|
|
idx = skip_closep (idx, in);
|
|
|
|
|
|
if (len < 0 || pos < 0 ||
|
|
pos > string.len
|
|
|| pos + len > string.len)
|
|
{
|
|
sb_add_string (out, " ");
|
|
}
|
|
else
|
|
{
|
|
sb_add_char (out, '"');
|
|
while (len > 0)
|
|
{
|
|
sb_add_char (out, string.ptr[pos++]);
|
|
len--;
|
|
}
|
|
sb_add_char (out, '"');
|
|
}
|
|
sb_kill(&string);
|
|
return idx;
|
|
}
|
|
|
|
/* scan line, change tokens in the hash table to their replacements */
|
|
void
|
|
process_assigns (idx, in, buf)
|
|
int idx;
|
|
sb *in;
|
|
sb *buf;
|
|
{
|
|
while (idx < in->len)
|
|
{
|
|
hash_entry *ptr;
|
|
if (in->ptr[idx] == '\\'
|
|
&& in->ptr[idx + 1] == '&')
|
|
{
|
|
idx = condass_lookup_name (in, idx + 2, buf, 1);
|
|
}
|
|
else if (in->ptr[idx] == '\\'
|
|
&& in->ptr[idx + 1] == '$')
|
|
{
|
|
idx = condass_lookup_name (in, idx + 2, buf, 0);
|
|
}
|
|
else if (idx + 3 < in->len
|
|
&& in->ptr[idx] == '.'
|
|
&& in->ptr[idx + 1] == 'L'
|
|
&& in->ptr[idx + 2] == 'E'
|
|
&& in->ptr[idx + 3] == 'N')
|
|
idx = dolen (idx + 4, in, buf);
|
|
else if (idx + 6 < in->len
|
|
&& in->ptr[idx] == '.'
|
|
&& in->ptr[idx + 1] == 'I'
|
|
&& in->ptr[idx + 2] == 'N'
|
|
&& in->ptr[idx + 3] == 'S'
|
|
&& in->ptr[idx + 4] == 'T'
|
|
&& in->ptr[idx + 5] == 'R')
|
|
idx = doinstr (idx + 6, in, buf);
|
|
else if (idx + 7 < in->len
|
|
&& in->ptr[idx] == '.'
|
|
&& in->ptr[idx + 1] == 'S'
|
|
&& in->ptr[idx + 2] == 'U'
|
|
&& in->ptr[idx + 3] == 'B'
|
|
&& in->ptr[idx + 4] == 'S'
|
|
&& in->ptr[idx + 5] == 'T'
|
|
&& in->ptr[idx + 6] == 'R')
|
|
idx = dosubstr (idx + 7, in, buf);
|
|
else if (ISFIRSTCHAR (in->ptr[idx]))
|
|
{
|
|
/* may be a simple name subsitution, see if we have a word */
|
|
sb acc;
|
|
int cur = idx + 1;
|
|
while (cur < in->len
|
|
&& (ISNEXTCHAR (in->ptr[cur])))
|
|
cur++;
|
|
|
|
sb_new (&acc);
|
|
sb_add_buffer (&acc, in->ptr + idx, cur - idx);
|
|
ptr = hash_lookup (&assign_hash_table, &acc);
|
|
if (ptr)
|
|
{
|
|
/* Found a definition for it */
|
|
sb_add_sb (buf, &ptr->value.s);
|
|
}
|
|
else
|
|
{
|
|
/* No definition, just copy the word */
|
|
sb_add_sb (buf, &acc);
|
|
}
|
|
sb_kill (&acc);
|
|
idx = cur;
|
|
}
|
|
else
|
|
{
|
|
sb_add_char (buf, in->ptr[idx++]);
|
|
}
|
|
}
|
|
}
|
|
|
|
static int
|
|
get_and_process (idx, in, out)
|
|
int idx;
|
|
sb *in;
|
|
sb *out;
|
|
{
|
|
sb t;
|
|
sb_new (&t);
|
|
idx = get_any_string (idx, in, &t);
|
|
process_assigns (0, &t, out);
|
|
sb_kill (&t);
|
|
return idx;
|
|
}
|
|
|
|
static
|
|
void
|
|
process_file ()
|
|
{
|
|
sb line;
|
|
sb t1, t2;
|
|
sb acc;
|
|
sb label_in;
|
|
int more;
|
|
|
|
sb_new (&line);
|
|
sb_new (&t1);
|
|
sb_new (&t2);
|
|
sb_new(&acc);
|
|
sb_new (&label_in);
|
|
sb_reset (&line);
|
|
more = get_line (&line);
|
|
while (more)
|
|
{
|
|
/* Find any label and pseudo op that we're intested in */
|
|
int l;
|
|
if (line.len == 0)
|
|
{
|
|
if (condass_on ())
|
|
fprintf (outfile, "\n");
|
|
}
|
|
else
|
|
{
|
|
l = grab_label (&line, &label_in);
|
|
sb_reset (&label);
|
|
if (label_in.len)
|
|
{
|
|
/* Munge any label */
|
|
|
|
|
|
process_assigns (0, &label_in, &label);
|
|
}
|
|
|
|
if (line.ptr[l] == ':')
|
|
l++;
|
|
while (ISWHITE (line.ptr[l]) && l < line.len)
|
|
l++;
|
|
|
|
if (l < line.len)
|
|
{
|
|
if (process_pseudo_op (l, &line, &acc))
|
|
{
|
|
|
|
|
|
|
|
}
|
|
else if (condass_on ())
|
|
{
|
|
if (macro_op (l, &line))
|
|
{
|
|
|
|
|
|
}
|
|
else
|
|
{
|
|
{
|
|
if (label.len)
|
|
{
|
|
fprintf (outfile, "%s:\t", sb_name (&label));
|
|
}
|
|
else
|
|
fprintf (outfile, "\t");
|
|
sb_reset(&t1);
|
|
process_assigns (l, &line, &t1);
|
|
sb_reset (&t2);
|
|
change_base (0, &t1, &t2);
|
|
fprintf (outfile, "%s\n", sb_name (&t2));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
/* Only a label on this line */
|
|
if (label.len && condass_on())
|
|
{
|
|
fprintf (outfile, "%s:\n", sb_name (&label));
|
|
}
|
|
}
|
|
}
|
|
|
|
if (had_end)
|
|
break;
|
|
sb_reset (&line);
|
|
more = get_line (&line);
|
|
}
|
|
|
|
if (!had_end)
|
|
WARNING ((stderr, "END missing from end of file.\n"));
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
free_old_entry (ptr)
|
|
hash_entry *ptr;
|
|
{
|
|
if (ptr)
|
|
{
|
|
if (ptr->type == hash_string)
|
|
sb_kill(&ptr->value.s);
|
|
}
|
|
}
|
|
|
|
/* name: .ASSIGNA <value> */
|
|
|
|
void
|
|
do_assigna (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
sb tmp;
|
|
int val;
|
|
sb_new (&tmp);
|
|
|
|
process_assigns (idx, in, &tmp);
|
|
idx = exp_get_abs (".ASSIGNA needs constant expression argument.\n", 0, &tmp, &val);
|
|
|
|
if (!label.len)
|
|
{
|
|
ERROR ((stderr, ".ASSIGNA without label.\n"));
|
|
}
|
|
else
|
|
{
|
|
hash_entry *ptr = hash_create (&vars, &label);
|
|
free_old_entry (ptr);
|
|
ptr->type = hash_integer;
|
|
ptr->value.i = val;
|
|
}
|
|
sb_kill (&tmp);
|
|
}
|
|
|
|
/* name: .ASSIGNC <string> */
|
|
|
|
void
|
|
do_assignc (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
sb acc;
|
|
sb_new (&acc);
|
|
idx = getstring (idx, in, &acc);
|
|
|
|
if (!label.len)
|
|
{
|
|
ERROR ((stderr, ".ASSIGNS without label.\n"));
|
|
}
|
|
else
|
|
{
|
|
hash_entry *ptr = hash_create (&vars, &label);
|
|
free_old_entry (ptr);
|
|
ptr->type = hash_string;
|
|
sb_new (&ptr->value.s);
|
|
sb_add_sb (&ptr->value.s, &acc);
|
|
}
|
|
sb_kill (&acc);
|
|
}
|
|
|
|
|
|
/* name: .REG (reg) */
|
|
|
|
static void
|
|
do_reg (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
/* remove reg stuff from inside parens */
|
|
sb what;
|
|
idx = skip_openp (idx, in);
|
|
sb_new (&what);
|
|
while (idx < in->len && in->ptr[idx] != ')')
|
|
{
|
|
sb_add_char (&what, in->ptr[idx]);
|
|
idx++;
|
|
}
|
|
hash_add_to_string_table (&assign_hash_table, &label, &what, 1);
|
|
sb_kill (&what);
|
|
}
|
|
|
|
|
|
static int
|
|
condass_lookup_name (inbuf, idx, out, warn)
|
|
sb *inbuf;
|
|
int idx;
|
|
sb *out;
|
|
int warn;
|
|
{
|
|
hash_entry *ptr;
|
|
sb condass_acc;
|
|
sb_new (&condass_acc);
|
|
|
|
while (idx < inbuf->len
|
|
&& ISNEXTCHAR (inbuf->ptr[idx]))
|
|
{
|
|
sb_add_char (&condass_acc, inbuf->ptr[idx++]);
|
|
}
|
|
|
|
if (inbuf->ptr[idx] == '\'')
|
|
idx++;
|
|
ptr = hash_lookup (&vars, &condass_acc);
|
|
|
|
|
|
if (!ptr)
|
|
{
|
|
if (warn)
|
|
{
|
|
WARNING ((stderr, "Can't find preprocessor variable %s.\n", sb_name (&condass_acc)));
|
|
}
|
|
else
|
|
{
|
|
sb_add_string (out, "0");
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (ptr->type == hash_integer)
|
|
{
|
|
char buffer[30];
|
|
sprintf (buffer, "%d", ptr->value.i);
|
|
sb_add_string (out, buffer);
|
|
}
|
|
else
|
|
{
|
|
sb_add_sb (out, &ptr->value.s);
|
|
}
|
|
}
|
|
sb_kill (&condass_acc);
|
|
return idx;
|
|
}
|
|
|
|
#define EQ 1
|
|
#define NE 2
|
|
#define GE 3
|
|
#define LT 4
|
|
#define LE 5
|
|
#define GT 6
|
|
#define NEVER 7
|
|
|
|
int
|
|
whatcond (idx, in, val)
|
|
int idx;
|
|
sb *in;
|
|
int *val;
|
|
{
|
|
int cond;
|
|
char *p;
|
|
idx = sb_skip_white (idx, in);
|
|
p = in->ptr + idx;
|
|
if (p[0] == 'E' && p[1] == 'Q')
|
|
cond = EQ;
|
|
else if (p[0] == 'N' && p[1] == 'E')
|
|
cond = NE;
|
|
else if (p[0] == 'L' && p[1] == 'T')
|
|
cond = LT;
|
|
else if (p[0] == 'L' && p[1] == 'E')
|
|
cond = LE;
|
|
else if (p[0] == 'G' && p[1] == 'T')
|
|
cond = GT;
|
|
else if (p[0] == 'G' && p[1] == 'E')
|
|
cond = GE;
|
|
else
|
|
{
|
|
ERROR ((stderr, "Comparison operator must be one of EQ, NE, LT, LE, GT or GE.\n"));
|
|
cond = NEVER;
|
|
}
|
|
idx = sb_skip_white (idx + 2, in);
|
|
*val = cond;
|
|
return idx;
|
|
}
|
|
|
|
int
|
|
istrue (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
int res;
|
|
sb acc_a;
|
|
sb cond;
|
|
sb acc_b;
|
|
sb_new (&acc_a);
|
|
sb_new (&cond);
|
|
sb_new (&acc_b);
|
|
idx = sb_skip_white (idx, in);
|
|
|
|
if (in->ptr[idx] == '"')
|
|
{
|
|
int cond;
|
|
int same;
|
|
/* This is a string comparision */
|
|
idx = getstring (idx, in, &acc_a);
|
|
idx = whatcond (idx, in, &cond);
|
|
idx = getstring (idx, in, &acc_b);
|
|
same = acc_a.len == acc_b.len && (strncmp (acc_a.ptr, acc_b.ptr, acc_a.len) == 0);
|
|
|
|
if (cond != EQ && cond != NE)
|
|
{
|
|
ERROR ((stderr, "Comparison operator for strings must be EQ or NE\n"));
|
|
res = 0;
|
|
}
|
|
else
|
|
res = cond == EQ && same;
|
|
}
|
|
else
|
|
/* This is a numeric expression */
|
|
{
|
|
int vala;
|
|
int valb;
|
|
int cond;
|
|
idx = exp_get_abs ("Conditional operator must have absolute operands.\n", idx, in, &vala);
|
|
idx = whatcond (idx, in, &cond);
|
|
idx = sb_skip_white (idx, in);
|
|
if (in->ptr[idx] == '"')
|
|
{
|
|
WARNING ((stderr, "String compared against expression.\n"));
|
|
res = 0;
|
|
}
|
|
else
|
|
{
|
|
idx = exp_get_abs ("Conditional operator must have absolute operands.\n", idx, in, &valb);
|
|
switch (cond)
|
|
{
|
|
case EQ:
|
|
res = vala == valb;
|
|
break;
|
|
case NE:
|
|
res = vala != valb;
|
|
break;
|
|
case LT:
|
|
res = vala < valb;
|
|
break;
|
|
case LE:
|
|
res = vala <= valb;
|
|
break;
|
|
case GT:
|
|
res = vala > valb;
|
|
break;
|
|
case GE:
|
|
res = vala >= valb;
|
|
break;
|
|
case NEVER:
|
|
res = 0;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
sb_kill (&acc_a);
|
|
sb_kill (&cond);
|
|
sb_kill (&acc_b);
|
|
return res;
|
|
}
|
|
|
|
/* .AIF */
|
|
static void
|
|
do_aif (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
if (ifi >= IFNESTING)
|
|
{
|
|
FATAL ((stderr, "AIF nesting unreasonable.\n"));
|
|
}
|
|
ifi++;
|
|
ifstack[ifi].on = istrue (idx, in);
|
|
ifstack[ifi].hadelse = 0;
|
|
}
|
|
|
|
|
|
/* .AELSE */
|
|
static void
|
|
do_aelse ()
|
|
{
|
|
ifstack[ifi].on = !ifstack[ifi].on;
|
|
if (ifstack[ifi].hadelse)
|
|
{
|
|
ERROR ((stderr, "Multiple AELSEs in AIF.\n"));
|
|
}
|
|
ifstack[ifi].hadelse = 1;
|
|
}
|
|
|
|
|
|
/* .AENDI */
|
|
static void
|
|
do_aendi ()
|
|
{
|
|
if (ifi != 0)
|
|
{
|
|
ifi--;
|
|
}
|
|
else
|
|
{
|
|
ERROR ((stderr, "AENDI without AIF.\n"));
|
|
}
|
|
}
|
|
|
|
static int
|
|
condass_on ()
|
|
{
|
|
return ifstack[ifi].on;
|
|
}
|
|
|
|
|
|
/* Read input lines till we get to a TO string.
|
|
Increase nesting depth if we geta FROM string.
|
|
Put the results into sb at PTR. */
|
|
|
|
static void
|
|
buffer_and_nest (from, to, ptr)
|
|
char *from;
|
|
char *to;
|
|
sb *ptr;
|
|
{
|
|
int from_len = strlen (from);
|
|
int to_len = strlen (to);
|
|
int depth = 1;
|
|
int line_start = ptr->len;
|
|
int line = linecount ();
|
|
|
|
int more = get_line (ptr);
|
|
|
|
while (more)
|
|
{
|
|
/* Try and find the first pseudo op on the line */
|
|
int i = line_start;
|
|
|
|
if (!alternate)
|
|
{
|
|
/* With normal syntax we can suck what we want till we get to the dot.
|
|
With the alternate, labels have to start in the first column, since
|
|
we cant tell what's a label and whats a pseudoop */
|
|
|
|
/* Skip leading whitespace */
|
|
while (i < ptr->len
|
|
&& ISWHITE (ptr->ptr[i]))
|
|
i++;
|
|
|
|
/* Skip over a label */
|
|
while (i < ptr->len
|
|
&& ISNEXTCHAR (ptr->ptr[i]))
|
|
i++;
|
|
|
|
/* And a colon */
|
|
if (i < ptr->len
|
|
&& ptr->ptr[i] == ':')
|
|
i++;
|
|
|
|
}
|
|
/* Skip trailing whitespace */
|
|
while (i < ptr->len
|
|
&& ISWHITE (ptr->ptr[i]))
|
|
i++;
|
|
|
|
if (i < ptr->len && (ptr->ptr[i] == '.'
|
|
|| alternate))
|
|
{
|
|
if (ptr->ptr[i] == '.')
|
|
i++;
|
|
if (strncmp (ptr->ptr + i, from, from_len) == 0)
|
|
depth++;
|
|
if (strncmp (ptr->ptr + i, to, to_len) == 0)
|
|
{
|
|
depth--;
|
|
if (depth == 0)
|
|
{
|
|
/* Reset the string to not include the ending rune */
|
|
ptr->len = line_start;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Add a CR to the end and keep running */
|
|
sb_add_char (ptr, '\n');
|
|
line_start = ptr->len;
|
|
more = get_line (ptr);
|
|
}
|
|
|
|
|
|
if (depth)
|
|
FATAL ((stderr, "End of file whilst inside %s, started on line %d.\n", from, line));
|
|
}
|
|
|
|
|
|
/* .ENDR */
|
|
void
|
|
do_aendr ()
|
|
{
|
|
ERROR ((stderr, "AENDR without a AREPEAT.\n"));
|
|
}
|
|
|
|
/* .AWHILE */
|
|
|
|
static
|
|
void
|
|
do_awhile (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
sb exp;
|
|
|
|
sb sub;
|
|
|
|
int doit;
|
|
sb_new (&sub);
|
|
sb_new (&exp);
|
|
|
|
process_assigns (idx, in, &exp);
|
|
doit = istrue (0, &exp);
|
|
|
|
buffer_and_nest ("AWHILE", "AENDW", &sub);
|
|
|
|
/* Turn
|
|
.AWHILE exp
|
|
foo
|
|
.AENDW
|
|
into
|
|
foo
|
|
.AWHILE exp
|
|
foo
|
|
.ENDW
|
|
*/
|
|
|
|
if (doit)
|
|
{
|
|
int index = include_next_index ();
|
|
|
|
sb copy;
|
|
sb_new (©);
|
|
sb_add_sb (©, &sub);
|
|
sb_add_sb (©, in);
|
|
sb_add_string (©, "\n");
|
|
sb_add_sb (©, &sub);
|
|
sb_add_string (©, "\t.AENDW\n");
|
|
/* Push another WHILE */
|
|
include_buf (&exp, ©, include_while, index);
|
|
sb_kill (©);
|
|
}
|
|
sb_kill (&exp);
|
|
sb_kill (&sub);
|
|
}
|
|
|
|
|
|
/* .AENDW */
|
|
|
|
static void
|
|
do_aendw ()
|
|
{
|
|
ERROR ((stderr, "AENDW without a AENDW.\n"));
|
|
}
|
|
|
|
|
|
/* .EXITM
|
|
|
|
Pop things off the include stack until the type and index changes */
|
|
|
|
static void
|
|
do_exitm ()
|
|
{
|
|
include_type type = sp->type;
|
|
if (type == include_repeat
|
|
|| type == include_while
|
|
|| type == include_macro)
|
|
{
|
|
int index = sp->index;
|
|
include_pop ();
|
|
while (sp->index == index
|
|
&& sp->type == type)
|
|
{
|
|
include_pop ();
|
|
}
|
|
}
|
|
}
|
|
|
|
/* .AREPEAT */
|
|
|
|
static void
|
|
do_arepeat (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
sb exp; /* buffer with expression in it */
|
|
sb copy; /* expanded repeat block */
|
|
sb sub; /* contents of AREPEAT */
|
|
int rc;
|
|
char buffer[30];
|
|
sb_new (&exp);
|
|
sb_new (©);
|
|
sb_new (&sub);
|
|
process_assigns (idx, in, &exp);
|
|
idx = exp_get_abs ("AREPEAT must have absolute operand.\n", 0, &exp, &rc);
|
|
buffer_and_nest ("AREPEAT", "AENDR", &sub);
|
|
if (rc > 0)
|
|
{
|
|
/* Push back the text following the repeat, and another repeat block
|
|
so
|
|
.AREPEAT 20
|
|
foo
|
|
.AENDR
|
|
gets turned into
|
|
foo
|
|
.AREPEAT 19
|
|
foo
|
|
.AENDR
|
|
*/
|
|
int index = include_next_index ();
|
|
sb_add_sb (©, &sub);
|
|
if (rc > 1)
|
|
{
|
|
sprintf (buffer, "\t.AREPEAT %d\n", rc - 1);
|
|
sb_add_string (©, buffer);
|
|
sb_add_sb (©, &sub);
|
|
sb_add_string (©, " .AENDR\n");
|
|
}
|
|
|
|
include_buf (&exp, ©, include_repeat, index);
|
|
}
|
|
sb_kill (&exp);
|
|
sb_kill (&sub);
|
|
sb_kill (©);
|
|
}
|
|
|
|
/* .ENDM */
|
|
|
|
static void
|
|
do_endm ()
|
|
{
|
|
ERROR ((stderr, ".ENDM without a matching .MACRO.\n"));
|
|
}
|
|
|
|
|
|
/* MARRO PROCESSING */
|
|
|
|
static int number;
|
|
hash_table macro_table;
|
|
|
|
/* Understand
|
|
|
|
.MACRO <name>
|
|
stuff
|
|
.ENDM
|
|
*/
|
|
|
|
static int
|
|
do_formals (macro, idx, in)
|
|
macro_entry *macro;
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
formal_entry **p = ¯o->formals;
|
|
macro->formal_count = 0;
|
|
hash_new_table (5, ¯o->formal_hash);
|
|
while (idx < in->len)
|
|
{
|
|
formal_entry *formal;
|
|
|
|
formal = (formal_entry *) xmalloc (sizeof (formal_entry));
|
|
|
|
sb_new (&formal->name);
|
|
sb_new (&formal->def);
|
|
sb_new (&formal->actual);
|
|
|
|
idx = sb_skip_white (idx, in);
|
|
idx = get_token (idx, in, &formal->name);
|
|
if (formal->name.len == 0)
|
|
break;
|
|
idx = sb_skip_white (idx, in);
|
|
if (formal->name.len)
|
|
{
|
|
/* This is a formal */
|
|
if (idx < in->len && in->ptr[idx] == '=')
|
|
{
|
|
/* Got a default */
|
|
idx = get_any_string (idx + 1, in, &formal->def);
|
|
}
|
|
}
|
|
|
|
{
|
|
/* Add to macro's hash table */
|
|
|
|
hash_entry *p = hash_create (¯o->formal_hash, &formal->name);
|
|
p->type = hash_formal;
|
|
p->value.f = formal;
|
|
}
|
|
|
|
formal->index = macro->formal_count;
|
|
idx = sb_skip_comma (idx, in);
|
|
macro->formal_count++;
|
|
*p = formal;
|
|
p = &formal->next;
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/* Parse off LOCAL n1, n2,... Invent a label name for it */
|
|
static
|
|
void
|
|
do_local (idx, line)
|
|
int idx;
|
|
sb *line;
|
|
{
|
|
static int ln;
|
|
sb acc;
|
|
sb sub;
|
|
char subs[10];
|
|
sb_new (&acc);
|
|
sb_new (&sub);
|
|
idx = sb_skip_white (idx, line);
|
|
while (!eol(idx, line))
|
|
{
|
|
sb_reset (&acc);
|
|
sb_reset (&sub);
|
|
ln++;
|
|
sprintf(subs, "LL%04x", ln);
|
|
idx = get_token(idx, line, &acc);
|
|
sb_add_string (&sub, subs);
|
|
hash_add_to_string_table (&assign_hash_table, &acc, &sub, 1);
|
|
idx = sb_skip_comma (idx, line);
|
|
}
|
|
sb_kill (&sub);
|
|
sb_kill (&acc);
|
|
}
|
|
|
|
static
|
|
void
|
|
do_macro (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
macro_entry *macro;
|
|
sb name;
|
|
|
|
macro = (macro_entry *) xmalloc (sizeof (macro_entry));
|
|
sb_new (¯o->sub);
|
|
sb_new (&name);
|
|
|
|
macro->formal_count = 0;
|
|
macro->formals = 0;
|
|
|
|
idx = sb_skip_white (idx, in);
|
|
buffer_and_nest ("MACRO", "ENDM", ¯o->sub);
|
|
if (label.len)
|
|
{
|
|
|
|
sb_add_sb (&name, &label);
|
|
if (in->ptr[idx] == '(')
|
|
{
|
|
/* It's the label: MACRO (formals,...) sort */
|
|
idx = do_formals (macro, idx + 1, in);
|
|
if (in->ptr[idx] != ')')
|
|
ERROR ((stderr, "Missing ) after formals.\n"));
|
|
}
|
|
else {
|
|
/* It's the label: MACRO formals,... sort */
|
|
idx = do_formals (macro, idx, in);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
idx = get_token (idx, in, &name);
|
|
idx = sb_skip_white (idx, in);
|
|
idx = do_formals (macro, idx, in);
|
|
}
|
|
|
|
/* and stick it in the macro hash table */
|
|
hash_create (¯o_table, &name)->value.m = macro;
|
|
}
|
|
|
|
static
|
|
int
|
|
get_token (idx, in, name)
|
|
int idx;
|
|
sb *in;
|
|
sb *name;
|
|
{
|
|
if (idx < in->len
|
|
&& ISFIRSTCHAR (in->ptr[idx]))
|
|
{
|
|
sb_add_char (name, in->ptr[idx++]);
|
|
while (idx < in->len
|
|
&& ISNEXTCHAR (in->ptr[idx]))
|
|
{
|
|
sb_add_char (name, in->ptr[idx++]);
|
|
}
|
|
}
|
|
return idx;
|
|
}
|
|
|
|
/* Scan a token, but stop if a ' is seen */
|
|
static int
|
|
get_apost_token (idx, in, name, kind)
|
|
int idx;
|
|
sb *in;
|
|
sb *name;
|
|
int kind;
|
|
{
|
|
idx = get_token (idx, in, name);
|
|
if (idx < in->len && in->ptr[idx] == kind)
|
|
idx++;
|
|
return idx;
|
|
}
|
|
|
|
static int
|
|
sub_actual (src, in, t, m, kind, out, copyifnotthere)
|
|
int src;
|
|
sb *in;
|
|
sb *t;
|
|
macro_entry *m;
|
|
int kind;
|
|
sb *out;
|
|
int copyifnotthere;
|
|
{
|
|
/* This is something to take care of */
|
|
hash_entry *ptr;
|
|
src = get_apost_token (src, in, t, kind);
|
|
/* See if it's in the macro's hash table */
|
|
ptr = hash_lookup (&m->formal_hash, t);
|
|
if (ptr)
|
|
{
|
|
if (ptr->value.f->actual.len)
|
|
{
|
|
sb_add_sb (out, &ptr->value.f->actual);
|
|
}
|
|
else
|
|
{
|
|
sb_add_sb (out, &ptr->value.f->def);
|
|
}
|
|
}
|
|
else if (copyifnotthere)
|
|
{
|
|
sb_add_sb (out, t);
|
|
}
|
|
else
|
|
{
|
|
sb_add_char (out, '\\');
|
|
sb_add_sb (out, t);
|
|
}
|
|
return src;
|
|
}
|
|
|
|
static
|
|
void
|
|
macro_expand (name, idx, in, m)
|
|
sb *name;
|
|
int idx;
|
|
sb *in;
|
|
macro_entry *m;
|
|
{
|
|
sb t;
|
|
sb out;
|
|
hash_entry *ptr;
|
|
formal_entry *f;
|
|
int is_positional = 0;
|
|
int is_keyword = 0;
|
|
|
|
sb_new (&t);
|
|
sb_new (&out);
|
|
|
|
/* Reset any old value the actuals may have */
|
|
for (f = m->formals; f; f = f->next)
|
|
sb_reset (&f->actual);
|
|
f = m->formals;
|
|
/* Peel off the actuals and store them away in the hash tables' actuals */
|
|
while (!eol(idx, in))
|
|
{
|
|
int scan;
|
|
idx = sb_skip_white (idx, in);
|
|
/* Look and see if it's a positional or keyword arg */
|
|
scan = idx;
|
|
while (scan < in->len
|
|
&& !ISSEP (in->ptr[scan])
|
|
&& in->ptr[scan] != '=')
|
|
scan++;
|
|
if (scan < in->len && in->ptr[scan] == '=')
|
|
{
|
|
is_keyword = 1;
|
|
if (is_positional)
|
|
{
|
|
ERROR ((stderr, "Can't mix positional and keyword arguments.\n"));
|
|
return;
|
|
}
|
|
/* This is a keyword arg, fetch the formal name and
|
|
then the actual stuff */
|
|
sb_reset (&t);
|
|
idx = get_token (idx, in, &t);
|
|
if (in->ptr[idx] != '=')
|
|
ERROR ((stderr, "confused about formal params.\n"));
|
|
|
|
/* Lookup the formal in the macro's list */
|
|
ptr = hash_lookup (&m->formal_hash, &t);
|
|
if (!ptr)
|
|
{
|
|
ERROR ((stderr, "MACRO formal argument %s does not exist.\n", sb_name (&t)));
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
/* Insert this value into the right place */
|
|
sb_reset (&ptr->value.f->actual);
|
|
idx = get_any_string (idx + 1, in, &ptr->value.f->actual);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* This is a positional arg */
|
|
is_positional = 1;
|
|
if (is_keyword)
|
|
{
|
|
ERROR ((stderr, "Can't mix positional and keyword arguments.\n"));
|
|
return;
|
|
}
|
|
if (!f)
|
|
{
|
|
ERROR ((stderr, "Too many positional arguments.\n"));
|
|
return;
|
|
}
|
|
|
|
sb_reset (&f->actual);
|
|
idx = get_any_string (idx, in, &f->actual);
|
|
f = f->next;
|
|
}
|
|
idx = sb_skip_comma (idx, in);
|
|
}
|
|
|
|
/* Copy the stuff from the macro buffer into a safe place and substitute any args */
|
|
|
|
{
|
|
int src = 0;
|
|
int inquote = 0;
|
|
sb *in = &m->sub;
|
|
sb_reset (&out);
|
|
|
|
while (src < in->len)
|
|
{
|
|
if (in->ptr[src] == '&')
|
|
{
|
|
sb_reset (&t);
|
|
src = sub_actual (src + 1, in, &t, m, '&', &out, 0);
|
|
}
|
|
else if (in->ptr[src] == '\\')
|
|
{
|
|
src++;
|
|
if (in->ptr[src] == comment_char)
|
|
{
|
|
/* This is a comment, just drop the rest of the line */
|
|
while (src < in->len
|
|
&& in->ptr[src] != '\n')
|
|
src++;
|
|
|
|
}
|
|
else if (in->ptr[src] == '(')
|
|
{
|
|
/* Sub in till the next ')' literally */
|
|
src++;
|
|
while (src < in->len && in->ptr[src] != ')')
|
|
{
|
|
sb_add_char (&out, in->ptr[src++]);
|
|
}
|
|
if (in->ptr[src] == ')')
|
|
src++;
|
|
else
|
|
ERROR ((stderr, "Missplaced ).\n"));
|
|
}
|
|
else if (in->ptr[src] == '@')
|
|
{
|
|
/* Sub in the macro invocation number */
|
|
|
|
char buffer[6];
|
|
src++;
|
|
sprintf (buffer, "%05d", number);
|
|
sb_add_string (&out, buffer);
|
|
}
|
|
else if (in->ptr[src] == '&')
|
|
{
|
|
/* This is a preprocessor variable name, we don't do them
|
|
here */
|
|
sb_add_char (&out, '\\');
|
|
sb_add_char (&out, '&');
|
|
src++;
|
|
}
|
|
else
|
|
{
|
|
sb_reset (&t);
|
|
src = sub_actual (src, in, &t, m, '\'', &out, 0);
|
|
}
|
|
}
|
|
else if (ISFIRSTCHAR (in->ptr[src]) && alternate)
|
|
{
|
|
sb_reset (&t);
|
|
src = sub_actual (src, in, &t, m, '\'', &out, 1);
|
|
}
|
|
else if (ISCOMMENTCHAR (in->ptr[src])
|
|
&& src + 1 < in->len
|
|
&& ISCOMMENTCHAR (in->ptr[src+1])
|
|
&& !inquote)
|
|
{
|
|
/* Two comment chars in a row cause the rest of the line to be dropped */
|
|
while (src < in->len && in->ptr[src] != '\n')
|
|
src++;
|
|
}
|
|
else if (in->ptr[src] == '"')
|
|
{
|
|
inquote = !inquote;
|
|
sb_add_char (&out, in->ptr[src++]);
|
|
}
|
|
else
|
|
{
|
|
sb_add_char (&out, in->ptr[src++]);
|
|
}
|
|
}
|
|
include_buf (name, &out, include_macro, include_next_index ());
|
|
}
|
|
sb_kill (&t);
|
|
sb_kill (&out);
|
|
number++;
|
|
}
|
|
|
|
static int
|
|
macro_op (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
int res = 0;
|
|
/* The macro name must be the first thing on the line */
|
|
if (idx < in->len)
|
|
{
|
|
sb name;
|
|
hash_entry *ptr;
|
|
sb_new (&name);
|
|
idx = get_token (idx, in, &name);
|
|
|
|
if (name.len)
|
|
{
|
|
/* Got a name, look it up */
|
|
|
|
ptr = hash_lookup (¯o_table, &name);
|
|
|
|
if (ptr)
|
|
{
|
|
/* It's in the table, copy out the stuff and convert any macro args */
|
|
macro_expand (&name, idx, in, ptr->value.m);
|
|
res = 1;
|
|
}
|
|
}
|
|
sb_kill (&name);
|
|
}
|
|
|
|
|
|
return res;
|
|
}
|
|
|
|
|
|
/* STRING HANDLING */
|
|
|
|
static int
|
|
getstring (idx, in, acc)
|
|
int idx;
|
|
sb *in;
|
|
sb *acc;
|
|
{
|
|
idx = sb_skip_white (idx, in);
|
|
|
|
while (idx < in->len
|
|
&& (in->ptr[idx] == '"'
|
|
|| in->ptr[idx] == '<'
|
|
|| (in->ptr[idx] == '\'' && alternate)))
|
|
{
|
|
if (in->ptr[idx] == '<')
|
|
{
|
|
if (alternate)
|
|
{
|
|
int nest = 0;
|
|
idx++;
|
|
while ((in->ptr[idx] != '>' || nest)
|
|
&& idx < in->len)
|
|
{
|
|
if (in->ptr[idx] == '!')
|
|
{
|
|
idx++ ;
|
|
sb_add_char (acc, in->ptr[idx++]);
|
|
}
|
|
else {
|
|
if (in->ptr[idx] == '>')
|
|
nest--;
|
|
if (in->ptr[idx] == '<')
|
|
nest++;
|
|
sb_add_char (acc, in->ptr[idx++]);
|
|
}
|
|
}
|
|
idx++;
|
|
}
|
|
else {
|
|
int code;
|
|
idx++;
|
|
idx = exp_get_abs ("Character code in string must be absolute expression.\n",
|
|
idx, in, &code);
|
|
sb_add_char (acc, code);
|
|
|
|
if (in->ptr[idx] != '>')
|
|
ERROR ((stderr, "Missing > for character code.\n"));
|
|
idx++;
|
|
}
|
|
}
|
|
else if (in->ptr[idx] == '"' || in->ptr[idx] == '\'')
|
|
{
|
|
char tchar = in->ptr[idx];
|
|
idx++;
|
|
while (idx < in->len)
|
|
{
|
|
if (alternate && in->ptr[idx] == '!')
|
|
{
|
|
idx++ ;
|
|
sb_add_char (acc, in->ptr[idx++]);
|
|
}
|
|
else {
|
|
if (in->ptr[idx] == tchar)
|
|
{
|
|
idx++;
|
|
if (idx >= in->len || in->ptr[idx] != tchar)
|
|
break;
|
|
}
|
|
sb_add_char (acc, in->ptr[idx]);
|
|
idx++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return idx;
|
|
}
|
|
|
|
/* .SDATA[C|Z] <string> */
|
|
|
|
static
|
|
void
|
|
do_sdata (idx, in, type)
|
|
int idx;
|
|
sb *in;
|
|
char type;
|
|
{
|
|
int nc = 0;
|
|
int pidx = -1;
|
|
sb acc;
|
|
sb_new (&acc);
|
|
fprintf (outfile, ".byte\t");
|
|
|
|
while (!eol (idx, in))
|
|
{
|
|
int i;
|
|
sb_reset (&acc);
|
|
idx = sb_skip_white (idx, in);
|
|
while (!eol (idx, in))
|
|
{
|
|
pidx = idx = get_any_string (idx, in, &acc);
|
|
if (type == 'c')
|
|
{
|
|
if (acc.len > 255)
|
|
{
|
|
ERROR ((stderr, "string for SDATAC longer than 255 characters (%d).\n", acc.len));
|
|
}
|
|
fprintf (outfile, "%d", acc.len);
|
|
nc = 1;
|
|
}
|
|
|
|
for (i = 0; i < acc.len; i++)
|
|
{
|
|
if (nc)
|
|
{
|
|
fprintf (outfile, ",");
|
|
}
|
|
fprintf (outfile, "%d", acc.ptr[i]);
|
|
nc = 1;
|
|
}
|
|
|
|
if (type == 'z')
|
|
{
|
|
if (nc)
|
|
fprintf (outfile, ",");
|
|
fprintf (outfile, "0");
|
|
}
|
|
idx = sb_skip_comma (idx, in);
|
|
if (idx == pidx) break;
|
|
}
|
|
if (!alternate && in->ptr[idx] != ',' && idx != in->len)
|
|
{
|
|
fprintf (outfile, "\n");
|
|
ERROR ((stderr, "illegal character in SDATA line (0x%x).\n", in->ptr[idx]));
|
|
break;
|
|
}
|
|
idx++;
|
|
}
|
|
sb_kill (&acc);
|
|
fprintf (outfile, "\n");
|
|
}
|
|
|
|
/* .SDATAB <count> <string> */
|
|
|
|
static void
|
|
do_sdatab (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
int repeat;
|
|
int i;
|
|
sb acc;
|
|
sb_new (&acc);
|
|
|
|
idx = exp_get_abs ("Must have absolute SDATAB repeat count.\n", idx, in, &repeat);
|
|
if (repeat <= 0)
|
|
{
|
|
ERROR ((stderr, "Must have positive SDATAB repeat count (%d).\n", repeat));
|
|
repeat = 1;
|
|
}
|
|
|
|
idx = sb_skip_comma (idx, in);
|
|
idx = getstring (idx, in, &acc);
|
|
|
|
for (i = 0; i < repeat; i++)
|
|
{
|
|
if (i)
|
|
fprintf (outfile, "\t");
|
|
fprintf (outfile, ".byte\t");
|
|
sb_print (&acc);
|
|
fprintf (outfile, "\n");
|
|
}
|
|
sb_kill (&acc);
|
|
|
|
}
|
|
|
|
int
|
|
new_file (name)
|
|
char *name;
|
|
{
|
|
FILE *newone = fopen (name, "r");
|
|
if (!newone)
|
|
return 0;
|
|
|
|
if (isp == MAX_INCLUDES)
|
|
FATAL ((stderr, "Unreasonable include depth (%d).\n", isp));
|
|
|
|
sp++;
|
|
sp->handle = newone;
|
|
|
|
sb_new (&sp->name);
|
|
sb_add_string (&sp->name, name);
|
|
|
|
sp->linecount = 1;
|
|
sp->pushback_index = 0;
|
|
sp->type = include_file;
|
|
sp->index = 0;
|
|
sb_new (&sp->pushback);
|
|
return 1;
|
|
}
|
|
|
|
static void
|
|
do_include (idx, in)
|
|
int idx;
|
|
sb *in;
|
|
{
|
|
sb t;
|
|
char *text;
|
|
sb_new (&t);
|
|
idx = getstring (idx, in, &t);
|
|
text = sb_name (&t);
|
|
if (!new_file (text))
|
|
{
|
|
FATAL ((stderr, "Can't open include file `%s'.\n", text));
|
|
}
|
|
sb_kill (&t);
|
|
}
|
|
|
|
static void
|
|
include_pop ()
|
|
{
|
|
if (sp != include_stack)
|
|
{
|
|
if (sp->handle)
|
|
fclose (sp->handle);
|
|
sp--;
|
|
}
|
|
}
|
|
|
|
/* Get the next character from the include stack. If there's anything
|
|
in the pushback buffer, take that first. If we're at eof, pop from
|
|
the stack and try again. Keep the linecount up to date. */
|
|
|
|
static int
|
|
get ()
|
|
{
|
|
int r;
|
|
|
|
if (sp->pushback.len != sp->pushback_index)
|
|
{
|
|
r = (char) (sp->pushback.ptr[sp->pushback_index++]);
|
|
/* When they've all gone, reset the pointer */
|
|
if (sp->pushback_index == sp->pushback.len)
|
|
{
|
|
sp->pushback.len = 0;
|
|
sp->pushback_index = 0;
|
|
}
|
|
}
|
|
else if (sp->handle)
|
|
{
|
|
r = getc (sp->handle);
|
|
}
|
|
else
|
|
r = EOF;
|
|
|
|
if (r == EOF && isp)
|
|
{
|
|
include_pop ();
|
|
r = get ();
|
|
while (r == EOF && isp)
|
|
{
|
|
include_pop ();
|
|
r = get ();
|
|
}
|
|
return r;
|
|
}
|
|
if (r == '\n')
|
|
{
|
|
sp->linecount++;
|
|
}
|
|
|
|
return r;
|
|
}
|
|
|
|
static int
|
|
linecount ()
|
|
{
|
|
return sp->linecount;
|
|
}
|
|
|
|
static int
|
|
include_next_index ()
|
|
{
|
|
static int index;
|
|
if (!unreasonable
|
|
&& index > MAX_REASONABLE)
|
|
FATAL ((stderr, "Unreasonable expansion (-u turns off check).\n"));
|
|
return ++index;
|
|
}
|
|
|
|
|
|
/* Initialize the chartype vector. */
|
|
|
|
static void
|
|
chartype_init ()
|
|
{
|
|
int x;
|
|
for (x = 0; x < 256; x++)
|
|
{
|
|
if (isalpha (x) || x == '_' || x == '$')
|
|
chartype[x] |= FIRSTBIT;
|
|
|
|
if (isdigit (x) || isalpha (x) || x == '_' || x == '$')
|
|
chartype[x] |= NEXTBIT;
|
|
|
|
if (x == ' ' || x == '\t' || x == ',' || x == '"' || x == ';'
|
|
|| x == '"' || x == '<' || x == '>' || x == ')' || x == '(')
|
|
chartype[x] |= SEPBIT;
|
|
|
|
if (x == ' ' || x == '\t')
|
|
chartype[x] |= WHITEBIT;
|
|
|
|
if (x == comment_char)
|
|
chartype[x] |= COMMENTBIT;
|
|
}
|
|
}
|
|
|
|
|
|
|
|
/* What to do with all the keywords */
|
|
#define PROCESS 0x1000 /* Run substitution over the line */
|
|
#define LAB 0x2000 /* Spit out the label */
|
|
|
|
#define K_EQU PROCESS|1
|
|
#define K_ASSIGN PROCESS|2
|
|
#define K_REG PROCESS|3
|
|
#define K_ORG PROCESS|4
|
|
#define K_RADIX PROCESS|5
|
|
#define K_DATA LAB|PROCESS|6
|
|
#define K_DATAB LAB|PROCESS|7
|
|
#define K_SDATA LAB|PROCESS|8
|
|
#define K_SDATAB LAB|PROCESS|9
|
|
#define K_SDATAC LAB|PROCESS|10
|
|
#define K_SDATAZ LAB|PROCESS|11
|
|
#define K_RES LAB|PROCESS|12
|
|
#define K_SRES LAB|PROCESS|13
|
|
#define K_SRESC LAB|PROCESS|14
|
|
#define K_SRESZ LAB|PROCESS|15
|
|
#define K_EXPORT LAB|PROCESS|16
|
|
#define K_GLOBAL LAB|PROCESS|17
|
|
#define K_PRINT LAB|PROCESS|19
|
|
#define K_FORM LAB|PROCESS|20
|
|
#define K_HEADING LAB|PROCESS|21
|
|
#define K_PAGE LAB|PROCESS|22
|
|
#define K_IMPORT LAB|PROCESS|23
|
|
#define K_PROGRAM LAB|PROCESS|24
|
|
#define K_END PROCESS|25
|
|
#define K_INCLUDE PROCESS|26
|
|
#define K_IGNORED PROCESS|27
|
|
#define K_ASSIGNA PROCESS|28
|
|
#define K_ASSIGNC 29
|
|
#define K_AIF PROCESS|30
|
|
#define K_AELSE PROCESS|31
|
|
#define K_AENDI PROCESS|32
|
|
#define K_AREPEAT PROCESS|33
|
|
#define K_AENDR PROCESS|34
|
|
#define K_AWHILE 35
|
|
#define K_AENDW PROCESS|36
|
|
#define K_EXITM 37
|
|
#define K_MACRO PROCESS|38
|
|
#define K_ENDM 39
|
|
#define K_ALIGN PROCESS|LAB|40
|
|
#define K_ALTERNATE 41
|
|
#define K_DB LAB|PROCESS|42
|
|
#define K_DW LAB|PROCESS|43
|
|
#define K_DL LAB|PROCESS|44
|
|
#define K_LOCAL 45
|
|
|
|
|
|
static struct
|
|
{
|
|
char *name;
|
|
int code;
|
|
int extra;
|
|
}
|
|
kinfo[] =
|
|
{
|
|
{ "EQU", K_EQU, 0 },
|
|
{ "ALTERNATE", K_ALTERNATE, 0 },
|
|
{ "ASSIGN", K_ASSIGN, 0 },
|
|
{ "REG", K_REG, 0 },
|
|
{ "ORG", K_ORG, 0 },
|
|
{ "RADIX", K_RADIX, 0 },
|
|
{ "DATA", K_DATA, 0 },
|
|
{ "DB", K_DB, 0 },
|
|
{ "DW", K_DW, 0 },
|
|
{ "DL", K_DL, 0 },
|
|
{ "DATAB", K_DATAB, 0 },
|
|
{ "SDATA", K_SDATA, 0 },
|
|
{ "SDATAB", K_SDATAB, 0 },
|
|
{ "SDATAZ", K_SDATAZ, 0 },
|
|
{ "SDATAC", K_SDATAC, 0 },
|
|
{ "RES", K_RES, 0 },
|
|
{ "SRES", K_SRES, 0 },
|
|
{ "SRESC", K_SRESC, 0 },
|
|
{ "SRESZ", K_SRESZ, 0 },
|
|
{ "EXPORT", K_EXPORT, 0 },
|
|
{ "GLOBAL", K_GLOBAL, 0 },
|
|
{ "PRINT", K_PRINT, 0 },
|
|
{ "FORM", K_FORM, 0 },
|
|
{ "HEADING", K_HEADING, 0 },
|
|
{ "PAGE", K_PAGE, 0 },
|
|
{ "PROGRAM", K_IGNORED, 0 },
|
|
{ "END", K_END, 0 },
|
|
{ "INCLUDE", K_INCLUDE, 0 },
|
|
{ "ASSIGNA", K_ASSIGNA, 0 },
|
|
{ "ASSIGNC", K_ASSIGNC, 0 },
|
|
{ "AIF", K_AIF, 0 },
|
|
{ "AELSE", K_AELSE, 0 },
|
|
{ "AENDI", K_AENDI, 0 },
|
|
{ "AREPEAT", K_AREPEAT, 0 },
|
|
{ "AENDR", K_AENDR, 0 },
|
|
{ "EXITM", K_EXITM, 0 },
|
|
{ "MACRO", K_MACRO, 0 },
|
|
{ "ENDM", K_ENDM, 0 },
|
|
{ "AWHILE", K_AWHILE, 0 },
|
|
{ "ALIGN", K_ALIGN, 0 },
|
|
{ "AENDW", K_AENDW, 0 },
|
|
{ "ALTERNATE", K_ALTERNATE, 0 },
|
|
{ "LOCAL", K_LOCAL, 0 },
|
|
{ NULL, 0, 0 }
|
|
};
|
|
|
|
/* Look for a pseudo op on the line. If one's there then call
|
|
its handler. */
|
|
|
|
static int
|
|
process_pseudo_op (idx, line, acc)
|
|
int idx;
|
|
sb *line;
|
|
sb *acc;
|
|
{
|
|
|
|
|
|
if (line->ptr[idx] == '.' || alternate)
|
|
{
|
|
/* Scan forward and find pseudo name */
|
|
char *in;
|
|
hash_entry *ptr;
|
|
|
|
char *s;
|
|
char *e;
|
|
if (line->ptr[idx] == '.')
|
|
idx++;
|
|
in = line->ptr + idx;
|
|
s = in;
|
|
e = s;
|
|
sb_reset (acc);
|
|
|
|
while (idx < line->len && *e && ISFIRSTCHAR (*e))
|
|
{
|
|
sb_add_char (acc, *e);
|
|
e++;
|
|
idx++;
|
|
}
|
|
|
|
ptr = hash_lookup (&keyword_hash_table, acc);
|
|
|
|
if (!ptr)
|
|
{
|
|
#if 0
|
|
/* This one causes lots of pain when trying to preprocess
|
|
ordinary code */
|
|
WARNING ((stderr, "Unrecognised pseudo op `%s'.\n", sb_name (acc)));
|
|
#endif
|
|
return 0;
|
|
}
|
|
if (ptr->value.i & LAB)
|
|
{ /* output the label */
|
|
if (label.len)
|
|
{
|
|
fprintf (outfile, "%s:\t", sb_name (&label));
|
|
}
|
|
else
|
|
fprintf (outfile, "\t");
|
|
}
|
|
|
|
if (ptr->value.i & PROCESS)
|
|
{
|
|
/* Polish the rest of the line before handling the pseudo op */
|
|
#if 0
|
|
strip_comments(line);
|
|
#endif
|
|
sb_reset (acc);
|
|
process_assigns (idx, line, acc);
|
|
sb_reset(line);
|
|
change_base (0, acc, line);
|
|
idx = 0;
|
|
}
|
|
if (!condass_on ())
|
|
{
|
|
switch (ptr->value.i)
|
|
{
|
|
case K_AELSE:
|
|
do_aelse ();
|
|
break;
|
|
case K_AENDI:
|
|
do_aendi ();
|
|
break;
|
|
}
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
switch (ptr->value.i)
|
|
{
|
|
case K_ALTERNATE:
|
|
alternate = 1;
|
|
return 1;
|
|
case K_AELSE:
|
|
do_aelse ();
|
|
return 1;
|
|
case K_AENDI:
|
|
do_aendi ();
|
|
return 1;
|
|
case K_ORG:
|
|
ERROR ((stderr, "ORG command not allowed.\n"));
|
|
break;
|
|
case K_RADIX:
|
|
do_radix (line);
|
|
return 1;
|
|
case K_DB:
|
|
do_data (idx, line, 1);
|
|
return 1;
|
|
case K_DW:
|
|
do_data (idx, line, 2);
|
|
return 1;
|
|
case K_DL:
|
|
do_data (idx, line, 4);
|
|
return 1;
|
|
case K_DATA:
|
|
do_data (idx, line, 0);
|
|
return 1;
|
|
case K_DATAB:
|
|
do_datab (idx, line);
|
|
return 1;
|
|
case K_SDATA:
|
|
do_sdata (idx, line, 0);
|
|
return 1;
|
|
case K_SDATAB:
|
|
do_sdatab (idx, line);
|
|
return 1;
|
|
case K_SDATAC:
|
|
do_sdata (idx, line, 'c');
|
|
return 1;
|
|
case K_SDATAZ:
|
|
do_sdata (idx, line, 'z');
|
|
return 1;
|
|
case K_ASSIGN:
|
|
do_assign (1, 0, line);
|
|
return 1;
|
|
case K_AIF:
|
|
do_aif (idx, line);
|
|
return 1;
|
|
case K_AREPEAT:
|
|
do_arepeat (idx, line);
|
|
return 1;
|
|
case K_AENDW:
|
|
do_aendw ();
|
|
return 1;
|
|
case K_AWHILE:
|
|
do_awhile (idx, line);
|
|
return 1;
|
|
case K_AENDR:
|
|
do_aendr ();
|
|
return 1;
|
|
case K_EQU:
|
|
do_assign (0, idx, line);
|
|
return 1;
|
|
case K_ALIGN:
|
|
do_align (idx, line);
|
|
return 1;
|
|
case K_RES:
|
|
do_res (idx, line, 0);
|
|
return 1;
|
|
case K_SRES:
|
|
do_res (idx, line, 's');
|
|
return 1;
|
|
case K_INCLUDE:
|
|
do_include (idx, line);
|
|
return 1;
|
|
case K_LOCAL:
|
|
do_local (idx, line);
|
|
return 1;
|
|
case K_MACRO:
|
|
do_macro (idx, line);
|
|
return 1;
|
|
case K_ENDM:
|
|
do_endm ();
|
|
return 1;
|
|
case K_SRESC:
|
|
do_res (idx, line, 'c');
|
|
return 1;
|
|
case K_PRINT:
|
|
do_print (idx, line);
|
|
return 1;
|
|
case K_FORM:
|
|
do_form (idx, line);
|
|
return 1;
|
|
case K_HEADING:
|
|
do_heading (idx, line);
|
|
return 1;
|
|
case K_PAGE:
|
|
do_page ();
|
|
return 1;
|
|
case K_GLOBAL:
|
|
case K_EXPORT:
|
|
do_export (line);
|
|
return 1;
|
|
case K_IMPORT:
|
|
return 1;
|
|
case K_SRESZ:
|
|
do_res (idx, line, 'z');
|
|
return 1;
|
|
case K_IGNORED:
|
|
return 1;
|
|
case K_END:
|
|
do_end ();
|
|
return 1;
|
|
case K_ASSIGNA:
|
|
do_assigna (idx, line);
|
|
return 1;
|
|
case K_ASSIGNC:
|
|
do_assignc (idx, line);
|
|
return 1;
|
|
case K_EXITM:
|
|
do_exitm ();
|
|
return 1;
|
|
case K_REG:
|
|
do_reg (idx, line);
|
|
return 1;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* Build the keyword hash table - put each keyword in the table twice,
|
|
once upper and once lower case.*/
|
|
|
|
static void
|
|
process_init ()
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; kinfo[i].name; i++)
|
|
{
|
|
sb label;
|
|
int j;
|
|
sb_new (&label);
|
|
sb_add_string (&label, kinfo[i].name);
|
|
|
|
hash_add_to_int_table (&keyword_hash_table, &label, kinfo[i].code);
|
|
|
|
sb_reset (&label);
|
|
for (j = 0; kinfo[i].name[j]; j++)
|
|
sb_add_char (&label, kinfo[i].name[j] - 'A' + 'a');
|
|
hash_add_to_int_table (&keyword_hash_table, &label, kinfo[i].code);
|
|
|
|
sb_kill (&label);
|
|
}
|
|
}
|
|
|
|
|
|
static void
|
|
do_define (string)
|
|
char *string;
|
|
{
|
|
sb label;
|
|
int res = 1;
|
|
hash_entry *ptr;
|
|
sb_new (&label);
|
|
|
|
|
|
while (*string)
|
|
{
|
|
if (*string == '=')
|
|
{
|
|
sb value;
|
|
sb_new (&value);
|
|
string++;
|
|
while (*string)
|
|
{
|
|
sb_add_char (&value, *string);
|
|
string++;
|
|
}
|
|
exp_get_abs ("Invalid expression on command line.\n", 0, &value, &res);
|
|
sb_kill (&value);
|
|
break;
|
|
}
|
|
sb_add_char (&label, *string);
|
|
|
|
string ++;
|
|
}
|
|
|
|
ptr = hash_create (&vars, &label);
|
|
free_old_entry (ptr);
|
|
ptr->type = hash_integer;
|
|
ptr->value.i = res;
|
|
sb_kill (&label);
|
|
}
|
|
char *program_name;
|
|
|
|
/* The list of long options. */
|
|
static struct option long_options[] =
|
|
{
|
|
{ "alternate", no_argument, 0, 'a' },
|
|
{ "commentchar", required_argument, 0, 'c' },
|
|
{ "copysource", no_argument, 0, 's' },
|
|
{ "debug", no_argument, 0, 'd' },
|
|
{ "help", no_argument, 0, 'h' },
|
|
{ "output", required_argument, 0, 'o' },
|
|
{ "print", no_argument, 0, 'p' },
|
|
{ "unreasonable", no_argument, 0, 'u' },
|
|
{ "version", no_argument, 0, 'v' },
|
|
{ "define", required_argument, 0, 'd' },
|
|
{ NULL, no_argument, 0, 0 }
|
|
};
|
|
|
|
/* Show a usage message and exit. */
|
|
static void
|
|
show_usage (file, status)
|
|
FILE *file;
|
|
int status;
|
|
{
|
|
fprintf (file, "\
|
|
Usage: %s \n\
|
|
[-a] [--alternate] enter alternate macro mode\n\
|
|
[-c char] [--commentchar char] change the comment character from !\n\
|
|
[-d] [--debug] print some debugging info\n\
|
|
[-h] [--help] print this message\n\
|
|
[-o out] [--output out] set the output file\n\
|
|
[-p] [--print] print line numbers\n\
|
|
[-s] [--copysource] copy source through as comments \n\
|
|
[-u] [--unreasonable] allow unreasonable nesting\n\
|
|
[-v] [--version] print the program version\n\
|
|
[-Dname=value] create preprocessor variable called name, with value\n\
|
|
[in-file]\n", program_name);
|
|
exit (status);
|
|
}
|
|
|
|
/* Display a help message and exit. */
|
|
static void
|
|
show_help ()
|
|
{
|
|
printf ("%s: Gnu Assembler Macro Preprocessor\n",
|
|
program_name);
|
|
show_usage (stdout, 0);
|
|
}
|
|
|
|
int
|
|
main (argc, argv)
|
|
int argc;
|
|
char **argv;
|
|
{
|
|
int opt;
|
|
char *out_name = 0;
|
|
sp = include_stack;
|
|
|
|
ifstack[0].on = 1;
|
|
ifi = 0;
|
|
|
|
|
|
|
|
program_name = argv[0];
|
|
xmalloc_set_program_name (program_name);
|
|
|
|
hash_new_table (101, ¯o_table);
|
|
hash_new_table (101, &keyword_hash_table);
|
|
hash_new_table (101, &assign_hash_table);
|
|
hash_new_table (101, &vars);
|
|
|
|
sb_new (&label);
|
|
process_init ();
|
|
|
|
while ((opt = getopt_long (argc, argv, "sdhavc:upo:D:", long_options,
|
|
(int *) NULL))
|
|
!= EOF)
|
|
{
|
|
switch (opt)
|
|
{
|
|
case 'o':
|
|
out_name = optarg;
|
|
break;
|
|
case 'u':
|
|
unreasonable = 1;
|
|
break;
|
|
case 'p':
|
|
print_line_number = 1;
|
|
break;
|
|
case 'c':
|
|
comment_char = optarg[0];
|
|
break;
|
|
case 'a':
|
|
alternate = 1;
|
|
break;
|
|
case 's':
|
|
copysource = 1;
|
|
break;
|
|
case 'd':
|
|
stats = 1;
|
|
break;
|
|
case 'D':
|
|
do_define (optarg);
|
|
break;
|
|
case 'h':
|
|
show_help ();
|
|
/*NOTREACHED*/
|
|
case 'v':
|
|
printf ("GNU %s version %s\n", program_name, program_version);
|
|
exit (0);
|
|
/*NOTREACHED*/
|
|
case 0:
|
|
break;
|
|
default:
|
|
show_usage (stderr, 1);
|
|
/*NOTREACHED*/
|
|
}
|
|
}
|
|
|
|
|
|
if (out_name) {
|
|
outfile = fopen (out_name, "w");
|
|
if (!outfile)
|
|
{
|
|
fprintf (stderr, "%s: Can't open output file `%s'.\n",
|
|
program_name, out_name);
|
|
exit (1);
|
|
}
|
|
}
|
|
else {
|
|
outfile = stdout;
|
|
}
|
|
|
|
chartype_init ();
|
|
if (!outfile)
|
|
outfile = stdout;
|
|
|
|
/* Process all the input files */
|
|
|
|
while (optind < argc)
|
|
{
|
|
if (new_file (argv[optind]))
|
|
{
|
|
process_file ();
|
|
}
|
|
else
|
|
{
|
|
fprintf (stderr, "%s: Can't open input file `%s'.\n",
|
|
program_name, argv[optind]);
|
|
exit (1);
|
|
}
|
|
optind++;
|
|
}
|
|
|
|
quit ();
|
|
return 0;
|
|
}
|