293 lines
10 KiB
C
293 lines
10 KiB
C
/*-*- mode:c;indent-tabs-mode:nil;c-basic-offset:2;tab-width:8;coding:utf-8 -*-│
|
|
│vi: set net ft=c ts=2 sts=2 sw=2 fenc=utf-8 :vi│
|
|
╞══════════════════════════════════════════════════════════════════════════════╡
|
|
│ Copyright 2020 Justine Alexandra Roberts Tunney │
|
|
│ │
|
|
│ This program is free software; you can redistribute it and/or modify │
|
|
│ it under the terms of the GNU General Public License as published by │
|
|
│ the Free Software Foundation; version 2 of the License. │
|
|
│ │
|
|
│ This program is distributed in the hope that it will be useful, but │
|
|
│ WITHOUT ANY WARRANTY; without even the implied warranty of │
|
|
│ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU │
|
|
│ General Public License for more details. │
|
|
│ │
|
|
│ You should have received a copy of the GNU General Public License │
|
|
│ along with this program; if not, write to the Free Software │
|
|
│ Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA │
|
|
│ 02110-1301 USA │
|
|
╚─────────────────────────────────────────────────────────────────────────────*/
|
|
#include "libc/bits/weaken.h"
|
|
#include "libc/conv/conv.h"
|
|
#include "libc/fmt/fmt.h"
|
|
#include "libc/mem/mem.h"
|
|
#include "libc/runtime/runtime.h"
|
|
#include "libc/str/str.h"
|
|
#include "libc/str/tpdecodecb.h"
|
|
#include "libc/sysv/errfuns.h"
|
|
|
|
/**
|
|
* String / file / stream decoder.
|
|
*
|
|
* This scanf implementation is able to tokenize strings containing
|
|
* 8-bit through 128-bit integers (with validation), floating point
|
|
* numbers, etc. It can also be used to convert UTF-8 to UTF-16/32.
|
|
*
|
|
* - `%d` parses integer
|
|
* - `%ms` parses string allocating buffer assigning pointer
|
|
*
|
|
* @param callback supplies UTF-8 characters using -1 sentinel
|
|
* @param fmt is a computer program embedded inside a c string, written
|
|
* in a domain-specific programming language that, by design, lacks
|
|
* Turing-completeness
|
|
* @param va points to the variadic argument state
|
|
* @see libc/fmt/pflink.h (dynamic memory is not a requirement)
|
|
*/
|
|
int vcscanf(int callback(void *), void *arg, const char *fmt, va_list va) {
|
|
struct FreeMe {
|
|
struct FreeMe *next;
|
|
void *ptr;
|
|
} *freeme = NULL;
|
|
const unsigned char *p = (const unsigned char *)fmt;
|
|
unsigned i = 0;
|
|
int items = 0;
|
|
int c = callback(arg);
|
|
while (c != -1) {
|
|
switch (p[i++]) {
|
|
case '\0':
|
|
return items;
|
|
case ' ':
|
|
case '\t':
|
|
case '\n':
|
|
case '\r':
|
|
case '\v':
|
|
while (isspace(c)) c = callback(arg);
|
|
break;
|
|
case '%': {
|
|
uintmax_t number;
|
|
void *buf;
|
|
size_t bufsize;
|
|
unsigned width = 0;
|
|
unsigned char bits = 32;
|
|
unsigned char charbytes = sizeof(char);
|
|
unsigned char diglet;
|
|
unsigned char base;
|
|
unsigned char prefix;
|
|
bool rawmode = false;
|
|
bool issigned = false;
|
|
bool ismalloc = false;
|
|
bool isneg = false;
|
|
bool thousands = false;
|
|
bool discard = false;
|
|
for (;;) {
|
|
switch (p[i++]) {
|
|
case '%': /* %% → % */
|
|
goto NonDirectiveCharacter;
|
|
case '0':
|
|
case '1':
|
|
case '2':
|
|
case '3':
|
|
case '4':
|
|
case '5':
|
|
case '6':
|
|
case '7':
|
|
case '8':
|
|
case '9':
|
|
width *= 10;
|
|
width += p[i - 1] - '0';
|
|
break;
|
|
case '*':
|
|
discard = true;
|
|
break;
|
|
case 'm':
|
|
ismalloc = true;
|
|
break;
|
|
case 'c':
|
|
rawmode = true;
|
|
if (!width) width = 1;
|
|
/* εpsilon transition */
|
|
case 's':
|
|
goto DecodeString;
|
|
case '\'':
|
|
thousands = true;
|
|
break;
|
|
case 'j': /* 128-bit */
|
|
bits = sizeof(intmax_t) * 8;
|
|
break;
|
|
case 'l': /* long */
|
|
case 'L': /* loooong */
|
|
charbytes = sizeof(wchar_t);
|
|
/* fallthrough */
|
|
case 't': /* ptrdiff_t */
|
|
case 'Z': /* size_t */
|
|
case 'z': /* size_t */
|
|
bits = 64;
|
|
break;
|
|
case 'h': /* short and char */
|
|
charbytes = sizeof(char16_t);
|
|
bits >>= 1;
|
|
break;
|
|
case 'b': /* binary */
|
|
base = 2;
|
|
prefix = 'b';
|
|
goto ConsumeBasePrefix;
|
|
case 'p': /* pointer (NexGen32e) */
|
|
bits = 48;
|
|
/* fallthrough */
|
|
case 'x':
|
|
case 'X': /* hexadecimal */
|
|
base = 16;
|
|
prefix = 'x';
|
|
goto ConsumeBasePrefix;
|
|
case 'o': /* octal */
|
|
base = 8;
|
|
goto DecodeNumber;
|
|
case 'd': /* decimal */
|
|
case 'n': /* TODO(jart): flexidecimal */
|
|
issigned = true;
|
|
if (c == '+' || (isneg = c == '-')) {
|
|
c = callback(arg);
|
|
}
|
|
/* εpsilon transition */
|
|
case 'u':
|
|
base = 10;
|
|
goto DecodeNumber;
|
|
default:
|
|
items = einval();
|
|
goto Done;
|
|
}
|
|
}
|
|
ConsumeBasePrefix:
|
|
if (c == '0') {
|
|
c = callback(arg);
|
|
if (c == prefix || c == prefix + ('a' - 'A')) {
|
|
c = callback(arg);
|
|
}
|
|
}
|
|
DecodeNumber:
|
|
if (c != -1) {
|
|
number = 0;
|
|
do {
|
|
diglet = kBase36[(unsigned char)c];
|
|
if (1 <= diglet && diglet <= base) {
|
|
number *= base;
|
|
number += diglet - 1;
|
|
} else if (thousands && diglet == ',') {
|
|
/* ignore */
|
|
} else {
|
|
break;
|
|
}
|
|
} while ((c = callback(arg)) != -1);
|
|
if (!discard) {
|
|
uintmax_t bane = (uintmax_t)1 << (bits - 1);
|
|
if (!(number & ~((bane - 1) | (issigned ? 0 : bane))) ||
|
|
(issigned && number == bane /* two's complement bane */)) {
|
|
++items;
|
|
} else {
|
|
items = erange();
|
|
goto Done;
|
|
}
|
|
if (issigned && isneg) {
|
|
number = ~number + 1;
|
|
}
|
|
void *out = va_arg(va, void *);
|
|
switch (bits) {
|
|
case sizeof(uintmax_t) * CHAR_BIT:
|
|
*(uintmax_t *)out = number;
|
|
break;
|
|
case 48:
|
|
case 64:
|
|
*(uint64_t *)out = (uint64_t)number;
|
|
break;
|
|
case 32:
|
|
*(uint32_t *)out = (uint32_t)number;
|
|
break;
|
|
case 16:
|
|
*(uint16_t *)out = (uint16_t)number;
|
|
break;
|
|
case 8:
|
|
default:
|
|
*(uint8_t *)out = (uint8_t)number;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
continue;
|
|
DecodeString:
|
|
bufsize = !width ? 32 : rawmode ? width : width + 1;
|
|
if (discard) {
|
|
buf = NULL;
|
|
} else if (ismalloc) {
|
|
buf = weaken(malloc)(bufsize * charbytes);
|
|
struct FreeMe *entry;
|
|
if (buf && (entry = weaken(calloc)(1, sizeof(struct FreeMe)))) {
|
|
entry->ptr = buf;
|
|
entry->next = freeme;
|
|
freeme = entry;
|
|
}
|
|
} else {
|
|
buf = va_arg(va, void *);
|
|
}
|
|
if (buf) {
|
|
size_t j = 0;
|
|
for (;;) {
|
|
if (ismalloc && !width && j + 2 + 1 >= bufsize &&
|
|
!weaken(grow)(&buf, &bufsize, charbytes, 0)) {
|
|
width = bufsize - 1;
|
|
}
|
|
if (c != -1 && j + !rawmode < bufsize && (rawmode || !isspace(c))) {
|
|
if (charbytes == 1) {
|
|
((unsigned char *)buf)[j++] = (unsigned char)c;
|
|
c = callback(arg);
|
|
} else if (tpdecodecb((wint_t *)&c, c, (void *)callback, arg) !=
|
|
-1) {
|
|
if (charbytes == sizeof(char16_t)) {
|
|
j += abs(pututf16(&((char16_t *)buf)[j], bufsize - j - 1, c,
|
|
false));
|
|
} else {
|
|
((wchar_t *)buf)[j++] = (wchar_t)c;
|
|
}
|
|
c = callback(arg);
|
|
}
|
|
} else {
|
|
if (!rawmode && j < bufsize) {
|
|
if (charbytes == sizeof(char)) {
|
|
((unsigned char *)buf)[j] = '\0';
|
|
} else if (charbytes == sizeof(char16_t)) {
|
|
((char16_t *)buf)[j] = u'\0';
|
|
} else if (charbytes == sizeof(wchar_t)) {
|
|
((wchar_t *)buf)[j] = L'\0';
|
|
}
|
|
}
|
|
break;
|
|
}
|
|
}
|
|
++items;
|
|
if (ismalloc) {
|
|
*va_arg(va, char **) = buf;
|
|
}
|
|
} else {
|
|
do {
|
|
if (isspace(c)) break;
|
|
} while ((c = callback(arg)) != -1);
|
|
}
|
|
break;
|
|
}
|
|
default:
|
|
NonDirectiveCharacter:
|
|
c = (c == p[i - 1]) ? callback(arg) : -1;
|
|
break;
|
|
}
|
|
}
|
|
Done:
|
|
while (freeme) {
|
|
struct FreeMe *entry = freeme;
|
|
freeme = entry->next;
|
|
if (items == -1) {
|
|
weaken(free_s)((void **)&entry->ptr);
|
|
}
|
|
weaken(free_s)((void **)&entry);
|
|
}
|
|
return items;
|
|
}
|