ftp.nice.ch/pub/next/developer/nextsources/cctools.s.tar.gz#/cctools/as/read.c

This is read.c in view mode; [Download] [Up]

/* read.c - read a source file -
   Copyright (C) 1986,1987 Free Software Foundation, Inc.

This file is part of GAS, the GNU Assembler.

GAS 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 1, or (at your option)
any later version.

GAS 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 GAS; see the file COPYING.  If not, write to
the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.  */

#define MASK_CHAR (0xFF)	/* If your chars aren't 8 bits, you will
				   change this a bit.  But then, GNU isn't
				   spozed to run on your machine anyway.
				   (RMS is so shortsighted sometimes.)
				 */

#define MAXIMUM_NUMBER_OF_CHARS_FOR_FLOAT (16)
				/* This is the largest known floating point */
				/* format (for now). It will grow when we */
				/* do 4361 style flonums. */


/* Routines that read assembler source text to build spagetti in memory. */
/* Another group of these functions is in the expr.c module */

#include <ctype.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include "stuff/round.h"
#include "as.h"
#include "flonum.h"
#include "struc-symbol.h"
#include "expr.h"
#include "read.h"
#include "hash.h"
#include "obstack.h"
#include "md.h"
#include "symbols.h"
#include "sections.h"
#include "input-scrub.h"
#include "input-file.h"
#include "hex_value.h"
#include "messages.h"
#include "xmalloc.h"
#include "app.h"

/*
 * Parsing of input is done off of this pointer which points to the next char
 * of source file to parse.
 */
char *input_line_pointer = NULL;

/*
 * buffer_limit is the value returned by the input_scrub_next_buffer() in
 * read_a_source_file() and is not static only so read_an_include_file can save
 * and restore it.
 */
char *buffer_limit = NULL;	/* -> 1 + last char in buffer. */

/*
 * This table is used by the macros is_name_beginner() and is_part_of_name()
 * defined in read.h .
 */
const char lex_type[256] = {
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,       /* @ABCDEFGHIJKLMNO */
  0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,       /* PQRSTUVWXYZ[\]^_ */
  0, 0, 0, 0, 3, 0, 0, 0, 0, 0, 0, 0, 0, 0, 3, 0,       /* _!"#$%&'()*+,-./ */
  1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0,       /* 0123456789:;<=>? */
  0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,       /* @ABCDEFGHIJKLMNO */
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 3,       /* PQRSTUVWXYZ[\]^_ */
  0, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,       /* `abcdefghijklmno */
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0,       /* pqrstuvwxyz{|}~. */
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,	/* Allow all chars  */
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,	/* with the high bit */
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,	/* set in names */
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3,
  3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3 
};

/*
 * In: a character.
 * Out: TRUE if this character ends a line.
 */
#define _ (0)
static const char is_end_of_line[256] = {
  _, _, _, _, _, _, _, _, _, _,99, _, _, _, _, _, /* @abcdefghijklmno */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _,99, _, _, _, _, /* 0123456789:;<=>? */
#if defined(M88K) || defined(M98K) || defined(HPPA)
 99, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /* @ABCDEFGHIJKLMNO */
#else
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
#endif
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, /*                  */
  _, _, _, _, _, _, _, _, _, _, _, _, _, _, _, _  /*                  */
};
#undef _

/*
 * The conditional assembly feature (.if, .else, .elseif and .endif) is
 * implemented with cond_state that tells us what we are in the middle of 
 * processing.  ignore can be either TRUE or FALSE.  When TRUE we are ignoring
 * the block of code in the middle of a conditional.  MAX_IF_DEPTH is the
 * maximum depth that if's can be nested.
 */
#define MAX_IF_DEPTH 20
typedef enum {
    no_cond,	/* no conditional is being processed */
    if_cond,	/* inside if conditional */
    elseif_cond,/* inside elseif conditional */
    else_cond	/* inside else conditional */
}cond_type;

struct cond_state {
    cond_type	the_cond;
    int		cond_met;
    int		ignore;
};
typedef struct cond_state cond_stateS;
static cond_stateS the_cond_state = {no_cond, FALSE, FALSE};
static cond_stateS last_states[MAX_IF_DEPTH];
static int if_depth = 0;

/*
 * Assembler macros are implemented with these variables and functions.
 */
#define MAX_MACRO_DEPTH 20
static int macro_depth = 0;
static struct hash_control
	*ma_hash = NULL;	/* use before set up: NULL-> address error */
static struct obstack macros;	/* obstack for macro text */
static char *macro_name = NULL;	/* name of macro we are defining */
static int count_lines = TRUE;	/* turns line number counting on and off */
static int macros_on = TRUE;	/* .macros_on and .macros_off toggles this to
				   allow macros to be turned off, which allows
				   macros to override a machine instruction and
				   still use it. */
static void expand_macro(char *macro_contents);
static void macro_begin(void);


/*
 * The .dump and .load feature is implemented with these variables and
 * functions.
 */
static FILE *dump_fp = NULL;
static char *write_macro(char *string, char *value);
static char * write_symbol(char *string, char *value);


/* Functions private to this file */
static void parse_a_buffer(char *buffer);
static void parse_line_comment(char **buffer);
static segT get_segmented_expression(expressionS *expP);
static void pseudo_op_begin(void);
static void pseudo_set(symbolS *symbolP);
static void stab(int what);
static char get_absolute_expression_and_terminator(long *val_pointer);
static char *demand_copy_C_string(int *len_pointer);
static char *demand_copy_string(int *lenP);
static int is_it_end_of_statement(void);
static void equals(char *sym_name);
static int next_char_of_string(void);

#ifdef M68K /* we allow big cons only on the 68k machines */
/*
 * This is setup by read_begin() and used by big_cons() with using grow_bignum()
 * to make it bigger if needed.
 */
#define BIGNUM_BEGIN_SIZE (16)
static char *bignum_low;  /* Lowest char of bignum. */
static char *bignum_limit;/* 1st illegal address of bignum. */
static char *bignum_high; /* Highest char of bignum, may point to
			     (bignum_start-1), never >= bignum_limit. */
static void grow_bignum(void);
#endif /* M68K */
/*
 * This is set in read_a_source_file() to the section number of the text section
 * for used by the machine dependent md_assemble() to create line number stabs
 * for assembly instructions in the text section when -g is seen.
 */
unsigned long text_nsect = 0;

/*
 * These are the names of the section types used by the .section directive.
 */
struct type_name {
    char *name;
    unsigned type;
};
static struct type_name type_names[] = {
    { "regular",		  S_REGULAR },
    { "cstring_literals",	  S_CSTRING_LITERALS },
    { "4byte_literals",		  S_4BYTE_LITERALS },
    { "8byte_literals",		  S_8BYTE_LITERALS },
    { "literal_pointers",	  S_LITERAL_POINTERS },
    { "non_lazy_symbol_pointers", S_NON_LAZY_SYMBOL_POINTERS },
    { "lazy_symbol_pointers",	  S_LAZY_SYMBOL_POINTERS },
    { "symbol_stubs",		  S_SYMBOL_STUBS },
    { "mod_init_funcs",		  S_MOD_INIT_FUNC_POINTERS },
    { NULL, 0 }
};

/*
 * These are the names of the section attributes used by the .section directive.
 */
struct attribute_name {
    char *name;
    unsigned attribute;
};
static struct attribute_name attribute_names[] = {
    { "none",	  0 },
    { "pure_instructions", S_ATTR_PURE_INSTRUCTIONS },
    { "reloc_at_launch",   S_ATTR_RELOC_AT_LAUNCH },
    { NULL, 0 }
};

/*
 * These are the built in sections know to the assembler with a directive.
 * They are known as which segment and section name as well as the type &
 * attribute, and default alignment.
 */
struct builtin_section {
    char *directive;
    char *segname;
    char *sectname;
    unsigned long flags; /* type & attribute */
    unsigned long default_align;
    unsigned long sizeof_stub;
};
static const struct builtin_section builtin_sections[] = {
    /*
     * The text section must be first in this list as it is used by
     * read_a_source_file() to do the equivalent of a .text at the start
     * of the file and for s_builtin_section() to set S_ATTR_PURE_INSTRUCTIONS.
     */
    { "text",                "__TEXT", "__text" },
    { "const",               "__TEXT", "__const" },
    { "static_const",        "__TEXT", "__static_const" },
    { "cstring",             "__TEXT", "__cstring", S_CSTRING_LITERALS },
    { "literal4",            "__TEXT", "__literal4", S_4BYTE_LITERALS, 2 },
    { "literal8",            "__TEXT", "__literal8", S_8BYTE_LITERALS, 3 },
    { "constructor",         "__TEXT", "__constructor" },
    { "destructor",          "__TEXT", "__destructor" },
    { "fvmlib_init0",        "__TEXT", "__fvmlib_init0" },
    { "fvmlib_init1",        "__TEXT", "__fvmlib_init1" },
    { "mod_init_func",	     "__TEXT", "__mod_init_func",
		S_MOD_INIT_FUNC_POINTERS, 2 },
    { "symbol_stub",	     "__TEXT", "__symbol_stub",
		S_SYMBOL_STUBS | S_ATTR_PURE_INSTRUCTIONS,
#if defined(M68K)
		1, 20
#endif
#if defined(I386)
		0, 16
#endif
#if defined(HPPA)
		2, 28
#endif
		},
    { "picsymbol_stub",	     "__TEXT", "__picsymbol_stub",
		S_SYMBOL_STUBS | S_ATTR_PURE_INSTRUCTIONS,
#if defined(M68K)
		1, 22
#endif
#if defined(I386)
		0, 16
#endif
#if defined(HPPA)
		2, 28
#endif
		},
    { "non_lazy_symbol_pointer","__DATA","__nl_symbol_ptr",
		S_NON_LAZY_SYMBOL_POINTERS, 2 },
    { "lazy_symbol_pointer", "__DATA", "__la_symbol_ptr",
		S_LAZY_SYMBOL_POINTERS, 2 },
    { "dyld",		     "__DATA", "__dyld" },
    { "data",                "__DATA", "__data" },
    { "static_data",         "__DATA", "__static_data" },
    { "objc_class",          "__OBJC", "__class" },
    { "objc_meta_class",     "__OBJC", "__meta_class" },
    { "objc_string_object",  "__OBJC", "__string_object" },
    { "objc_protocol",       "__OBJC", "__protocol" },
    { "objc_cat_cls_meth",   "__OBJC", "__cat_cls_meth" },
    { "objc_cat_inst_meth",  "__OBJC", "__cat_inst_meth" },
    { "objc_cls_meth",       "__OBJC", "__cls_meth" },
    { "objc_inst_meth",      "__OBJC", "__inst_meth" },
    { "objc_message_refs",   "__OBJC", "__message_refs", S_LITERAL_POINTERS, 2},
    { "objc_cls_refs",       "__OBJC", "__cls_refs",     S_LITERAL_POINTERS, 2},
    { "objc_class_names",    "__OBJC", "__class_names", S_CSTRING_LITERALS },
    { "objc_module_info",    "__OBJC", "__module_info" },
    { "objc_symbols",        "__OBJC", "__symbols" },
    { "objc_category",       "__OBJC", "__category" },
    { "objc_meth_var_types", "__OBJC", "__meth_var_types", S_CSTRING_LITERALS },
    { "objc_class_vars",     "__OBJC", "__class_vars" },
    { "objc_instance_vars",  "__OBJC", "__instance_vars" },
    { "objc_meth_var_names", "__OBJC", "__meth_var_names", S_CSTRING_LITERALS },
    { "objc_selector_strs",  "__OBJC", "__selector_strs", S_CSTRING_LITERALS },
    { 0 }
};

/* set up pseudo-op tables */
static struct hash_control *po_hash = NULL;

#if !defined(I860) /* i860 has it's own align and org */
static void s_align(int value);
static void s_org(int value);
#endif
static void s_private_extern(int value);
static void s_indirect_symbol(int value);
static void s_abort(int value);
static void s_comm(int value);
static void s_desc(int value);
static void s_file(int value);
static void s_fill(int value);
static void s_lcomm(int value);
static void s_lsym(int value);
static void s_set(int value);
static void s_reference(int value);
static void s_include(int value);
static void s_dump(int value);
static void s_load(int value);
static void s_if(int value);
static void s_elseif(int value);
static void s_else(int value);
static void s_endif(int value);
static void s_macros_on(int value);
static void s_macros_off(int value);
static void s_section(int value);
static void s_zerofill(int value);
static unsigned long s_builtin_section(const struct builtin_section *s);

/*
 * The machine independent pseudo op table.
 */
static const pseudo_typeS pseudo_table[] = {
#if !defined(I860) /* i860 has it's own align and org */
  { "align",	s_align,	0	},
  { "org",	s_org,		0	},
#endif
#ifndef M88K /* m88k has it's own abs that uses the s_abs() in here */
  { "abs",	s_abs,		0	},
#endif
  { "private_extern",  s_private_extern, 0},
  { "indirect_symbol", s_indirect_symbol, 0},
  { "abort",	s_abort,	0	},
  { "ascii",	stringer,	0	},
  { "asciz",	stringer,	1	},
  { "byte",	cons,		1	},
  { "comm",	s_comm,		0	},
  { "desc",	s_desc,		0	},
  { "double",	float_cons,	'd'	},
  { "file",	s_file,		0	},
  { "fill",	s_fill,		0	},
  { "globl",	s_globl,	0	},
  { "lcomm",	s_lcomm,	0	},
  { "line",	s_line,		0	},
  { "long",	cons,		4	},
  { "lsym",	s_lsym,		0	},
  { "section",	s_section,	0	},
  { "zerofill",	s_zerofill,	0	},
  { "set",	s_set,		0	},
  { "short",	cons,		2	},
  { "single",	float_cons,	'f'	},
  { "space",	s_space,	0	},
  { "stabd",	stab,		'd'	},
  { "stabn",	stab,		'n'	},
  { "stabs",	stab,		's'	},
  { "reference",s_reference,	0	},
  { "include",	s_include,	0	},
  { "macro",	s_macro,	0	},
  { "endmacro",	s_endmacro,	0	},
  { "macros_on",s_macros_on,	0	},
  { "macros_off",s_macros_off,	0	},
  { "if",	s_if,		0	},
  { "elseif",	s_elseif,	0	},
  { "else",	s_else,		0	},
  { "endif",	s_endif,	0	},
  { "dump",	s_dump,		0	},
  { "load",	s_load,		0	},
  { NULL }	/* end sentinel */
};

/*
 * read_begin() initializes the assember to read assembler source input.
 */
void
read_begin(
void)
{
      pseudo_op_begin();
      macro_begin();
      obstack_begin(&notes, 5000);

#ifdef M68K /* we allow big cons only on the 68k machines */
      bignum_low = xmalloc((long)BIGNUM_BEGIN_SIZE);
      bignum_limit = bignum_low + BIGNUM_BEGIN_SIZE;
#endif
}

/*
 * pseudo_op_begin() creates a hash table of pseudo ops from the machine
 * independent and machine dependent pseudo op tables.
 */
static
void
pseudo_op_begin(
void)
{
    char *errtxt;
    const pseudo_typeS *pop;
    unsigned long i;
    pseudo_typeS *sections_pseudo_table;

	po_hash = hash_new();
	errtxt = "";
	for(pop = pseudo_table; pop->poc_name && *errtxt == '\0'; pop++)
	    errtxt = hash_insert(po_hash, pop->poc_name, (char *)pop);

	for(pop = md_pseudo_table; pop->poc_name && *errtxt == '\0'; pop++)
	    errtxt = hash_insert(po_hash, pop->poc_name, (char *)pop);

	for(i = 0; builtin_sections[i].directive != NULL; i++)
	    ;
	sections_pseudo_table = xmalloc((i + 1) * sizeof(pseudo_typeS));
	for(i = 0; builtin_sections[i].directive != NULL; i++){
	    sections_pseudo_table[i].poc_name = builtin_sections[i].directive;
	    sections_pseudo_table[i].poc_handler =
					      (void (*)(int))s_builtin_section;
	    sections_pseudo_table[i].poc_val = (int)(builtin_sections + i);
	}
	sections_pseudo_table[i].poc_name = NULL;
	for(pop = (const pseudo_typeS *)sections_pseudo_table;
	    pop->poc_name && *errtxt == '\0';
	    pop++)
	    errtxt = hash_insert(po_hash, pop->poc_name, (char *)pop);

	if(*errtxt != '\0'){
	    as_fatal("error constructing pseudo-op table (%s)", errtxt);
	}
}

/*
 * The NeXT version of: read_a_source_file()
 *
 * This differs from the GNU version by taking the guts of the GNU
 * read_a_source_file() (with the outer most loop removed) and renaming it
 * parse_a_buffer().  With the NeXT version of read_a_source file simply
 * containing that outer loop and a call to parse_a_buffer().  This is done
 * So that expand_macro() and parse_line_comment() can call parse_a_buffer()
 * with the buffers they create.
 */
void
read_a_source_file(
char *buffer)	/* 1st character of each buffer of lines is here. */
{
    cond_stateS	starting_cond_state;
    short starting_if_depth;

    symbolS *symbolP;

	starting_cond_state = the_cond_state;
	starting_if_depth = if_depth;

	/* Do not change segments or subsegments if this is a .include */
	if(doing_include == FALSE){
	    /*
	     * This is a new file so switch start as if a .text was seen.  This
	     * call to s_builtin_section() relys on the fact that the text
	     * section is first in the built in sections list.
	     */
	    if(flagseen['n'] == FALSE)
		text_nsect = s_builtin_section(builtin_sections);

	    /*
	     * If the -g flag is present generate the lead stabs for this
	     * physical file that is not an include file.  Each physical file's
	     * stabs are enclosed by a pair of source name stabs, N_SO, (one at
	     * the begining of the file with the name of the file and one at the
	     * end with the name "").  This is seen by nm(1) as:
	     * 	00000000 - 01 0000    SO {standard input}
	     *  ...
	     *	00000020 - 01 0000    SO
	     * To make the debugger work line numbers stabs, N_SLINE, must be
	     * contained "in a function" (after a function stab, N_FUN).  To
	     * make a function stab work it must have a type number.  Since type
	     * numbers 1 and 2 (the 1 in "int:t1=..." and the 2 in "char:t2=..."
	     * are "magic" to the debugger we use type 3 for the types of the
	     * function stabs we generate for each text label (see the routine
	     * make_stab_for_symbol() in symbols.c).  So at lead stabs at the
	     * begining of each physical file include three type stabs, L_LSYM
	     * with the correct symbol name.  The since we must have the types
	     * 1 and 2 they are just what the 'C' would produce but we don't
	     * use them.  Type 3 is the void type like the 'C' compiler would
	     * produce which we use for the function stabs' type.  These three
	     * look like this to nm(1):
	     *	00000000 - 00 0000  LSYM int:t1=r1;-2147483648;2147483647;
	     *	00000000 - 00 0000  LSYM char:t2=r2;0;127;
	     *	00000000 - 00 0000  LSYM void:t3=3
	     *
	     * Then for each text label we see, make_stab_for_symbol() will
	     * generate a stab like this (for the example lable _main):
	     *	00000000 - 01 0007   FUN _main:F3
	     * where the 'F' in F3 is an upper case 'F' for global labels and
	     * a lower case 'f' for non globals.
	     *
	     * Then for each instruction we assemble in the text we generate
	     * a line number, S_LINE, stab (see md_assembler in m68k.c, m88k.c
	     * etc).  These look like:
	     *	00000000 - 01 0008 SLINE
	     * where the 0008 is the line number.
	     */
	    if(flagseen['g']){
		symbolP = symbol_new(
			physical_input_file,
			100 /* N_SO */,
			text_nsect,
			0,
			obstack_next_free(&frags) - frag_now->fr_literal,
			frag_now);
		symbolP = symbol_new(
			"int:t1=r1;-2147483648;2147483647;",
			128 /* N_LSYM */,
			0,0,0,
			&zero_address_frag);
		symbolP = symbol_new(
			"char:t2=r2;0;127;",
			128 /* N_LSYM */,
			0,0,0,
			&zero_address_frag);
		symbolP = symbol_new(
			"void:t3=3",
			128 /* N_LSYM */,
			0,0,0,
			&zero_address_frag);
	    }
	}
	else{
	    /*
	     * If we are now reading an include file we will bracket it's
	     * stabs with a pair of:
	     *	00000010 - 01 0000   SOL include_file
	     *	...
	     *	0000001c - 01 0000   SOL previous_file
	     * We generate the first N_SOL here and the one for the
	     * previous_file in s_include() in read.c.
	     *
	     * CAVAT: This will only work if the include file starts off in the
	     * (__TEXT,__text) sections and ends in the (__TEXT,__text) section.
	     */
	    if(flagseen['g'] && frchain_now->frch_nsect == text_nsect){
		symbolP = symbol_new(
			physical_input_file,
			132 /* N_SOL */,
			text_nsect,
			0,
			obstack_next_free(&frags) - frag_now->fr_literal,
			frag_now);
	    }
	}

	while((buffer_limit = input_scrub_next_buffer(&buffer)) != NULL)
	    parse_a_buffer(buffer);

	if(the_cond_state.the_cond != starting_cond_state.the_cond ||
	   the_cond_state.ignore != starting_cond_state.ignore||
	   if_depth != starting_if_depth)
	    as_warn("file contains unmatched .ifs or .elses");

	if(doing_include == FALSE){
	    /* See the comment at the top of this routine for a description of
	       what is going on here */
	    if(flagseen['n'] == FALSE)
		text_nsect = s_builtin_section(builtin_sections);
	    if(flagseen['g']){
		(void)symbol_new(
			"",
			100 /* N_SO */,
			text_nsect,
			0,
			obstack_next_free(&frags) - frag_now->fr_literal,
			frag_now);
	    }
	}
}

/*
 * parse_a_buffer() operates on a buffer of lines.  It drives the
 * parsing of lines of assembly code.  The lines are assumed to be "well formed"
 * assembly so the syntax recognized in here is that produced by the output of
 * the assembly preprocessor (app) or by the compiler when it produces a file
 * that starts with "#NO_APP\n".  A "well formed" assembly is lines with exactly
 * zero or one leading "well formed space character" (' ', '\t' or '\f')
 * followed by lines of:
 *	zero or more lables (a name or a digit followed by a colon)
 *		each followed by zero or one "well formed space character"
 *	exactly one of the following followed by a logicial end of line:
 *    		a pseudo opcode
 *			followed by zero or one space (' ') characters and it's
 *			arguments (the space is required when the first
 *			character of the first argument could be part of a name)
 *    		a macro to be expanded
 *    		a machine opcode
 *    		a null statement
 *		an assignment to a symbol
 *    		a full line comment (in the case of "well formed" assembly it
 *				     must be "#APP\n" of a collection of lines
 *				     wrapped in "#APP\n ... #NO_APP\n")
 * 
 * input:
 *	buffer		pointer to the start of the buffer of lines
 *			(passed as an argument)
 *	buffer_limit	pointer to the end of the buffer of lines, that is the
 *			the character it points to is NOT part of the buffer
 *			(buffer_limit is declared in this file)
 *
 * Assumptions about the buffer of lines:
 *	buffer[-1] == '\n'	as done in input-scrub.c with the cpp macro
 *				BEFORE_STRING ("\n")
 *	buffer_limit[-1] == '\n' also as done in input-scrub.c which handles
 *				partial lines internally to itself and always
 *				passes back a buffer of complete lines.
 *
 * input/output: (for other parsing routines)
 *	input_line_pointer	pointer to the next thing in the buffer after
 * 				what has been recognized (a global)
 */
static
void
parse_a_buffer(
char *buffer)
{
    char c;		/* contains the first non-space character the current
			   word used to figure out what it is */
    char *s;		/* points to a name with character after the name
			   replaced with a '\0' so it is a 'C' string */
    char after_name;	/* contains that first character after a name that
			   got replaced with a '\0' */
    char *after_name_pointer;/* points to the end of the name where the '\0' is
			   for error use only */
    char end_of_line;	/* contains an end of line character that got replaced
			   with a '\0' */
    char *start_of_line;/* points to the locical start of line we're parsing,
			   used only for macro expansion */
    pseudo_typeS *pop;	/* pointer to a pseudo op stucture returned by
			   hash_find(po_hash, s+1) to determine if it is one */
    char *the_macro;	/* pointer to a macro name returned by
			   hash_find(ma_hash, s) to determine if it is one */
    int digit_value;	/* the value of a digit label as an integer, 1: == 1 */

	/* since this is a buffer of full lines it must end in a new line */
	know(buffer_limit[-1] == '\n');

	input_line_pointer = buffer;

	/* while we have more of this buffer to parse keep parsing */
	while(input_line_pointer < buffer_limit){
	    /*
	     * At the top of this loop we know that we just parsed a label or we
	     * are at the beginning of a logical line (since their can be more
	     * than one label on a line).  start_of_line is only used by
	     * expand_macro()
	     */
	    start_of_line = input_line_pointer;

	    /*
	     * If we are not counting lines (as in the case when called by
	     * expand_macro() ) and we just previously scaned over a newline
	     * (a physical end of line) bump the line counters (see the comments
	     * at the head of this routine about "assumptions about the buffer"
	     * and why it is safe to index input_line_pointer by -1.
	     */
	    if(count_lines == TRUE && input_line_pointer[-1] == '\n')
		bump_line_counters ();

	    /*
	     * We expect a "well-formed" assembler statement.  This means it was
	     * processed by app or produced by a compiler where the file started
	     * with a leading "#APP\n".  A "well-formed" statement allows zero
	     * or one leading white space characters.
	     */
	    c = *input_line_pointer;
	    input_line_pointer++;
	    if(c == '\t' || c == ' ' || c=='\f'){
		c = *input_line_pointer;
		input_line_pointer++;
	    }
	    know(c != ' ');	/* No further leading whitespace. */
	    /*
	     * c contains the 1st significant character, *input_line_pointer
	     * points after that character.
	     */

	    /*
	     * look for the begining of a name which could be one of the
	     * following assembly statements:
	     *    A pseudo opcode and locical end of line
	     *    A macro to be expanded and locical end of line
	     *    A machine opcode and locical end of line
	     *    A user-defined label (name not digit)(no end of line needed)
	     * At NeXT labels can be enclosed in ""'s so that Objective-C like
	     * names (with spaces and colons) can be part of a name, the
	     * routine get_symbol_end() knows about this.
	     */
	    if(is_name_beginner(c) || c == '"'){
		if( c == '"')
		    s = input_line_pointer--;
		else
		    s = --input_line_pointer;
		after_name = get_symbol_end(); /* name's delimiter */
		after_name_pointer = input_line_pointer;
		/*
		 * after_name is the character after symbol.  That character's
		 * place in the input line is now '\0',done by get_symbol_end().
		 * s points to the beginning of the symbol (in the case of a
		 * pseudo-op, *s == '.').  *input_line_pointer == '\0' where
		 * after_name was.  after_name_pointer is recorded so it their
		 * is an error after the line has been restored the '\0' can
		 * be reset and the name printed.
		 */

		/*
		 * Look for a name that should be a pseudo op.  That is it is
		 * not a user defined label or an assignment to a symbol name.
		 * This must be done so such thing as ".foo:" and ".bar=1" are
		 * not mistaken for illegal pseudo ops and that something like
		 * ".long: .long 1" creates a symbol named ".long".
		 */
		if(*s == '.' &&
		   (after_name != ':' &&
		    after_name != '=' &&
		   !((after_name == ' ' || after_name == '\t') &&
		     input_line_pointer[1] == '=') ) ){
		    /*
		     * Lookup what should be a pseudo op and then restore the
		     * line.
		     */
		    pop = (pseudo_typeS *)hash_find(po_hash, s+1);
		    *input_line_pointer = after_name;

		    /*
		     * A pseudo op must be followed by character that is not
		     * part of a name so it can be parsed.  If their is a first
		     * argument that could start with a character in a name then
		     * one "well formed space" (space or a tab) must follow the
		     * pseudo op (otherwise the space is optional).
		     */
		    if(after_name == ' ' || after_name == '\t')
			input_line_pointer++;

		    /*
		     * Now the current state of the line is the after_name has
		     * been placed back in the line (the line is restored) and
		     * input_line_pointer is at the start of the first argument
		     * of the pseudo op (if any).
		     */
		    if(the_cond_state.ignore){
			/*
			 * When ignoring a block of code during conditional
			 * assembly we can't ignore .if, .else, and .endif
			 * pseudo ops.
			 */
			if(pop != NULL &&
		           ( (pop->poc_handler == s_if) ||
			     (pop->poc_handler == s_elseif) ||
			     (pop->poc_handler == s_else) ||
			     (pop->poc_handler == s_endif) ) )
			    (*pop->poc_handler)(pop->poc_val);
			else
			    totally_ignore_line();
		    }
		    else if(macro_name){
			/*
			 * When defining a macro we can't ignore .endmacro
			 * pseudo ops.
			 */
			if(pop != NULL &&
			   pop->poc_handler == s_endmacro)
				(*pop->poc_handler)(pop->poc_val);
			else
			    add_to_macro_definition(start_of_line);
		    }
		    else{
			if(pop != NULL)
			    (*pop->poc_handler)(pop->poc_val);
			else{
			    after_name = *after_name_pointer;
			    *after_name_pointer = '\0';
			    /*
			     * If macros are on see if this is a use of a macro
			     * otherwise it is an unknown pseudo op.
			     */
			    if(macros_on == TRUE &&
			       (the_macro = hash_find(ma_hash, s)) != NULL){
				*after_name_pointer = after_name;
				expand_macro(the_macro);
			    }
			    else{
				as_warn ("Unknown pseudo-op: %s", s);
				*after_name_pointer = after_name;
				ignore_rest_of_line();
			    }
			}
		    }
		    continue;

		} /* if(*s == '.' && ... ) */

		/*
		 * If we are in a conditional and the state is that we are now
		 * not including lines to be assembled then ignore the line.
		 */
		if(the_cond_state.ignore){
		    *input_line_pointer = after_name;
		    totally_ignore_line();
		}
		/*
		 * If we are in the state of defining a macro then take the line
		 * for the macro definition.
		 */
		else if(macro_name != NULL){
		    *input_line_pointer = after_name;
		    add_to_macro_definition(start_of_line);
	        }
		/*
		 * Look for a user defined label.
		 */
		else if(after_name == ':'){
		    colon(s);
#ifdef I860
		    /*
		     * Intel :: feature, which makes the label global if
		     * followed by two "::"'s  . This is ifdef'ed in so their
		     * is no else cause thus the slightly odd logic.
		     */
		    if(input_line_pointer[1] == ':'){
			struct symbol *symbolP;

			symbolP = symbol_find_or_make(s);
			symbolP->sy_type |= N_EXT; /* make symbol name global */
			*input_line_pointer = ':'; /* Restore first ':' */
			input_line_pointer++;	 /* step over first ':' */
		    }
#endif
		    /* put ':' back for error messages and step over it */
		    *input_line_pointer = ':';
		    input_line_pointer++;
		}
		/*
		 * Parse the assignment to a symbol.  The syntax for this is
		 * <symbol><equal><expression>.
		 */
		else if(after_name == '=' ||
		       ((after_name == ' ' || after_name == '\t') &&
		       input_line_pointer[1] == '=')){
		    equals(s);
		    demand_empty_rest_of_line();
		}
		/*
		 * If macros are on see if this is a use of a macro.
		 */
		else if(macros_on == TRUE &&
			(the_macro = hash_find(ma_hash, s)) != NULL){
		    *input_line_pointer = after_name;
		    expand_macro(the_macro);
		}
		/*
		 * Now assume it is a machine instruction and if not it
		 * will be handled as an error.  Machine instructions must be
		 * one to a line.
		 */
		else{
		    *input_line_pointer = after_name;
		    while(is_end_of_line[(int)*input_line_pointer] == FALSE)
			input_line_pointer ++;
		    end_of_line = *input_line_pointer;
		    *input_line_pointer = '\0';
		    md_assemble(s);
		    *input_line_pointer = end_of_line;
		    input_line_pointer++;
		}
		/*
		 * At this point we have parsed all things that could have
		 * started with a name.  Since one of these things (user defined
		 * lables could appear more than once on a line we do a continue
		 * here and start parsing as if at the begining of another
		 * logicial line.
		 */
		continue;

	    } /* if(is_name_beginner(c) || c == '"') */

	    /* empty statement */
	    if(is_end_of_line[(int)c])
		continue;

	    /*
	     * If we are in a conditional and the state is that we are now
	     * not including lines to be assembled then ignore the line.
	     */
	    if(the_cond_state.ignore){
		totally_ignore_line();
		continue;
	    }

	    /*
	     * If we are in the state of defining a macro then take the line
	     * for the macro definition.
	     */
	    if(macro_name != NULL){
		add_to_macro_definition(start_of_line);
		continue;
	    }

	    /* local label  ("4:") */
	    if(isdigit(c)){
		digit_value = c - '0';
		if(*input_line_pointer++ == ':' ){
		    local_colon(digit_value);
		}
		else{
		    as_warn("Spurious digit %d.", digit_value);
		    input_line_pointer--;
		    ignore_rest_of_line();
		}
		continue;
	    }

	    /*
	     * The only full line comment that should make it here is the first
	     * of the pair of "#APP\n ... #NO_APP\n" that the compiler uses to
	     * wrap around asm() statements.  If that is the case then
	     * parse_line_comment() creates a buffer with those lines in it and
	     * calls parse_a_buffer() with that buffer.  Then returns here
	     * skiping over that part of the current buffer.
	     */
	    if(c != '\0' && strchr(md_line_comment_chars, c) != NULL){
		parse_line_comment(&buffer);
		continue;
	    }

	    as_warn("Junk character %d.",c);
	    ignore_rest_of_line();

	} /* while(input_line_pointer < buffer_limit) */
}

/*
 * parse_line_comment() parses a line comment for parse_a_buffer().  Since
 * parse_a_buffer() only operates on "well formed" assembly the only legal
 * line comment that should appear is a "#APP\n ... #NO_APP\n" pair which
 * tells us to scrub the characters between them and then parse them.
 */
static
void
parse_line_comment(
char **buffer)
{
    char *s;
    char *ends;

    char *new_buf;
    char *new_tmp;
    int	 new_length;

    char *tmp_buf;
    char *old_input_line_pointer;
    char *old_buffer_limit;


	/* parse_a_buffer should never see any line comment if app is on */
	know(preprocess == FALSE);

	s = input_line_pointer;
	/* This must be a #APP\n line comment if not ignore it */
	if(strncmp(s,"APP\n",4) != 0)
	    return;

	if(count_lines == TRUE)
	    bump_line_counters();
	s += sizeof("APP\n") - 1;

	/*
	 * Search for the matching #NO_APP\n in this buffer, if it is found
	 * in this buffer the un-scrubed characters between the "#APP\n" and
	 * "#NO_APP\n" start where s is pointing to and end where ends is
	 * pointing to.
	 */
	ends = strstr(s, "#NO_APP\n");

	tmp_buf = NULL;
	if(ends == NULL){
	    /* The matching #NO_APP\n for the #APP\n wasn't in this buffer. */
	    int	tmp_len;
	    int	num;

	    /*
	     * First create a temporary place (tmp_buf of size tmp_len) to
	     * collect the un-scrubbed characters between the "#APP\n" and the
	     * "#NO_APP\n" (or end of file) when we find it in some buffer.
	     */
	    tmp_len = buffer_limit - s;
	    tmp_buf = xmalloc(tmp_len);

	    /*
	     * Copy the end of the buffer that contains the first part of
	     * the un-scrubbed contents starting just after the "#APP\n".
	     * This is so the the current buffer (buffer) can be used to
	     * collect the the rest of the un-scrubbed contents and to find
	     * the matching "#NO_APP\n".
	     */
	    memcpy(tmp_buf, s, tmp_len);

	    /*
	     * This loop collects the remaining un-scrubed contents between
	     * "#APP\n" and the "#NO_APP\n" into tmp_buf (adjusting tmp_len)
	     * and looks for the matching "#NO_APP\n".
	     */
	    do{
		buffer_limit = input_scrub_next_buffer(buffer);
		/*
		 * We treat runing into the end of the file as if it was the
		 * "#NO_APP" we were looking for.
		 */
		if(buffer_limit == NULL)
		    break;

		ends = strstr(*buffer, "#NO_APP\n");
		if(ends != NULL)
		    num = ends - *buffer;
		else
		    num = buffer_limit - *buffer;

		tmp_buf = xrealloc(tmp_buf, tmp_len + num);
		memcpy(tmp_buf + tmp_len, *buffer, num);
		tmp_len += num;
	    }while(ends == NULL);

	    /*
	     * Now set up buffer, buffer_limit and input_line_pointer be past
	     * all the characters of the "#APP\n ... #NO_APP\n" set so that
	     * when we return parsing will be picked up from their.
	     */
	    if(ends != NULL)
		input_line_pointer = ends + sizeof("#NO_APP\n") - 1;
	    else{
		input_line_pointer = *buffer;
		buffer_limit = *buffer;
	    }

	    /*
	     * Now set s to the start, and ends to the end of the un-scrubed
	     * contents of the collected characters between the "#APP\n" and
	     * "#NO_APP\n" pair.
	     */
	    s = tmp_buf;
	    ends = s + tmp_len;
	}
	else{
	    /*
	     * The matching "#NO_APP\n" was in the buffer as we were called so
	     * s is the start, and ends is the end of the un-scrubed contents
	     * of the characters between the "#APP\n" and "#NO_APP\n" pair.
	     * Now to set up buffer, buffer_limit and input_line_pointer be past
	     * all the characters of the "#APP\n ... #NO_APP\n" set so that
	     * when we return parsing will be picked up from their all that has
	     * to be done is move the input_line_pointer past the "#NO_APP\n".
	     */
	    input_line_pointer = ends + sizeof("#NO_APP\n") - 1;
	}

	/*
	 * Now that we have the un-scrubed characters beween s and ends setup
	 * to scrub them into a new buffer (new_buf of size new_length to
	 * new_tmp).
	 */
	new_length = 100;
	new_buf = xmalloc(new_length);
	new_tmp = new_buf;
	*new_tmp++ = '\n'; /* place leading \n in buffer for parse_a_buffer */

	scrub_string = s;
	scrub_last_string = ends;
	for(;;){
	    int c;

	    c = do_scrub_next_char(scrub_from_string, scrub_to_string);
	    if(c == EOF)
		break;
	    *new_tmp++ = c;
	    if(new_tmp == new_buf + new_length){
		new_buf = xrealloc(new_buf, new_length + 100);
		new_tmp = new_buf + new_length;
		new_length += 100;
	    }
	}
	*new_tmp = '\n'; /* place trailing \n in buffer for parse_a_buffer */

	/*
	 * If we used a temporary buffer to collect the un-scrubbed characters
	 * it is no longer needed and can be free()'ed.
	 */
	if(tmp_buf != NULL)
	    free(tmp_buf);

	/*
	 * Now we are ready to recursively call parse_a_buffer() with our buffer
	 * of scrubed characters.  So save the state of parse_a_buffer() and set
	 * it up with our buffer of scrubed characters.
	 */
	old_input_line_pointer = input_line_pointer;
	old_buffer_limit = buffer_limit;

	input_line_pointer = new_buf;
	buffer_limit = new_tmp;
	parse_a_buffer(new_buf);

	/*
	 * Free the buffer that held the scrubbed characters
	 */
	free(new_buf);

	/*
	 * After coming back from our recursive call parse_a_buffer() we want 
	 * resume parsing after the "#NO_APP\n".  So bump the line counters
	 * for the "#NO_APP\n" and restore the state so we can return to
	 * parse_a_buffer().
	 */
	if(count_lines == TRUE)
	    bump_line_counters();
	input_line_pointer = old_input_line_pointer;
	buffer_limit = old_buffer_limit;

	return;
}

/*
 * s_abort() implements the pseudo op:
 *	.abort [ "abort_string" ]
 */
static
void
s_abort(
int value)
{
    char *p;

	p = input_line_pointer;
	while(is_end_of_line[(int)*p] == FALSE)
	    p++;
	*p = '\0';
	
	as_fatal(".abort %s detected.  Assembly stopping.", input_line_pointer);
}

#if !defined(I860) /* i860 has it's own align and org */
/*
 * s_align() implements the pseudo op
 *	.align expression [ , fill_expression ]
 */
static
void
s_align(
int value)
{
	int temp;
	long temp_fill;

	temp = get_absolute_expression();
#define MAX_ALIGNMENT (15)
	if(temp > MAX_ALIGNMENT)
	    as_warn("Alignment too large: %d. assumed.", temp = MAX_ALIGNMENT);
	else if(temp < 0){
	    as_warn("Alignment negative. 0 assumed.");
	    temp = 0;
	}
	if(*input_line_pointer == ','){
	    input_line_pointer ++;
	    temp_fill = get_absolute_expression ();
	}
	else
	    temp_fill = 0;

	/* Only make a frag if we HAVE to. . . */
	if(temp != 0)
	    frag_align(temp, (int)temp_fill);

	/*
	 * If this alignment is larger than any previous alignment then this
	 * becomes the section's alignment.
	 */
	if(frchain_now->frch_section.align < temp)
	    frchain_now->frch_section.align = temp;

	demand_empty_rest_of_line();
}
#endif /* !defined(I860) i860 has it's own align and org */

/*
 * s_comm() implements the pseudo op:
 *	.comm name , expression
 */
static
void
s_comm(
int value)
{
    char *name;
    char c;
    char *p;
    int temp;
    symbolS *symbolP;

	if(*input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	/* just after name is now '\0' */
	p = input_line_pointer;
	*p = c;
	SKIP_WHITESPACE();
	if(*input_line_pointer != ','){
	    as_warn("Expected comma after symbol-name");
	    ignore_rest_of_line();
	    return;
	}
	input_line_pointer++; /* skip ',' */
	if((temp = get_absolute_expression ()) < 0){
	    as_warn(".COMMon length (%d.) <0! Ignored.", temp);
	    ignore_rest_of_line();
	    return;
	}
	*p = 0;
	symbolP = symbol_find_or_make(name);
	*p = c;
	if((symbolP->sy_type & N_TYPE) != N_UNDF ||
	   symbolP->sy_other != 0 ||
	   symbolP->sy_desc != 0) {
	    as_warn("Ignoring attempt to re-define symbol");
	    ignore_rest_of_line();
	    return;
	}
	if(symbolP->sy_value != 0){
	    if(symbolP->sy_value != temp)
		as_warn("Length of .comm \"%s\" is already %ld. Not changed "
			"to %d.", symbolP->sy_name, symbolP->sy_value, temp);
	}
	else{
	    symbolP -> sy_value = temp;
	    symbolP -> sy_type |= N_EXT;
	}
	know(symbolP->sy_frag == &zero_address_frag);
	demand_empty_rest_of_line();
}

/*
 * s_desc() implements the pseudo op:
 *	.desc name , expression
 * sets the n_desc field of a symbol.
 */
static
void
s_desc(
int value)
{
    char *name;
    char c;
    char *p;
    symbolS *symbolP;
    int temp;

	if(*input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	p = input_line_pointer;
	symbolP = symbol_table_lookup(name);
	*p = c;
	SKIP_WHITESPACE();
	if(*input_line_pointer != ','){
	    *p = 0;
	    as_warn("Expected comma after name \"%s\"", name);
	    *p = c;
	    ignore_rest_of_line();
	}
	else{
	    input_line_pointer++;
	    temp = get_absolute_expression();
	    *p = 0;
	    symbolP = symbol_find_or_make(name);
	    *p = c;
	    symbolP->sy_desc = temp;
	}
	demand_empty_rest_of_line();
}

/*
 * s_file() implements the pseudo op:
 *	.file name [ level_number ]
 * the level number is generated by /lib/cpp and is just ignored.
 */
static
void
s_file(
int value)
{
    char *s;
    int length;
    struct symbol *symbolP;

	/* Some assemblers tolerate immediately following '"' */
	if((s = demand_copy_string(&length))){
	    SKIP_WHITESPACE();
	    if(*input_line_pointer >= '0' && *input_line_pointer <= '9'){
		while(*input_line_pointer >= '0' &&
		      *input_line_pointer <= '9')
		      input_line_pointer++;
	    }
	    new_logical_line(s, -1);
	    demand_empty_rest_of_line();

	    /*
	     * This is to generate stabs for debugging assembly code.
	     * See the comments about stabs in read_a_source_file()
	     * for a description of what is going on here.
	     */
	    if(flagseen['g'] && frchain_now->frch_nsect == text_nsect){
		symbolP = symbol_new(
			      logical_input_file,
			      132 /* N_SOL */,
			      text_nsect,
			      0,
			      obstack_next_free(&frags) - frag_now->fr_literal,
			      frag_now);
	    }
	}
}

/*
 * s_fill() implements the pseudo op:
 *	.fill repeat_expression , fill_size , fill_expression
 */
static
void
s_fill(
int value)
{
    long temp_repeat;
    long temp_size;
    long temp_fill;
    char *p;

	if(get_absolute_expression_and_terminator(&temp_repeat) != ','){
	    input_line_pointer--; /* Backup over what was not a ','. */
	    as_warn("Expect comma after rep-size in .fill");
	    ignore_rest_of_line();
	    return;
	}
	if(get_absolute_expression_and_terminator(&temp_size) != ','){
	    input_line_pointer--; /* Backup over what was not a ','. */
	    as_warn("Expected comma after size in .fill");
	    ignore_rest_of_line();
	    return;
	}
	/*
	 * This is to be compatible with BSD 4.2 AS, not for any rational
	 * reason.
	 */
#define BSD_FILL_SIZE_CROCK_8 (8)
	if(temp_size > BSD_FILL_SIZE_CROCK_8){
	    as_warn(".fill size clamped to %d.", BSD_FILL_SIZE_CROCK_8);
	    temp_size = BSD_FILL_SIZE_CROCK_8 ;
	}
	if(temp_size < 0){
	    as_warn("Size negative: .fill ignored.");
	    temp_size = 0;
	}
	/*
	 * bug fix, if md_number_to_chars() is called with something other than
	 * 1,2 or 4 it calls abort().  So we don't let the size be something
	 * like 3. Bug #13017.
	 */
	else if(temp_size != 0 &&
		temp_size != 1 &&
		temp_size != 2 &&
		temp_size != 4){
	    as_warn("Repeat must be 0,1,2 or 4, .fill ignored");
	    temp_size = 0;
	}
	else if(temp_repeat <= 0){
	    as_warn("Repeat < 0, .fill ignored");
	    temp_size = 0;
	}
	temp_fill = get_absolute_expression();
	/*
	 * Note: .fill (),0 emits no frag (since we are asked to .fill 0 bytes)
	 * but emits no error message because it seems a legal thing to do.
	 * It is a degenerate case of .fill but could be emitted by a compiler.
	 */
	if(temp_size != 0){
	      p = frag_var(rs_fill,
			   (int)temp_size,
			   (int)temp_size,
			   (relax_substateT)0,
			   (symbolS *)0,
			   temp_repeat,
			   (char *)0);
	      memset(p, '\0', (int)temp_size);
	      /*
	       * The magic number BSD_FILL_SIZE_CROCK_4 is from BSD 4.2 VAX
	       * flavoured AS. The following bizzare behaviour is to be
	       * compatible with above.  I guess they tried to take up to 8
	       * bytes from a 4-byte expression and they forgot to sign extend.
	       */
#define BSD_FILL_SIZE_CROCK_4 (4)
	      md_number_to_chars(p,
				 temp_fill,
				 temp_size > BSD_FILL_SIZE_CROCK_4 ?
					BSD_FILL_SIZE_CROCK_4 : (int)temp_size);
	}
	demand_empty_rest_of_line();
}

/*
 * s_globl() implements the pseudo op:
 *	.globl name [ , name ]
 */
void
s_globl(
int value)
{
    char *name;
    int c;
    symbolS *symbolP;

	do{
	    if(*input_line_pointer == '"')
		name = input_line_pointer + 1;
	    else
		name = input_line_pointer;
	    c = get_symbol_end();
	    symbolP = symbol_find_or_make(name);
	    *input_line_pointer = c;
	    SKIP_WHITESPACE();
	    symbolP->sy_type |= N_EXT;
	    if(c == ','){
		input_line_pointer++;
		SKIP_WHITESPACE();
		if(*input_line_pointer == '\n')
		    c = '\n';
	    }
	}while(c == ',');
	demand_empty_rest_of_line();
}

/*
 * s_private_extern() implements the pseudo op:
 *	.private_extern name [ , name ]
 */
static
void
s_private_extern(
int value)
{
    char *name;
    int c;
    symbolS *symbolP;

	if(!flagseen['k'])
	    as_fatal("incompatible feature used: .private_extern (must specify "
		     "-k to be used)");
	do{
	    if(*input_line_pointer == '"')
		name = input_line_pointer + 1;
	    else
		name = input_line_pointer;
	    c = get_symbol_end();
	    symbolP = symbol_find_or_make(name);
	    *input_line_pointer = c;
	    SKIP_WHITESPACE();
	    symbolP->sy_type |= N_EXT;
	    symbolP->sy_type |= N_PEXT;
	    if(c == ','){
		input_line_pointer++;
		SKIP_WHITESPACE();
		if(*input_line_pointer == '\n')
		    c = '\n';
	    }
	}while(c == ',');
	demand_empty_rest_of_line();
}

/*
 * s_indirect_symbol() implements the pseudo op:
 *	.indirect_symbol name
 */
static
void
s_indirect_symbol(
int value)
{
    char *name;
    int c;
    unsigned long section_type;

	if(!flagseen['k'])
	    as_fatal("incompatible feature used: .indirect_symbol (must "
		     "specify -k to be used)");
	if(frchain_now == NULL){
	    know(flagseen['n']);
	    as_fatal("with -n a section directive must be seen before assembly "
		     "can begin");
	}
	section_type = frchain_now->frch_section.flags & SECTION_TYPE;
	if(section_type != S_NON_LAZY_SYMBOL_POINTERS &&
	   section_type != S_LAZY_SYMBOL_POINTERS &&
	   section_type != S_SYMBOL_STUBS){
	    as_warn("indirect symbol not in a symbol pointer or stub section, "
		    ".indirect_symbol ignored");
	    ignore_rest_of_line();
	    return;
	}

	if(*input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	indirect_symbol_new(name,
			    frag_now,
			    obstack_next_free(&frags) - frag_now->fr_literal);
	*input_line_pointer = c;

	demand_empty_rest_of_line();
}

/*
 * s_lcomm() implements the pseudo op:
 *	.lcomm name , size_expression [ , align_expression ]
 */
static
void
s_lcomm(
int value)
{
    char *name;
    char c;
    char *p;
    int size;
    symbolS *symbolP;
    int align;
    static frchainS *bss = NULL;

	if(*input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	p = input_line_pointer;
	*p = c;
	SKIP_WHITESPACE();
	if(*input_line_pointer != ','){
	    as_warn("Expected comma after name");
	    ignore_rest_of_line();
	    return;
	}
	input_line_pointer ++;
	if((size = get_absolute_expression()) < 0){
	    as_warn("BSS length (%d.) <0! Ignored.", size);
	    ignore_rest_of_line();
	    return;
	}
#define MAX_ALIGNMENT (15)
	align = 0;
	if(*input_line_pointer == ','){
	    input_line_pointer++;
	    align = get_absolute_expression();
	    if(align > MAX_ALIGNMENT){
		as_warn("Alignment too large: %d. assumed.", MAX_ALIGNMENT);
		align = MAX_ALIGNMENT;
	    }
	    else if(align < 0){
		as_warn("Alignment negative. 0 assumed.");
		align = 0;
	    }
	}
	*p = 0;
	symbolP = symbol_find_or_make(name);
	*p = c;

	if((symbolP->sy_type & N_TYPE) == N_UNDF && symbolP->sy_value == 0){
	    if(bss == NULL){
		bss = section_new(SEG_DATA, SECT_BSS, S_ZEROFILL, 0, 0);
		bss->frch_root = xmalloc(SIZEOF_STRUCT_FRAG);
		memset(bss->frch_root, '\0', SIZEOF_STRUCT_FRAG);
		bss->frch_last = bss->frch_root;
	    }
	    bss->frch_root->fr_address = round(bss->frch_root->fr_address,
					       1 << align);
	    symbolP->sy_value = bss->frch_root->fr_address;
	    symbolP->sy_type  = N_SECT;
	    symbolP->sy_other = bss->frch_nsect;
	    symbolP->sy_frag  = bss->frch_root;
	    bss->frch_root->fr_address += size;
	    /*
	     * If this alignment is larger than any previous alignment then this
	     * becomes the section's alignment.
	     */
	    if(bss->frch_section.align < align)
		bss->frch_section.align = align;
	}
	else
	    as_warn("Ignoring attempt to re-define symbol.");
	demand_empty_rest_of_line();
}

/*
 * s_line() implements the pseudo op:
 *	.line line_number
 */
void
s_line(
int value)
{
	/*
	 * Assume delimiter is part of expression. BSD4.2 as fails with
	 * delightful bug, so we are not being incompatible here.
	 */
	/*
	 * Since the assembler bumps it's line counters at the end of a line
	 * and it is the case that the .line is on it's own line what the
	 * intent is that the line number is for the next line.  Thus
	 * the -1 .  This is the way cpp'ed assembler files work which is the
	 * common case.
	 */
	new_logical_line((char *)NULL, (int)(get_absolute_expression()) - 1);
	demand_empty_rest_of_line();
}

/*
 * s_lsym() implements the pseudo op:
 *	.lsym name , expression
 */
static
void
s_lsym(
int value)
{
    char *name;
    char c;
    char *p;
    segT segment;
    expressionS exp;
    symbolS *symbolP;

	/* we permit ANY expression: BSD4.2 demands constants */
	if(*input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	p = input_line_pointer;
	*p = c;
	SKIP_WHITESPACE();
	if(*input_line_pointer != ','){
	    *p = 0;
	    as_warn("Expected comma after name \"%s\"", name);
	    *p = c;
	    ignore_rest_of_line();
	    return;
	}
	input_line_pointer++;
	segment = expression(&exp);
	if(segment != SEG_ABSOLUTE && segment != SEG_SECT){
/* this warning still need fixing */
	    as_warn("Bad expression: %s", seg_name[(int)segment]);
	    ignore_rest_of_line();
	    return;
	}
	know(segment == SEG_ABSOLUTE || segment == SEG_SECT);
	*p = 0;
	if(segment == SEG_SECT)
	    symbolP = symbol_new(name,
				 N_SECT,
	    			 frchain_now->frch_nsect,
				 0,
				 (valueT)(exp.X_add_number),
				 &zero_address_frag);
	else
	    symbolP = symbol_new(name,
				 N_ABS,
	    			 0,
				 0,
				 (valueT)(exp.X_add_number),
				 &zero_address_frag);
	*p = c;
	demand_empty_rest_of_line();
}

#if !defined(I860) /* i860 has it's own align and org */
/*
 * s_org() implements the pseudo op:
 *	.org  expression
 */
static
void
s_org(
int value)
{
    segT segment;
    expressionS exp;
    long temp_fill;
    char *p;

	/*
	 * Don't believe the documentation of BSD 4.2 AS.
	 * There is no such thing as a sub-segment-relative origin.
	 * Any absolute origin is given a warning, then assumed to be
	 * segment-relative.
	 * Any segmented origin expression ("foo+42") had better be in the right
	 * segment or the .org is ignored.
	 *
	 * BSD 4.2 AS warns if you try to .org backwards. We cannot because we
	 * never know sub-segment sizes when we are reading code.
	 * BSD will crash trying to emit -ve numbers of filler bytes in certain
	 * .orgs. We don't crash, but see as-write for that code.
	 */
	segment = get_known_segmented_expression(&exp);
	if(*input_line_pointer == ','){
	    input_line_pointer ++;
	    temp_fill = get_absolute_expression ();
	}
	else
	    temp_fill = 0;
	if((segment != SEG_SECT ||
	    exp.X_add_symbol->sy_other != frchain_now->frch_nsect) &&
	    segment != SEG_ABSOLUTE)
	    as_warn("Illegal expression. current section assumed.");
	p = frag_var(rs_org,
		     1,
		     1,
		     (relax_substateT)0,
		     exp.X_add_symbol,
		     exp.X_add_number,
		     (char *)0);
	*p = temp_fill;
	demand_empty_rest_of_line();
}
#endif /* !defined(I860) i860 has it's own align and org */

/*
 * s_set() implements the pseudo op:
 *	.set name , expression
 */
static
void
s_set(
int value)
{
    char *name;
    char delim;
    char *end_name;
    symbolS *symbolP;

	if( * input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	delim = get_symbol_end();
	end_name = input_line_pointer;
	*end_name = delim;
	SKIP_WHITESPACE();
	if(*input_line_pointer != ','){
	    *end_name = 0;
	    as_warn("Expected comma after name \"%s\"", name);
	    *end_name = delim;
	    ignore_rest_of_line();
	    return;
	}
	input_line_pointer++;
	*end_name = 0;
	if(name[0] == '.' && name[1] == '\0'){
	    /* Turn 'set . , mumble' into a .org mumble */
	    segT segment;
	    expressionS exp;
	    char *ptr;

	    segment = get_known_segmented_expression(&exp);
	    if((segment != SEG_SECT ||
		exp.X_add_symbol->sy_other != frchain_now->frch_nsect) &&
		segment != SEG_ABSOLUTE)
		as_warn("Illegal expression. current section assumed.");
	    ptr = frag_var(rs_org,
			   1,
			   1,
			   (relax_substateT)0,
			   exp.X_add_symbol,
			   exp.X_add_number,
			   (char *)0);
	    *ptr = 0;
	    *end_name = delim;
	    return;
	}
	symbolP = symbol_find_or_make(name);
	*end_name = delim;
	pseudo_set(symbolP);
	demand_empty_rest_of_line();
}

/*
 * s_abs() implements the pseudo op:
 *	.abs name , expression
 * which sets symbol to 1 or 0 depending on if the expression is an absolute
 * expression.  This is intended for use in macros.
 */
void
s_abs(
int value)
{
    char *name;
    char c;
    char *p;
    segT segment;
    expressionS exp;
    symbolS *symbolP;

	if(*input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	p = input_line_pointer;
	*p = c;
	SKIP_WHITESPACE();
	if(*input_line_pointer != ','){
	    *p = 0;
	    as_warn("Expected comma after name \"%s\"", name);
	    *p = c;
	    ignore_rest_of_line();
	    return;
	}
	input_line_pointer++;
	*p = 0;
	segment = expression(&exp);
	symbolP = symbol_find_or_make(name);
	symbolP->sy_type = N_ABS;
	symbolP->sy_other = 0; /* NO_SECT */
	symbolP->sy_frag = &zero_address_frag;
	if(segment == SEG_ABSOLUTE)
	    symbolP->sy_value = 1;
	else
	    symbolP->sy_value = 0;
	*p = c;
	totally_ignore_line();
}

/*
 * s_space() implements the pseudo op:
 *	.space repeat_expression [ , fill_expression ]
 */
void
s_space(
int value)
{
    long temp_repeat;
    long temp_fill;
    char *p;

	/* Just like .fill, but temp_size = 1 */
	if(get_absolute_expression_and_terminator(&temp_repeat) == ','){
	    temp_fill = get_absolute_expression();
	}
	else{
	    input_line_pointer--; /* Backup over what was not a ','. */
	    temp_fill = 0;
	}
	if(temp_repeat <= 0){
	    as_warn("Repeat < 0, .space ignored");
	    ignore_rest_of_line();
	    return;
	}
	p = frag_var(rs_fill,
		     1,
		     1,
		     (relax_substateT)0,
		     (symbolS *)0,
		     temp_repeat,
		     (char *)0);
	*p = temp_fill;
	demand_empty_rest_of_line();
}

static
unsigned long
s_builtin_section(
const struct builtin_section *s)
{
    frchainS *frcP;

	if(!flagseen['k']){
	    if((s->flags & SECTION_TYPE) == S_NON_LAZY_SYMBOL_POINTERS ||
	       (s->flags & SECTION_TYPE) == S_LAZY_SYMBOL_POINTERS ||
	       (s->flags & SECTION_TYPE) == S_SYMBOL_STUBS ||
	       (s->flags & SECTION_TYPE) == S_MOD_INIT_FUNC_POINTERS ||
	       (s->flags & SECTION_ATTRIBUTES) != 0)
		as_fatal("incompatible feature used: directive .%s (must "
			 "specify -k to be used)", s->directive);
	}
	/*
	 * If we allowed to use the new features that are incompatible with 3.2
	 * and this is the text section (which relys on the fact that the text
	 * section is first in the built in sections list) then add the
	 * S_ATTR_PURE_INSTRUCTIONS to the section attributes.
	 */
	if(flagseen['k'] && s == builtin_sections){
	    frcP = section_new(s->segname, s->sectname,
			       s->flags & SECTION_TYPE,
			       (s->flags & SECTION_ATTRIBUTES) |
					S_ATTR_PURE_INSTRUCTIONS,
			       s->sizeof_stub);
	}
	else{
	    frcP = section_new(s->segname, s->sectname,
			       s->flags & SECTION_TYPE,
			       s->flags & SECTION_ATTRIBUTES, 
			       s->sizeof_stub);
	}
	if(frcP->frch_section.align < s->default_align)
	    frcP->frch_section.align = s->default_align;
	return(frcP->frch_nsect);
}

/*
 * s_section() implements the pseudo op:
 *	.section segname , sectname [[[ , type ] , attribute] , sizeof_stub]
 */
static
void
s_section(
int value)
{
    char *segname, *sectname, *typename;
    char c, d, e, *p, *q, *r;
    struct type_name *type_name;
    unsigned long type, attribute;
    struct section s;
    frchainS *frcP;
    unsigned long sizeof_stub;

    struct attribute_name *attribute_name;
    char *attributename, *sizeof_stub_name, f, g, *t, *u, *endp;

	segname = input_line_pointer;
	do{
	    c = *input_line_pointer++ ;
	}while(c != ',' && c != '\0' && c != '\n');
	if(c != ','){
	    as_warn("Expected comma after segment-name");
	    ignore_rest_of_line();
	    return;
	}
	p = input_line_pointer - 1;

	sectname = input_line_pointer;
	do{
	    d = *input_line_pointer++ ;
	}while(d != ',' && d != '\0' && d != '\n');
	if(p + 1 == input_line_pointer){
	    as_warn("Expected section-name after comma");
	    ignore_rest_of_line();
	    return;
	}
	q = input_line_pointer - 1;

	*p = 0;
	if(strlen(segname) > sizeof(s.segname)){
	    as_warn("segment-name: %s too long (maximum %ld characters)",
		    segname, sizeof(s.segname));
	    ignore_rest_of_line();
	    *p = c;
	    return;
	}

	*q = 0;
	if(strlen(sectname) > sizeof(s.sectname)){
	    as_warn("section-name: %s too long (maximum %ld characters)",
		    sectname, sizeof(s.sectname));
	    ignore_rest_of_line();
	    return;
	}
	/*
	 * Now see if the optional section type is present.
	 */
	type = 0;
	type_name = type_names;
	attribute = 0;
	attribute_name = attribute_names;
	sizeof_stub = 0;
	if(d == ','){
	    typename = input_line_pointer;
	    do{
		e = *input_line_pointer++ ;
	    }
	    while(e != ',' && e != '\0' && e != '\n');
	    r = input_line_pointer - 1;
	    *r = 0;
	    for(type_name = type_names; type_name->name != NULL; type_name++)
		if(strcmp(type_name->name, typename) == 0)
		    break;
	    if(type_name->name == NULL){
		as_warn("unknown section type: %s", typename);
		ignore_rest_of_line();
		return;
	    }
	    *r = e;
	    type = type_name->type;
	    /*
	     * Now see if the optional section attribute is present.
	     */
	    if(e == ','){
		attributename = input_line_pointer;
		do{
		    f = *input_line_pointer++ ;
		}while(f != ',' && f != '\0' && f != '\n');
		t = input_line_pointer - 1;
		*t = 0;
		for(attribute_name = attribute_names;
		    attribute_name->name != NULL;
		    attribute_name++)
		    if(strcmp(attribute_name->name, attributename) == 0)
			break;
		if(attribute_name->name == NULL){
		    as_warn("unknown section attribute: %s", attributename);
		    ignore_rest_of_line();
		    return;
		}
		*t = f;
		attribute = attribute_name->attribute;

		/*
		 * Now get the section stub size if this is a stub section.
		 */
		if(type == S_SYMBOL_STUBS){
		    if(f == ','){
			sizeof_stub_name = input_line_pointer;
			do{
			    g = *input_line_pointer++ ;
			}while(g != '\0' && g != '\n');
			u = input_line_pointer - 1;
			*u = 0;
			sizeof_stub = strtoul(sizeof_stub_name, &endp, 0);
			if(*endp != '\0'){
			    as_warn("size of stub section: %s not a proper "
				    "number", sizeof_stub_name);
			    ignore_rest_of_line();
			    return;
			}
			*u = g;
		    }
		    else{
			as_warn("missing size of stub section (%s,%s)", segname,
				sectname);
			ignore_rest_of_line();
			return;
		    }
		}
	    }
	    else if(type == S_SYMBOL_STUBS){
		as_warn("missing size of stub section (%s,%s)", segname,
			sectname);
		ignore_rest_of_line();
		return;
	    }
	}
	input_line_pointer--;

	if(!flagseen['k']){
	    if(type == S_NON_LAZY_SYMBOL_POINTERS ||
	       type == S_LAZY_SYMBOL_POINTERS ||
	       type == S_SYMBOL_STUBS ||
	       type == S_MOD_INIT_FUNC_POINTERS)
		as_fatal("incompatible feature used: section type %s (must "
			 "specify -k to be used)", type_name->name);
	    if(attribute != 0)
		as_fatal("incompatible feature used: section attribute %s "
			 "(must specify -k to be used)", attribute_name->name);
	}

	frcP = section_new(segname, sectname, type, attribute, sizeof_stub);
	*p = c;
	*q = d;
	demand_empty_rest_of_line();
}

/*
 * s_zerofill() implements the pseudo op:
 *	.zerofill segname , sectname [, symbolname , size_expression [ , align]]
 */
static
void
s_zerofill(
int value)
{
    char *segname, *sectname, c, d, *p, *q, *name;
    struct section s;
    frchainS *frcP;
    symbolS *symbolP;
    int size, align;

	segname = input_line_pointer;
	do{
	    c = *input_line_pointer++ ;
	}while(c != ',' && c != '\0' && c != '\n');
	if(c != ','){
	    as_warn("Expected comma after segment-name");
	    ignore_rest_of_line();
	    return;
	}
	p = input_line_pointer - 1;

	sectname = input_line_pointer;
	do{
	    d = *input_line_pointer++ ;
	}while(d != ',' && d != '\0' && d != '\n');
	if(p + 1 == input_line_pointer){
	    as_warn("Expected section-name after comma");
	    ignore_rest_of_line();
	    return;
	}
	q = input_line_pointer - 1;

	*p = 0;
	if(strlen(segname) > sizeof(s.segname)){
	    as_warn("segment-name: %s too long (maximum %ld characters)",
		    segname, sizeof(s.segname));
	    ignore_rest_of_line();
	    *p = c;
	    return;
	}

	*q = 0;
	if(strlen(sectname) > sizeof(s.sectname)){
	    as_warn("section-name: %s too long (maximum %ld characters)",
		    sectname, sizeof(s.sectname));
	    ignore_rest_of_line();
	    *p = c;
	    *q = d;
	    return;
	}

	frcP = section_new(segname, sectname, S_ZEROFILL, 0, 0);
	if(frcP->frch_root == NULL){
	    frcP->frch_root = xmalloc(SIZEOF_STRUCT_FRAG);
	    frcP->frch_last = frcP->frch_root;
	    memset(frcP->frch_root, '\0', SIZEOF_STRUCT_FRAG);
	}
	*p = c;
	*q = d;
	/*
	 * If this is the end of the line all that was wanted was to create the
	 * the section which is now done, so return.
	 */
	if(d != ',')
	    return;

	if(*input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	p = input_line_pointer;
	*p = c;
	SKIP_WHITESPACE();
	if(*input_line_pointer != ','){
	    as_warn("Expected comma after symbol-name");
	    ignore_rest_of_line();
	    return;
	}
	input_line_pointer ++;
	if((size = get_absolute_expression()) < 0){
	    as_warn("zerofill size (%d.) <0! Ignored.", size);
	    ignore_rest_of_line();
	    return;
	}
	align = 0;
	if(*input_line_pointer == ','){
	    input_line_pointer++;
	    align = get_absolute_expression();
	    if(align > MAX_ALIGNMENT){
		as_warn("Alignment too large: %d. assumed.", MAX_ALIGNMENT);
		align = MAX_ALIGNMENT;
	    }
	    else if(align < 0){
		as_warn("Alignment negative. 0 assumed.");
		align = 0;
	    }
	}
	*p = 0;
	symbolP = symbol_find_or_make(name);
	*p = c;

	if((symbolP->sy_type & N_TYPE) == N_UNDF && symbolP->sy_value == 0){
	    frcP->frch_root->fr_address = round(frcP->frch_root->fr_address,
					        1 << align);
	    symbolP->sy_value = frcP->frch_root->fr_address;
	    symbolP->sy_type  = N_SECT | (symbolP->sy_type & N_EXT);
	    symbolP->sy_other = frcP->frch_nsect;
	    symbolP->sy_frag  = frcP->frch_root;
	    frcP->frch_root->fr_address += size;
	}
	else
	    as_warn("Ignoring attempt to re-define symbol.");

	demand_empty_rest_of_line();
}

/*
 * s_reference() implements the pseudo op:
 *	.reference name
 */
static
void
s_reference(
int value)
{
    char *name;
    char c;
    char *p;
    symbolS *symbolP;

	if(* input_line_pointer == '"')
	    name = input_line_pointer + 1;
	else
	    name = input_line_pointer;
	c = get_symbol_end();
	p = input_line_pointer;

	*p = 0;
	symbolP = symbol_find_or_make(name);
	*p = c;
	demand_empty_rest_of_line();
}

/*
 * s_include() implements the pseudo op:
 *	.include "filename"
 */
static
void
s_include(
int value)
{
	char *filename;
	int length;
	symbolS *symbolP;

	/* Some assemblers tolerate immediately following '"' */
	if((filename = demand_copy_string( & length ) )) {
	    demand_empty_rest_of_line();
	    read_an_include_file(filename);
	}

	/*
	 * This is to generate stabs for debugging assembly code.
	 * See the second comment about stabs in read_a_source_file()
	 * for a description of what is going on here
	 */
	if(flagseen['g'] && frchain_now->frch_nsect == text_nsect){
	    symbolP = symbol_new(
			    physical_input_file,
			    132 /* N_SOL */,
			    text_nsect,
			    0,
			    obstack_next_free(&frags) - frag_now->fr_literal,
			    frag_now);
	}
}

/*
 * demand_empty_rest_of_line() checks to make sure we are at the end of a line
 * and if not ignores the rest of the line.
 * This is global so machine dependent pseudo-ops can use this.
 */
void
demand_empty_rest_of_line(
void)
{
	SKIP_WHITESPACE();
	if(is_end_of_line[(int)*input_line_pointer])
	    input_line_pointer++;
	else
	    ignore_rest_of_line();
}

/*
 * ignore_rest_of_line() advances input_line_pointer to the next line and if
 * there is anything left on the current line print a warning.
 * This is global so machine dependent pseudo-ops can use this.
 */
void
ignore_rest_of_line(
void)
{
	if(!is_end_of_line[(int)*input_line_pointer]){
	    as_warn("Rest of line ignored. 1st junk character valued %d (%c).",
		    *input_line_pointer, *input_line_pointer);
	    while(input_line_pointer < buffer_limit &&
		  !is_end_of_line[(int)*input_line_pointer])
		input_line_pointer++;
	}
	input_line_pointer++;	/* Return pointing just after end-of-line. */
	know(is_end_of_line[(int)(input_line_pointer[-1])]);
}

/*
 *			stab()
 *
 * Handle .stabX directives, which used to be open-coded.
 * So much creeping featurism overloaded the semantics that we decided
 * to put all .stabX thinking in one place. Here.
 *
 * We try to make any .stabX directive legal. Other people's AS will often
 * do assembly-time consistency checks: eg assigning meaning to n_type bits
 * and "protecting" you from setting them to certain values. (They also zero
 * certain bits before emitting symbols. Tut tut.)
 *
 * If an expression is not absolute we either gripe or use the relocation
 * information. Other people's assemblers silently forget information they
 * don't need and invent information they need that you didn't supply.
 *
 * .stabX directives always make a symbol table entry. It may be junk if
 * the rest of your .stabX directive is malformed.
 */
static
void
stab(
int what) /* d == .stabd, n == .stabn, and s == .stabs */
{
    symbolS *symbolP;
    char *string;
    int saved_type;
    int length;
    int goof;	/* TRUE if we have aborted. */
    long longint;

	saved_type = 0;
	/*
	 * Enter with input_line_pointer pointing past .stabX and any following
	 * whitespace.
	 */
	goof = FALSE;
	if(what == 's'){
	    string = demand_copy_C_string(&length);
	    SKIP_WHITESPACE();
	    if(*input_line_pointer == ',')
		input_line_pointer ++;
	    else{
		as_warn("I need a comma after symbol's name");
		goof = TRUE;
	    }
	}
	else
	    string = "";

	/*
	 * Input_line_pointer->after ','.  String -> symbol name.
	 */
	if(!goof){
	    symbolP = symbol_new(string, 0,0,0,0,(struct frag *)0);
	    switch(what){
	    case 'd':
		symbolP->sy_name = NULL; /* .stabd feature. */
		symbolP->sy_value = obstack_next_free(&frags) -
				    frag_now->fr_literal;
		symbolP->sy_frag = frag_now;
		break;

	    case 'n':
	    case 's':
		symbolP->sy_frag = &zero_address_frag;
		break;

	    default:
		BAD_CASE( what );
		break;
	    }
	    if(get_absolute_expression_and_terminator(&longint) == ','){
		saved_type = longint;
		symbolP->sy_type = longint;
	    }
	    else{
		as_warn("I want a comma after the n_type expression");
		goof = TRUE;
		input_line_pointer--; /* Backup over a non-',' char. */
	    }
	}

	if(!goof){
	    if(get_absolute_expression_and_terminator(&longint) == ',')
		symbolP->sy_other = longint;
	    else {
		as_warn("I want a comma after the n_other expression");
		goof = TRUE;
		input_line_pointer--; /* Backup over a non-',' char. */
	    }
	}

	if(!goof){
	    symbolP->sy_desc = get_absolute_expression();
	    if(what == 's' || what == 'n'){
		if(*input_line_pointer != ','){
		    as_warn( "I want a comma after the n_desc expression" );
		    goof = TRUE;
		}
		else
		    input_line_pointer ++;
	    }
	}

	if((!goof) && (what=='s' || what=='n')){
	    pseudo_set(symbolP);
	    symbolP->sy_type = saved_type;
	}
	else if(!goof){
	    /* for stabd the sy_other (n_sect) gets set to the current section*/
	    symbolP->sy_other = frchain_now->frch_nsect;
	}
	if(goof)
	    ignore_rest_of_line();
	else
	    demand_empty_rest_of_line();
}

/*
 *			pseudo_set()
 *
 * In:	Pointer to a symbol.
 *	Input_line_pointer -> expression.
 *
 * Out:	Input_line_pointer -> just after any whitespace after expression.
 *	Tried to set symbol to value of expression.
 *	Will change sy_type, sy_value, sy_frag;
 *(old ->> May set need_pass_2 == TRUE. <<-- commented out by GNU below it
 * uses symbolP->sy_forward = exp.X_add_symbol;)
 */
static
void
pseudo_set(
symbolS *symbolP)
{
    expressionS exp;
    segT segment;
    int ext;

	know(symbolP);		/* NULL pointer is logic error. */
	ext = (symbolP->sy_type & N_EXT);
	segment = expression(&exp);

	switch(segment){
	case SEG_BIG:
	    as_warn("%s number illegal. Absolute 0 assumed.",
		    exp.X_add_number > 0 ? "Bignum" : "Floating-Point");
	    symbolP->sy_type = N_ABS | ext;
	    symbolP->sy_other = 0; /* NO_SECT */
	    symbolP->sy_value = 0;
	    symbolP->sy_frag = &zero_address_frag;
	    break;

	case SEG_NONE:
	    as_warn("No expression:  Using absolute 0");
	    symbolP->sy_type = N_ABS | ext;
	    symbolP->sy_other = 0; /* NO_SECT */
	    symbolP->sy_value = 0;
	    symbolP->sy_frag = &zero_address_frag;
	    break;

	case SEG_DIFFSECT:
	    if(exp.X_add_symbol && exp.X_subtract_symbol &&
	       exp.X_add_symbol->sy_other == exp.X_subtract_symbol->sy_other)
		exp.X_add_number += exp.X_add_symbol->sy_value -
				    exp.X_subtract_symbol->sy_value;
	    else
		as_warn("Complex expression. Absolute segment assumed." );
	    /* fall through */

	case SEG_ABSOLUTE:
	    symbolP->sy_type = N_ABS | ext;
	    symbolP->sy_other = 0; /* NO_SECT */
	    symbolP->sy_value = exp.X_add_number;
	    symbolP->sy_frag = &zero_address_frag;
	    break;

	case SEG_SECT:
	    symbolP->sy_type  = N_SECT | ext;
	    symbolP->sy_other = exp.X_add_symbol->sy_other;
	    symbolP->sy_value = exp.X_add_number + exp.X_add_symbol->sy_value;
	    symbolP->sy_frag  = exp.X_add_symbol->sy_frag;
	    break;
	  
	case SEG_UNKNOWN:
	    symbolP->sy_forward = exp.X_add_symbol;
/* commented out by GNU */
/* as_warn("unknown symbol"); */
/* need_pass_2 = TRUE; */
	    break;
	  
	default:
	    BAD_CASE(segment);
	    break;
	}
}

/*
 *			cons()
 *
 * CONStruct more frag of .bytes, or .words etc.
 * This understands EXPRESSIONS, as opposed to big_cons().
 *
 * Bug (?)
 *
 * This has a split personality. We use expression() to read the
 * value. We can detect if the value won't fit in a byte or word.
 * But we can't detect if expression() discarded significant digits
 * in the case of a long. Not worth the crocks required to fix it.
 *
 * Worker function to do .byte, .short, .long, statements.
 * This clobbers input_line_pointer, checks end-of-line.
 */
void
cons(	
int nbytes) /* nbytes == 1 for .byte, 2 for .word, 4 for .long */
{
    char c;
    long mask;		/* high-order bits to truncate */
    long unmask;	/* what bits we will store */
    long get;		/* the bits of the expression we get */
    long use;		/* the bits of the expression after truncation */
    char *p;		/* points into the frag */
    segT segment;
    expressionS exp;

	/*
	 * Input_line_pointer -> 1st char after pseudo-op-code and could legally
	 * be a end-of-line. (Or, less legally an eof - which we cope with.)
	 */
	if(nbytes >= sizeof(long int))
	    mask = 0;
	else 
	    mask = ~0 << (BITS_PER_CHAR * nbytes); /* Don't store these bits. */
	unmask = ~mask;		/* Do store these bits. */

	/*
	 * The following awkward logic is to parse ZERO or more expressions,
	 * comma seperated. Recall an expression includes its leading &
	 * trailing blanks. We fake a leading ',' if there is (supposed to
	 * be) a 1st expression, and keep demanding 1 expression for each ','.
	 */
	if(is_it_end_of_statement()){
	    c = 0;			/* Skip loop. */
	    input_line_pointer++;	/* Matches end-of-loop 'correction'. */
	}
	else
	    c = ',';			/* Do loop. */

	while(c == ','){
	    segment = expression(&exp); /* At least scan over the expression */

	    if(segment == SEG_DIFFSECT && exp.X_add_symbol == NULL){
		as_warn("Subtracting symbol \"%s\"(segment\"%s\") is too "
			"hard. Absolute segment assumed.",
			exp.X_subtract_symbol->sy_name,
			seg_name[(int)N_TYPE_seg[
			    exp.X_subtract_symbol->sy_type & N_TYPE]]);
		segment = SEG_ABSOLUTE;
		/* Leave exp .X_add_number alone. */
	    }
	    p = frag_more(nbytes);
	    switch(segment){
	    case SEG_BIG:
		as_warn("%s number illegal. Absolute 0 assumed.",
			exp.X_add_number > 0 ? "Bignum" : "Floating-Point");
		md_number_to_chars(p, (long)0, nbytes);
		break;

	    case SEG_NONE:
		as_warn("0 assumed for missing expression");
		exp.X_add_number = 0;
		know(exp.X_add_symbol == NULL);
		/* fall into SEG_ABSOLUTE */

	    case SEG_ABSOLUTE:
		get = exp.X_add_number;
		use = get & unmask;
		if((get & mask) && (get & mask) != mask){
		    /* Leading bits contain both 0s & 1s. */
		    as_warn("Value x%x truncated to x%x.", (unsigned int)get,
			    (unsigned int)use);
		}
		/* put bytes in right order. */
		md_number_to_chars(p, use, nbytes);
		break;

	    case SEG_DIFFSECT:
	    case SEG_UNKNOWN:
	    case SEG_SECT:
		fix_new(frag_now,
			p - frag_now->fr_literal,
			nbytes,
			exp.X_add_symbol,
			exp.X_subtract_symbol,
			exp.X_add_number,
			0,
			0,
			0);
		break;

	    default:
		BAD_CASE(segment);
		break;
	    }			/* switch(segment) */
	    c = *input_line_pointer++;
	}				/* while(c==',') */
	input_line_pointer--;	/* Put terminator back into stream. */
	demand_empty_rest_of_line();
}

#ifdef M68K /* we allow big cons only on the 68k machines */
/*
 *			big_cons()
 *
 * CONStruct more frag(s) of .quads, or .octa etc.
 * Makes 0 or more new frags.
 * This understands only bignums, not expressions. Cons() understands
 * expressions.
 *
 * Constants recognised are '0...'(octal) '0x...'(hex) '...'(decimal).
 *
 * This creates objects with struct obstack_control objs, destroying
 * any context objs held about a partially completed object. Beware!
 *
 *
 * I think it sucks to have 2 different types of integers, with 2
 * routines to read them, store them etc.
 * It would be nicer to permit bignums in expressions and only
 * complain if the result overflowed. However, due to "efficiency"...
 *
 * Worker function to do .quad and .octa statements.
 * This clobbers input_line_pointer, checks end-of-line.
 */
void
big_cons(
int nbytes) /* 8 == .quad, 16 == .octa ... */
{
    char c;	/* input_line_pointer -> c. */
    int radix;
    long length;/* Number of chars in an object. */
    int digit;	/* Value of 1 digit. */
    int carry;	/* For multi-precision arithmetic. */
    int work;	/* For multi-precision arithmetic. */
    char *p,*q;	/* For multi-precision arithmetic. */
    int i;

	/*
	 * The following awkward logic is to parse ZERO or more strings,
	 * comma seperated. Recall an expression includes its leading &
	 * trailing blanks. We fake a leading ',' if there is (supposed to
	 * be) a 1st expression, and keep demanding 1 expression for each ','.
	 */
	if(is_it_end_of_statement()){
	    c = 0;			/* Skip loop. */
	}
	else{
	    c = ',';			/* Do loop. */
	    --input_line_pointer;
	}
	while(c == ','){
	    ++input_line_pointer;
	    SKIP_WHITESPACE();
	    c = *input_line_pointer;
	    /* c contains 1st non-blank char of what we hope is a number */
	    if(c == '0'){
		c = *++input_line_pointer;
		if(c == 'x' || c=='X'){
		    c = *++input_line_pointer;
		    radix = 16;
		}
		else{
		    radix = 8;
		}
	    }
	    else{
		radix = 10;
	    }
	    /*
	     * This feature (?) is here to stop people worrying about
	     * mysterious zero constants: which is what they get when
	     * they completely omit digits.
	     */
	    if(hex_value[(int)c] >= radix){
		as_warn("Missing digits. 0 assumed.");
	    }
	    bignum_high = bignum_low - 1; /* Start constant with 0 chars. */
	    for( ;
		(digit = hex_value[(int)c]) < radix;
		c = *++input_line_pointer){
		/* Multiply existing number by radix, then add digit. */
		carry = digit;
		for(p = bignum_low; p <= bignum_high; p++){
		    work = (*p & MASK_CHAR) * radix + carry;
		    *p = work & MASK_CHAR;
		    carry = work >> BITS_PER_CHAR;
		}
		if(carry){
		    grow_bignum();
		    *bignum_high = carry & MASK_CHAR;
		    know((carry & ~ MASK_CHAR) == 0);
		}
	    }
	    length = bignum_high - bignum_low + 1;
	    if(length > nbytes){
		as_warn("Most significant bits truncated in integer constant.");
	    }
	    else{
		long leading_zeroes;

		for(leading_zeroes = nbytes - length;
		    leading_zeroes;
		    leading_zeroes--){
		    grow_bignum();
		    *bignum_high = 0;
		}
	    }
	    p = frag_more(nbytes);
	    if(md_target_byte_sex == BIG_ENDIAN_BYTE_SEX){
		q = (char *)bignum_low;
		for(i = nbytes - 1; i >= 0; i--)
		    *p++ = q[i];
	    }
	    else{
		memcpy(p, bignum_low, (int)nbytes);
	    }
	    /* C contains character after number. */
	    SKIP_WHITESPACE();
	    c = *input_line_pointer;
	    /* C contains 1st non-blank character after number. */
	}
	demand_empty_rest_of_line();
}

/*
 * grow_bignum() extends bignum (that is adjust bignum_low, bignum_high and
 * bignum_limit).
 */
static
void
grow_bignum(
void)
{
    long length;

	bignum_high++;
	if(bignum_high >= bignum_limit)
	{
	    length = bignum_limit - bignum_low;
	    bignum_low = xrealloc(bignum_low, length + length);
	    bignum_high = bignum_low + length;
	    bignum_limit = bignum_low + length + length;
	}
}
#endif /* M68K we allow big cons only on the 68k machines */

/*
 *			float_cons()
 *
 * CONStruct some more frag chars of .floats .ffloats etc.
 * Makes 0 or more new frags.
 * This understands only floating literals, not expressions. Sorry.
 *
 * A floating constant is defined by atof_generic(), except it is preceded
 * by 0d 0f 0g or 0h. After observing the STRANGE way my BSD AS does its
 * reading, I decided to be incompatible. This always tries to give you
 * rounded bits to the precision of the pseudo-op. Former AS did premature
 * truncatation, restored noisy bits instead of trailing 0s AND gave you
 * a choice of 2 flavours of noise according to which of 2 floating-point
 * scanners you directed AS to use.
 *
 * In:	input_line_pointer -> whitespace before, or '0' of flonum.
 *
 * Worker function to do .double, .float, .single statements.
 * This clobbers input_line-pointer, checks end-of-line.
 */
void
float_cons(
int float_type) /* 'f':.ffloat ... 'F':.float ... */
{
    char *p;
    char c;
    int length;	/* Number of chars in an object. */
    char *err;	/* Error from scanning floating literal. */
    char temp[MAXIMUM_NUMBER_OF_CHARS_FOR_FLOAT];

	/*
	 * The following awkward logic is to parse ZERO or more strings,
	 * comma seperated. Recall an expression includes its leading &
	 * trailing blanks. We fake a leading ',' if there is (supposed to
	 * be) a 1st expression, and keep demanding 1 expression for each ','.
	 */
	if(is_it_end_of_statement()){
	    c = 0;			/* Skip loop. */
	    ++input_line_pointer;	/* -> past termintor. */
	}
	else{
	    c = ',';			/* Do loop. */
	}
	while(c == ','){
	    /* input_line_pointer -> 1st char of a flonum (we hope!). */
	    SKIP_WHITESPACE();
	    /*
	     * Skip any 0{letter} that may be present. Don't even check if the
	     * letter is legal. Someone may invent a "z" format and this routine
	     * has no use for such information. Lusers beware: you get
	     * diagnostics if your input is ill-conditioned.
	     */
	    if(input_line_pointer[0] == '0' && isalpha(input_line_pointer[1]))
		input_line_pointer+=2;

	    err = md_atof(float_type, temp, &length);
	    know(length <=  MAXIMUM_NUMBER_OF_CHARS_FOR_FLOAT);
	    know(length > 0);
	    if(*err != '\0'){
		as_warn( "Bad floating literal: %s", err);
		ignore_rest_of_line();
		/* Input_line_pointer -> just after end-of-line. */
		c = 0;		/* Break out of loop. */
	    }
	    else{
		p = frag_more(length);
		memcpy(p, temp, length);
		SKIP_WHITESPACE();
		c = *input_line_pointer ++;
		/* C contains 1st non-white character after number. */
		/* input_line_pointer -> just after terminator (c). */
	    }
	}
	--input_line_pointer;		/* -> terminator (is not ','). */
	demand_empty_rest_of_line();
}

/*
 *			stringer()
 *
 * We read 0 or more ',' seperated, double-quoted strings.
 *
 * Worker function to do .ascii etc statements.
 * Checks end-of-line.
 */
void
stringer(
int append_zero) /* 0: don't append '\0', else 1 */
{
    int c;

	/*
	 * The following awkward logic is to parse ZERO or more strings,
	 * comma seperated. Recall a string expression includes spaces
	 * before the opening '\"' and spaces after the closing '\"'.
	 * We fake a leading ',' if there is (supposed to be)
	 * a 1st, expression. We keep demanding expressions for each
	 * ','.
	 */
	if(is_it_end_of_statement()){
	    c = 0;			/* Skip loop. */
	    ++ input_line_pointer;	/* Compensate for end of loop. */
	}
	else{
	    c = ',';			/* Do loop. */
	}
	for( ; c == ',';  c = *input_line_pointer++){
	    SKIP_WHITESPACE();
	    if(*input_line_pointer == '\"'){
		++input_line_pointer; /* -> 1st char of string. */
		while((c = next_char_of_string()) >= 0){
		    FRAG_APPEND_1_CHAR(c);
		}
		if(append_zero){
		    FRAG_APPEND_1_CHAR(0);
		}
		know(input_line_pointer[-1] == '\"');
	    }
	    else{
		as_warn("Expected \"-ed string");
	    }
	    SKIP_WHITESPACE();
	}
	--input_line_pointer;
	demand_empty_rest_of_line();
}

/*
 * next_char_of_string() is used by stringer() and demand_copy_string() and
 * returns the next character from input_line_pointer that is in the string or 
 * -1 for the trailing " character.  This routine handles escaped characters
 * like \b, \f, etc.
 */
static
int
next_char_of_string(
void)
{
    int c;
    long number, i;

	c = *input_line_pointer++;
	/* make sure the 0xff char is not returned as -1 */
	c = (c & MASK_CHAR);
	switch(c){
	case '\"':
	    c = -1;
	    break;

	case '\\':
	    c = *input_line_pointer++;
	    switch(c){
	    case 'b':
		c = '\b';
		break;
	    case 'f':
		c = '\f';
		break;
	    case 'n':
		c = '\n';
		break;
	    case 'r':
		c = '\r';
		break;
	    case 't':
		c = '\t';
		break;
	    case '\\':
	    case '"':
		break;		/* As itself. */
	    case '0':
	    case '1':
	    case '2':
	    case '3':
	    case '4':
	    case '5':
	    case '6':
	    case '7':
	    case '8':
	    case '9':
		for(i = 0, number = 0;
		    i < 3 && isdigit(c) && c < '8';
		    i++, c = *input_line_pointer++)
		    number = number * 8 + c - '0';
		c = number;
		--input_line_pointer;
		break;
	    case '\n':
		/* To be compatible with BSD 4.2 as: give the user a linefeed */
		c = '\n';
		break;

	    default:
		as_warn( "Bad escaped character in string, '?' assumed" );
		c = '?';
		break;
	    }
	    break;
	default:
	    break;
	}
	return(c);
}

/*
 * get_segmented_expression() is passed an expression to fill in and return that
 * is anything except a bignum or a missing expression.
 */
static
segT
get_segmented_expression(
expressionS *expP)
{
    segT retval;

	retval = expression(expP);
	if(retval == SEG_NONE || retval == SEG_BIG){
	    as_warn("Expected address expression: absolute 0 assumed");
	    retval = expP->X_seg = SEG_ABSOLUTE;
	    expP->X_add_number   = 0;
	    expP->X_add_symbol   = NULL;
	    expP->X_subtract_symbol = NULL;
	}
	return(retval);		/* SEG_ ABSOLUTE,UNKNOWN,SECT */
}

/*
 * get_known_segmented_expression() is passed an expression to fill in and
 * return that is anything except an unknown, bignum or a missing expression.
 */
segT
get_known_segmented_expression(
expressionS *expP)
{
    segT retval;
    char *name1;
    char *name2;

	retval = get_segmented_expression(expP);
	if(retval == SEG_UNKNOWN){
	    name1 = expP->X_add_symbol ?
		    expP->X_add_symbol->sy_name : "";
	    name2 = expP->X_subtract_symbol ?
		    expP->X_subtract_symbol->sy_name : "";
	    if(name1 && name2){
		as_warn("Symbols \"%s\" \"%s\" are undefined: absolute 0 "
			"assumed.", name1, name2);
	    }
	    else{
		as_warn("Symbol \"%s\" undefined: absolute 0 assumed.",
			name1 ? name1 : name2);
	    }
	    retval      = SEG_ABSOLUTE;
	    expP->X_seg = SEG_ABSOLUTE;
	    expP->X_add_number = 0;
	    expP->X_add_symbol      = NULL;
	    expP->X_subtract_symbol = NULL;
	}
	know(retval == SEG_ABSOLUTE ||
	     retval == SEG_SECT ||
	     retval == SEG_DIFFSECT);
	return(retval);
}

/*
 * get_absolute_expression() gets an absolute expression and returns the value
 * of that expression.
 */
long
get_absolute_expression(
void)
{
    expressionS exp;
    segT s;

	s = expression(&exp);
	if(s != SEG_ABSOLUTE){
/* is this right? if not absolute: no message and return 0 */
	    if(s != SEG_NONE){
		as_warn("Bad Absolute Expression, absolute 0 assumed.");
	    }
	    exp.X_add_number = 0;
	}
	return(exp.X_add_number);
}

/*
 * get_absolute_expression_and_terminator() gets an absolute expression and
 * returning the value of that expression indirectly through val_pointer and
 * returns the terminator.
 */
static
char			/* return terminator */
get_absolute_expression_and_terminator(
long *val_pointer)	/* return value of expression */
{
    *val_pointer = get_absolute_expression();
    return(*input_line_pointer++);
}

/*
 *			demand_copy_C_string()
 *
 * Like demand_copy_string, but return NULL if the string contains any '\0's.
 * Give a warning if that happens.
 */
static
char *
demand_copy_C_string(
int *len_pointer)
{
    char *s;
    int len;

	if((s = demand_copy_string(len_pointer))){
	    for(len = *len_pointer; len > 0; len--){
		if(*s == '\0'){
		    s = 0;
		    len = 1;
		    *len_pointer = 0;
		    as_warn("This string may not contain \'\\0\'");
		}
	    }
	}
	return(s);
}

/*
 *			demand_copy_string()
 *
 * Demand string, but return a safe (=private) copy of the string.
 * Return NULL if we can't read a string here.
 */
static
char *
demand_copy_string(
int *lenP)
{
    int c;
    int len;
    char *retval;

	len = 0;
	SKIP_WHITESPACE();
	if(*input_line_pointer == '\"'){
	    input_line_pointer++;	/* Skip opening quote. */
	    while((c = next_char_of_string()) >= 0){
		obstack_1grow(&notes, c);
		len++;
	    }
	    /*
	     * This next line is so demand_copy_C_string will return a null
	     * termanated string.
	     */
	    obstack_1grow(&notes, '\0');
	    retval = obstack_finish(&notes);
	}
	else{
	    as_warn("Missing string");
	    retval = NULL;
	    ignore_rest_of_line();
	}
	*lenP = len;
	return(retval);
}

/*
 *		is_it_end_of_statement()
 *
 * In:	Input_line_pointer -> next character.
 *
 * Do:	Skip input_line_pointer over all whitespace.
 *
 * Out:	TRUE if input_line_pointer -> end-of-line.
 */
static
int
is_it_end_of_statement(
void)
{
	SKIP_WHITESPACE();
	return(is_end_of_line[(int)*input_line_pointer]);
}

/*
 * equals() implements the assembly statement:
 *	 x = expression
 */
static
void
equals(
char *sym_name)
{
    struct symbol *symbolP;
    segT segment;
    expressionS exp;
    char *p;

	/* Turn '. = mumble' into a .org mumble */
	if(sym_name[0]=='.' && sym_name[1]=='\0'){
	    if(input_line_pointer[1] == '=')
		input_line_pointer += 2;
	    else
		*input_line_pointer++ = '=';		/* Put it back */
	    if(*input_line_pointer==' ' || *input_line_pointer=='\t')
		input_line_pointer++;
	    segment = get_known_segmented_expression(&exp);
	    if((segment != SEG_SECT ||
		exp.X_add_symbol->sy_other != frchain_now->frch_nsect) &&
		segment != SEG_ABSOLUTE)
	    as_warn("Illegal expression. current section assumed.");
	    p = frag_var(rs_org,
			 1,
			 1,
			 (relax_substateT)0,
			 exp.X_add_symbol,
			 exp.X_add_number,
			 (char *)0);
	    *p = 0;
	    return;
	}

	symbolP = symbol_find_or_make(sym_name);
	if(input_line_pointer[1] == '=')
	    input_line_pointer += 2;
	else
	    *input_line_pointer++ = '=';		/* Put it back */
	if(*input_line_pointer==' ' || *input_line_pointer=='\t')
	    input_line_pointer++;
	pseudo_set(symbolP);
}

/*
 * s_if() implements the pseudo op:
 *	.if expression
 * that does conditional assembly using assembler defined expressions.
 */
static
void
s_if(
int value)
{
	if(if_depth >= MAX_IF_DEPTH)
	    as_fatal("You can't nest if's more than %d levels deep",
		     MAX_IF_DEPTH);
	last_states[if_depth++] = the_cond_state;
	the_cond_state.the_cond = if_cond;
	if(the_cond_state.ignore)
	    totally_ignore_line();
	else{
	    the_cond_state.cond_met = get_absolute_expression();
	    the_cond_state.ignore = !the_cond_state.cond_met;
	    demand_empty_rest_of_line();
	}
}

/*
 * s_elseif() implements the pseudo op:
 *	.elseif expression
 * that does conditional assembly using assembler defined expressions.
 */
static
void
s_elseif(
int value)
{
    int last_ignore_state;

	if(the_cond_state.the_cond != if_cond &&
	   the_cond_state.the_cond != elseif_cond)
	    as_fatal("Encountered a .elseif that doesn't follow a .if or an "
		     ".elseif");
	the_cond_state.the_cond = elseif_cond;

	last_ignore_state = FALSE;
	if(if_depth)
	    last_ignore_state = last_states[if_depth-1].ignore;
        if(last_ignore_state || the_cond_state.cond_met){
	    the_cond_state.ignore = TRUE;
	    totally_ignore_line();
	}
	else{
	    the_cond_state.cond_met = get_absolute_expression();
	    the_cond_state.ignore = !the_cond_state.cond_met;
	    demand_empty_rest_of_line();
	}
}

/*
 * s_else() implements the pseudo op:
 *	.else
 * that does conditional assembly using assembler defined expressions.
 */
static
void
s_else(
int value)
{
    int last_ignore_state;

	if(the_cond_state.the_cond != if_cond &&
	   the_cond_state.the_cond != elseif_cond)
	    as_fatal("Encountered a .else that doesn't follow a .if or an "
		     ".elseif");
	the_cond_state.the_cond = else_cond;
	last_ignore_state = FALSE;
	if(if_depth)
	    last_ignore_state = last_states[if_depth-1].ignore;
        if(last_ignore_state || the_cond_state.cond_met)
	    the_cond_state.ignore = TRUE;
	else
	    the_cond_state.ignore = FALSE;
	demand_empty_rest_of_line();
}

/*
 * s_endif() implements the pseudo op:
 *	.endif
 * that does conditional assembly using assembler defined expressions.
 */
static
void
s_endif(
int value)
{
	if((the_cond_state.the_cond == no_cond) || (if_depth == 0))
	    as_fatal("Encountered a .endif that doesn't follow a .if or .else");
	the_cond_state = last_states[--if_depth];
	demand_empty_rest_of_line();
}

/* 
 * totally_ignore_line() ignores lines during conditional assembly.
 */
void
totally_ignore_line(
void)
{
	if(!is_end_of_line[(int)*input_line_pointer]){
	    while(input_line_pointer < buffer_limit &&
		  !is_end_of_line[(int)*input_line_pointer]){
		input_line_pointer ++;
	    }
	}
	input_line_pointer++;	/* Return pointing just after end-of-line. */
	know(is_end_of_line[(int)(input_line_pointer[-1])]);
}

/*
 * s_macros_on() implements the pseudo op:
 *	.macros_on
 */
static
void
s_macros_on(
int value)
{
	macros_on = TRUE;
	demand_empty_rest_of_line();
}

/*
 * s_macros_off() implements the pseudo op:
 *	.macros_off
 */
void
s_macros_off(
int value)
{
	macros_on = FALSE;
	demand_empty_rest_of_line();
}

/*
 * s_macro() implements the pseudo op:
 *	.macro macro_name
 * that defines a macro.
 */
void
s_macro(
int value)
{
    int c;
    pseudo_typeS *pop;

	if(macro_name)
	    as_warn("Can't define a macro inside another macro definition");
	else{
	    SKIP_WHITESPACE();
	    while(is_part_of_name(c = *input_line_pointer ++))
		obstack_1grow (&macros, c);
	    obstack_1grow(&macros, '\0');
	    --input_line_pointer;
	    macro_name = obstack_finish(&macros);
	    if(macro_name == "")
		as_warn("Missing name of macro");
	    if(*macro_name == '.'){
		pop = (pseudo_typeS *)hash_find(po_hash, macro_name + 1);
		if(pop != NULL)
		    as_warn("Pseudo-op name: %s can't be a macro name",
			    macro_name);
	    }
	}
	totally_ignore_line();
}

/*
 * s_endmacro() implements the pseudo op:
 *	.endmacro
 * which is the end of a macro definition.
 */
void
s_endmacro(
int value)
{
    char *errorString;

	if(!macro_name){
	    as_warn ("This .endmacro does not match with a preceeding .macro");
	    ignore_rest_of_line();
	}
	else{
	    obstack_1grow(&macros, '\0');
	    errorString = hash_insert(ma_hash, macro_name,
				      obstack_finish(&macros));
	    if(*errorString)
		as_warn("The macro named \"%s\" is already defined",
			macro_name);
	    macro_name = FALSE;
	}
}

/*
 * macro_begin() initializes macros.
 */
static
void
macro_begin(
void)
{
	ma_hash = hash_new();
	obstack_begin(&macros, 5000);
}

/*
 * add_to_macro_definition() is called after a .macro to store the contents of
 * a macro into the obstack.
 */
void
add_to_macro_definition(
char *char_pointer)
{
    char c;

	do{
	    c = *char_pointer ++;
	    know(c != '\0');
	    obstack_1grow(&macros, c);
	}while((c != ':') && !(is_end_of_line[(int)c]));
	if(char_pointer > input_line_pointer)
	    input_line_pointer = char_pointer;
}

/*
 * expand_macro() is called to expand macros.
 */
static
void
expand_macro(
char *macro_contents)
{
    char *buffer;
    char c;
    int index, nargs;
    char *last_buffer_limit;
    int last_count_lines;
    char *last_input_line_pointer;
    char *arguments [10]; /* at most 10 arguments, each is substituted */

	if(macro_depth >= MAX_MACRO_DEPTH)
	   as_fatal("You can't nest macro's more than %d levels deep",
		    MAX_MACRO_DEPTH);
	macro_depth++;

	/* copy each argument to a object in the macro obstack */
	nargs = 0;
	for(index = 0; index < 10; index ++){
	    if(*input_line_pointer == ' ')
		++input_line_pointer;
	    know(*input_line_pointer != ' ');
	    c = *input_line_pointer;
	    if(is_end_of_line[(int)c])
		arguments[index] = NULL;
	    else{
		int parenthesis_depth = 0;
		do{
		    c = *input_line_pointer++;
		    if(parenthesis_depth){
			if(c == ')')
			    parenthesis_depth --;
		    }
		    else{
			if(c == '(')
			    parenthesis_depth ++;
			else
			    if(is_end_of_line[(int)c] ||
			       (c == ' ') || (c == ','))
			    break;
		    }
		    know(c != '\0');
		    if(is_end_of_line[(int)c])
			as_warn("missmatched parenthesis");
		    obstack_1grow(&macros, c);
		}while(1);
		obstack_1grow(&macros, '\0');
		arguments[index] = obstack_finish(&macros);
		nargs++;
		if(is_end_of_line[(int)c])
		    --input_line_pointer;
		else if(c == ' ')
		    if(*input_line_pointer == ',')
			input_line_pointer++;
	    }
	}
	if(!is_end_of_line[(int)c]){
	    as_warn("More than 10 arguments not allowed for macros");
	    ignore_rest_of_line();
	}
	/*
	 * Build a buffer containing the macro contents with arguments
	 * substituted
	 */
	obstack_1grow(&macros, '\n');
	while((c = *macro_contents++)){
	    if(c == '$'){
		if((*macro_contents >= '0') && (*macro_contents <= '9')){
		    index = *macro_contents++ - '0';
		    last_input_line_pointer = macro_contents;
		    macro_contents = arguments[index];
		    if(macro_contents){
			while ((c = * macro_contents ++))
			obstack_1grow (&macros, c);
		    }
		    macro_contents = last_input_line_pointer;
		    continue;
		}
		else if (*macro_contents == 'n'){
		    macro_contents++ ;
		    obstack_1grow(&macros, nargs + '0');
		    continue;
		}
	    }
	    obstack_1grow (&macros, c);
	}
	obstack_1grow (&macros, '\n');
	obstack_1grow (&macros, '\0');
	last_buffer_limit = buffer_limit;
	last_count_lines = count_lines;
	last_input_line_pointer = input_line_pointer;
	buffer_limit = obstack_next_free (&macros) - 1;
	buffer = obstack_finish (&macros);
	count_lines = FALSE;
	/*
	printf("expanded macro: %s", buffer + 1);
	*/
	parse_a_buffer(buffer + 1);
	obstack_free (&macros, buffer);
	for(index = 9; index >= 0; index --)
	    if(arguments[index])
		obstack_free(&macros, arguments[index]);
	buffer_limit = last_buffer_limit;
	count_lines = last_count_lines;
	input_line_pointer = last_input_line_pointer;
	macro_depth--;
}

/*
 * s_dump() implements the pseudo op:
 *	.dump filename
 * that does a quick binary dump of symbol tables.
 */
static
void
s_dump(
int value)
{
    char *filename;
    int length;
    static char null_string[] = "";

	if((filename = demand_copy_string(&length))){
	    demand_empty_rest_of_line();
	    if((dump_fp = fopen(filename, "w+"))){
		hash_apply(ma_hash, write_macro);
		fwrite(null_string, 1, 1, dump_fp);
		hash_apply(sy_hash, write_symbol);
		fwrite(null_string, 1, 1, dump_fp);
		fclose(dump_fp);
	    }
	    else
		as_warn("couldn't write to dump file: \"%s\"", filename);
	}
}

/*
 * write_macro() used by hash_apply indirectly through s_dump() to write one
 * macro.
 */
static
char *
write_macro(
char *string,
char *value)
{
	know(string);
	know(value);
	know(strlen(string));
	fwrite(string, (strlen(string) + 1), 1, dump_fp);
	fwrite(value, (strlen(value) + 1), 1, dump_fp);
	return(NULL);
}

/*
 * write_symbol() used by hash_apply indirectly through s_dump() to write one
 * N_ABS symbol and its value.
 */
static
char *
write_symbol(
char *string,
char *value)
{
    symbolS *symbolP;

    	symbolP = (symbolS *)value;
	know(symbolP);
	if(((symbolP->sy_type) & N_TYPE) == N_ABS){
	    know(string);
	    know(strlen(string));
	    fwrite(string, (strlen(string) + 1), 1, dump_fp);
	    fwrite(&(symbolP -> sy_value), 4, 1, dump_fp);
	}
	return(NULL);
}

/*
 * s_load() implements the pseudo op:
 *	.load filename
 * that does a quick binary load of symbol tables.
 */
static
void
s_load(
int value)
{
    char *char_pointer;
    char *filename;
    int length;
    char the_char;
    symbolS	*the_symbol;
    symbolS	*temp_symbol_lastP;
    static symbolS *dump_symbol_lastP;

	if((filename = demand_copy_string(&length))){
	    demand_empty_rest_of_line();
	    if((dump_fp = fopen(filename, "r+"))){
		do{
		    do{
			the_char = getc(dump_fp);
			obstack_1grow(&macros, the_char);
		    }while(the_char);
		    char_pointer = obstack_finish (&macros);
		    if(!(*char_pointer))
			break;
		    do{
			the_char = getc(dump_fp);
			obstack_1grow(&macros, the_char);
		    }while(the_char);
		    if(*hash_insert(ma_hash, char_pointer,
				    obstack_finish(&macros)))
			as_warn("a macro named \"%s\" encountered in a .load "
			        "is already defined", char_pointer);
		}while(1);
	        /*
		 * We don't want to link in symbols that were loaded so they
		 * don't go out in the object file.  Instead these symbols
		 * should go out in the object file that did the .dump .
		 */
		temp_symbol_lastP = symbol_lastP;
		symbol_lastP = dump_symbol_lastP;
		do{
		    do{
			the_char = getc(dump_fp);
			obstack_1grow(&macros, the_char);
		    }while(the_char);
		    char_pointer = obstack_base(&macros);
		    obstack_next_free(&macros) = char_pointer;
		    if(!(*char_pointer))
			break;
		    the_symbol = symbol_find_or_make(char_pointer);
		    the_symbol->sy_type = N_ABS;
		    char_pointer = (char *)&the_symbol->sy_value;
		    *char_pointer++ = getc(dump_fp);
		    *char_pointer++ = getc(dump_fp);
		    *char_pointer++ = getc(dump_fp);
		    *char_pointer++ = getc(dump_fp);
		    the_symbol->sy_frag = &zero_address_frag;
		}while(1);
		dump_symbol_lastP = symbol_lastP;
		symbol_lastP = temp_symbol_lastP;
		fclose(dump_fp);
	    }
	    else
		as_fatal("Couldn't find the dump file: \"%s\"", filename);
	}
}

These are the contents of the former NiCE NeXT User Group NeXTSTEP/OpenStep software archive, currently hosted by Netfuture.ch.