/*  LIBPAWNC.C
 *
 *  A "glue file" for building the Pawn compiler as a DLL or shared library.
 *
 *  Copyright (c) ITB CompuPhase, 2000-2005
 *
 *  This software is provided "as-is", without any express or implied warranty.
 *  In no event will the authors be held liable for any damages arising from
 *  the use of this software.
 *
 *  Permission is granted to anyone to use this software for any purpose,
 *  including commercial applications, and to alter it and redistribute it
 *  freely, subject to the following restrictions:
 *
 *  1.  The origin of this software must not be misrepresented; you must not
 *      claim that you wrote the original software. If you use this software in
 *      a product, an acknowledgment in the product documentation would be
 *      appreciated but is not required.
 *  2.  Altered source versions must be plainly marked as such, and must not be
 *      misrepresented as being the original software.
 *  3.  This notice may not be removed or altered from any source distribution.
 */

#include <assert.h>
#include <stdio.h>
#include <stddef.h>
#include <stdlib.h>
#include <string.h>
#include "sc.h"

#if defined PAWNC_DLL

#if !defined(__linux__) && !defined(__APPLE__)
#include "dllmain.c"
#endif

# define MAX_ARGS   100
# if !defined UNUSED_PARAM
#   define UNUSED_PARAM(p) ((void)(p))
# endif

#if PAWN_CELL_SIZE==32
#define EXCOMPILER	Compile32
#else
#define EXCOMPILER	Compile64
#endif

# if defined __WIN32__ || defined _WIN32 || defined WIN32 || defined __NT__
  __declspec (dllexport)
  void EXCOMPILER(int argc, char **argv)
# else
  void extern __attribute__((visibility("default"))) EXCOMPILER(int argc, char **argv)
# endif
  {
      pc_compile(argc, argv);
  }
#endif /* PAWNC_DLL */

#if defined LINUX || defined __FreeBSD__ || defined __OpenBSD__ || defined __APPLE__
  #include <sys/types.h>
  #include <sys/stat.h>
#endif

/* pc_printf()
 * Called for general purpose "console" output. This function prints general
 * purpose messages; errors go through pc_error(). The function is modelled
 * after printf().
 */
#if PAWN_CELL_SIZE==32
#if defined __WIN32__ || defined _WIN32 || defined WIN32
 __declspec (dllexport)
int pc_printf(const char *message,...)
#else
extern int __attribute__((visibility("default"))) pc_printf(const char *message,...)
#endif
#else
int pc_printf(const char *message, ...)
#endif
{
#if PAWN_CELL_SIZE==32
  int ret;
  va_list argptr;

  va_start(argptr,message);
  ret=vprintf(message,argptr);
  va_end(argptr);

  return ret;
#else
  return 1;
#endif
}

/* pc_error()
 * Called for producing error output.
 *    number      the error number (as documented in the manual)
 *    message     a string describing the error with embedded %d and %s tokens
 *    filename    the name of the file currently being parsed
 *    firstline   the line number at which the expression started on which
 *                the error was found, or -1 if there is no "starting line"
 *    lastline    the line number at which the error was detected
 *    argptr      a pointer to the first of a series of arguments (for macro
 *                "va_arg")
 * Return:
 *    If the function returns 0, the parser attempts to continue compilation.
 *    On a non-zero return value, the parser aborts.
 */
int pc_error(int number,char *message,char *filename,int firstline,int lastline,va_list argptr)
{
#if PAWN_CELL_SIZE==32
static char *prefix[3]={ "error", "fatal error", "warning" };

  if (number!=0) {
    char *pre;
    int idx;

    if (number < 100 || (number >= 200 && sc_warnings_are_errors))
      idx = 0;
    else if (number < 200)
      idx = 1;
    else
      idx = 2;

    pre=prefix[idx];
    if (firstline>=0)
      pc_printf("%s(%d -- %d) : %s %03d: ",filename,firstline,lastline,pre,number);
    else
      pc_printf("%s(%d) : %s %03d: ",filename,lastline,pre,number);
  } /* if */
  vprintf(message,argptr);
#endif
  return 0;
}

typedef struct src_file_s {
  FILE *fp;         // Set if writing.
  char *buffer;     // IO buffer.
  char *pos;        // IO position.
  char *end;        // End of buffer.
  size_t maxlength; // Maximum length of the writable buffer.
} src_file_t;

/* pc_opensrc()
 * Opens a source file (or include file) for reading. The "file" does not have
 * to be a physical file, one might compile from memory.
 *    filename    the name of the "file" to read from
 * Return:
 *    The function must return a pointer, which is used as a "magic cookie" to
 *    all I/O functions. When failing to open the file for reading, the
 *    function must return NULL.
 * Note:
 *    Several "source files" may be open at the same time. Specifically, one
 *    file can be open for reading and another for writing.
 */
void *pc_opensrc(char *filename)
{
  FILE *fp = NULL;
  long length;
  src_file_t *src = NULL;

#if defined LINUX || defined __FreeBSD__ || defined __OpenBSD__ || defined DARWIN
  struct stat fileInfo;
  if (stat(filename, &fileInfo) != 0) {
    return NULL;
  }

  if (S_ISDIR(fileInfo.st_mode)) {
    return NULL;
  }
#endif

  if ((fp = fopen(filename, "rb")) == NULL)
    return NULL;
  if (fseek(fp, 0, SEEK_END) == -1)
    goto err;
  if ((length = ftell(fp)) == -1)
    goto err;
  if (fseek(fp, 0, SEEK_SET) == -1)
    goto err;

  if ((src = (src_file_t *)calloc(1, sizeof(src_file_t))) == NULL)
    goto err;
  if ((src->buffer = (char *)calloc(length, sizeof(char))) == NULL)
    goto err;
  if (fread(src->buffer, length, 1, fp) != 1)
    goto err;

  src->pos = src->buffer;
  src->end = src->buffer + length;
  fclose(fp);
  return src;

err:
  pc_closesrc(src);
  fclose(fp);
  return NULL;
}

/* pc_createsrc()
 * Creates/overwrites a source file for writing. The "file" does not have
 * to be a physical file, one might compile from memory.
 *    filename    the name of the "file" to create
 * Return:
 *    The function must return a pointer, which is used as a "magic cookie" to
 *    all I/O functions. When failing to open the file for reading, the
 *    function must return NULL.
 * Note:
 *    Several "source files" may be open at the same time. Specifically, one
 *    file can be open for reading and another for writing.
 */
void *pc_createsrc(char *filename)
{
  src_file_t *src = (src_file_t *)calloc(1, sizeof(src_file_t));
  if (!src)
    return NULL;
  if ((src->fp = fopen(filename, "wt")) == NULL) {
    pc_closesrc(src);
    return NULL;
  }

  src->maxlength = 1024;
  if ((src->buffer = (char *)calloc(1, src->maxlength)) == NULL) {
    pc_closesrc(src);
    return NULL;
  }

  src->pos = src->buffer;
  src->end = src->buffer;
  return src;
}

/* pc_closesrc()
 * Closes a source file (or include file). The "handle" parameter has the
 * value that pc_opensrc() returned in an earlier call.
 */
void pc_closesrc(void *handle)
{
  src_file_t *src = (src_file_t *)handle;
  if (!src)
    return;
  if (src->fp) {
    fwrite(src->buffer, src->pos - src->buffer, 1, src->fp);
    fclose(src->fp);
  }
  free(src->buffer);
  free(src);
}

/* pc_resetsrc()
 * "position" may only hold a pointer that was previously obtained from
 * pc_getpossrc()
 */
void pc_resetsrc(void *handle,void *position)
{
  src_file_t *src = (src_file_t *)handle;
  ptrdiff_t pos = (ptrdiff_t)position;

  assert(!src->fp);
  assert(pos >= 0 && src->buffer + pos <= src->end);
  src->pos = src->buffer + pos;
}

/* pc_readsrc()
 * Reads a single line from the source file (or up to a maximum number of
 * characters if the line in the input file is too long).
 */
char *pc_readsrc(void *handle,unsigned char *target,int maxchars)
{
  src_file_t *src = (src_file_t *)handle;
  char *outptr = (char *)target;
  char *outend = outptr + maxchars;

  assert(!src->fp);

  if (src->pos == src->end)
    return NULL;

  while (outptr < outend && src->pos < src->end) {
    char c = *src->pos++;
    *outptr++ = c;

    if (c == '\n')
      break;
    if (c == '\r') {
      // Handle CRLF.
      if (src->pos < src->end && *src->pos == '\n') {
        src->pos++;
        if (outptr < outend)
          *outptr++ = '\n';
      }
      break;
    }
  }

  // Caller passes in a buffer of size >= maxchars+1.
  *outptr = '\0';
  return (char *)target;
}

/* pc_writesrc()
 * Writes to to the source file. There is no automatic line ending; to end a
 * line, write a "\n".
 */
int pc_writesrc(void *handle,unsigned char *source)
{
  char *str = (char *)source;
  size_t len = strlen(str);
  src_file_t *src = (src_file_t *)handle;

  assert(src->fp && src->maxlength);

  if (src->pos + len > src->end) {
    char *newbuf;
    size_t newmax = src->maxlength;
    size_t newlen = (src->pos - src->buffer) + len;
    while (newmax < newlen) {
      // Grow by 1.5X
      newmax += newmax + newmax / 2;
      if (newmax < src->maxlength)
        abort();
    }

    newbuf = (char *)realloc(src->buffer, newmax);
    if (!newbuf)
      abort();
    src->pos = newbuf + (src->pos - src->buffer);
    src->end = newbuf + newmax;
    src->buffer = newbuf;
    src->maxlength = newmax;
  }

  strcpy(src->pos, str);
  src->pos += len;
  return 0;
}

void *pc_getpossrc(void *handle)
{
  src_file_t *src = (src_file_t *)handle;

  assert(!src->fp);
  return (void *)(ptrdiff_t)(src->pos - src->buffer);
}

int pc_eofsrc(void *handle)
{
  src_file_t *src = (src_file_t *)handle;

  assert(!src->fp);
  return src->pos == src->end;
}

/* should return a pointer, which is used as a "magic cookie" to all I/O
 * functions; return NULL for failure
 */
void *pc_openasm(char *filename)
{
  #if defined __MSDOS__ || defined SC_LIGHT
    return fopen(filename,"w+t");
  #else
    return mfcreate(filename);
  #endif
}

void pc_closeasm(void *handle, int deletefile)
{
  #if defined __MSDOS__ || defined SC_LIGHT
    if (handle!=NULL)
      fclose((FILE*)handle);
    if (deletefile)
      remove(outfname);
  #else
    if (handle!=NULL) {
      if (!deletefile)
        mfdump((MEMFILE*)handle);
      mfclose((MEMFILE*)handle);
    } /* if */
  #endif
}

void pc_resetasm(void *handle)
{
  assert(handle!=NULL);
  #if defined __MSDOS__ || defined SC_LIGHT
    fflush((FILE*)handle);
    fseek((FILE*)handle,0,SEEK_SET);
  #else
    mfseek((MEMFILE*)handle,0,SEEK_SET);
  #endif
}

int pc_writeasm(void *handle,char *string)
{
  #if defined __MSDOS__ || defined SC_LIGHT
    return fputs(string,(FILE*)handle) >= 0;
  #else
    return mfputs((MEMFILE*)handle,string);
  #endif
}

char *pc_readasm(void *handle, char *string, int maxchars)
{
  #if defined __MSDOS__ || defined SC_LIGHT
    return fgets(string,maxchars,(FILE*)handle);
  #else
    return mfgets((MEMFILE*)handle,string,maxchars);
  #endif
}

/* Should return a pointer, which is used as a "magic cookie" to all I/O
 * functions; return NULL for failure.
 */
void *pc_openbin(char *filename)
{
  return fopen(filename,"wb");
}

void pc_closebin(void *handle,int deletefile)
{
  fclose((FILE*)handle);
  if (deletefile)
    remove(binfname);
}

/* pc_resetbin()
 * Can seek to any location in the file.
 * The offset is always from the start of the file.
 */
void pc_resetbin(void *handle,long offset)
{
  fflush((FILE*)handle);
  fseek((FILE*)handle,offset,SEEK_SET);
}

int pc_writebin(void *handle,void *buffer,int size)
{
  return (int)fwrite(buffer,1,size,(FILE*)handle) == size;
}

long pc_lengthbin(void *handle)
{
  return ftell((FILE*)handle);
}
