/* $VER: pasm support.c V0.5 (12.10.97)
 *
 * This file is part of pasm, a portable PowerPC assembler.
 * Copyright (c) 1997-98  Frank Wille
 *
 * pasm is freeware and part of the portable and retargetable ANSI C
 * compiler vbcc, copyright (c) 1995-98 by Volker Barthelmann.
 * pasm may be freely redistributed as long as no modifications are
 * made and nothing is charged for it. Non-commercial usage is allowed
 * without any restrictions.
 * EVERY PRODUCT OR PROGRAM DERIVED DIRECTLY FROM MY SOURCE MAY NOT BE
 * SOLD COMMERCIALLY WITHOUT PERMISSION FROM THE AUTHOR.
 *
 *
 * v0.5 (12.10.97) phx
 *      lower_case(), converts a whole string to lower case.
 *      try_mapfile() adds a '\n'-byte to every file it reads.
 * v0.4 (28.06.97) phx
 *      remnode(), removes a node from a list.
 *      mapfile() automatically adds '/' to an include path.
 * v0.3 (26.03.97)
 *      Little to big endian conversion routine - l2bh(), l2bw().
 * v0.2 (25.03.97) phx
 *      Writes ELF object for 32-bit PowerPC big-endian. Either absolute
 *      or ELF output format may be selected. ELF is default for all
 *      currently supported platforms. PPCasm supports nine different
 *      relocation types (there are much more...).
 *      Compiles and works also under NetBSD/amiga (68k).
 *      Changed function declaration to 'new style' in all sources
 *      (to avoid problems with '...' for example).
 *      checkrange() prints a warning message on overflow and no error.
 *      remhead() returns NULL when list is empty.
 * v0.1 (11.03.97) phx
 *      First test version with all PowerPC instructions and most
 *      important directives. Only raw, absolute output.
 *      checkrange() size 3 checks for 26-bit values instead 24-bit.
 *      Do we need a special allocation routine for strings?
 * v0.0 (15.02.97) phx
 *      File created.
 */


#define SUPPORT_C
#include "ppcasm.h"


void *alloc(size_t);
void *alloczero(size_t);
char *allocstring(char *);
void initlist(struct list *);
void addtail(struct list *,struct node *);
struct node *remhead(struct list *);
char *mapfile(struct GlobalVars *,char *);
void checkrange(uint32,int,bool);
#ifdef LITTLEENDIAN
uint16 l2bh(uint16);
uint32 l2bw(uint32);
#endif

static char *try_mapfile(char *);
static size_t filesize(FILE *,char *);



void *alloc(size_t size)
/* allocate memory and print error message if not enough available */
{
  void *p;

  if (!size)
    size = 1;
  if (!(p = malloc(size)))
    error(1);  /* out of memory */
  return (p);
}


void *alloczero(size_t size)
/* same as alloc() but zeroes the allocated memory */
{
  void *p = alloc(size);

  memset(p,0,size);
  return (p);
}


char *allocstring(char *s)
/* allocate space for a single string */
/* @@@ this should be improved by some kind of string buffer */
{
  char *p = alloc(strlen(s)+1);

  strcpy(p,s);
  return (p);
}


void initlist(struct list *l)
/* initializes a list structure */
{
  l->first = (struct node *)&l->dummy;
  l->dummy = NULL;
  l->last = (struct node *)&l->first;
}


void addtail(struct list *l,struct node *n)
/* add node as last element of list */
{
  struct node *ln = l->last;

  n->next = ln->next;
  ln->next = n;
  n->pred = ln;
  l->last = n;
}


struct node *remhead(struct list *l)
/* remove first node in list and return a pointer to it */
{
  struct node *n = l->first;

  if (n->next) {
    l->first = n->next;
    n->next->pred = n->pred;
    return (n);
  }
  return (NULL);
}


struct node *remnode(struct node *n)
/* remove a node from a list */
{
  n->next->pred = n->pred;
  n->pred->next = n->next;
  return (n);
}


char *mapfile(struct GlobalVars *gv,char *name)
/* map a complete file into memory and return its address */
/* the file's length is returned in *(p-sizeof(size_t)) */
/* all defined paths will be searched for the file, before aborting */
{
  char *p;
  int i;
  size_t l;
  char full_name[FNAMEBUFSIZE];

  if (p = try_mapfile(name))
    return (p);
  for (i=0; i<MAX_INCPATHS; i++) {
    if (gv->incpaths[i]) {
      strncpy(full_name,gv->incpaths[i],FNAMEBUFSIZE-1);
      l = strlen(gv->incpaths[i]);
      if (l < (FNAMEBUFSIZE-2)) {
        if (full_name[l-1]!='/' && full_name[l-1]!=':')
          full_name[l++] = '/';
        strncat(full_name,name,(FNAMEBUFSIZE-1)-l);
        if (p = try_mapfile(full_name))
          return (p);
      }
    }
  }
  error(6,name);  /* can't open file */
}


static char *try_mapfile(char *name)
{
  FILE *fp;
  char *p=NULL;
  size_t fsiz;

  if (fp = fopen(name,"r")) {
    fsiz = filesize(fp,name);
    p = alloc(fsiz+1+sizeof(size_t));
    *(size_t *)p = fsiz + 1;  /* store file size before the text starts */
    p += sizeof(size_t);
    if (fread(p,1,fsiz,fp) != fsiz) {
      fclose(fp);
      error(5,name);  /* read error */
    }
    fclose(fp);
    *(p+fsiz) = '\n';  /* always have a '\n'-byte at the end */
  }
  return (p);
}


static size_t filesize(FILE *fp,char *name)
{
  /* somebody knows a better way to determine file size in ANSI C? */
  long oldpos,size;

  if ((oldpos = ftell(fp)) >= 0)
    if (fseek(fp,0,SEEK_END) >= 0)
      if ((size = ftell(fp)) >= 0)
        if (fseek(fp,oldpos,SEEK_SET) >= 0)
          return ((size_t)size);
  fclose(fp);
  error(5,name);  /* read error - doesn't return */
}


void checkrange(uint32 val,int size,bool sign)
/* checks if an integer value is in range, size=3 means 26-bit (B-instr.) */
{
  int sval;

  if (sign) {
    sval = (int)val;
    switch (size) {
      case 1:
        if (sval>0x7f || sval<-0x80)
          error(30,8);  /* immediate operand doesn't fit into 8 bits */
        break;
      case 2:
        if (sval>0x7fff || sval<-0x8000)
          error(30,16);
        break;
      case 3:
        if (sval>0x1ffffff || sval<-0x2000000)
          error(30,24);
        break;
    }
  }
  else {
    switch (size) {
      case 1:
        if (val>0xff)
          error(30,8);
        break;
      case 2:
        if (val>0xffff)
          error(30,16);
        break;
      case 3:
        if (val>0x3ffffff)
          error(30,24);
        break;
    }
  }
}


void lower_case(char *s)
/* convert a whole string to lower case */
{
  unsigned char c;

  while (c = (unsigned char)*s)
    *s++ = tolower((int)c);
}


#ifdef LITTLEENDIAN
uint16 l2bh(uint16 x)
/* little endian half word conversion */
{
  return ECH(x);
}


uint32 l2bw(uint32 x)
/* little endian word conversion */
{
  return ECW(x);
}
#endif
