view lwasm/pseudo.c @ 240:b43e3e23583c

Added not about likely incompatibilities with source code from other assemblers when using the OS9 module target
author lost
date Sun, 16 Aug 2009 00:25:55 +0000
parents a58f49a77441
children 31231d1c87e6
line wrap: on
line source

/*
pseudo.c
Copyright © 2009 William Astle

This file is part of LWASM.

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

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

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


This file implements the various pseudo operations.
*/
#include <config.h>
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "lwasm.h"
#include "instab.h"
#include "expr.h"
#include "util.h"

extern int lwasm_read_file(asmstate_t *as, const char *filename);

OPFUNC(pseudo_noop)
{
	skip_operand(p);
}

OPFUNC(pseudo_org)
{
	int v, r;

	if (as -> csect)
	{
		register_error(as, l, 1, "ORG not allowed within sections");
		return;
	}

	if (as -> passnum != 1)
	{
		// org is not needed to be processed on pass 2
		// this will prevent phasing errors for forward references that
		// resolve on the second pass
		// we saved the org address in l -> codeaddr on pass 1
		as -> addr = l -> codeaddr;
		skip_operand(p);
		return;
	}
	
	if (l -> sym)
	{
		register_error(as, l, 1, "No symbol allowed with ORG");
	}
	
	r = lwasm_expr_result2(as, l, p, EXPR_PASS1CONST, &v, 0);
	if (r != 0)
		return;
	l -> codeaddr = v;
	l -> addrset = 1;
	as -> addr = v;
}

/*
The operand for include is a string optionally enclosed in "
*/
OPFUNC(pseudo_include)
{
	int v1;
	char *fn;
	
	// only include files on pass 1
	// but make sure local include context is right
	// for the next line...
	if (as -> passnum != 1)
	{
		as -> context = lwasm_next_context(as);
		skip_operand(p);
		return;
	}

	while (**p && isspace(**p))
		(*p)++;

	if (!**p)
	{
		register_error(as, l, 1, "Bad file name");
		return;
	}

	if (**p == '"')
	{
		// search for ending "
		(*p)++;
		for (v1 = 0; *((*p)+v1) && *((*p)+v1) != '"'; v1++)
			/* do nothing */ ;
		if (*((*p)+v1) != '"')
		{
			register_error(as, l, 1, "Bad file name");
			return;
		}
	}
	else
	{
		// search for a space type character
		for (v1 = 0; *((*p)+v1) && !isspace(*((*p)+v1)); v1++)
			;
	}

	fn = lwasm_alloc(v1 + 1);
	memcpy(fn, *p, v1);
	fn[v1] = '\0';

	(*p) += v1;
	if (**p == '"')
		(*p)++;

	// end local label context on include	
	as -> context = lwasm_next_context(as);
	if (lwasm_read_file(as, fn) < 0)
	{
		register_error(as, l, 1, "File include error (%s)", fn);
	}
	lwasm_free(fn);
}

/*
The operand for includebin is a string optionally enclosed in "
*/
OPFUNC(pseudo_includebin)
{
	int v1;
	char *fn;
	FILE *f;
	
	// only include files on pass 1
	while (**p && isspace(**p))
		(*p)++;

	if (!**p)
	{
		register_error(as, l, 1, "Bad file name");
		return;
	}

	if (**p == '"')
	{
		// search for ending "
		(*p)++;
		for (v1 = 0; *((*p)+v1) && *((*p)+v1) != '"'; v1++)
			/* do nothing */ ;
		if (*((*p)+v1) != '"')
		{
			register_error(as, l, 1, "Bad file name");
			return;
		}
	}
	else
	{
		// search for a space type character
		for (v1 = 0; *((*p)+v1) && !isspace(*((*p)+v1)); v1++)
			;
	}

	fn = lwasm_alloc(v1 + 1);
	memcpy(fn, *p, v1);
	fn[v1] = '\0';

	(*p) += v1;
	if (**p == '"')
		(*p)++;

	// open the file
	f = fopen(fn, "rb");
	if (!f)
	{
		register_error(as, l, 1, "Cannot open file: %s", strerror(errno));
		register_error(as, l, 2, "Cannot open file: %s", strerror(errno));
	}
	
	// don't need fn any more
	lwasm_free(fn);
	
	// read the contents of the file and "emit()" it
	while (!feof(f) && !ferror(f))
	{
		v1 = fgetc(f);
		if (v1 == EOF)
			break;
		lwasm_emit(as, l, v1);
	}
	// close file
	fclose(f);
}

OPFUNC(pseudo_rmb)
{
	int r, v;
	
	if (as -> passnum == 2)
	{
		as -> addr += l -> nocodelen;
		skip_operand(p);
		return;
	}
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, -1);
	if (r != 0)
		return;
	l -> nocodelen = v;
	as -> addr += v;
}

OPFUNC(pseudo_rmd)
{
	int r, v;
	
	if (as -> passnum == 2)
	{
		as -> addr += l -> nocodelen;
		skip_operand(p);
		return;
	}
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r != 0)
		return;
	v *= 2;
	l -> nocodelen = v;
	as -> addr += v;
}

OPFUNC(pseudo_rmq)
{
	int r, v;
	
	if (as -> passnum == 2)
	{
		as -> addr += l -> nocodelen;
		skip_operand(p);
		return;
	}
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r != 0)
		return;
	v *= 4;
	l -> nocodelen = v;
	as -> addr += v;
}

OPFUNC(pseudo_zmb)
{
	int r, v;
	
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r != 0)
		return;
	while (v--)
		lwasm_emit(as, l, 0);
}

OPFUNC(pseudo_zmd)
{
	int r, v;
	
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r != 0)
		return;
	v *= 2;
	while (v--)
		lwasm_emit(as, l, 0);
}

OPFUNC(pseudo_zmq)
{
	int r, v;
	
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r != 0)
		return;
	v *= 4;
	while (v--)
		lwasm_emit(as, l, 0);
}

OPFUNC(pseudo_end)
{
	int r, v;
	lwasm_expr_stack_t *s;
	

	as -> endseen = 1;
	
	// address only matters for DECB output
	if (as -> outformat != OUTPUT_DECB)
	{
		skip_operand(p);
		return;
	}
	
	r = lwasm_expr_result2(as, l, p, 0, &v, 0);
	if (r != 0)
	{
		register_error(as, l, 2, "Bad operand");
	}

	v = v & 0xffff;
	if (as -> passnum == 2)
	{
		as -> execaddr = v;
		l -> symaddr = v;
		l -> addrset = 2;
	}
}


OPFUNC(pseudo_align)
{
	int cn;
	int r, v;
	int pad = 0;
// we have to parse this on pass 2 so that we get the pad value	
//	if (as -> passnum == 2)
//	{
//		skip_operand(p);
//		while (as -> addr < l -> symaddr)
//			lwasm_emit(as, l, 0);
//		return;
//	}
	
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r != 0)
	{
		l -> symaddr = as -> addr;
		return;
	}

	if (v < 1)
	{
		register_error(as, l, 1, "Illegal alignment %d", v);
		return;
	}
	
	cn = l -> codeaddr % v;
	if (cn)
		cn = v - cn; 

	if (**p == ',')
	{
		// we have a padding value specified
		(*p)++;
		r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST, &pad, 1);
		if (r != 0 && as -> passnum == 2)
		{
			register_error(as, l, 2, "Illegal padding value - must be constant on pass 2");
			return;
		}
	}

	while (cn--)
	{
		lwasm_emit(as, l, pad);
	}
	l -> symaddr = as -> addr;
}

OPFUNC(pseudo_equ)
{
	int r, v;

	if (l -> sym == NULL)
	{
		register_error(as, l, 1, "No symbol specified");
		return;
	}

	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r < 0)
		v = 0;

	l -> symaddr = v & 0xFFFF;
	l -> addrset = 2;
	
	// note: we need to do this because the symbol might have resolved
	// to a constant!
	lwasm_register_symbol(as, l, l -> sym, v, (r > 0 ? SYMBOL_COMPLEX: SYMBOL_NORM) | SYMBOL_FORCE | (l -> forceglobal ? SYMBOL_GLOBAL : SYMBOL_NORM));
}

OPFUNC(pseudo_set)
{
	int r, v;

	// set MUST run on both passes as the symbol value changes!

	if (l -> sym == NULL)
	{
		register_error(as, l, 1, "No symbol specified");
		return;
	}

	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r < 0)
		v = 0;

	l -> symaddr = v & 0xFFFF;
	l -> addrset = 2;
	
	lwasm_register_symbol(as, l, l -> sym, v, (r > 0 ? SYMBOL_COMPLEX: SYMBOL_NORM) | SYMBOL_SET);
}

OPFUNC(pseudo_setdp)
{
	int r, v;

	if (as -> outformat == OUTPUT_OBJ)
	{
		register_error(as, l, 1, "SETDP not permitted with OBJ target");
		return;
	}
	
	// setdp is needed on both passes; must resolve to a constant on pass 1
	r = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v, 0);
	if (r != 0)
		return;

	if (v < -127 || v > 255)
	{
		register_error(as, l, 1, "Byte overflow");
		return;
	}
	
	l -> symaddr = v & 0xFF;
	l -> addrset = 2;
	
	as -> dpval = v & 0xFF;
}

// used to get a byte from a string
// -1 is end of line
int pseudo_fcc_fetchchar(asmstate_t *as, char **p)
{
	int c;
	
	// -
	if (!**p)
		return -1;
	
	c = (unsigned char)(**p);
	(*p)++;
	
	if (as -> pragmas & PRAGMA_CESCAPES && c == '\\')
	{
		// decode escapes if needed
		if (!**p)
			return c;
		
		c = **p;
		(*p)++;
		
		switch (c)
		{
		// octal value
		// 1, 2, or 3 digits
		// NOTE: \0 for NUL is included in this...
		case '0':
		case '1':
		case '2':
		case '3':
		case '4':
		case '5':
		case '6':
		case '7':
			c -= '0';
			if (**p < '0' || **p > '9')
				return c;
			c = c << 3;
			c |= **p - '0';
			(*p)++;
			if (**p < '0' || **p > '9')
				return c;
			c = c << 3;
			c |= **p - '0';
			(*p)++;
			return c;

		// LF
		case 'n':
			return 10;
		
		// CR
		case 'r':
			return 13;
		
		// TAB
		case 't':
			return 9;
		
		// VT
		case 'v':
			return 11;
		
		// BS
		case 'b':
			return 8;
		
		// FF
		case 'f':
			return 12;
		
		// BEL
		case 'a':
			return 7;
		
		// hex char code (2 chars)
		case 'x':
		{
			int c2;
			if (!**p)
				return 'x';
			c = toupper(**p);
			(*p)++;
			if (c < '0' || (c > '9' && c < 'A') || c > 'F')
				return 0;
			c -= '0';
			if (c > 9)
				c -= 7;
			c2 = c << 4;
			if (!**p)
				return 0;
			c = toupper(**p);
			(*p)++;
			if (c < '0' || (c > '9' && c < 'A') || c > 'F')
				return 0;
			c -= '0';
			if (c > 9)
				c -= 7;
			c2 |= c;
			return c2;
		}
		// everything else stands for itself as a fall back or legit		
		default:
			return c;
		}
	}
	return c;
}

OPFUNC(pseudo_fcc)
{
	int delim = 0;
	int c;
				
	delim = **p;
	if (!delim)
	{
		register_error(as, l, 1, "Bad operand");
		return;
	}
	*p += 1;
	for (;;)
	{
		c = pseudo_fcc_fetchchar(as, p);
		if (c == delim || c < 0)
			break;

		lwasm_emit(as, l, c);
	}
}
		

OPFUNC(pseudo_fcs)
{
	int delim = 0;
	int c, lc = -1;
	
	delim = **p;
	if (!delim)
	{
		register_error(as, l, 1, "Bad operand");
		return;
	}
	*p += 1;
	for (;;)
	{
		c = pseudo_fcc_fetchchar(as, p);
		if (c == delim || c < 0)
		{
			if (lc >= 0)
				lwasm_emit(as, l, lc | 0x80);
			break;
		}
		if (lc >= 0)
			lwasm_emit(as, l, lc);
		lc = c;
	}
}

OPFUNC(pseudo_fcn)
{		
	int delim = 0;
	int c;
	
	delim = **p;
	if (!delim)
	{
		register_error(as, l, 1, "Bad operand");
		return;
	}
	*p += 1;
	for (;;)
	{
		c = pseudo_fcc_fetchchar(as, p);
		if (c == delim || c < 0)
			break;

		lwasm_emit(as, l, c);
	}
	lwasm_emit(as, l, 0);
}

OPFUNC(pseudo_fcb)
{
	int r, v;
	
fcb_again:
	r = lwasm_expr_result2(as, l, p, 0, &v, -1);
	if (r < 0)
		return;

	if (r > 0)
	{
		register_error(as, l, 2, "Illegal external or inter-segment reference");
		v = 0;
	}

	if (v < -127 || v > 255)
	{
		register_error(as, l, 1, "Byte overflow");
	}
	
	lwasm_emit(as, l, v);
	if (**p == ',')
	{
		(*p)++;
		goto fcb_again;
	}
}

// FIXME: handle external references in an intelligent way
OPFUNC(pseudo_fdb)
{			
	int r, v;
	int extseen = 0;
	char *p1;
	
fdb_again:
	p1 = *p;
	r = lwasm_expr_result2(as, l, p, 0, &v, -1);
	if (r < 0)
		return;

	if (r > 0 && extseen == 1)
	{
		register_error(as, l, 2, "Illegal external or inter-segment reference (only 1 per FDB line)");
		v = 0;
	}
	else if (r > 0)
	{
		l -> relocoff = as -> addr - l -> codeaddr;
		*p = p1;
		r = lwasm_expr_result2(as, l, p, 0, &v, 0);
	}

	lwasm_emit(as, l, v >> 8);
	lwasm_emit(as, l, v & 0xff);
	if (**p == ',')
	{
		(*p)++;
		goto fdb_again;
	}
}

// FIXME: handle external references in a sensible way
OPFUNC(pseudo_fqb)
{	
	int r, v;
	
fqb_again:
	r = lwasm_expr_result2(as, l, p, 0, &v, -1);
	if (r < 0)
		return;

	if (r > 0)
	{
		register_error(as, l, 2, "Illegal external or inter-segment reference");
		v = 0;
	}

	lwasm_emit(as, l, v >> 24);
	lwasm_emit(as, l, v >> 16);
	lwasm_emit(as, l, v >> 8);
	lwasm_emit(as, l, v & 0xff);
	if (**p == ',')
	{
		(*p)++;
		goto fqb_again;
	}
}

// don't need to do anything if we are executing one of these
OPFUNC(pseudo_endc)
{
	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount -= 1;
		if (as -> skipcount <= 0)
		{
			as -> skipcond = 0;
		}
	}
	return;
}

// if "else" executes, we must be going into an "ignore" state
OPFUNC(pseudo_else)
{
	if (as -> skipmacro)
		return;
	
	if (as -> skipcond)
	{
		if (as -> skipcount == 1)
		{
			as -> skipcount = 0;
			as -> skipcond = 0;
		}
		return;
	}
	
	as -> skipcond = 1;
	as -> skipcount = 1;
}

OPFUNC(pseudo_ifne)
{
	int v1;
	int rval;

	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	rval = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v1, 0);
	if (rval != 0)
		return;
	if (!v1)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}

OPFUNC(pseudo_ifdef)
{
	lwasm_symbol_ent_t *se;
	char *sym;
	char *p2;
	
	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	if (as -> passnum != 1)
	{
		skip_operand(p);
		if (!(l -> fsize))
		{
			as -> skipcond = 1;
			as -> skipcount = 1;
		}
		return;
	}

	if (!**p)
	{
		register_error(as, l, 1, "Need symbol name");
		return;
	}
	
	for (p2 = *p; **p && !isspace(**p); (*p)++)
		/* do nothing */ ;
	
	sym = lwasm_alloc(*p - p2 + 1);
	memcpy(sym, p2, *p - p2);
	sym[*p - p2] = '\0';

//	fprintf(stderr, "STUFF: %s; '%s'; '%s' (%d)\n", p2, *p, sym, as -> passnum);
	se = lwasm_find_symbol(as, sym, l -> context);
	if (!se)
		se = lwasm_find_symbol(as, sym, -1);
	
	lwasm_free(sym);
	
	if (!se)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
		l -> fsize = 0;
	}
	else
	{
		l -> fsize = 1;
	}
}

OPFUNC(pseudo_ifndef)
{
	lwasm_symbol_ent_t *se;
	char *sym;
	char *p2;

	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	if (as -> passnum != 1)
	{
		skip_operand(p);
		if (l -> fsize)
		{
			as -> skipcond = 1;
			as -> skipcount = 1;
		}
		return;
	}

	if (!**p)
	{
		register_error(as, l, 1, "Need symbol name");
		return;
	}
	
	for (p2 = *p; *p2 && !isspace(*p2); p2++)
		/* do nothing */ ;
	
	sym = lwasm_alloc(p2 - *p + 1);
	memcpy(sym, *p, p2 - *p);
	sym[p2 - *p] = '\0';
	
	*p = p2;

//	fprintf(stderr, "STUFF2: %s; '%s'; '%s' (%d)\n", *p, p2, sym, as -> passnum);
	se = lwasm_find_symbol(as, sym, l -> context);
	if (!se)
		se = lwasm_find_symbol(as, sym, -1);
	
	lwasm_free(sym);
	
	if (se)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
		l -> fsize = 0;
	}
	else
	{
		l -> fsize = 1;
	}
}

OPFUNC(pseudo_ifp1)
{
	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	if (as -> passnum != 1)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}
	
OPFUNC(pseudo_ifp2)
{
	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	if (as -> passnum != 2)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}
	
OPFUNC(pseudo_ifeq)
{
	int v1;
	int rval;

	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	rval = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v1, 0);
	if (rval != 0)
		return;
	if (v1)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}

OPFUNC(pseudo_iflt)
{
	int v1;
	int rval;

	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	rval = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v1, 0);
	if (rval != 0)
		return;
	if (v1 >= 0)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}

OPFUNC(pseudo_ifle)
{
	int v1;
	int rval;

	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	rval = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v1, 0);
	if (rval != 0)
		return;
	if (v1 > 0)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}

OPFUNC(pseudo_ifgt)
{
	int v1;
	int rval;

	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	rval = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v1, 0);
	if (rval != 0)
		return;
	if (v1 <= 0)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}

OPFUNC(pseudo_ifge)
{
	int v1;
	int rval;

	if (as -> skipcond && !(as -> skipmacro))
	{
		as -> skipcount++;
		skip_operand(p);
		return;
	}

	rval = lwasm_expr_result2(as, l, p, EXPR_SECTCONST | EXPR_PASS1CONST, &v1, 0);
	if (rval != 0)
		return;
	if (v1 < 0)
	{
		as -> skipcond = 1;
		as -> skipcount = 1;
	}
}

OPFUNC(pseudo_error)
{
	register_error(as, l, 1, "User error: %s", *p);
}


OPFUNC(pseudo_section)
{
	sectiontab_t *s;
	char *p2;
	char *sn;
	char *opts;
	

	if (as -> outformat != OUTPUT_OBJ)
	{
		register_error(as, l, 1, "Sections only supported for obj target");
		return;
	}
	
	if (as -> csect)
	{
		as -> csect -> offset = as -> addr;
		as -> csect = NULL;
	}
	
	if (!**p)
	{
		register_error(as, l, 1, "Need section name");
		return;
	}
	
	for (p2 = *p; *p2 && !isspace(*p2); p2++)
		/* do nothing */ ;
	
	sn = lwasm_alloc(p2 - *p + 1);
	memcpy(sn, *p, p2 - *p);
	sn[p2 - *p] = '\0';
	
	*p = p2;
	
	opts = strchr(sn, ',');
	if (opts)
	{
		*opts++ = '\0';
	}
	
	// have we seen the section name already?
	for (s = as -> sections; s; s = s -> next)
	{
		if (!strcmp(s -> name, sn))
			break;
	}
	
	if (s && as -> passnum == 1)
	{
		lwasm_free(sn);
		if (opts)
		{
			register_error(as, l, 1, "Section options can only be specified the first time");
			return;
		}
	}
	else if (!s)
	{
		s = lwasm_alloc(sizeof(sectiontab_t));
		s -> name = sn;
		s -> offset = 0;
		s -> flags = 0;
		s -> obytes = NULL;
		s -> oblen = 0;
		s -> obsize = 0;
		s -> rl = NULL;
		s -> exports = NULL;
		// if name of section is "bss" or ".bss", assume bss flag
		// which can be overridden with !bss flag
		if (!strcasecmp(sn, "bss") || !strcasecmp(sn, ".bss"))
			s -> flags = SECTION_BSS;
		// parse options; only one "bss"
		if (opts && as -> passnum == 1)
		{
			if (!strcasecmp(opts, "bss"))
			{
				s -> flags |= SECTION_BSS;
			}
			else if (!strcasecmp(opts, "!bss"))
			{
				s -> flags &= ~SECTION_BSS;
			}
			else
			{
				register_error(as, l, 1, "Unrecognized section option '%s'", opts);
				lwasm_free(s -> name);
				lwasm_free(s);
				return;
			}
		}
		
		s -> next = as -> sections;
		as -> sections = s;
	}
	as -> addr = s -> offset;
	as -> csect = s;
	as -> context = lwasm_next_context(as);
}

OPFUNC(pseudo_endsection)
{
	if (as -> outformat != OUTPUT_OBJ)
	{
		register_error(as, l, 1, "Sections only supported for obj target");
		return;
	}
	
	if (!(as -> csect))
	{
		register_error(as, l, 1, "ENDSECTION when not in a section");
		return;
	}
	
	as -> csect -> offset = as -> addr;
	as -> addr = 0;
	as -> csect = 0;
	as -> context = lwasm_next_context(as);
	skip_operand(p);
}

OPFUNC(pseudo_extern)
{
	if (as -> passnum != 1)
		return;

	if (as -> outformat != OUTPUT_OBJ)
	{
		register_error(as, l, 1, "External references only supported for obj target");
		return;
	}
	
	if (as -> csect)
	{
		register_error(as, l, 1, "Cannot declare external symbols within a section");
		return;
	}
	
	if (l -> sym)
	{
		lwasm_register_symbol(as, l, l -> sym, 0, SYMBOL_EXTERN);
		for ( ; **p; (*p)++) ;
		return;
	}
	
	while (**p)
	{
		char *sym2, *sym3;
		for (sym2 = *p; **p && !isspace(**p) && **p != ','; (*p)++)
			/* do nothing */ ;

		if (l -> sym)
			lwasm_free(l -> sym);

		sym3 = lwasm_alloc(*p - sym2 + 1);
		memcpy(sym3, sym2, *p - sym2);
		sym3[*p - sym2] = '\0';
			
		l -> sym = sym3;
		debug_message(2, "import symbol: '%s'", sym3);
		lwasm_register_symbol(as, l, l -> sym, 0, SYMBOL_EXTERN);
		if (**p && (**p != ','))
		{
			register_error(as, l, 1, "Bad import list");
			return;
		}
		if (**p == ',')
			(*p)++;
	}
	if (!(l -> sym))
		register_error(as, l, 1, "Bad import list");
}

OPFUNC(pseudo_export)
{
	lwasm_symbol_ent_t *se;
	export_list_t *ex;
	char *sym2, *sym3;
	int after = 0;

	if (as -> outformat != OUTPUT_OBJ)
	{
		register_error(as, l, 1, "Symbol exports only supported for obj target");
		return;
	}

	if (as -> passnum == 1)
	{
		skip_operand(p);
		return;
	}

	if (l -> sym)
	{
		for ( ; **p; (*p)++) ;
	}

again:
	if (!(l -> sym) || after == 1)
	{
	
		after = 1;
		// look for symbol after op
		if (**p)
		{
			for (sym2 = *p; **p && !isspace(**p) && **p != ','; (*p)++)
				/* do nothing */ ;

			debug_message(2, "export expression: %s", sym2);			
			if (l -> sym)
				lwasm_free(l -> sym);
			sym3 = lwasm_alloc(*p - sym2 + 1);
			memcpy(sym3, sym2, *p - sym2);
			sym3[*p - sym2] = '\0';
			
			l -> sym = sym3;
			debug_message(2, "export symbol: '%s'", sym3);
		}
	}
	if (!(l -> sym))
	{
		register_error(as, l, 2, "No symbol");
		return;
	}


	// the symbol better be defined at this point (pass 2)
	// local symbols cannot be exported nor can "global" symbols
	se = lwasm_find_symbol(as, l -> sym, -1);
	if ((!se || (se -> flags & SYMBOL_EXTERN)) && (as -> pragmas & PRAGMA_IMPORTUNDEFEXPORT))
	{
		void *p;
		p = as -> csect;
		as -> csect = NULL;
		lwasm_register_symbol(as, l, l -> sym, 0, SYMBOL_EXTERN);
		as -> csect = p;
	}
	else
	{
		if (!se)
		{
			register_error(as, l, 2, "Exported symbols must be fully defined within a section");
			return;
		}
		if (se -> sect == NULL)
		{
			register_error(as, l, 2, "Only non-local symbols within a section can be exported");
			return;
		}

		if (se -> flags & SYMBOL_SET)
		{
			register_error(as, l, 2, "You cannot export symbols defined with SET");
			return;
		}

		// if the symbol is not already a simple value, re-evaluate it
		// and see if it becomes simple
	

		if (se -> flags & SYMBOL_COMPLEX)
		{
			register_error(as, l, 2, "Exported symbols must be fully resolved on pass 2");
			return;
		}

		// search for existing export
		for (ex = se -> sect -> exports; ex; ex = ex -> next)
			if (!strcmp(l -> sym, ex -> sym))
				break;
		if (ex)
		{
			register_error(as, l, 2, "Symbol %s already exported", l -> sym);
			return;
		}

		// add an external reference
		ex = lwasm_alloc(sizeof(export_list_t));
		ex -> next = se -> sect -> exports;
		se -> sect -> exports = ex;
		ex -> offset = se -> value;
		ex -> sym = lwasm_strdup(se -> sym);
	}
next:
	if (after == 1)
	{
		if (**p == ',')
		{
			debug_message(2, "Export another symbol: %s", *p);
			(*p)++;
			for ( ; **p && isspace(**p); (*p)++)
				/* do nothing */ ;
			goto again;
		}
	}
}