/* vi:set ts=4 sw=4:

  strscan.c version 0.5.4

    Copyright (c) 1999 Minero Aoki <aamine@dp.u-netsurf.ne.jp>

    This library is free software.
    You can distribute/modify this library under the terms of
    the GNU Lesser General Public License version 2 or later.

*/


#include <stdio.h>
#ifdef __STDC__
#  include <stdlib.h>
#endif

#include "ruby.h"
#include "re.h"


struct strscanner
{
    char *pbeg;
    char *ptr;
    char *pend;
    char *pre;
    unsigned int flags;
    struct re_registers regi;
};

#define MATCH_FLAG (1U << 0)
#define DUP_FLAG   (1U << 1)

#define MATCHED_P(p) (p->flags & MATCH_FLAG)

#define GET_SCANNER(obj,dat) Data_Get_Struct(obj, struct strscanner, dat)
#define SCAN_FINISHED(dat) ((dat)->ptr >= (dat)->pend)

static VALUE StringScanner;
static VALUE ScanError;


/* ------------------------------------------------------------- */


static void
strscan_free(p)
    struct strscanner *p;
{
    if (p->flags & DUP_FLAG)
        free(p->pbeg);
    ruby_re_free_registers(&(p->regi));
    free(p);
}


static VALUE
strscan_s_new(argc, argv, klass)
    int argc;
    VALUE *argv, klass;
{
    VALUE str, dup_p;
    struct strscanner *p;

    if (rb_scan_args(argc, argv, "11", &str, &dup_p) == 1)
        dup_p = Qtrue;
    Check_Type(str, T_STRING);

    p = ALLOC_N(struct strscanner, 1);
    p->flags = 0U;
    if (rb_safe_level() >= 4 || RTEST(dup_p)) {
        p->flags |= DUP_FLAG;
        p->ptr = ALLOC_N(char, RSTRING(str)->len) ;
        memcpy(p->ptr, RSTRING(str)->ptr, sizeof(char) * RSTRING(str)->len);
    } else {
        p->ptr = RSTRING(str)->ptr;
    }
    p->pbeg = p->ptr;
    p->pend = p->ptr + RSTRING(str)->len;
    p->pre = 0;
    MEMZERO(&(p->regi), struct re_registers, 1);

    return Data_Wrap_Struct(klass, 0, strscan_free, p);
}


static VALUE
strscan_reset(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    p->ptr = p->pbeg;
    p->pre = 0;
    p->flags &= ~MATCH_FLAG;
    return self;
}


static VALUE
strscan_clear(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    p->ptr = p->pend;
    p->pre = 0;
    p->flags &= ~MATCH_FLAG;
    return self;
}


static VALUE
strscan_do_scan(self, regex, ptrflag, strflag)
    VALUE self, regex;
    int ptrflag, strflag;
{
    struct strscanner *p;
    int ret;

    Check_Type(regex, T_REGEXP);
    GET_SCANNER(self, p);

    p->flags &= ~MATCH_FLAG;
    ret = re_match(RREGEXP(regex)->ptr,
                   p->ptr,
                   p->pend - p->ptr,
                   0,
                   &(p->regi));
    if (ret == -2) {
        rb_raise(ScanError, "regexp buffer overflow");
    }
    else if ( ret < 0 ) {
        return Qnil;
    }
    else {
        size_t len = p->regi.end[0];

        p->flags |= MATCH_FLAG;
        if (ptrflag) {
            p->pre = p->ptr;
            p->ptr += len;
            if (strflag)
                return rb_str_new(p->pre, len);
        }
        else {
            if (strflag)
                return rb_str_new(p->ptr, len);
        }
        return INT2FIX(len);
    }

    return Qnil; /* not reach */
}


static VALUE
strscan_scan(self, reg)
    VALUE self, reg;
{
    return strscan_do_scan(self, reg, 1, 1);
}


static VALUE
strscan_match_p(self, reg)
    VALUE self, reg;
{
    return strscan_do_scan(self, reg, 0, 0);
}


static VALUE
strscan_skip(self, reg)
    VALUE self, reg;
{
    return strscan_do_scan(self, reg, 1, 0);
}


static VALUE
strscan_check(self, reg)
    VALUE self, reg;
{
    return strscan_do_scan(self, reg, 0, 1);
}


static VALUE
strscan_scan_full(self, reg, s, f)
{
    return strscan_do_scan(self, reg, RTEST(s), RTEST(f));
}


static VALUE
strscan_getch(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    p->flags &= ~MATCH_FLAG;
    if (SCAN_FINISHED(p))
        return Qnil;

    p->flags |= MATCH_FLAG;
    p->pre = p->ptr;
    p->ptr += mbclen(*p->ptr);
    if (p->ptr > p->pend)
        p->ptr = p->pend;
    return rb_str_new(p->pre, p->ptr - p->pre);
}


static VALUE
strscan_empty_p(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    if (SCAN_FINISHED(p))
        return Qtrue;
    else
        return Qfalse;
}


static VALUE
strscan_rest_p(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    if (SCAN_FINISHED(p))
        return Qfalse;
    else
        return Qtrue;
}


static VALUE
strscan_matched_p(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    if (MATCHED_P(p))
        return Qtrue;
    else
        return Qfalse;
}


static VALUE
strscan_restsize(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    return INT2FIX(p->pend - p->ptr);
}


static VALUE
strscan_unscan(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    if (! MATCHED_P(p))
        rb_raise(ScanError, "cannot unscan: not scanned yet");
    p->ptr = p->pre;
    p->pre = 0;
    return Qtrue;
}


static VALUE
strscan_matched(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    if (MATCHED_P(p))
        return rb_str_new(p->pre, p->ptr - p->pre);
    else
        return Qnil;
}


static VALUE
strscan_pre_match(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    return rb_str_new(p->pbeg, p->pre - p->pbeg);
}


static VALUE
strscan_post_match(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    return rb_str_new(p->ptr, p->pend - p->ptr);
}


static VALUE
strscan_aref(self, idx)
    VALUE self, idx;
{
    struct strscanner *p;
    long i;

    GET_SCANNER(self, p);
    if (! MATCHED_P(p))
        return Qnil;
    
    i = NUM2LONG(idx);
    if (i >= p->regi.num_regs)
        return Qnil;
    
    if (p->regi.beg[i] == -1)
        return Qnil;
    
    return rb_str_new(p->ptr + p->regi.beg[i],
                      p->regi.end[i] - p->regi.beg[i]);
}


static VALUE
strscan_matchedsize(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    if (p->pre == 0)
        return Qnil;
    else
        return INT2NUM(p->ptr - p->pre);
}


static VALUE
strscan_string(self)
    VALUE self;
{
    struct strscanner *p;

    GET_SCANNER(self, p);
    return rb_str_new(p->pbeg, p->pend - p->pbeg);
}


static VALUE
strscan_peep(self, vlen)
    VALUE self, vlen;
{
    struct strscanner *p;
    long len;

    GET_SCANNER(self, p);

    len = NUM2LONG(vlen);
    if (p->ptr + len > p->pend)
        len = p->pend - p->ptr;

    return rb_str_new(p->ptr, len);
}



void
Init_strscan()
{
    ScanError = rb_define_class("ScanError", rb_eStandardError);
    StringScanner = rb_define_class("StringScanner", rb_cObject);

    rb_define_singleton_method(StringScanner, "new", strscan_s_new, -1);
    rb_define_method(StringScanner, "reset",       strscan_reset,       0);
    rb_define_method(StringScanner, "clear",       strscan_clear,       0);
    rb_define_method(StringScanner, "scan",        strscan_scan,        1);
    rb_define_method(StringScanner, "skip",        strscan_skip,        1);
    rb_define_method(StringScanner, "match?",      strscan_match_p,     1);
    rb_define_method(StringScanner, "check",       strscan_check,       1);
    rb_define_method(StringScanner, "scan_full",   strscan_scan_full,   3);
    rb_define_method(StringScanner, "getch",       strscan_getch,       0);
    rb_define_method(StringScanner, "empty?",      strscan_empty_p,     0);
    rb_define_method(StringScanner, "rest?",       strscan_rest_p,      0);
    rb_define_method(StringScanner, "unscan",      strscan_unscan,      0);
    rb_define_method(StringScanner, "matched?",    strscan_matched_p,   0);
    rb_define_method(StringScanner, "matched",     strscan_matched,     0);
    rb_define_method(StringScanner, "matchedsize", strscan_matchedsize, 0);
    rb_define_method(StringScanner, "pre_match",   strscan_pre_match,   0);
    rb_define_method(StringScanner, "post_match",  strscan_post_match,  0);
    rb_define_method(StringScanner, "rest",        strscan_post_match,  0);
    rb_define_method(StringScanner, "restsize",    strscan_restsize,    0);
    rb_define_method(StringScanner, "[]",          strscan_aref,        1);
    rb_define_method(StringScanner, "string",      strscan_string,      0);
    rb_define_method(StringScanner, "peep",        strscan_peep,        1);
}
