/* token.m,
 */

#include <assert.h>
#include <ctype.h>
#include <stdlib.h>
#include "seborrhea.h"
#include "token.h"
#include "zzpk.h"


#define MAX_TOKEN_STRING_LEN	512

static int (*metafile_getc)(void *fp) = NULL;
static int (*metafile_ungetc)(int c, void *fp) = NULL;


/*--------------------------------------------------------------*/
/* Tokens (mainly for metafiles).				*/
/*--------------------------------------------------------------*/

@implementation Token
- initAsType:(enum TOKEN_TYPE)type_
{
    [self init];

    assert(type_ > TOKEN_UNINITIALISED && type_ < TOKEN__MAXTYPE);

    type = type_;
    switch (type) {
	case TOKEN_SYMBOL:
	case TOKEN_STRING:
	    data.string.str = calloc(1, MAX_TOKEN_STRING_LEN);
	    data.string.len = 0;
	    break;
	case TOKEN_FIXNUM:
	    data.fixnum.num = 0;
	    break;
	default:
	    break;
    }

    return self;
}

- free
{
    switch (type) {
	case TOKEN_SYMBOL:
	case TOKEN_STRING:
	    free(data.string.str);
	    break;
	default:
	    break;
    }

    type = TOKEN_UNINITIALISED;

    return [super free];
}

- (void) appendChar:(char)c
{
    assert(type == TOKEN_STRING || type == TOKEN_SYMBOL);
    assert(data.string.len < MAX_TOKEN_STRING_LEN-2);

    data.string.str[data.string.len] = c;
    data.string.len++;
    data.string.str[data.string.len] = '\0';
}

- (void) setFixnum:(int)n { assert(type == TOKEN_FIXNUM); data.fixnum.num = n; }

- (enum TOKEN_TYPE) type { return type; }
- (BOOL) isType:(enum TOKEN_TYPE)type_ { return type == type_; }

- (const char *) getSymbol { assert(type == TOKEN_SYMBOL); return data.string.str; }
- (const char *) getString { assert(type == TOKEN_STRING); return data.string.str; }
- (int) getFixnum	   { assert(type == TOKEN_FIXNUM); return data.fixnum.num; }
@end


/*--------------------------------------------------------------*/
/* Normal file readers.						*/
/*--------------------------------------------------------------*/

static int file_fgetc(void *fp)
{
    FILE *f = fp;
    return fgetc(f);
}

static int file_ungetc(int c, void *fp)
{
    FILE *f = fp;
    return ungetc(c, f);
}

void set_token_reader_stdio(void)
{
    metafile_getc = file_fgetc;
    metafile_ungetc = file_ungetc;
}

/*--------------------------------------------------------------*/
/* ZZIP file readers.						*/
/*--------------------------------------------------------------*/

#ifndef NO_ZZIP
static int zzip_fgetc(void *fp)
{
    ZZIP_FILE *f = fp;
    return zzpk_getc(f);
}

static int zzip_ungetc(int c, void *fp)
{
    ZZIP_FILE *f = fp;
    return zzpk_ungetc(c, f); 
}

void set_token_reader_zzpk(void)
{
    metafile_getc = zzip_fgetc;
    metafile_ungetc = zzip_ungetc;
}
#endif

/*--------------------------------------------------------------*/
/* Token reading.						*/
/*--------------------------------------------------------------*/

int read_char_skip_comment(void *fp)
{
    int c;

    while ((c = metafile_getc(fp)) != EOF) {
	if (c == ';') {
	    while ((c = metafile_getc(fp)) != '\n')
		if (c == EOF) return EOF;
	}
	else {
	    return c;
	}
    }

    return EOF;
}

int read_char_non_ws(void *fp)
{
    int c;
    while (isspace(c = read_char_skip_comment(fp)));
    return c;
}

BOOL expect_char(void *fp, char expect)
{
    int c = read_char_non_ws(fp);
    if (c == expect)
	return YES;
    else {
	metafile_ungetc(c, fp);
	return NO;
    }
}

BOOL is_delimiter(char c) { return strchr(";()[],#'\"", c) ? YES : NO; }

Token *read_symbol(void *fp)
{
    Token *tok;
    int c;

    c = read_char_non_ws(fp);
    if (c == EOF)
	return nil;
    if (is_delimiter(c)) {
	metafile_ungetc(c, fp);
	return nil;
    }

    tok = [[Token alloc] initAsType:TOKEN_SYMBOL];
    [tok appendChar:c];

    while ((c = metafile_getc(fp)) != EOF) {
	if (isspace(c) || is_delimiter(c)) {
	    metafile_ungetc(c, fp);
	    break;
	}
	[tok appendChar:c];
    }

    return tok;
}

Token *read_string(void *fp)
{
    Token *tok;
    BOOL backslash = NO;
    int c;

    c = read_char_non_ws(fp);
    if (c == EOF || c != '"')
	return nil;

    tok = [[Token alloc] initAsType:TOKEN_STRING];

    while ((c = metafile_getc(fp)) != EOF) {
	if (c == '\\') {
	    if (not backslash)
		backslash = YES;
	    else {
		c = metafile_getc(fp);
		switch (c) {
		    case EOF: goto abort;
		    case 'n': [tok appendChar:'\n']; break;
		    default:  [tok appendChar:c]; break;
		}
		backslash = NO;
	    }
	}
	else {
	    if (c == '"')
		return tok;
	    else
		[tok appendChar:c];
	}
    }

 abort:

    [tok free];
    return nil;
}

Token *read_fixnum(void *fp)
{
    Token *tok;
    int acc = 0;
    int c;

    c = read_char_non_ws(fp);
    if (c == EOF)
	return nil;

    if (not isdigit(c)) {
	metafile_ungetc(c, fp);
	return nil;
    }

    acc = c - '0';

    while ((c = metafile_getc(fp)) != EOF) {
	if (isdigit(c))
	    acc = (acc*10) + (c - '0');
	else {
	    metafile_ungetc(c, fp);
	    if (isspace(c) || is_delimiter(c))
		break;
	    else
		return nil;
	}
    }

    tok = [[Token alloc] initAsType:TOKEN_FIXNUM];
    [tok setFixnum:acc];
    return tok;
}

Token *read_token(void *fp)
{
    int c;

    c = read_char_non_ws(fp);
    if (c == EOF)
	return nil;

    metafile_ungetc(c, fp);
    if (c == '"')
	return read_string(fp);
    else if (isdigit(c))
	return read_fixnum(fp);
    else
	return read_symbol(fp);
}
