Fixes #97: SQLite is now async, optimized, tests
This commit is contained in:
@@ -0,0 +1,3 @@
|
||||
b9365f4aa1d3047a8d80d6bfe90e705c80e158c3 sqlite_dl.zip
|
||||
022ae45d50b124b9df68be065d65b9a607923362 wasi_dl_linux.tar.gz
|
||||
bc76d264214c21a603fc38adb405622a2fcda8b3 wasi_dl_darwin.tar.gz
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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_
|
||||
+241619
File diff suppressed because it is too large
Load Diff
+12836
File diff suppressed because it is too large
Load Diff
@@ -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
@@ -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
Executable
BIN
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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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 };
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user