// sscanf / fscanf — minimal scanf family for the W65816 runtime.
// Format directives:
//   %d / %i  signed int (decimal)
//   %u       unsigned int (decimal)
//   %x %X    unsigned int (hex; "0x" prefix optional)
//   %o       unsigned int (octal)
//   %ld %lu %lx %li %lo  long-int variants (32-bit)
//   %s       whitespace-terminated string into char*
//   %c       single char into char*
//   %%       literal %
// Whitespace in format matches zero or more whitespace chars in input.

#include <stdio.h>
#include <stdarg.h>

extern int isspace(int);
extern int fgetc(FILE *);
extern int ungetc(int, FILE *);


// ---- string-source variant ----


static const char *skipWsStr(const char *s) {
    while (*s && isspace(*s)) s++;
    return s;
}


static int parseULStr(const char **pp, int base, unsigned long *out) {
    const char *p = *pp;
    unsigned long v = 0;
    int saw = 0;
    while (*p) {
        int c = *p;
        int d;
        if (c >= '0' && c <= '9')      d = c - '0';
        else if (c >= 'a' && c <= 'z') d = 10 + c - 'a';
        else if (c >= 'A' && c <= 'Z') d = 10 + c - 'A';
        else break;
        if (d < 0 || d >= base) break;
        v = v * (unsigned long)base + (unsigned long)d;
        saw = 1;
        p++;
    }
    *pp = p;
    *out = v;
    return saw;
}


int vsscanf(const char *str, const char *fmt, va_list ap) {
    int matched = 0;
    const char *s = str;
    while (*fmt) {
        if (isspace(*fmt)) {
            while (*fmt && isspace(*fmt)) fmt++;
            s = skipWsStr(s);
            continue;
        }
        if (*fmt != '%') {
            if (*s != *fmt) break;
            fmt++; s++;
            continue;
        }
        fmt++;
        if (*fmt == 0) break;
        int isLong = 0;
        if (*fmt == 'l') { isLong = 1; fmt++; if (*fmt == 0) break; }
        char spec = *fmt;
        if (spec == '%') {
            if (*s != '%') break;
            s++; fmt++;
            continue;
        }
        if (spec == 'c') {
            char *out = va_arg(ap, char *);
            if (!*s) break;
            *out = *s++;
            matched++;
            fmt++;
            continue;
        }
        if (spec == 's') {
            char *out = va_arg(ap, char *);
            s = skipWsStr(s);
            if (!*s) break;
            int n = 0;
            while (*s && !isspace(*s)) { *out++ = *s++; n++; }
            *out = 0;
            if (n) matched++;
            fmt++;
            continue;
        }
        s = skipWsStr(s);
        int neg = 0;
        if ((spec == 'd' || spec == 'i') && (*s == '+' || *s == '-')) {
            neg = (*s == '-');
            s++;
        }
        int base = 10;
        if (spec == 'x' || spec == 'X') base = 16;
        else if (spec == 'o') base = 8;
        else if (spec == 'i') {
            if (s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) {
                base = 16; s += 2;
            } else if (s[0] == '0') {
                base = 8;
            }
        }
        if ((spec == 'x' || spec == 'X') && s[0] == '0' &&
            (s[1] == 'x' || s[1] == 'X')) s += 2;
        unsigned long v;
        if (!parseULStr(&s, base, &v)) break;
        if (isLong) {
            if (spec == 'd' || spec == 'i') {
                long *out = va_arg(ap, long *);
                *out = neg ? -(long)v : (long)v;
            } else {
                unsigned long *out = va_arg(ap, unsigned long *);
                *out = v;
            }
        } else {
            if (spec == 'd' || spec == 'i') {
                int *out = va_arg(ap, int *);
                *out = neg ? -(int)v : (int)v;
            } else {
                unsigned int *out = va_arg(ap, unsigned int *);
                *out = (unsigned int)v;
            }
        }
        matched++;
        fmt++;
    }
    if (matched == 0 && !*s) return -1;
    return matched;
}


int sscanf(const char *str, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int r = vsscanf(str, fmt, ap);
    va_end(ap);
    return r;
}


// ---- file-source variant ----
//
// Bridge fscanf to vsscanf via a stack buffer.  Reads up to BUF-1
// bytes from the file (stopping at the first newline) into buf and
// runs vsscanf on it.  The trailing tail of buf is silently discarded
// — fine for single-line records, less so for streamed parsing.
//
// Why bridge instead of an inline vfscanf body: a from-scratch vfscanf
// hit a high-pressure regalloc bug where `fmt` got register-clobbered
// across fgetc/ungetc helper calls, exiting the outer loop after one
// conversion.  Re-using vsscanf side-steps the issue by keeping all
// the parsing in a single tight function.
#define VFSCANF_BUF 256
int vfscanf(FILE *f, const char *fmt, va_list ap) {
    char buf[VFSCANF_BUF];
    int n = 0;
    int c;
    int sawAny = 0;
    while (n < VFSCANF_BUF - 1) {
        c = fgetc(f);
        if (c < 0) break;
        sawAny = 1;
        buf[n++] = (char)c;
        if (c == '\n') break;
    }
    buf[n] = 0;
    if (!sawAny) return -1;
    int r = vsscanf(buf, fmt, ap);
    return r;
}


int fscanf(FILE *f, const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int r = vfscanf(f, fmt, ap);
    va_end(ap);
    return r;
}


extern FILE *stdin;


int vscanf(const char *fmt, va_list ap) {
    return vfscanf(stdin, fmt, ap);
}


int scanf(const char *fmt, ...) {
    va_list ap;
    va_start(ap, fmt);
    int r = vfscanf(stdin, fmt, ap);
    va_end(ap);
    return r;
}
