Fixes #97: SQLite is now async, optimized, tests

This commit is contained in:
Zef Hemel
2022-10-21 10:00:43 +02:00
parent b9da9b7965
commit c1a78e0105
65 changed files with 329 additions and 142 deletions
@@ -0,0 +1,3 @@
b9365f4aa1d3047a8d80d6bfe90e705c80e158c3 sqlite_dl.zip
022ae45d50b124b9df68be065d65b9a607923362 wasi_dl_linux.tar.gz
bc76d264214c21a603fc38adb405622a2fcda8b3 wasi_dl_darwin.tar.gz
+131
View File
@@ -0,0 +1,131 @@
DENO ?= deno
WASI ?= ./wasi-sdk
CC = $(WASI)/bin/clang
OUT_WA = "sqlite.wasm"
OUT_BN = "sqlite.js"
OUT_TY = "sqlite.d.ts"
SQLITE_DLD = "https://sqlite.org/2022/sqlite-src-3390200.zip"
SQLITE_DIR = "sqlite-src-3390200"
WASI_DLD = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-linux.tar.gz"
WASI_TAR = wasi_dl_linux.tar.gz
ifeq ($(shell uname), Darwin)
WASI_DLD = "https://github.com/WebAssembly/wasi-sdk/releases/download/wasi-sdk-16/wasi-sdk-16.0-macos.tar.gz"
WASI_TAR = wasi_dl_darwin.tar.gz
endif
CSRC = ""
CSRC += $(shell find ./src -name "*.c")
CSRC += $(shell find ./lib -name "*.c")
CSRC += $(shell find ./hask -name "*.c")
FLGS = -Wall
RFLG = -Os
DFLG = -DDEBUG_BUILD
WAFLG = --target=wasm32-unknown-wasi -Wl,--no-entry -nostartfiles --sysroot $(WASI)/share/wasi-sysroot\
-DWASI_BUILD -Wl,--export,malloc -Wl,--export,free -Wl,--allow-undefined-file=vfs.syms
INCS = -Ilib
# Location of wrapper library which contains all c-land export
CWRP = "./src/wrapper.c"
# Configure sqlite for out use-case
SQLFLG = -DSQLITE_DQS=0 -DSQLITE_THREADSAFE=0 -DSQLITE_LIKE_DOESNT_MATCH_BLOBS\
-DSQLITE_DEFAULT_FOREIGN_KEYS=1 -DSQLITE_TEMP_STORE=2\
-DSQLITE_OMIT_DEPRECATED -DSQLITE_OMIT_UTF16 -DSQLITE_OMIT_SHARED_CACHE\
-DSQLITE_OMIT_LOAD_EXTENSION -DSQLITE_OMIT_PROGRESS_CALLBACK -DSQLITE_OMIT_TRACE\
-DSQLITE_OS_OTHER=1 -DSQLITE_OMIT_COMPLETE -DSQLITE_OMIT_WAL\
-DNDEBUG=1 -DSQLITE_ENABLE_COLUMN_METADATA -DHAVE_LOCALTIME_R\
-DSQLITE_OMIT_DESERIALIZE -DSQLITE_ENABLE_FTS5
# Rational:
# SQLITE_DQS -> we do not need to have backwards comp
# SQLITE_THREADSAFE -> we run single-threaded
# SQLITE_LIKE_DOESNT_MATCH_BLOBS -> faster (is recommended if no backwards comp)
# SQLITE_DEFAULT_FOREIGN_KEYS -> this should be the default
# SQLITE_TEMP_STORE -> in memory is faster, so it's the better default
# SQLITE_OMIT_DEPRECATED -> we do not need to have backwards comp
# SQLITE_OMIT_UTF16 -> we only support utf-8 encoded strings
# SQLITE_OMIT_SHARED_CACHE -> we only ever open one connection
# SQLITE_OMIT_LOAD_EXTENSION -> we don't use it
# SQLITE_OMIT_PROGRESS_CALLBACK -> we don't use it
# SQLITE_OMIT_TRACE -> we make no use of these
# SQLITE_OS_OTHER -> we provide our own vfs
# SQLITE_OMIT_COMPLETE -> we don't need these
# SQLITE_OMIT_WAL -> this is doggy, as we can not memory map files
# DNDEBUG -> "use for maximum speed"
# SQLITE_ENABLE_COLUMN_METADATA -> we depend on column metadata interfaces (`sqlite3_column_table_name` and `sqlite3_column_origin_name`)
# SQLITE_OMIT_DESERIALIZE -> we don't use these interfaces
all: release
build:
$(DENO) run --allow-read --allow-write hack/gen_syms.js vfs.syms
$(CC) $(WAFLG) $(FLGS) $(INCS) $(CSRC) $(SQLFLG) -o $(OUT_WA)
bundle:
$(DENO) run --allow-read --allow-write hack/bundle.js $(OUT_WA) $(OUT_BN)
$(DENO) fmt $(OUT_BN)
types:
$(DENO) run --allow-read --allow-write hack/gen_types.ts $(CWRP) $(OUT_TY)
$(DENO) fmt $(OUT_TY)
size.txt: sqlite.js sqlite.wasm
rm -f size.txt *.gz *.br
gzip --best < sqlite.js > sqlite.js.gz
gzip --best < sqlite.wasm > sqlite.wasm.gz
brotli --best -o sqlite.js.br < sqlite.js && \
brotli --best -o sqlite.wasm.br < sqlite.wasm || \
echo "WARN: brotli size comparison unavailable"
ls -l sqlite.* | awk '{printf "%-15s➜%7s bytes\n",$$9,$$5}' | tee size.txt
debug: FLGS += $(DFLG)
debug: build
debug: bundle
debug: types
release: FLGS += $(RFLG)
release: build
release: bundle
release: types
release: size.txt
amalgamation:
make -C sqlite-src clean
make -C sqlite-src sqlite3.c SQLFLG="$(SQLFLG)"
mv sqlite-src/sqlite3.c lib/sqlite3.c
mv sqlite-src/sqlite3.h lib/sqlite3.h
dlsqlite:
curl "$(SQLITE_DLD)" -o "sqlite_dl.zip"
sed -n '/sqlite_dl.zip/p' ".checksums" | shasum -c -
rm -rf "sqlite-src"
unzip "sqlite_dl.zip"
mv $(SQLITE_DIR) "sqlite-src"
cp "Makefile.sqlite" "sqlite-src/Makefile"
rm "sqlite_dl.zip"
dlwasi:
curl -L "$(WASI_DLD)" -o "$(WASI_TAR)"
sed -n '/${WASI_TAR}/p' ".checksums" | shasum -c -
rm -rf "wasi-sdk"
tar -xzvf "$(WASI_TAR)"
mv "wasi-sdk-16.0" "wasi-sdk"
rm "${WASI_TAR}"
testdb:
gcc -Ilib lib/sqlite3.c hack/gen_test_db.c -DSQLITE_THREADSAFE=0 -DSQLITE_OMIT_LOAD_EXTENSION -o gen_test_db
rm -f 2GB_test.db
./gen_test_db
rm gen_test_db
setup: dlsqlite
setup: dlwasi
clean:
rm -rf sqlite-src
rm -rf wasi-sdk
rm -f 2GB_test.db
.PHONY: build amalgamation dlsqlite dlwasi setup clean
@@ -0,0 +1,62 @@
const [src, dest] = Deno.args;
const wasm = await Deno.readFile(src);
function encode(bytes) {
let binary = "";
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i]);
}
return btoa(binary).replace(/\n/g, "");
}
await Deno.writeFile(
dest,
new TextEncoder().encode(
`/// <reference types="./sqlite.d.ts" />
/* This file is automatically generated. Do not edit directly. */
import env from "./vfs.js";
const wasm =
"${encode(wasm)}";
function decode(base64) {
const bytesStr = atob(base64);
const bytes = new Uint8Array(bytesStr.length);
for (let i = 0, c = bytesStr.length; i < c; i++) {
bytes[i] = bytesStr.charCodeAt(i);
}
return bytes;
}
const moduleOrInstance = {
module: null,
instances: [],
};
export async function compile() {
moduleOrInstance.module = await WebAssembly.compile(decode(wasm));
}
export async function instantiateBrowser() {
const placeholder = { exports: null };
const instance = await WebAssembly.instantiate(moduleOrInstance.module, env(placeholder));
placeholder.exports = instance.exports;
instance.exports.seed_rng(Date.now());
moduleOrInstance.instances.push(instance);
}
export function instantiate() {
if (moduleOrInstance.instances.length) {
return moduleOrInstance.instances.pop();
} else {
const placeholder = { exports: null };
const instance = new WebAssembly.Instance(moduleOrInstance.module, env(placeholder));
placeholder.exports = instance.exports;
instance.exports.seed_rng(Date.now());
return instance;
}
}`,
),
);
@@ -0,0 +1,10 @@
// Generate available import symbols
// from vfs.js file
import env from "../vfs.js";
let symbols = "";
for (const symbol of Object.keys(env().env)) {
symbols += `${symbol}\n`;
}
await Deno.writeFile(Deno.args[0], new TextEncoder().encode(symbols));
@@ -0,0 +1,98 @@
#include <stdio.h>
#include <stdlib.h>
#include <sqlite3.h>
#define TRUE 1
#define FALSE 0
#define TEST_DB_FILE "2GB_test.db"
#define SQL_CREATE_TBL "CREATE TABLE test (id INTEGER PRIMARY KEY, value TEXT)"
#define SQL_INSERT_VAL "INSERT INTO test (value) VALUES (?)"
#define VAL_LEN 65536
#define VAL_NUM 45000
void rand_str(char *dest, size_t length) {
char charset[] = "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
while (length --> 0) {
size_t index = (double) rand() / RAND_MAX * (sizeof charset - 1);
*dest++ = charset[index];
}
*dest = '\0';
}
int execute_query(sqlite3* db, char* query) {
sqlite3_stmt* stmt_create;
if (sqlite3_prepare_v2(db, query, -1, &stmt_create, NULL) != SQLITE_OK) {
printf("Failed to prepare query statement: %s\n", sqlite3_errmsg(db));
return FALSE;
}
if (sqlite3_step(stmt_create) != SQLITE_DONE) {
printf("Failed to run query statement: %s\n", sqlite3_errmsg(db));
return FALSE;
}
sqlite3_finalize(stmt_create);
return TRUE;
}
int main(int argc, char* argv[]) {
sqlite3* db;
if (sqlite3_open(TEST_DB_FILE, &db) != SQLITE_OK) {
printf("Failed to open database: %s\n", sqlite3_errmsg(db));
return 1;
}
// create database table
sqlite3_stmt* stmt_create;
if (sqlite3_prepare_v2(db, SQL_CREATE_TBL, -1, &stmt_create, NULL) != SQLITE_OK) {
printf("Failed to prepare create table statement: %s\n", sqlite3_errmsg(db));
return 1;
}
if (sqlite3_step(stmt_create) != SQLITE_DONE) {
printf("Failed to run create table statement: %s\n", sqlite3_errmsg(db));
return 1;
}
sqlite3_finalize(stmt_create);
// insert values to reach 2GB
sqlite3_stmt* stmt_insert;
if (sqlite3_prepare_v2(db, SQL_INSERT_VAL, -1, &stmt_insert, NULL) != SQLITE_OK) {
printf("Failed to prepare insert table statement: %s\n", sqlite3_errmsg(db));
return 1;
}
// begin transaction
if (!execute_query(db, "begin")) {
return 1;
}
char* buffer = malloc(VAL_LEN + 1);
for (int64_t i = 0; i < VAL_NUM; i++) {
rand_str(buffer, VAL_LEN);
if (sqlite3_bind_text(stmt_insert, 1, buffer, VAL_LEN, NULL) != SQLITE_OK) {
printf("Failed to bind value `%s`: %s\n", buffer, sqlite3_errmsg(db));
return 1;
}
if (sqlite3_step(stmt_insert) != SQLITE_DONE) {
printf("Failed to run insert statement: %s\n", sqlite3_errmsg(db));
return 1;
}
if (sqlite3_reset(stmt_insert) != SQLITE_OK) {
printf("Failed to reset statement: %s\n", sqlite3_errmsg(db));
return 1;
}
}
// end transaction
if (!execute_query(db, "commit")) {
return 1;
}
sqlite3_finalize(stmt_insert);
sqlite3_close(db);
printf("Database created successfully.\n");
return 0;
}
@@ -0,0 +1,156 @@
interface Item {
name: string;
arguments: Argument[];
returnType: Type;
}
interface Argument {
name: string;
type: Type;
}
enum Type {
Void,
VoidPtr,
StringPtr,
StatementPtr,
Double,
Int,
}
const items = [
// exported manually in compiler invocation
{
name: "malloc",
arguments: [{ name: "size", type: Type.Int }],
returnType: Type.VoidPtr,
},
{
name: "free",
arguments: [{ name: "ptr", type: Type.VoidPtr }],
returnType: Type.Void,
},
];
const [src, dest] = Deno.args;
const wrapperSrc = await Deno.readTextFile(src);
// int EXPORT(bind_int) (sqlite3_stmt* stmt, int idx, double value)
const typeRegexp =
`(const +)?(sqlite3_stmt\\*|char\\*|void\\*|int|uint32_t|double|void)`;
const argRegexp = `${typeRegexp} +[a-z_]+`;
const exportSignature = new RegExp(
`${typeRegexp} +EXPORT\\([a-z_]+\\) +\\(((${argRegexp}( *, *${argRegexp})*)|)\\)`,
);
function nullThrows<T>(value: T | null | undefined): T {
if (value == null) {
throw new Error("Got a null value");
}
return value as T;
}
function typeFromCType(cType: string): Type {
cType = cType.replace("const", "").replace(/ /g, "");
switch (cType) {
case "void":
return Type.Void;
case "void*":
return Type.VoidPtr;
case "char*":
return Type.StringPtr;
case "sqlite3_stmt*":
return Type.StatementPtr;
case "double":
return Type.Double;
case "int":
case "uint32_t":
return Type.Int;
default:
throw new Error("Unknown type");
}
}
function getReturnType(line: string): Type {
const regexp = new RegExp(typeRegexp);
const [, _const, cType] = nullThrows(regexp.exec(line));
return typeFromCType(cType);
}
function getName(line: string): string {
const [, name] = nullThrows(/EXPORT\(([a-z_]+)\)/.exec(line));
return name;
}
function getArguments(line: string): Argument[] {
const [, argList] = nullThrows(/EXPORT\([a-z_]+\) *\(([^)]*)\)/.exec(line));
if (argList.length === 0) {
return [];
} else {
return argList.split(",").map((arg) => {
const regexp = new RegExp(`${typeRegexp} +([a-z_]+)`);
const [, _const, cType, name] = nullThrows(regexp.exec(arg));
return {
name,
type: typeFromCType(cType),
};
});
}
}
function generateType(tp: Type): string {
switch (tp) {
case Type.Void:
return "void";
case Type.VoidPtr:
return "VoidPtr";
case Type.StringPtr:
return "StringPtr";
case Type.StatementPtr:
return "StatementPtr";
case Type.Int:
case Type.Double:
return "number";
default:
throw new Error("Unknown type");
}
}
function generateDecl(item: Item): string {
const args = item.arguments.map((arg) =>
`${arg.name}: ${generateType(arg.type)}`
).join(", ");
return `${item.name}: (${args}) => ${generateType(item.returnType)}`;
}
const exportLines = wrapperSrc.split("\n").filter((line) =>
exportSignature.test(line)
);
for (const line of exportLines) {
const name = getName(line);
const returnType = getReturnType(line);
const args = getArguments(line);
items.push({ name, returnType, arguments: args });
}
const typeDeclaration =
`/* This file is automatically generated. Do not edit directly. */
export type VoidPtr = number;
export type StringPtr = number;
export type StatementPtr = number;
export interface Wasm {
memory: WebAssembly.Memory;
${items.map(generateDecl).join(";\n ")};
}
export function compile(): Promise<void>;
export function instantiateBrowser(): Promise<void>;
export function instantiate(): { exports: Wasm };
`;
await Deno.writeTextFile(dest, typeDeclaration);
+34
View File
@@ -0,0 +1,34 @@
#include "pcg.h"
// Random number generator.
// Based on:
// *Really* minimal PCG32 code / (c) 2014 M.E. O'Neill / pcg-random.org
// Licensed under Apache License 2.0 (NO WARRANTY, etc. see website)
uint64_t state = 0x853c49e6748fea9bULL;
uint64_t inc = 0xda3e39cb94b95bdbULL;
// Update seed of generator.
void pcg_seed(uint64_t seed) {
state = seed;
}
// Generate random integer.
uint32_t pcg_rand() {
uint64_t oldstate = state;
// Advance internal state
state = oldstate * 6364136223846793005ULL + (inc|1);
// Calculate output function (XSH RR), uses old state for max ILP
uint32_t xorshifted = ((oldstate >> 18u) ^ oldstate) >> 27u;
uint32_t rot = oldstate >> 59u;
return (xorshifted >> rot) | (xorshifted << ((-rot) & 31));
}
// Fill out buffer with size random bytes.
void pcg_bytes(char* out, int size) {
// TODO: We can be more efficient by using all 4
// pieces of the random number returned.
for (int i = 0; i < size; i ++) {
out[i] = (char)pcg_rand();
}
}
+13
View File
@@ -0,0 +1,13 @@
#ifndef PCG_H
#define PCG_H
#include <stdint.h>
// Seed the random number generator
void pcg_seed(uint64_t seed);
// Get random numbers and random bits
uint32_t pcg_rand();
void pcg_bytes(char* out, int size);
#endif // PCG_H
+914
View File
@@ -0,0 +1,914 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and (v)snprintf implementation, optimized for speed on
// embedded systems with a very limited resources. These routines are thread
// safe and reentrant!
// Use this instead of the bloated standard/newlib printf cause these use
// malloc for printf (and may not be thread safe).
//
///////////////////////////////////////////////////////////////////////////////
#include <stdbool.h>
#include <stdint.h>
#include "printf.h"
// define this globally (e.g. gcc -DPRINTF_INCLUDE_CONFIG_H ...) to include the
// printf_config.h header file
// default: undefined
#ifdef PRINTF_INCLUDE_CONFIG_H
#include "printf_config.h"
#endif
// 'ntoa' conversion buffer size, this must be big enough to hold one converted
// numeric number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_NTOA_BUFFER_SIZE
#define PRINTF_NTOA_BUFFER_SIZE 32U
#endif
// 'ftoa' conversion buffer size, this must be big enough to hold one converted
// float number including padded zeros (dynamically created on stack)
// default: 32 byte
#ifndef PRINTF_FTOA_BUFFER_SIZE
#define PRINTF_FTOA_BUFFER_SIZE 32U
#endif
// support for the floating point type (%f)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_FLOAT
#define PRINTF_SUPPORT_FLOAT
#endif
// support for exponential floating point notation (%e/%g)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_EXPONENTIAL
#define PRINTF_SUPPORT_EXPONENTIAL
#endif
// define the default floating point precision
// default: 6 digits
#ifndef PRINTF_DEFAULT_FLOAT_PRECISION
#define PRINTF_DEFAULT_FLOAT_PRECISION 6U
#endif
// define the largest float suitable to print with %f
// default: 1e9
#ifndef PRINTF_MAX_FLOAT
#define PRINTF_MAX_FLOAT 1e9
#endif
// support for the long long types (%llu or %p)
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_LONG_LONG
#define PRINTF_SUPPORT_LONG_LONG
#endif
// support for the ptrdiff_t type (%t)
// ptrdiff_t is normally defined in <stddef.h> as long or long long type
// default: activated
#ifndef PRINTF_DISABLE_SUPPORT_PTRDIFF_T
#define PRINTF_SUPPORT_PTRDIFF_T
#endif
///////////////////////////////////////////////////////////////////////////////
// internal flag definitions
#define FLAGS_ZEROPAD (1U << 0U)
#define FLAGS_LEFT (1U << 1U)
#define FLAGS_PLUS (1U << 2U)
#define FLAGS_SPACE (1U << 3U)
#define FLAGS_HASH (1U << 4U)
#define FLAGS_UPPERCASE (1U << 5U)
#define FLAGS_CHAR (1U << 6U)
#define FLAGS_SHORT (1U << 7U)
#define FLAGS_LONG (1U << 8U)
#define FLAGS_LONG_LONG (1U << 9U)
#define FLAGS_PRECISION (1U << 10U)
#define FLAGS_ADAPT_EXP (1U << 11U)
// import float.h for DBL_MAX
#if defined(PRINTF_SUPPORT_FLOAT)
#include <float.h>
#endif
// output function type
typedef void (*out_fct_type)(char character, void* buffer, size_t idx, size_t maxlen);
// wrapper (used as buffer) for output function type
typedef struct {
void (*fct)(char character, void* arg);
void* arg;
} out_fct_wrap_type;
// internal buffer output
static inline void _out_buffer(char character, void* buffer, size_t idx, size_t maxlen)
{
if (idx < maxlen) {
((char*)buffer)[idx] = character;
}
}
// internal null output
static inline void _out_null(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)character; (void)buffer; (void)idx; (void)maxlen;
}
// internal _putchar wrapper
static inline void _out_char(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)buffer; (void)idx; (void)maxlen;
if (character) {
_putchar(character);
}
}
// internal output function wrapper
static inline void _out_fct(char character, void* buffer, size_t idx, size_t maxlen)
{
(void)idx; (void)maxlen;
if (character) {
// buffer is the output fct pointer
((out_fct_wrap_type*)buffer)->fct(character, ((out_fct_wrap_type*)buffer)->arg);
}
}
// internal secure strlen
// \return The length of the string (excluding the terminating 0) limited by 'maxsize'
static inline unsigned int _strnlen_s(const char* str, size_t maxsize)
{
const char* s;
for (s = str; *s && maxsize--; ++s);
return (unsigned int)(s - str);
}
// internal test if char is a digit (0-9)
// \return true if char is a digit
static inline bool _is_digit(char ch)
{
return (ch >= '0') && (ch <= '9');
}
// internal ASCII string to unsigned int conversion
static unsigned int _atoi(const char** str)
{
unsigned int i = 0U;
while (_is_digit(**str)) {
i = i * 10U + (unsigned int)(*((*str)++) - '0');
}
return i;
}
// output the specified string in reverse, taking care of any zero-padding
static size_t _out_rev(out_fct_type out, char* buffer, size_t idx, size_t maxlen, const char* buf, size_t len, unsigned int width, unsigned int flags)
{
const size_t start_idx = idx;
// pad spaces up to given width
if (!(flags & FLAGS_LEFT) && !(flags & FLAGS_ZEROPAD)) {
for (size_t i = len; i < width; i++) {
out(' ', buffer, idx++, maxlen);
}
}
// reverse string
while (len) {
out(buf[--len], buffer, idx++, maxlen);
}
// append pad spaces up to given width
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width) {
out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
// internal itoa format
static size_t _ntoa_format(out_fct_type out, char* buffer, size_t idx, size_t maxlen, char* buf, size_t len, bool negative, unsigned int base, unsigned int prec, unsigned int width, unsigned int flags)
{
// pad leading zeros
if (!(flags & FLAGS_LEFT)) {
if (width && (flags & FLAGS_ZEROPAD) && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < prec) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
while ((flags & FLAGS_ZEROPAD) && (len < width) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
// handle hash
if (flags & FLAGS_HASH) {
if (!(flags & FLAGS_PRECISION) && len && ((len == prec) || (len == width))) {
len--;
if (len && (base == 16U)) {
len--;
}
}
if ((base == 16U) && !(flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'x';
}
else if ((base == 16U) && (flags & FLAGS_UPPERCASE) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'X';
}
else if ((base == 2U) && (len < PRINTF_NTOA_BUFFER_SIZE)) {
buf[len++] = 'b';
}
if (len < PRINTF_NTOA_BUFFER_SIZE) {
buf[len++] = '0';
}
}
if (len < PRINTF_NTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}
// internal itoa for 'long' type
static size_t _ntoa_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long value, bool negative, unsigned long base, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) {
flags &= ~FLAGS_HASH;
}
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
}
// internal itoa for 'long long' type
#if defined(PRINTF_SUPPORT_LONG_LONG)
static size_t _ntoa_long_long(out_fct_type out, char* buffer, size_t idx, size_t maxlen, unsigned long long value, bool negative, unsigned long long base, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_NTOA_BUFFER_SIZE];
size_t len = 0U;
// no hash for 0 values
if (!value) {
flags &= ~FLAGS_HASH;
}
// write if precision != 0 and value is != 0
if (!(flags & FLAGS_PRECISION) || value) {
do {
const char digit = (char)(value % base);
buf[len++] = digit < 10 ? '0' + digit : (flags & FLAGS_UPPERCASE ? 'A' : 'a') + digit - 10;
value /= base;
} while (value && (len < PRINTF_NTOA_BUFFER_SIZE));
}
return _ntoa_format(out, buffer, idx, maxlen, buf, len, negative, (unsigned int)base, prec, width, flags);
}
#endif // PRINTF_SUPPORT_LONG_LONG
#if defined(PRINTF_SUPPORT_FLOAT)
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
// forward declaration so that _ftoa can switch to exp notation for values > PRINTF_MAX_FLOAT
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags);
#endif
// internal ftoa for fixed decimal floating point
static size_t _ftoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
{
char buf[PRINTF_FTOA_BUFFER_SIZE];
size_t len = 0U;
double diff = 0.0;
// powers of 10
static const double pow10[] = { 1, 10, 100, 1000, 10000, 100000, 1000000, 10000000, 100000000, 1000000000 };
// test for special values
if (value != value)
return _out_rev(out, buffer, idx, maxlen, "nan", 3, width, flags);
if (value < -DBL_MAX)
return _out_rev(out, buffer, idx, maxlen, "fni-", 4, width, flags);
if (value > DBL_MAX)
return _out_rev(out, buffer, idx, maxlen, (flags & FLAGS_PLUS) ? "fni+" : "fni", (flags & FLAGS_PLUS) ? 4U : 3U, width, flags);
// test for very large values
// standard printf behavior is to print EVERY whole number digit -- which could be 100s of characters overflowing your buffers == bad
if ((value > PRINTF_MAX_FLOAT) || (value < -PRINTF_MAX_FLOAT)) {
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
return _etoa(out, buffer, idx, maxlen, value, prec, width, flags);
#else
return 0U;
#endif
}
// test for negative
bool negative = false;
if (value < 0) {
negative = true;
value = 0 - value;
}
// set default precision, if not set explicitly
if (!(flags & FLAGS_PRECISION)) {
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
}
// limit precision to 9, cause a prec >= 10 can lead to overflow errors
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (prec > 9U)) {
buf[len++] = '0';
prec--;
}
int whole = (int)value;
double tmp = (value - whole) * pow10[prec];
unsigned long frac = (unsigned long)tmp;
diff = tmp - frac;
if (diff > 0.5) {
++frac;
// handle rollover, e.g. case 0.99 with prec 1 is 1.0
if (frac >= pow10[prec]) {
frac = 0;
++whole;
}
}
else if (diff < 0.5) {
}
else if ((frac == 0U) || (frac & 1U)) {
// if halfway, round up if odd OR if last digit is 0
++frac;
}
if (prec == 0U) {
diff = value - (double)whole;
if ((!(diff < 0.5) || (diff > 0.5)) && (whole & 1)) {
// exactly 0.5 and ODD, then round up
// 1.5 -> 2, but 2.5 -> 2
++whole;
}
}
else {
unsigned int count = prec;
// now do fractional part, as an unsigned number
while (len < PRINTF_FTOA_BUFFER_SIZE) {
--count;
buf[len++] = (char)(48U + (frac % 10U));
if (!(frac /= 10U)) {
break;
}
}
// add extra 0s
while ((len < PRINTF_FTOA_BUFFER_SIZE) && (count-- > 0U)) {
buf[len++] = '0';
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
// add decimal
buf[len++] = '.';
}
}
// do whole part, number is reversed
while (len < PRINTF_FTOA_BUFFER_SIZE) {
buf[len++] = (char)(48 + (whole % 10));
if (!(whole /= 10)) {
break;
}
}
// pad leading zeros
if (!(flags & FLAGS_LEFT) && (flags & FLAGS_ZEROPAD)) {
if (width && (negative || (flags & (FLAGS_PLUS | FLAGS_SPACE)))) {
width--;
}
while ((len < width) && (len < PRINTF_FTOA_BUFFER_SIZE)) {
buf[len++] = '0';
}
}
if (len < PRINTF_FTOA_BUFFER_SIZE) {
if (negative) {
buf[len++] = '-';
}
else if (flags & FLAGS_PLUS) {
buf[len++] = '+'; // ignore the space if the '+' exists
}
else if (flags & FLAGS_SPACE) {
buf[len++] = ' ';
}
}
return _out_rev(out, buffer, idx, maxlen, buf, len, width, flags);
}
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
// internal ftoa variant for exponential floating-point type, contributed by Martijn Jasperse <m.jasperse@gmail.com>
static size_t _etoa(out_fct_type out, char* buffer, size_t idx, size_t maxlen, double value, unsigned int prec, unsigned int width, unsigned int flags)
{
// check for NaN and special values
if ((value != value) || (value > DBL_MAX) || (value < -DBL_MAX)) {
return _ftoa(out, buffer, idx, maxlen, value, prec, width, flags);
}
// determine the sign
const bool negative = value < 0;
if (negative) {
value = -value;
}
// default precision
if (!(flags & FLAGS_PRECISION)) {
prec = PRINTF_DEFAULT_FLOAT_PRECISION;
}
// determine the decimal exponent
// based on the algorithm by David Gay (https://www.ampl.com/netlib/fp/dtoa.c)
union {
uint64_t U;
double F;
} conv;
conv.F = value;
int exp2 = (int)((conv.U >> 52U) & 0x07FFU) - 1023; // effectively log2
conv.U = (conv.U & ((1ULL << 52U) - 1U)) | (1023ULL << 52U); // drop the exponent so conv.F is now in [1,2)
// now approximate log10 from the log2 integer part and an expansion of ln around 1.5
int expval = (int)(0.1760912590558 + exp2 * 0.301029995663981 + (conv.F - 1.5) * 0.289529654602168);
// now we want to compute 10^expval but we want to be sure it won't overflow
exp2 = (int)(expval * 3.321928094887362 + 0.5);
const double z = expval * 2.302585092994046 - exp2 * 0.6931471805599453;
const double z2 = z * z;
conv.U = (uint64_t)(exp2 + 1023) << 52U;
// compute exp(z) using continued fractions, see https://en.wikipedia.org/wiki/Exponential_function#Continued_fractions_for_ex
conv.F *= 1 + 2 * z / (2 - z + (z2 / (6 + (z2 / (10 + z2 / 14)))));
// correct for rounding errors
if (value < conv.F) {
expval--;
conv.F /= 10;
}
// the exponent format is "%+03d" and largest value is "307", so set aside 4-5 characters
unsigned int minwidth = ((expval < 100) && (expval > -100)) ? 4U : 5U;
// in "%g" mode, "prec" is the number of *significant figures* not decimals
if (flags & FLAGS_ADAPT_EXP) {
// do we want to fall-back to "%f" mode?
if ((value >= 1e-4) && (value < 1e6)) {
if ((int)prec > expval) {
prec = (unsigned)((int)prec - expval - 1);
}
else {
prec = 0;
}
flags |= FLAGS_PRECISION; // make sure _ftoa respects precision
// no characters in exponent
minwidth = 0U;
expval = 0;
}
else {
// we use one sigfig for the whole part
if ((prec > 0) && (flags & FLAGS_PRECISION)) {
--prec;
}
}
}
// will everything fit?
unsigned int fwidth = width;
if (width > minwidth) {
// we didn't fall-back so subtract the characters required for the exponent
fwidth -= minwidth;
} else {
// not enough characters, so go back to default sizing
fwidth = 0U;
}
if ((flags & FLAGS_LEFT) && minwidth) {
// if we're padding on the right, DON'T pad the floating part
fwidth = 0U;
}
// rescale the float value
if (expval) {
value /= conv.F;
}
// output the floating part
const size_t start_idx = idx;
idx = _ftoa(out, buffer, idx, maxlen, negative ? -value : value, prec, fwidth, flags & ~FLAGS_ADAPT_EXP);
// output the exponent part
if (minwidth) {
// output the exponential symbol
out((flags & FLAGS_UPPERCASE) ? 'E' : 'e', buffer, idx++, maxlen);
// output the exponent value
idx = _ntoa_long(out, buffer, idx, maxlen, (expval < 0) ? -expval : expval, expval < 0, 10, 0, minwidth-1, FLAGS_ZEROPAD | FLAGS_PLUS);
// might need to right-pad spaces
if (flags & FLAGS_LEFT) {
while (idx - start_idx < width) out(' ', buffer, idx++, maxlen);
}
}
return idx;
}
#endif // PRINTF_SUPPORT_EXPONENTIAL
#endif // PRINTF_SUPPORT_FLOAT
// internal vsnprintf
static int _vsnprintf(out_fct_type out, char* buffer, const size_t maxlen, const char* format, va_list va)
{
unsigned int flags, width, precision, n;
size_t idx = 0U;
if (!buffer) {
// use null output function
out = _out_null;
}
while (*format)
{
// format specifier? %[flags][width][.precision][length]
if (*format != '%') {
// no
out(*format, buffer, idx++, maxlen);
format++;
continue;
}
else {
// yes, evaluate it
format++;
}
// evaluate flags
flags = 0U;
do {
switch (*format) {
case '0': flags |= FLAGS_ZEROPAD; format++; n = 1U; break;
case '-': flags |= FLAGS_LEFT; format++; n = 1U; break;
case '+': flags |= FLAGS_PLUS; format++; n = 1U; break;
case ' ': flags |= FLAGS_SPACE; format++; n = 1U; break;
case '#': flags |= FLAGS_HASH; format++; n = 1U; break;
default : n = 0U; break;
}
} while (n);
// evaluate width field
width = 0U;
if (_is_digit(*format)) {
width = _atoi(&format);
}
else if (*format == '*') {
const int w = va_arg(va, int);
if (w < 0) {
flags |= FLAGS_LEFT; // reverse padding
width = (unsigned int)-w;
}
else {
width = (unsigned int)w;
}
format++;
}
// evaluate precision field
precision = 0U;
if (*format == '.') {
flags |= FLAGS_PRECISION;
format++;
if (_is_digit(*format)) {
precision = _atoi(&format);
}
else if (*format == '*') {
const int prec = (int)va_arg(va, int);
precision = prec > 0 ? (unsigned int)prec : 0U;
format++;
}
}
// evaluate length field
switch (*format) {
case 'l' :
flags |= FLAGS_LONG;
format++;
if (*format == 'l') {
flags |= FLAGS_LONG_LONG;
format++;
}
break;
case 'h' :
flags |= FLAGS_SHORT;
format++;
if (*format == 'h') {
flags |= FLAGS_CHAR;
format++;
}
break;
#if defined(PRINTF_SUPPORT_PTRDIFF_T)
case 't' :
flags |= (sizeof(ptrdiff_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
#endif
case 'j' :
flags |= (sizeof(intmax_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
case 'z' :
flags |= (sizeof(size_t) == sizeof(long) ? FLAGS_LONG : FLAGS_LONG_LONG);
format++;
break;
default :
break;
}
// evaluate specifier
switch (*format) {
case 'd' :
case 'i' :
case 'u' :
case 'x' :
case 'X' :
case 'o' :
case 'b' : {
// set the base
unsigned int base;
if (*format == 'x' || *format == 'X') {
base = 16U;
}
else if (*format == 'o') {
base = 8U;
}
else if (*format == 'b') {
base = 2U;
}
else {
base = 10U;
flags &= ~FLAGS_HASH; // no hash for dec format
}
// uppercase
if (*format == 'X') {
flags |= FLAGS_UPPERCASE;
}
// no plus or space flag for u, x, X, o, b
if ((*format != 'i') && (*format != 'd')) {
flags &= ~(FLAGS_PLUS | FLAGS_SPACE);
}
// ignore '0' flag when precision is given
if (flags & FLAGS_PRECISION) {
flags &= ~FLAGS_ZEROPAD;
}
// convert the integer
if ((*format == 'i') || (*format == 'd')) {
// signed
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
const long long value = va_arg(va, long long);
idx = _ntoa_long_long(out, buffer, idx, maxlen, (unsigned long long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
const long value = va_arg(va, long);
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
}
else {
const int value = (flags & FLAGS_CHAR) ? (char)va_arg(va, int) : (flags & FLAGS_SHORT) ? (short int)va_arg(va, int) : va_arg(va, int);
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned int)(value > 0 ? value : 0 - value), value < 0, base, precision, width, flags);
}
}
else {
// unsigned
if (flags & FLAGS_LONG_LONG) {
#if defined(PRINTF_SUPPORT_LONG_LONG)
idx = _ntoa_long_long(out, buffer, idx, maxlen, va_arg(va, unsigned long long), false, base, precision, width, flags);
#endif
}
else if (flags & FLAGS_LONG) {
idx = _ntoa_long(out, buffer, idx, maxlen, va_arg(va, unsigned long), false, base, precision, width, flags);
}
else {
const unsigned int value = (flags & FLAGS_CHAR) ? (unsigned char)va_arg(va, unsigned int) : (flags & FLAGS_SHORT) ? (unsigned short int)va_arg(va, unsigned int) : va_arg(va, unsigned int);
idx = _ntoa_long(out, buffer, idx, maxlen, value, false, base, precision, width, flags);
}
}
format++;
break;
}
#if defined(PRINTF_SUPPORT_FLOAT)
case 'f' :
case 'F' :
if (*format == 'F') flags |= FLAGS_UPPERCASE;
idx = _ftoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
format++;
break;
#if defined(PRINTF_SUPPORT_EXPONENTIAL)
case 'e':
case 'E':
case 'g':
case 'G':
if ((*format == 'g')||(*format == 'G')) flags |= FLAGS_ADAPT_EXP;
if ((*format == 'E')||(*format == 'G')) flags |= FLAGS_UPPERCASE;
idx = _etoa(out, buffer, idx, maxlen, va_arg(va, double), precision, width, flags);
format++;
break;
#endif // PRINTF_SUPPORT_EXPONENTIAL
#endif // PRINTF_SUPPORT_FLOAT
case 'c' : {
unsigned int l = 1U;
// pre padding
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// char output
out((char)va_arg(va, int), buffer, idx++, maxlen);
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 's' : {
const char* p = va_arg(va, char*);
unsigned int l = _strnlen_s(p, precision ? precision : (size_t)-1);
// pre padding
if (flags & FLAGS_PRECISION) {
l = (l < precision ? l : precision);
}
if (!(flags & FLAGS_LEFT)) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
// string output
while ((*p != 0) && (!(flags & FLAGS_PRECISION) || precision--)) {
out(*(p++), buffer, idx++, maxlen);
}
// post padding
if (flags & FLAGS_LEFT) {
while (l++ < width) {
out(' ', buffer, idx++, maxlen);
}
}
format++;
break;
}
case 'p' : {
width = sizeof(void*) * 2U;
flags |= FLAGS_ZEROPAD | FLAGS_UPPERCASE;
#if defined(PRINTF_SUPPORT_LONG_LONG)
const bool is_ll = sizeof(uintptr_t) == sizeof(long long);
if (is_ll) {
idx = _ntoa_long_long(out, buffer, idx, maxlen, (uintptr_t)va_arg(va, void*), false, 16U, precision, width, flags);
}
else {
#endif
idx = _ntoa_long(out, buffer, idx, maxlen, (unsigned long)((uintptr_t)va_arg(va, void*)), false, 16U, precision, width, flags);
#if defined(PRINTF_SUPPORT_LONG_LONG)
}
#endif
format++;
break;
}
case '%' :
out('%', buffer, idx++, maxlen);
format++;
break;
default :
out(*format, buffer, idx++, maxlen);
format++;
break;
}
}
// termination
out((char)0, buffer, idx < maxlen ? idx : maxlen - 1U, maxlen);
// return written chars without terminating \0
return (int)idx;
}
///////////////////////////////////////////////////////////////////////////////
int printf_(const char* format, ...)
{
va_list va;
va_start(va, format);
char buffer[1];
const int ret = _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
int sprintf_(char* buffer, const char* format, ...)
{
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, (size_t)-1, format, va);
va_end(va);
return ret;
}
int snprintf_(char* buffer, size_t count, const char* format, ...)
{
va_list va;
va_start(va, format);
const int ret = _vsnprintf(_out_buffer, buffer, count, format, va);
va_end(va);
return ret;
}
int vprintf_(const char* format, va_list va)
{
char buffer[1];
return _vsnprintf(_out_char, buffer, (size_t)-1, format, va);
}
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va)
{
return _vsnprintf(_out_buffer, buffer, count, format, va);
}
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...)
{
va_list va;
va_start(va, format);
const out_fct_wrap_type out_fct_wrap = { out, arg };
const int ret = _vsnprintf(_out_fct, (char*)(uintptr_t)&out_fct_wrap, (size_t)-1, format, va);
va_end(va);
return ret;
}
+117
View File
@@ -0,0 +1,117 @@
///////////////////////////////////////////////////////////////////////////////
// \author (c) Marco Paland (info@paland.com)
// 2014-2019, PALANDesign Hannover, Germany
//
// \license The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
//
// \brief Tiny printf, sprintf and snprintf implementation, optimized for speed on
// embedded systems with a very limited resources.
// Use this instead of bloated standard/newlib printf.
// These routines are thread safe and reentrant.
//
///////////////////////////////////////////////////////////////////////////////
#ifndef _PRINTF_H_
#define _PRINTF_H_
#include <stdarg.h>
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
#endif
/**
* Output a character to a custom device like UART, used by the printf() function
* This function is declared here only. You have to write your custom implementation somewhere
* \param character Character to output
*/
void _putchar(char character);
/**
* Tiny printf implementation
* You have to implement _putchar if you use printf()
* To avoid conflicts with the regular printf() API it is overridden by macro defines
* and internal underscore-appended functions like printf_() are used
* \param format A string that specifies the format of the output
* \return The number of characters that are written into the array, not counting the terminating null character
*/
#define printf printf_
int printf_(const char* format, ...);
/**
* Tiny sprintf implementation
* Due to security reasons (buffer overflow) YOU SHOULD CONSIDER USING (V)SNPRINTF INSTEAD!
* \param buffer A pointer to the buffer where to store the formatted string. MUST be big enough to store the output!
* \param format A string that specifies the format of the output
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
*/
#define sprintf sprintf_
int sprintf_(char* buffer, const char* format, ...);
/**
* Tiny snprintf/vsnprintf implementation
* \param buffer A pointer to the buffer where to store the formatted string
* \param count The maximum number of characters to store in the buffer, including a terminating null character
* \param format A string that specifies the format of the output
* \param va A value identifying a variable arguments list
* \return The number of characters that COULD have been written into the buffer, not counting the terminating
* null character. A value equal or larger than count indicates truncation. Only when the returned value
* is non-negative and less than count, the string has been completely written.
*/
#define snprintf snprintf_
#define vsnprintf vsnprintf_
int snprintf_(char* buffer, size_t count, const char* format, ...);
int vsnprintf_(char* buffer, size_t count, const char* format, va_list va);
/**
* Tiny vprintf implementation
* \param format A string that specifies the format of the output
* \param va A value identifying a variable arguments list
* \return The number of characters that are WRITTEN into the buffer, not counting the terminating null character
*/
#define vprintf vprintf_
int vprintf_(const char* format, va_list va);
/**
* printf with output function
* You may use this as dynamic alternative to printf() with its fixed _putchar() output
* \param out An output function which takes one character and an argument pointer
* \param arg An argument pointer for user data passed to output function
* \param format A string that specifies the format of the output
* \return The number of characters that are sent to the output function, not counting the terminating null character
*/
int fctprintf(void (*out)(char character, void* arg), void* arg, const char* format, ...);
#ifdef __cplusplus
}
#endif
#endif // _PRINTF_H_
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+7
View File
@@ -0,0 +1,7 @@
sqlite.d.ts ➜ 2213 bytes
sqlite.js ➜1116045 bytes
sqlite.js.br ➜ 374501 bytes
sqlite.js.gz ➜ 457972 bytes
sqlite.wasm ➜ 836080 bytes
sqlite.wasm.br ➜ 299791 bytes
sqlite.wasm.gz ➜ 352828 bytes
+59
View File
@@ -0,0 +1,59 @@
/* This file is automatically generated. Do not edit directly. */
export type VoidPtr = number;
export type StringPtr = number;
export type StatementPtr = number;
export interface Wasm {
memory: WebAssembly.Memory;
malloc: (size: number) => VoidPtr;
free: (ptr: VoidPtr) => void;
str_len: (str: StringPtr) => number;
seed_rng: (seed: number) => void;
get_status: () => number;
open: (filename: StringPtr, flags: number) => number;
close: () => number;
get_sqlite_error_str: () => StringPtr;
prepare: (sql: StringPtr) => StatementPtr;
finalize: (stmt: StatementPtr) => number;
reset: (stmt: StatementPtr) => number;
clear_bindings: (stmt: StatementPtr) => number;
exec: (sql: StringPtr) => number;
bind_int: (stmt: StatementPtr, idx: number, value: number) => number;
bind_double: (stmt: StatementPtr, idx: number, value: number) => number;
bind_text: (stmt: StatementPtr, idx: number, value: StringPtr) => number;
bind_blob: (
stmt: StatementPtr,
idx: number,
value: VoidPtr,
size: number,
) => number;
bind_big_int: (
stmt: StatementPtr,
idx: number,
sign: number,
high: number,
low: number,
) => number;
bind_null: (stmt: StatementPtr, idx: number) => number;
bind_parameter_index: (stmt: StatementPtr, name: StringPtr) => number;
step: (stmt: StatementPtr) => number;
column_count: (stmt: StatementPtr) => number;
column_type: (stmt: StatementPtr, col: number) => number;
column_int: (stmt: StatementPtr, col: number) => number;
column_double: (stmt: StatementPtr, col: number) => number;
column_text: (stmt: StatementPtr, col: number) => StringPtr;
column_blob: (stmt: StatementPtr, col: number) => VoidPtr;
column_bytes: (stmt: StatementPtr, col: number) => number;
column_name: (stmt: StatementPtr, col: number) => StringPtr;
column_origin_name: (stmt: StatementPtr, col: number) => StringPtr;
column_table_name: (stmt: StatementPtr, col: number) => StringPtr;
last_insert_rowid: () => number;
changes: () => number;
total_changes: () => number;
}
export function compile(): Promise<void>;
export function instantiateBrowser(): Promise<void>;
export function instantiate(): { exports: Wasm };
File diff suppressed because one or more lines are too long
Binary file not shown.
@@ -0,0 +1,27 @@
#ifndef DEBUG_H
#define DEBUG_H
#ifdef DEBUG_BUILD
#include <stdlib.h>
#include <printf.h>
#include "imports.h"
// Print debug messages
#define debug_printf(...) { \
char* __debug_msg = malloc(2048); \
if (!__debug_msg) { \
js_print("ERROR: No memory for debug message.\n"); \
} else { \
size_t __used = snprintf(__debug_msg, 2048, "DEBUG: %s:%d:%s(): ", __FILE__, __LINE__, __func__); \
snprintf(&__debug_msg[__used], 2048 - __used, __VA_ARGS__); \
js_print(__debug_msg); \
free(__debug_msg); \
} \
}
#else // DEBUG_BUILD
#define debug_printf(...)
#endif // DEBUG_BUILD
#endif // DEBUG_H
@@ -0,0 +1,22 @@
#ifndef IMPORTS_H
#define IMPORTS_H
// WASM imports specified in vfs.syms
extern void js_print(const char*);
extern int js_open(const char*, int, int);
extern void js_close(int);
extern void js_delete(const char*);
extern int js_read(int, const char*, double, int);
extern int js_write(int, const char*, double, int);
extern void js_truncate(int, double);
extern void js_sync(int);
extern double js_size(int);
extern void js_lock(int, int);
extern void js_unlock(int);
extern double js_time();
extern int js_timezone();
extern int js_exists(const char*);
extern int js_access(const char*);
#endif // DEBUG_H
+302
View File
@@ -0,0 +1,302 @@
#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <time.h>
#include <sqlite3.h>
#include <pcg.h>
#include "debug.h"
#include "imports.h"
// SQLite VFS component.
// Based on demoVFS from SQLlite.
// https://www.sqlite.org/src/doc/trunk/src/test_demovfs.c
#define MAXPATHNAME 1024
#define JS_MAX_SAFE_INTEGER 9007199254740991
// When using this VFS, the sqlite3_file* handles that SQLite uses are
// actually pointers to instances of type DenoFile.
typedef struct DenoFile DenoFile;
struct DenoFile {
sqlite3_file base;
// Deno file resource id
int rid;
};
static int denoClose(sqlite3_file *pFile) {
DenoFile* p = (DenoFile*)pFile;
js_close(p->rid);
debug_printf("closing file (rid %i)\n", p->rid);
return SQLITE_OK;
}
// Read data from a file.
static int denoRead(sqlite3_file *pFile, void *zBuf, int iAmt, sqlite_int64 iOfst) {
DenoFile *p = (DenoFile*)pFile;
int read_bytes = 0;
if (iOfst <= JS_MAX_SAFE_INTEGER) {
// Read bytes from buffer
read_bytes = js_read(p->rid, (char*)zBuf, (double)iOfst, iAmt);
debug_printf("attempt to read from file (rid %i, amount %i, offset %lli, read %i)\n",
p->rid, iAmt, iOfst, read_bytes);
} else {
debug_printf("read offset %lli overflows JS_MAX_SAFE_INTEGER\n", iOfst);
}
// Zero memory if read was short
if (read_bytes < iAmt)
memset(&((char*)zBuf)[read_bytes], 0, iAmt-read_bytes);
return read_bytes < iAmt ? SQLITE_IOERR_SHORT_READ : SQLITE_OK;
}
// Write data to a file.
static int denoWrite(sqlite3_file *pFile, const void *zBuf, int iAmt, sqlite_int64 iOfst) {
DenoFile *p = (DenoFile*)pFile;
int write_bytes = 0;
if (iOfst <= JS_MAX_SAFE_INTEGER) {
// Write bytes to buffer
write_bytes = js_write(p->rid, (char*)zBuf, (double)iOfst, iAmt);
debug_printf("attempt to write to file (rid %i, amount %i, offset %lli, written %i)\n",
p->rid, iAmt, iOfst, write_bytes);
} else {
debug_printf("write offset %lli overflows JS_MAX_SAFE_INTEGER\n", iOfst);
}
return write_bytes == iAmt ? SQLITE_OK : SQLITE_IOERR_WRITE;
}
// Truncate file.
static int denoTruncate(sqlite3_file *pFile, sqlite_int64 size) {
DenoFile *p = (DenoFile*)pFile;
if (size <= JS_MAX_SAFE_INTEGER) {
js_truncate(p->rid, (double)size);
debug_printf("truncating file (rid %i, size: %lli)\n", p->rid, size);
return SQLITE_OK;
} else {
debug_printf("truncate length %lli overflows JS_MAX_SAFE_INTEGER\n", size);
return SQLITE_IOERR;
}
}
// Deno provides no explicit sync for us, so we
// just have a no-op here.
// TODO(dyedgreen): Investigate if there is a better way
static int denoSync(sqlite3_file *pFile, int flags) {
DenoFile *p = (DenoFile*)pFile;
js_sync(p->rid);
debug_printf("syncing file (rid %i)\n", p->rid);
return SQLITE_OK;
}
// Write the size of the file in bytes to *pSize.
static int denoFileSize(sqlite3_file *pFile, sqlite_int64 *pSize) {
DenoFile *p = (DenoFile*)pFile;
*pSize = (sqlite_int64)js_size(p->rid);
debug_printf("read file size: %lli (rid %i)\n", *pSize, p->rid);
return SQLITE_OK;
}
// File locking
static int denoLock(sqlite3_file *pFile, int eLock) {
DenoFile *p = (DenoFile*)pFile;
switch (eLock) {
case SQLITE_LOCK_NONE:
// no op
break;
case SQLITE_LOCK_SHARED:
case SQLITE_LOCK_RESERVED: // one WASM process <-> one open database
js_lock(p->rid, 0);
break;
case SQLITE_LOCK_PENDING:
case SQLITE_LOCK_EXCLUSIVE:
js_lock(p->rid, 1);
break;
}
return SQLITE_OK;
}
static int denoUnlock(sqlite3_file *pFile, int eLock) {
DenoFile *p = (DenoFile*)pFile;
switch (eLock) {
case SQLITE_LOCK_NONE:
// no op
break;
case SQLITE_LOCK_SHARED:
case SQLITE_LOCK_RESERVED:
case SQLITE_LOCK_PENDING:
case SQLITE_LOCK_EXCLUSIVE:
js_unlock(p->rid);
break;
}
return SQLITE_OK;
}
static int denoCheckReservedLock(sqlite3_file *pFile, int *pResOut) {
*pResOut = 0;
return SQLITE_OK;
}
// No xFileControl() verbs are implemented by this VFS.
static int denoFileControl(sqlite3_file *pFile, int op, void *pArg) {
return SQLITE_NOTFOUND;
}
// TODO(dyedgreen): Should we try to get these?
static int denoSectorSize(sqlite3_file *pFile) {
return 0;
}
static int denoDeviceCharacteristics(sqlite3_file *pFile) {
return 0;
}
// Open a file handle.
static int denoOpen(
sqlite3_vfs *pVfs, /* VFS */
const char *zName, /* File to open, or 0 for a temp file */
sqlite3_file *pFile, /* Pointer to DenoFile struct to populate */
int flags, /* Input SQLITE_OPEN_XXX flags */
int *pOutFlags /* Output SQLITE_OPEN_XXX flags (or NULL) */
) {
static const sqlite3_io_methods denoio = {
1, /* iVersion */
denoClose, /* xClose */
denoRead, /* xRead */
denoWrite, /* xWrite */
denoTruncate, /* xTruncate */
denoSync, /* xSync */
denoFileSize, /* xFileSize */
denoLock, /* xLock */
denoUnlock, /* xUnlock */
denoCheckReservedLock, /* xCheckReservedLock */
denoFileControl, /* xFileControl */
denoSectorSize, /* xSectorSize */
denoDeviceCharacteristics /* xDeviceCharacteristics */
};
DenoFile *p = (DenoFile*)pFile;
p->base.pMethods = &denoio;
// TODO(dyedgreen): The current approach is to raise
// the permission error on the vfs.js side of things,
// should the error be propagates through the wrapper
// and be raised on the wrapper side of things?
p->rid = js_open(zName, zName ? 0 : 1, flags);
if (pOutFlags) {
*pOutFlags = flags;
}
debug_printf("opened file (rid %i)\n", p->rid);
debug_printf("file path name: '%s'\n", zName);
return SQLITE_OK;
}
// Delete the file at the path.
static int denoDelete(sqlite3_vfs *pVfs, const char *zPath, int dirSync) {
js_delete(zPath);
return SQLITE_OK;
}
// All valid id files are accessible.
static int denoAccess(sqlite3_vfs *pVfs, const char *zPath, int flags, int *pResOut) {
switch (flags) {
case SQLITE_ACCESS_EXISTS:
*pResOut = js_exists(zPath);
break;
default:
*pResOut = js_access(zPath);
break;
}
debug_printf("determining file access (path %s, access %i)\n", zPath, *pResOut);
return SQLITE_OK;
}
// TODO(dyedgreen): Actually resolve the full path name
static int denoFullPathname(sqlite3_vfs *pVfs, const char *zPath, int nPathOut, char *zPathOut) {
sqlite3_snprintf(nPathOut, zPathOut, "%s", zPath);
debug_printf("requesting full path name for path: %s\n", zPath);
return SQLITE_OK;
}
// We don't support shared objects
static void *denoDlOpen(sqlite3_vfs *pVfs, const char *zPath) {
return 0;
}
static void denoDlError(sqlite3_vfs *pVfs, int nByte, char *zErrMsg) {
sqlite3_snprintf(nByte, zErrMsg, "Loadable extensions are not supported");
zErrMsg[nByte-1] = '\0';
}
static void (*denoDlSym(sqlite3_vfs *pVfs, void *pH, const char *z))(void) {
return 0;
}
static void denoDlClose(sqlite3_vfs *pVfs, void *pHandle) {
return;
}
// Generate pseudo-random data
static int denoRandomness(sqlite3_vfs *pVfs, int nByte, char *zByte) {
pcg_bytes(zByte, nByte);
return SQLITE_OK;
}
// TODO(dyedgreen): Can anything be done here?
static int denoSleep(sqlite3_vfs *pVfs, int nMicro) {
return 0;
}
// Retrieve the current time
static int denoCurrentTime(sqlite3_vfs *pVfs, double *pTime) {
*pTime = js_time() / 1000 / 86400.0 + 2440587.5;
return SQLITE_OK;
}
// Implement localtime_r
struct tm* localtime_r(const time_t *time, struct tm *result) {
debug_printf("running localtime_r");
time_t shifted = *time - 60 * js_timezone();
return gmtime_r(&shifted, result);
}
// This function returns a pointer to the VFS implemented in this file.
sqlite3_vfs *sqlite3_denovfs(void) {
static sqlite3_vfs denovfs = {
3, /* iVersion */
sizeof(DenoFile), /* szOsFile */
MAXPATHNAME, /* mxPathname */
0, /* pNext */
"deno", /* zName */
0, /* pAppData */
denoOpen, /* xOpen */
denoDelete, /* xDelete */
denoAccess, /* xAccess */
denoFullPathname, /* xFullPathname */
denoDlOpen, /* xDlOpen */
denoDlError, /* xDlError */
denoDlSym, /* xDlSym */
denoDlClose, /* xDlClose */
denoRandomness, /* xRandomness */
denoSleep, /* xSleep */
denoCurrentTime, /* xCurrentTime */
0, /* xGetLastError */
0, /* xCurrentTimeInt64 */
0, /* xSetSystemCall */
0, /* xGetSystemCall */
0, /* xNextSystemCall */
};
return &denovfs;
}
int sqlite3_os_init(void) {
debug_printf("running sqlite3_os_init\n");
// Register VFS
return sqlite3_vfs_register(sqlite3_denovfs(), 1);
}
int sqlite3_os_end(void) {
return SQLITE_OK;
}
@@ -0,0 +1,247 @@
#include <stdlib.h>
#include <sqlite3.h>
#include <pcg.h>
#include "debug.h"
#define EXPORT(name) __attribute__((used)) __attribute__((export_name (#name))) name
#define ERROR_VAL -1
#define BIG_INT_TYPE 6
#define JS_MAX_SAFE_INTEGER 9007199254740991
#define JS_MIN_SAFE_INTEGER (-JS_MAX_SAFE_INTEGER)
// Status returned by last instruction
int last_status = SQLITE_OK;
// Database handle for this instance
sqlite3* database = NULL;
// Return length of string pointed to by str.
int EXPORT(str_len) (const char* str) {
int len;
for (len = 0; str[len] != '\0'; len ++);
return len;
}
// Seed the random number generator. We pass a double, to
// get as many bytes from the JS number as possible.
void EXPORT(seed_rng) (double seed) {
pcg_seed((uint64_t)seed);
}
// Return last status encountered.
int EXPORT(get_status) () {
return last_status;
}
// Initialize the database and return the status.
int EXPORT(open) (const char* filename, int flags) {
// Return error is database is already open
if (database) {
last_status = SQLITE_MISUSE;
return last_status;
}
// Open SQLite db connection
last_status = sqlite3_open_v2(filename, &database, flags, NULL);
if (last_status != SQLITE_OK) {
debug_printf("failed to open database with status %i\n", last_status);
return last_status;
}
debug_printf("opened database at path '%s'\n", filename);
return last_status;
}
// Attempt to close the database connection.
int EXPORT(close) () {
last_status = sqlite3_close(database);
if (last_status == SQLITE_OK) {
database = NULL;
debug_printf("closed database");
} else {
debug_printf("failed to close database with status %i\n", last_status);
}
return last_status;
}
// Return most recent SQLite error as a string
const char* EXPORT(get_sqlite_error_str) () {
if (!database)
return "No open database.";
return sqlite3_errmsg(database);
}
// Wraps sqlite3_prepare. Returns statement id.
sqlite3_stmt* EXPORT(prepare) (const char* sql) {
// Prepare sqlite statement
sqlite3_stmt* stmt;
last_status = sqlite3_prepare_v2(database, sql, -1, &stmt, NULL);
debug_printf("prepared sql statement (status %i)\n", last_status);
if (last_status != SQLITE_OK)
return NULL;
return stmt;
}
// Destruct the given statement/ transaction. This will destruct the SQLite
// statement and free up it's transaction slot. Regardless of returned
// status, the statement id will be freed up.
int EXPORT(finalize) (sqlite3_stmt* stmt) {
last_status = sqlite3_finalize(stmt);
debug_printf("finalized statement (status %i)\n", last_status);
return last_status;
}
// Reset a given statement so it can be re-used.
int EXPORT(reset) (sqlite3_stmt* stmt) {
last_status = sqlite3_reset(stmt);
debug_printf("reset statement (status %i)\n", last_status);
return last_status;
}
// Resets all bound parameter values for this statement.
int EXPORT(clear_bindings) (sqlite3_stmt* stmt) {
last_status = sqlite3_clear_bindings(stmt);
debug_printf("clear bindings (status %i)\n", last_status);
return last_status;
}
// Execute multiple statements from a single string. This ignores any result
// rows.
int EXPORT(exec) (const char* sql) {
last_status = sqlite3_exec(database, sql, NULL, NULL, NULL);
debug_printf("ran exec (status %i)\n", last_status);
return last_status;
}
// Wrappers for bind statements, these return the status directly
int EXPORT(bind_int) (sqlite3_stmt* stmt, int idx, double value) {
// we use double to pass in the value, as JS does not support 64 bit integers,
// but handles floats and we can contain a 32 bit in in a 64 bit float, so there
// should be no loss.
last_status = sqlite3_bind_int64(stmt, idx, (sqlite3_int64)value);
debug_printf("binding int %lli (status %i)\n", (sqlite3_int64)value, last_status);
return last_status;
}
int EXPORT(bind_double) (sqlite3_stmt* stmt, int idx, double value) {
last_status = sqlite3_bind_double(stmt, idx, value);
debug_printf("binding double %f (status %i)\n", value, last_status);
return last_status;
}
int EXPORT(bind_text) (sqlite3_stmt* stmt, int idx, const char* value) {
// SQLite retrains the string until we execute the statement, but any strings
// passed in from JS are freed when the function returns. Thus we need to mark
// is as transient.
last_status = sqlite3_bind_text(stmt, idx, value, -1, SQLITE_TRANSIENT);
debug_printf("binding text '%s' (status %i)\n", value, last_status);
return last_status;
}
int EXPORT(bind_blob) (sqlite3_stmt* stmt, int idx, void* value, int size) {
// SQLite retrains the pointer until we execute the statement, but any pointers
// passed in from JS are freed when the function returns. Thus we need to mark
// is as transient.
last_status = sqlite3_bind_blob(stmt, idx, value, size, SQLITE_TRANSIENT);
debug_printf("binding blob '%s' (status %i)\n", value, last_status);
return last_status;
}
int EXPORT(bind_big_int) (sqlite3_stmt* stmt, int idx, int sign, uint32_t high, uint32_t low) {
// Bind a big integer within the 64 bit integer range by passing it as two 32
// bit integers. The integers are assumed to be positive, and a sign is passed
// separately.
sqlite3_int64 int_val = ((sqlite3_int64)low + ((sqlite3_int64)high << 32)) * (sqlite3_int64)sign;
debug_printf("binding big_int %lld", int_val);
last_status = sqlite3_bind_int64(stmt, idx, int_val);
return last_status;
}
int EXPORT(bind_null) (sqlite3_stmt* stmt, int idx) {
last_status = sqlite3_bind_null(stmt, idx);
debug_printf("binding null (status %i)\n", last_status);
return last_status;
}
// Determine parameter index for named parameters
int EXPORT(bind_parameter_index) (sqlite3_stmt* stmt, const char* name) {
int index = sqlite3_bind_parameter_index(stmt, name);
if (index == 0) {
debug_printf("parameter '%s' does not exist", name);
// Normalize SQLite returning 0 for not found to ERROR_VAL
return ERROR_VAL;
}
debug_printf("obtained parameter index (param '%s', index %i)\n", name, index);
return index;
}
// Wraps running statements, this returns the status directly
int EXPORT(step) (sqlite3_stmt* stmt) {
last_status = sqlite3_step(stmt);
debug_printf("stepping statement (status %i)\n", last_status);
return last_status;
}
// Count columns returned by statement.
int EXPORT(column_count) (sqlite3_stmt* stmt) {
return sqlite3_column_count(stmt);
}
// Determine type of column. Returns SQLITE column types.
int EXPORT(column_type) (sqlite3_stmt* stmt, int col) {
int type = sqlite3_column_type(stmt, col);
if (type == SQLITE_INTEGER) {
// handle integers that exceed JS_MAX_SAFE_INTEGER
sqlite3_int64 col_val = sqlite3_column_int64(stmt, col);
if (col_val > JS_MAX_SAFE_INTEGER || col_val < JS_MIN_SAFE_INTEGER) {
debug_printf("detected big integer: %lld\n", col_val);
return BIG_INT_TYPE;
}
}
return type;
}
// Wrap result returning functions.
double EXPORT(column_int) (sqlite3_stmt* stmt, int col) {
return (double)sqlite3_column_int64(stmt, col);
}
double EXPORT(column_double) (sqlite3_stmt* stmt, int col) {
return sqlite3_column_double(stmt, col);
}
const char* EXPORT(column_text) (sqlite3_stmt* stmt, int col) {
return (const char*)sqlite3_column_text(stmt, col);
}
const void* EXPORT(column_blob) (sqlite3_stmt* stmt, int col) {
return sqlite3_column_blob(stmt, col);
}
int EXPORT(column_bytes) (sqlite3_stmt* stmt, int col) {
return sqlite3_column_bytes(stmt, col);
}
const char* EXPORT(column_name) (sqlite3_stmt* stmt, int col) {
return sqlite3_column_name(stmt, col);
}
const char* EXPORT(column_origin_name) (sqlite3_stmt* stmt, int col) {
return sqlite3_column_origin_name(stmt, col);
}
const char* EXPORT(column_table_name) (sqlite3_stmt* stmt, int col) {
return sqlite3_column_table_name(stmt, col);
}
double EXPORT(last_insert_rowid) () {
return (double)sqlite3_last_insert_rowid(database);
}
double EXPORT(changes) () {
return (double)sqlite3_changes(database);
}
double EXPORT(total_changes) () {
return (double)sqlite3_total_changes(database);
}
+120
View File
@@ -0,0 +1,120 @@
import { getStr } from "../src/wasm.ts";
const isWindows = Deno.build.os === "windows";
// Closure to return an environment that links
// the current wasm context
export default function env(inst) {
// Exported environment
const env = {
// Print a string pointer to console
js_print: (str_ptr) => {
const text = getStr(inst.exports, str_ptr);
console.log(text[text.length - 1] === "\n" ? text.slice(0, -1) : text);
},
// Open the file at path, mode = 0 is open RW, mode = 1 is open TEMP
js_open: (path_ptr, mode, flags) => {
let path;
switch (mode) {
case 0:
path = getStr(inst.exports, path_ptr);
break;
case 1:
path = Deno.makeTempFileSync({ prefix: "deno_sqlite" });
break;
}
const write = !!(flags & 0x00000002);
const create = !!(flags & 0x00000004);
const rid = Deno.openSync(path, { read: true, write, create }).rid;
return rid;
},
// Close a file
js_close: (rid) => {
Deno.close(rid);
},
// Delete file at path
js_delete: (path_ptr) => {
const path = getStr(inst.exports, path_ptr);
Deno.removeSync(path);
},
// Read from a file to a buffer in the module
js_read: (rid, buffer_ptr, offset, amount) => {
const buffer = new Uint8Array(
inst.exports.memory.buffer,
buffer_ptr,
amount,
);
Deno.seekSync(rid, offset, Deno.SeekMode.Start);
return Deno.readSync(rid, buffer);
},
// Write to a file from a buffer in the module
js_write: (rid, buffer_ptr, offset, amount) => {
const buffer = new Uint8Array(
inst.exports.memory.buffer,
buffer_ptr,
amount,
);
Deno.seekSync(rid, offset, Deno.SeekMode.Start);
return Deno.writeSync(rid, buffer);
},
// Truncate the given file
js_truncate: (rid, size) => {
Deno.ftruncateSync(rid, size);
},
// Sync file data to disk
js_sync: (rid) => {
Deno.fdatasyncSync(rid);
},
// Retrieve the size of the given file
js_size: (rid) => {
return Deno.fstatSync(rid).size;
},
// Acquire a SHARED or EXCLUSIVE file lock
js_lock: (rid, exclusive) => {
// this is unstable and has issues on Windows ...
if (Deno.flockSync && !isWindows) Deno.flockSync(rid, exclusive !== 0);
},
// Release a file lock
js_unlock: (rid) => {
// this is unstable and has issues on Windows ...
if (Deno.funlockSync && !isWindows) Deno.funlockSync(rid);
},
// Return current time in ms since UNIX epoch
js_time: () => {
return Date.now();
},
// Return the timezone offset in minutes for
// the current locale.
js_timezone: () => {
return (new Date()).getTimezoneOffset();
},
// Determine if a path exists
js_exists: (path_ptr) => {
const path = getStr(inst.exports, path_ptr);
try {
Deno.statSync(path);
} catch (e) {
if (e instanceof Deno.errors.NotFound) {
return 0;
}
}
return 1;
},
// Determine if a path is accessible i.e. if it has read/write permissions
// TODO(dyedgreen): Properly determine if there are read permissions
js_access: (path_ptr) => {
const path = getStr(inst.exports, path_ptr);
try {
Deno.statSync(path);
} catch (e) {
if (e instanceof Deno.errors.PermissionDenied) {
return 0;
}
}
return 1;
},
};
return { env };
}
+15
View File
@@ -0,0 +1,15 @@
js_print
js_open
js_close
js_delete
js_read
js_write
js_truncate
js_sync
js_size
js_lock
js_unlock
js_time
js_timezone
js_exists
js_access