Implement tonumber

This commit is contained in:
Laria 2023-07-03 23:33:19 +02:00
parent 1650a8f8be
commit 97f5986781
7 changed files with 231 additions and 6 deletions

View file

@ -16,6 +16,7 @@ set(commonfiles
hashmap.c
io.c
messages.c
numparse.c
parser.c
position.c
resizable.c
@ -119,6 +120,7 @@ functionaltest("pairs")
functionaltest("symbols")
functionaltest("get-optional")
functionaltest("has-key")
functionaltest("tonumber")
install(TARGETS apfl DESTINATION lib)
install(TARGETS apfl-bin DESTINATION bin)

View file

@ -10,6 +10,7 @@
#include "bytecode.h"
#include "context.h"
#include "modules.h"
#include "parsing.h"
#include "scope.h"
#define TRY_FORMAT(ctx, x) \
@ -284,6 +285,75 @@ tostring(apfl_ctx ctx)
apfl_tostring(ctx, -1);
}
struct numparse_data {
struct apfl_string_view sv;
size_t off;
};
static enum read_result
numparse_read(void *opaque, unsigned char *b)
{
struct numparse_data *data = opaque;
if (data->off >= data->sv.len) {
return RR_EOF;
}
*b = data->sv.bytes[data->off];
data->off++;
return RR_OK;
}
static void
numparse_unread(void *opaque)
{
struct numparse_data *data = opaque;
assert(data->off > 0);
data->off--;
}
static void
tonumber(apfl_ctx ctx)
{
(void)ctx;
if (apfl_len(ctx, 0) != 2) {
apfl_raise_const_error(ctx, "tonumber needs exactly 2 arguments");
}
apfl_get_list_member_by_index(ctx, 0, 0);
unsigned base = (unsigned)apfl_get_number(ctx, -1);
apfl_get_list_member_by_index(ctx, 0, 1);
struct apfl_string_view sv = apfl_get_string(ctx, -1);
apfl_drop(ctx, 0);
if (base < 2 || base > 36) {
apfl_raise_const_error(ctx, "base must be between 2 and 36");
}
bool negative = false;
if (sv.len > 0 && sv.bytes[0] == '-') {
negative = true;
sv = apfl_string_view_offset(sv, 1);
}
apfl_number number = 0;
struct numparse_data data = {
.sv = sv,
.off = 0,
};
bool ok = apfl_parse_number(base, numparse_read, numparse_unread, &data, &number);
assert(ok);
if (data.off != sv.len) {
apfl_raise_const_error(ctx, "Can not parse as number");
}
if (negative) {
number *= -1;
}
apfl_push_number(ctx, number);
}
static void
not(apfl_ctx ctx)
{
@ -780,4 +850,5 @@ apfl_builtins(apfl_ctx ctx)
add_builtin(ctx, "getsym-Some", apfl_sym_some);
add_builtin(ctx, "get-argv", get_argv);
add_builtin(ctx, "cmod-searcher", cmod_searcher);
add_builtin(ctx, "tonumber", tonumber);
}

View file

@ -0,0 +1,14 @@
===== script =====
print (tonumber 2 "101010")
print (tonumber 10 "123.45")
print (tonumber 16 "2a")
print (tonumber 16 "2A")
print (tonumber 16 "-2A")
print (tonumber 36 "cool")
===== output =====
42
123.45
42
42
-42
591861

View file

@ -238,6 +238,15 @@
((load-file f))
}
tonumber := {
x ->
tonumber 10 x
10 x?(has type 'number) ->
x
base x ->
builtins.tonumber base (tostring x)
}
modules := ({
loaded-modules := [->]
searchers := []
@ -309,6 +318,7 @@
'dump -> dump
'disasm -> disasm
'tostring -> tostring
'tonumber -> tonumber
'not -> not
'len -> len
'type -> type

103
src/numparse.c Normal file
View file

@ -0,0 +1,103 @@
#include <assert.h>
#include "apfl.h"
#include "parsing.h"
static int
byte_to_digit(unsigned char b)
{
switch (b) {
case '0': return 0;
case '1': return 1;
case '2': return 2;
case '3': return 3;
case '4': return 4;
case '5': return 5;
case '6': return 6;
case '7': return 7;
case '8': return 8;
case '9': return 9;
case 'a': case 'A': return 10;
case 'b': case 'B': return 11;
case 'c': case 'C': return 12;
case 'd': case 'D': return 13;
case 'e': case 'E': return 14;
case 'f': case 'F': return 15;
case 'g': case 'G': return 16;
case 'h': case 'H': return 17;
case 'i': case 'I': return 18;
case 'j': case 'J': return 19;
case 'k': case 'K': return 20;
case 'l': case 'L': return 21;
case 'm': case 'M': return 22;
case 'n': case 'N': return 23;
case 'o': case 'O': return 24;
case 'p': case 'P': return 25;
case 'q': case 'Q': return 26;
case 'r': case 'R': return 27;
case 's': case 'S': return 28;
case 't': case 'T': return 29;
case 'u': case 'U': return 30;
case 'v': case 'V': return 31;
case 'w': case 'W': return 32;
case 'x': case 'X': return 33;
case 'y': case 'Y': return 34;
case 'z': case 'Z': return 35;
default:
return -1;
}
}
bool
apfl_parse_number(
unsigned base,
enum read_result (*read)(void *, unsigned char *),
void (*unread_last)(void *),
void *opaque,
apfl_number *restrict out
) {
assert(2 <= base && base <= 36);
*out = 0;
apfl_number divisor = 1;
bool seen_period = false;
unsigned char b;
for (;;) {
switch (read(opaque, &b)) {
case RR_OK:
break;
case RR_ERR:
return false;
case RR_EOF:
goto finalize;
}
if (b == '.') {
if (seen_period) {
unread_last(opaque);
goto finalize;
}
seen_period = true;
continue;
}
int digit = byte_to_digit(b);
if (digit < 0 || (unsigned)digit >= base) {
unread_last(opaque);
goto finalize;
}
*out *= base;
*out += digit;
if (seen_period) {
divisor *= base;
}
}
finalize:
*out /= divisor;
return true;
}

30
src/parsing.h Normal file
View file

@ -0,0 +1,30 @@
#ifndef APFL_PARSING_H
#define APFL_PARSING_H
#ifdef __cplusplus
extern "C" {
#endif
#include <stdbool.h>
#include "apfl.h"
enum read_result {
RR_OK,
RR_ERR,
RR_EOF,
};
bool apfl_parse_number(
unsigned base,
enum read_result (*read)(void *, unsigned char *),
void (*unread_last)(void *),
void *opaque,
apfl_number *restrict out
);
#ifdef __cplusplus
}
#endif
#endif

View file

@ -9,6 +9,7 @@
#include "apfl.h"
#include "alloc.h"
#include "parsing.h"
#define BUFSIZE 4096
typedef int buf_offset;
@ -93,12 +94,6 @@ apfl_tokenizer_get_error(apfl_tokenizer_ptr tokenizer)
return tokenizer->error;
}
enum read_result {
RR_OK,
RR_ERR,
RR_EOF,
};
static enum read_result
read_byte(apfl_tokenizer_ptr tokenizer, char *byte, bool need)
{