Skip to content

Support float to string #70

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Apr 18, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
111 changes: 69 additions & 42 deletions dist-c/microvium.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h> // Note: only uses snprintf from stdio.h

// See microvium.c for design notes.

Expand Down Expand Up @@ -591,6 +592,19 @@ typedef mvm_TeError TeError;
#define MVM_FLOAT_NEG_ZERO (-0.0)
#endif

// Note: the only format specifiers that Microvium uses are "%.15g" and "%d"
#ifndef MVM_SNPRINTF
#define MVM_SNPRINTF snprintf
#endif

#ifndef MVM_MALLOC
#define MVM_MALLOC malloc
#endif

#ifndef MVM_FREE
#define MVM_FREE free
#endif

/**
* mvm_Value
*
Expand Down Expand Up @@ -5637,6 +5651,54 @@ TeError mvm_releaseHandle(VM* vm, mvm_Handle* handle) {
return vm_newError(vm, MVM_E_INVALID_HANDLE);
}

#if MVM_SUPPORT_FLOAT
static Value vm_float64ToStr(VM* vm, Value value) {
CODE_COVERAGE(619); // Hit

VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm); // Because we allocate a new string

// I don't think this is 100% compliant, but it's probably fine for most use
// cases, and most importantly it's small.

double x = mvm_toFloat64(vm, value);

char buf[64];
char* p = buf;

// NaN should be represented as VM_VALUE_NAN not a float with NaN
VM_ASSERT(vm, !isnan(x));

if (isinf(x)) {
CODE_COVERAGE(621); // Hit
if (x < 0) {
CODE_COVERAGE(622); // Hit
*p++ = '-';
}
strcpy_s(p, sizeof buf - 1, "Infinity");
p += 8;
} else {
CODE_COVERAGE(657); // Hit
p += MVM_SNPRINTF(p, sizeof buf, "%.15g", x);
VM_ASSERT(vm, p < buf + sizeof buf);
}

return mvm_newString(vm, buf, p - buf);
}
#endif // MVM_SUPPORT_FLOAT

static Value vm_intToStr(VM* vm, int32_t i) {
CODE_COVERAGE(618); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);

char buf[32];
size_t size;

size = MVM_SNPRINTF(buf, sizeof buf, "%d", i);
VM_ASSERT(vm, size < sizeof buf);

return mvm_newString(vm, buf, size);
}

static Value vm_convertToString(VM* vm, Value value) {
CODE_COVERAGE(23); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);
Expand All @@ -5652,8 +5714,12 @@ static Value vm_convertToString(VM* vm, Value value) {
return vm_intToStr(vm, i);
}
case TC_REF_FLOAT64: {
CODE_COVERAGE_UNTESTED(248); // Not hit
constStr = "[Float]"; // Temporary until we support float to string
CODE_COVERAGE(248); // Hit
#if MVM_SUPPORT_FLOAT
return vm_float64ToStr(vm, value);
#else
constStr = "";
#endif
break;
}
case TC_REF_STRING: {
Expand Down Expand Up @@ -5731,7 +5797,7 @@ static Value vm_convertToString(VM* vm, Value value) {
break;
}
case TC_VAL_NAN: {
CODE_COVERAGE_UNTESTED(262); // Not hit
CODE_COVERAGE(262); // Hit
constStr = "NaN";
break;
}
Expand All @@ -5757,45 +5823,6 @@ static Value vm_convertToString(VM* vm, Value value) {
return vm_newStringFromCStrNT(vm, constStr);
}

static Value vm_intToStr(VM* vm, int32_t i) {
CODE_COVERAGE(618); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);
// TODO: Is this really logic we can't just assume in the C standard library?
// What if we made it a port entry? Maybe all uses of the standard library
// should be port entries anyway.

static const char strMinInt[] = "-2147483648";
char buf[12]; // Up to 11 digits plus a minus sign
char* cur = &buf[sizeof buf];
bool negative = false;
if (i < 0) {
CODE_COVERAGE(619); // Hit
// Special case for this value because `-i` overflows.
if (i == (int32_t)0x80000000) {
CODE_COVERAGE(621); // Hit
return vm_newStringFromCStrNT(vm, strMinInt);
} else {
CODE_COVERAGE(622); // Hit
}
negative = true;
i = -i;
}
else {
CODE_COVERAGE(620); // Hit
negative = false;
}
do {
*--cur = '0' + i % 10;
i /= 10;
} while (i);

if (negative) {
*--cur = '-';
}

return mvm_newString(vm, cur, &buf[sizeof buf] - cur);
}

static Value vm_concat(VM* vm, Value* left, Value* right) {
CODE_COVERAGE(553); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);
Expand Down
4 changes: 2 additions & 2 deletions dist-c/microvium_port_example.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ static uint16_t crc16(MVM_LONG_PTR_TYPE lp, uint16_t size) {
* The `context` passed to these macros is whatever value that the host passes
* to `mvm_restore`. It can be any value that fits in a pointer.
*/
#define MVM_CONTEXTUAL_MALLOC(size, context) malloc(size)
#define MVM_CONTEXTUAL_FREE(ptr, context) free(ptr)
#define MVM_CONTEXTUAL_MALLOC(size, context) MVM_MALLOC(size)
#define MVM_CONTEXTUAL_FREE(ptr, context) MVM_FREE(ptr)

/**
* If defined, this will enable the API methods `mvm_stopAfterNInstructions` and
Expand Down
2 changes: 1 addition & 1 deletion doc/supported-language.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ To date, only a (small) subset of the JavaScript language is supported in Microv
Note: the most up-to-date authority on supported features is the [set of test scripts](../test/end-to-end/tests), each file of which is a stand-alone Microvium script that exercises a series of features in the language.

- Basic control flow statements (`if`/`else`, `while`, `do..while`, `for`)
- Primitive operators (`+`, `++`, `-`, `--`, `/`, `%`, `*`, `**`, `&`, `|`, `>>`, `>>>`, `<<`, `^`, `===`, `!==`, `>`, `<`, `>=`, `<=`, `!`, `~`, `? :`, `typeof`)
- Primitive operators (`+`, `++`, `-`, `--`, `/`, `%`, `*`, `**`, `&`, `|`, `>>`, `>>>`, `<<`, `^`, `===`, `!==`, `>`, `<`, `>=`, `<=`, `!`, `~`, `? :`, `typeof`), with the exception that inequality operators (`>`, `<`, `>=`, `<=`) only work on numbers at the moment.
- Variable declarations: `var`, `let`, and `const`
- Nested functions (closures) and function/arrow expressions
- Dynamically-sized arrays and objects (with limitations, see the next section), computed properties (`o[p]`).
Expand Down
1 change: 1 addition & 0 deletions native-vm-vs-project/allocator.c
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ void* allocator_malloc(size_t size) {
}

void allocator_free(void* ptr) {
if (!ptr) return;
assert((intptr_t)ptr - (intptr_t)ALLOCATOR_START_ADDR < 0x10000);
uint16_t* p = (uint16_t*)ptr;
p--; // Go to header
Expand Down
2 changes: 1 addition & 1 deletion native-vm-vs-project/project.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ using namespace std;
using namespace filesystem;

// Set to the empty string "" if you want to run all tests
const string runOnlyTest = "closure-embedding";
const string runOnlyTest = "number-operations";
//const string runOnlyTest = "";

// Bytecode addresses to break on. To have no breakpoints, set to single value of { 0 }
Expand Down
98 changes: 56 additions & 42 deletions native-vm/microvium.c
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@

#include <ctype.h>
#include <stdlib.h>
#include <stdio.h> // Note: only uses snprintf from stdio.h

#include "microvium_internals.h"

Expand Down Expand Up @@ -4118,6 +4119,54 @@ TeError mvm_releaseHandle(VM* vm, mvm_Handle* handle) {
return vm_newError(vm, MVM_E_INVALID_HANDLE);
}

#if MVM_SUPPORT_FLOAT
static Value vm_float64ToStr(VM* vm, Value value) {
CODE_COVERAGE(619); // Hit

VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm); // Because we allocate a new string

// I don't think this is 100% compliant, but it's probably fine for most use
// cases, and most importantly it's small.

double x = mvm_toFloat64(vm, value);

char buf[64];
char* p = buf;

// NaN should be represented as VM_VALUE_NAN not a float with NaN
VM_ASSERT(vm, !isnan(x));

if (isinf(x)) {
CODE_COVERAGE(621); // Hit
if (x < 0) {
CODE_COVERAGE(622); // Hit
*p++ = '-';
}
strcpy_s(p, sizeof buf - 1, "Infinity");
p += 8;
} else {
CODE_COVERAGE(657); // Hit
p += MVM_SNPRINTF(p, sizeof buf, "%.15g", x);
VM_ASSERT(vm, p < buf + sizeof buf);
}

return mvm_newString(vm, buf, p - buf);
}
#endif // MVM_SUPPORT_FLOAT

static Value vm_intToStr(VM* vm, int32_t i) {
CODE_COVERAGE(618); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);

char buf[32];
size_t size;

size = MVM_SNPRINTF(buf, sizeof buf, "%d", i);
VM_ASSERT(vm, size < sizeof buf);

return mvm_newString(vm, buf, size);
}

static Value vm_convertToString(VM* vm, Value value) {
CODE_COVERAGE(23); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);
Expand All @@ -4133,8 +4182,12 @@ static Value vm_convertToString(VM* vm, Value value) {
return vm_intToStr(vm, i);
}
case TC_REF_FLOAT64: {
CODE_COVERAGE_UNTESTED(248); // Not hit
constStr = "[Float]"; // Temporary until we support float to string
CODE_COVERAGE(248); // Hit
#if MVM_SUPPORT_FLOAT
return vm_float64ToStr(vm, value);
#else
constStr = "";
#endif
break;
}
case TC_REF_STRING: {
Expand Down Expand Up @@ -4212,7 +4265,7 @@ static Value vm_convertToString(VM* vm, Value value) {
break;
}
case TC_VAL_NAN: {
CODE_COVERAGE_UNTESTED(262); // Not hit
CODE_COVERAGE(262); // Hit
constStr = "NaN";
break;
}
Expand All @@ -4238,45 +4291,6 @@ static Value vm_convertToString(VM* vm, Value value) {
return vm_newStringFromCStrNT(vm, constStr);
}

static Value vm_intToStr(VM* vm, int32_t i) {
CODE_COVERAGE(618); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);
// TODO: Is this really logic we can't just assume in the C standard library?
// What if we made it a port entry? Maybe all uses of the standard library
// should be port entries anyway.

static const char strMinInt[] = "-2147483648";
char buf[12]; // Up to 11 digits plus a minus sign
char* cur = &buf[sizeof buf];
bool negative = false;
if (i < 0) {
CODE_COVERAGE(619); // Hit
// Special case for this value because `-i` overflows.
if (i == (int32_t)0x80000000) {
CODE_COVERAGE(621); // Hit
return vm_newStringFromCStrNT(vm, strMinInt);
} else {
CODE_COVERAGE(622); // Hit
}
negative = true;
i = -i;
}
else {
CODE_COVERAGE(620); // Hit
negative = false;
}
do {
*--cur = '0' + i % 10;
i /= 10;
} while (i);

if (negative) {
*--cur = '-';
}

return mvm_newString(vm, cur, &buf[sizeof buf] - cur);
}

static Value vm_concat(VM* vm, Value* left, Value* right) {
CODE_COVERAGE(553); // Hit
VM_ASSERT_NOT_USING_CACHED_REGISTERS(vm);
Expand Down
13 changes: 13 additions & 0 deletions native-vm/microvium_internals.h
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,19 @@ typedef mvm_TeError TeError;
#define MVM_FLOAT_NEG_ZERO (-0.0)
#endif

// Note: the only format specifiers that Microvium uses are "%.15g" and "%d"
#ifndef MVM_SNPRINTF
#define MVM_SNPRINTF snprintf
#endif

#ifndef MVM_MALLOC
#define MVM_MALLOC malloc
#endif

#ifndef MVM_FREE
#define MVM_FREE free
#endif

/**
* mvm_Value
*
Expand Down
4 changes: 2 additions & 2 deletions native-vm/microvium_port_example.h
Original file line number Diff line number Diff line change
Expand Up @@ -287,8 +287,8 @@ static uint16_t crc16(MVM_LONG_PTR_TYPE lp, uint16_t size) {
* The `context` passed to these macros is whatever value that the host passes
* to `mvm_restore`. It can be any value that fits in a pointer.
*/
#define MVM_CONTEXTUAL_MALLOC(size, context) malloc(size)
#define MVM_CONTEXTUAL_FREE(ptr, context) free(ptr)
#define MVM_CONTEXTUAL_MALLOC(size, context) MVM_MALLOC(size)
#define MVM_CONTEXTUAL_FREE(ptr, context) MVM_FREE(ptr)

/**
* If defined, this will enable the API methods `mvm_stopAfterNInstructions` and
Expand Down
2 changes: 1 addition & 1 deletion size-test/output/size.txt
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
text data bss dec hex filename
9872 0 0 9872 2690 output/microvium.o
9934 0 0 9934 26ce output/microvium.o
2 changes: 1 addition & 1 deletion test/end-to-end/artifacts/code-coverage-summary.txt
Original file line number Diff line number Diff line change
@@ -1 +1 @@
microvium.c code coverage: 561 of 776 (72.3%)
microvium.c code coverage: 563 of 776 (72.6%)
2 changes: 1 addition & 1 deletion test/end-to-end/artifacts/number-operations/0.meta.yaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
description: >
Tests various operations that should classify as vm_TeNumberOp operations
runExportedFunction: 0
assertionCount: 138
assertionCount: 153
Loading