Add json native instead of hello. Remove metabuild

code.
This commit is contained in:
Calvin Rose 2018-08-26 11:28:51 -04:00
parent 84f2c84fb5
commit 73b397f7de
10 changed files with 648 additions and 278 deletions

View File

@ -130,11 +130,11 @@ valtest: $(DST_TARGET)
###################
natives: $(DST_TARGET)
$(MAKE) -C natives/hello
$(MAKE) -C natives/json
$(MAKE) -j 8 -C natives/sqlite3
clean-natives:
$(MAKE) -C natives/hello clean
$(MAKE) -C natives/json clean
$(MAKE) -C natives/sqlite3 clean
#################
@ -154,6 +154,10 @@ install: $(DST_TARGET)
cp $(DST_LIBRARY) $(LIBDIR)/$(DST_LIBRARY)
$(LDCONFIG)
install-libs: natives
cp lib/* $(DST_PATH)
cp natives/*/*.so $(DST_PATH)
uninstall:
-rm $(BINDIR)/$(DST_TARGET)
-rm $(LIBDIR)/$(DST_LIBRARY)

View File

@ -105,27 +105,3 @@
(when x
(def thehead (get HEAD x))
(if thehead (tuple thehead (take-while pred (get TAIL x)))))))
# Iterators are a concept that looks a lot like lazy seq
# The following functions turn iterators to lazy seq and vice versa
(defn- iter-self
[next more]
(delay
(if (more) (tuple (next) (iter-self next more)))))
(defn iter2lazy
"Create a lazy sequence from an iterator"
[iter]
(def {:more more :next next} iter)
(iter-self next more))
(defn lazy2iter
"turn a lazy-seq to an iterator"
[lazy-seq]
(var node lazy-seq)
{:more (fn [] (node))
:next (fn []
(when-let [n (node)]
(:= node (get n 1))
(get n 0)))})

View File

@ -1,103 +0,0 @@
# A script to help generate build files on different platforms
# The Makefile generator
(def- make-cflags @["-std=c99" "-Wall" "-Wextra" "-O2" "-shared" "-fpic"])
(def- make-ldflags @[])
(if (= :macos (os.which))
(array.push make-cflags "undefined -dynamic_lookup"))
(defn- $
"Do and get the value of a subshell command"
[command]
(def f (file.popen command))
(def ret (file.read f :all))
(file.close f)
ret)
(defn- emit-rule
"Emit a rule in a makefile"
@[out target deps recipe]
(default recipe "@echo '-----'")
(file.write
out
target
": "
(if (indexed? deps) (string.join deps " ") deps)
"\n\t"
(if (indexed? recipe) (string.join recipe "\n\t") recipe)
"\n\n")
out)
(defn- generate-make
"Generate a makefile"
[out-path sources target]
(def out (file.open out-path :w))
(def csources (filter (fn [x] (= ".c" (string.slice x -2))) sources))
(def hsources (filter (fn [x] (= ".h" (string.slice x -2))) sources))
(file.write
out
"# Autogenerated Makefile, do not edit\n"
"# Generated at " ($ `date`)
"\nCFLAGS:=" (string.join make-cflags " ")
"\nLDFLAGS:=" (string.join make-ldflags " ")
"\nSOURCES:=" (string.join csources " ")
"\nHEADERS:=" (string.join hsources " ")
"\nOBJECTS:=$(patsubst %.c,%.o,${SOURCES})"
"\nTARGET:=" target ".so"
"\n\n")
(emit-rule out "all" "${TARGET}")
(emit-rule out "%.o" @["%.c" "${HEADERS}"] "${CC} ${CFLAGS} -o $@ $< ${LDFLAGS}")
(emit-rule out "${TARGET}" "${OBJECTS}" "${CC} ${CFLAGS} -o $@ $^ ${LDFLAGS}")
(emit-rule out "clean" "" "rm ${OBJECTS}")
(emit-rule out "clean" "" @["rm ${OBJECTS}" "rm @{TARGET}"])
# Phony targets
(emit-rule out ".PHONY" @["all" "clean"])
nil)
# The batch script generator (windows)
(defn- batch-crule
"Create a snippet to compile a single C file to an .obj file"
[file-path]
(string
`cl /nologo /I..\..\src\include /c /O2 /W3 `
file-path
"\n@if errorlevel 1 goto :BUILDFAIL"))
(defn- generate-batch
"Generate a batch file for windows"
[out-path sources target]
(def out (file.open out-path :w))
(def csources (filter (fn [x] (= ".c" (string.slice x -2))) sources))
(file.write
out
"@rem Generated batch script, run in 'Visual Studio Developer Prompt'\n"
"\n@rem \n\n"
"@echo off\n\n"
(string.join (map batch-crule csources) "\n\n")
"\n\n"
`link /nologo /dll ..\..\dst.lib /out:` target `.dll *.obj`
"\nif errorlevel 1 goto :BUILDFAIL"
"\n\n@echo .\n@echo ======\n@echo Build Succeeded.\n@echo =====\n"
"exit /b 0\n\n"
":BUILDFAIL\n"
"@echo .\n"
"@echo =====\n"
"@echo BUILD FAILED. See Output For Details.\n"
"@echo =====\n"
"@echo .\n"
"exit /b 1\n")
(file.flush out)
(file.close out)
nil)
(defn generate
"Generate the build files for a given library."
[out-path sources target]
((if (= :windows (os.which)) generate-batch generate-make)
out-path
sources
target))

View File

@ -1,76 +0,0 @@
# Created by https://www.gitignore.io/api/c
### C ###
# Prerequisites
*.d
# Object files
*.o
*.ko
*.obj
*.elf
# Linker output
*.ilk
*.map
*.exp
# Precompiled Headers
*.gch
*.pch
# Libraries
*.lib
*.a
*.la
*.lo
# Shared objects (inc. Windows DLLs)
*.dll
*.so
*.so.*
*.dylib
# Executables
*.exe
*.out
*.app
*.i*86
*.x86_64
*.hex
# Debug files
*.dSYM/
*.su
*.idb
*.pdb
# Kernel Module Compile Results
*.mod*
*.cmd
.tmp_versions/
modules.order
Module.symvers
Mkfile.old
dkms.conf
# End of https://www.gitignore.io/api/c
# Created by https://www.gitignore.io/api/cmake
### CMake ###
CMakeCache.txt
CMakeFiles
CMakeScripts
Testing
Makefile
cmake_install.cmake
install_manifest.txt
compile_commands.json
CTestTestfile.cmake
build
# End of https://www.gitignore.io/api/cmake

View File

@ -1,24 +0,0 @@
# Autogenerated Makefile, do not edit
# Generated at Fri Aug 3 22:55:50 EDT 2018
CFLAGS:=-std=c99 -Wall -Wextra -O2 -shared -fpic
LDFLAGS:=
SOURCES:=main.c
HEADERS:=
OBJECTS:=$(patsubst %.c,%.o,${SOURCES})
TARGET:=hello.so
all: ${TARGET}
%.o: %.c ${HEADERS}
${CC} ${CFLAGS} -o $@ -c $< ${LDFLAGS}
${TARGET}: ${OBJECTS}
${CC} ${CFLAGS} -o $@ $^ ${LDFLAGS}
clean:
rm ${OBJECTS}
rm ${TARGET}
.PHONY: all clean

View File

@ -1,42 +0,0 @@
/*
* Copyright (c) 2017 Calvin Rose
*
* 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.
*/
#include <dst/dst.h>
static int hello(DstArgs args) {
(void) args;
printf("Hello, world!\n");
return 0;
}
/*****************************************************************************/
static const DstReg cfuns[] = {
{"hello", hello},
{NULL, NULL}
};
DST_MODULE_ENTRY (DstArgs args) {
DstTable *env = dst_env_arg(args);
dst_env_cfuns(env, cfuns);
return 0;
}

View File

@ -1,5 +0,0 @@
#!/usr/bin/env dst
(import metabuild :as mb)
(mb.generate "hello" @["main.c"])

44
natives/json/Makefile Normal file
View File

@ -0,0 +1,44 @@
# Copyright (c) 2018 Calvin Rose
#
# 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.
CFLAGS:=-std=c99 -Wall -Wextra -O2 -shared -fpic
CFLAGS=-std=c99 -Wall -Wextra -I../../src/include -O2 -shared -fpic
OBJECTS:=json.o
TARGET:=json.so
# MacOS specifics
UNAME:=$(shell uname -s)
ifeq ($(UNAME), Darwin)
CFLAGS:=$(CFLAGS) -undefined dynamic_lookup
endif
all: $(TARGET)
%.o: %.c $(HEADERS)
$(CC) $(CFLAGS) -c $<
$(TARGET): $(OBJECTS)
$(CC) $(CFLAGS) -o $@ $^
clean:
rm $(OBJECTS)
rm $(TARGET)
.PHONY: all clean

View File

@ -4,10 +4,10 @@
@echo off
cl /nologo /I..\..\src\include /c /O2 /W3 hello.c
cl /nologo /I..\..\src\include /c /O2 /W3 json.c
@if errorlevel 1 goto :BUILDFAIL
link /nologo /dll ..\..\dst.lib /out:hello.dll *.obj
link /nologo /dll ..\..\dst.lib /out:json.dll *.obj
if errorlevel 1 goto :BUILDFAIL
@echo .

596
natives/json/json.c Normal file
View File

@ -0,0 +1,596 @@
/*
* Copyright (c) 2018 Calvin Rose
*
* 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.
*/
#include <dst/dst.h>
#include <stdlib.h>
#include <errno.h>
/*****************/
/* JSON Decoding */
/*****************/
/* Check if a character is whitespace */
static int white(uint8_t c) {
return c == '\t' || c == '\n' || c == ' ' || c == '\r';
}
/* Skip whitespace */
static void skipwhite(const char **p) {
const char *cp = *p;
for (;;) {
if (white(*cp))
cp++;
else
break;
}
*p = cp;
}
/* Get a hex digit value */
static int hexdig(char dig) {
if (dig >= '0' && dig <= '9')
return dig - '0';
if (dig >= 'a' && dig <= 'f')
return 10 + dig - 'a';
if (dig >= 'A' && dig <= 'F')
return 10 + dig - 'A';
return -1;
}
/* Read the hex value for a unicode escape */
static char *decode_utf16_escape(const char *p, uint32_t *outpoint) {
if (!p[0] || !p[1] || !p[2] || !p[3])
return "unexpected end of source";
int d1 = hexdig(p[0]);
int d2 = hexdig(p[1]);
int d3 = hexdig(p[2]);
int d4 = hexdig(p[3]);
if (d1 < 0 || d2 < 0 || d3 < 0 || d4 < 0)
return "invalid hex digit";
*outpoint = d4 | (d3 << 4) | (d2 << 8) | (d1 << 12);
return NULL;
}
/* Parse a string */
const char *decode_string(const char **p, Dst *out) {
DstBuffer *buffer = dst_buffer(0);
const char *cp = *p;
while (*cp != '"') {
uint8_t b = (uint8_t) *cp;
if (b < 32) return "invalid character in string";
if (b == '\\') {
cp++;
switch(*cp) {
default:
return "unknown string escape";
case 'b':
b = '\b';
break;
case 'f':
b = '\f';
break;
case 'n':
b = '\n';
break;
case 'r':
b = '\r';
break;
case 't':
b = '\t';
break;
case '"':
b = '"';
break;
case '\\':
b = '\\';
break;
case 'u':
{
/* Get codepoint and check for surrogate pair */
uint32_t codepoint;
const char *err = decode_utf16_escape(cp + 1, &codepoint);
if (err) return err;
if (codepoint >= 0xDC00 && codepoint <= 0xDFFF) {
return "unexpected utf-16 low surrogate";
} else if (codepoint >= 0xD800 && codepoint <= 0xDBFF) {
if (cp[5] != '\\') return "expected utf-16 low surrogate pair";
if (cp[6] != 'u') return "expected utf-16 low surrogate pair";
uint32_t lowsur;
const char *err = decode_utf16_escape(cp + 7, &lowsur);
if (err) return err;
if (lowsur < 0xDC00 || lowsur > 0xDFFF)
return "expected utf-16 low surrogate pair";
codepoint = ((codepoint - 0xD800) << 10) +
(lowsur - 0xDC00) + 0x10000;
cp += 11;
} else {
cp += 5;
}
/* Write codepoint */
if (codepoint <= 0x7F) {
dst_buffer_push_u8(buffer, codepoint);
} else if (codepoint <= 0x7FF) {
dst_buffer_push_u8(buffer, ((codepoint >> 6) & 0x1F) | 0xC0);
dst_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80);
} else if (codepoint <= 0xFFFF) {
dst_buffer_push_u8(buffer, ((codepoint >> 12) & 0x0F) | 0xE0);
dst_buffer_push_u8(buffer, ((codepoint >> 6) & 0x3F) | 0x80);
dst_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80);
} else {
dst_buffer_push_u8(buffer, ((codepoint >> 18) & 0x07) | 0xF0);
dst_buffer_push_u8(buffer, ((codepoint >> 12) & 0x3F) | 0x80);
dst_buffer_push_u8(buffer, ((codepoint >> 6) & 0x3F) | 0x80);
dst_buffer_push_u8(buffer, ((codepoint >> 0) & 0x3F) | 0x80);
}
}
continue;
}
}
dst_buffer_push_u8(buffer, b);
cp++;
}
*out = dst_stringv(buffer->data, buffer->count);
*p = cp + 1;
return NULL;
}
static const char *decode_one(const char **p, Dst *out, int depth) {
/* Prevent stack overflow */
if (depth > DST_RECURSION_GUARD) goto recurdepth;
/* Skip leading whitepspace */
skipwhite(p);
/* Main switch */
switch (**p) {
default:
goto badchar;
case '\0':
goto eos;
/* Numbers */
case '-': case '0': case '1' : case '2': case '3' : case '4':
case '5': case '6': case '7' : case '8': case '9':
{
errno = 0;
char *end = NULL;
double x = strtod(*p, &end);
if (end == *p) goto badnum;
*p = end;
*out = dst_wrap_real(x);
break;
}
/* false, null, true */
case 'f':
{
const char *cp = *p;
if (cp[1] != 'a' || cp[2] != 'l' || cp[3] != 's' || cp[4] != 'e')
goto badident;
*out = dst_wrap_false();
*p = cp + 5;
break;
}
case 'n':
{
const char *cp = *p;
if (cp[1] != 'u' || cp[2] != 'l' || cp[3] != 'l')
goto badident;
*out = dst_wrap_nil();
*p = cp + 4;
break;
}
case 't':
{
const char *cp = *p;
if (cp[1] != 'r' || cp[2] != 'u' || cp[3] != 'e')
goto badident;
*out = dst_wrap_true();
*p = cp + 4;
break;
}
/* String */
case '"':
{
const char *cp = *p + 1;
const char *start = cp;
while (*cp >= 32 && *cp != '"' && *cp != '\\')
cp++;
/* Only use a buffer for strings with escapes, else just copy
* memory from source */
if (*cp == '\\') {
*p = *p + 1;
const char *err = decode_string(p, out);
if (err) return err;
break;
}
if (*cp != '"') goto badchar;
*p = cp + 1;
*out = dst_stringv((const uint8_t *)start, cp - start);
break;
}
/* Array */
case '[':
{
*p = *p + 1;
DstArray *array = dst_array(0);
const char *err;
Dst subval;
skipwhite(p);
while (**p != ']') {
err = decode_one(p, &subval, depth + 1);
if (err) return err;
dst_array_push(array, subval);
skipwhite(p);
if (**p == ']') break;
if (**p != ',') goto wantcomma;
*p = *p + 1;
}
*p = *p + 1;
*out = dst_wrap_array(array);
}
break;
/* Object */
case '{':
{
*p = *p + 1;
DstTable *table = dst_table(0);
const char *err;
Dst subkey, subval;
skipwhite(p);
while (**p != '}') {
skipwhite(p);
if (**p != '"') goto wantstring;
err = decode_one(p, &subkey, depth + 1);
if (err) return err;
skipwhite(p);
if (**p != ':') goto wantcolon;
*p = *p + 1;
err = decode_one(p, &subval, depth + 1);
if (err) return err;
dst_table_put(table, subkey, subval);
skipwhite(p);
if (**p == '}') break;
if (**p != ',') goto wantcomma;
*p = *p + 1;
}
*p = *p + 1;
*out = dst_wrap_table(table);
break;
}
}
/* Good return */
return NULL;
/* Errors */
recurdepth:
return "recured too deeply";
eos:
return "unexpected end of source";
badident:
return "bad identifier";
badnum:
return "bad number";
wantcomma:
return "expected comma";
wantcolon:
return "expected colon";
badchar:
return "unexpected character";
wantstring:
return "expected json string";
}
static int json_decode(DstArgs args) {
Dst ret;
DST_FIXARITY(args, 1);
const char *err;
const char *start;
const char *p;
if (dst_checktype(args.v[0], DST_BUFFER)) {
DstBuffer *buffer = dst_unwrap_buffer(args.v[0]);
/* Ensure 0 padded */
dst_buffer_push_u8(buffer, 0);
start = p = (const char *)buffer->data;
err = decode_one(&p, &ret, 0);
buffer->count--;
} else {
const uint8_t *bytes;
int32_t len;
DST_ARG_BYTES(bytes, len, args, 0);
start = p = (const char *)bytes;
err = decode_one(&p, &ret, 0);
}
/* Check trailing values */
if (!err) {
skipwhite(&p);
if (*p)
err = "unexpected extra token";
}
if (err) {
DST_THROWV(args, dst_wrap_string(dst_formatc(
"decode error at postion %d: %s",
p - start,
err)));
}
DST_RETURN(args, ret);
}
/*****************/
/* JSON Encoding */
/*****************/
typedef struct {
DstBuffer *buffer;
int32_t indent;
const uint8_t *tab;
const uint8_t *newline;
int32_t tablen;
int32_t newlinelen;
} Encoder;
static const char *encode_newline(Encoder *e) {
if (dst_buffer_push_bytes(e->buffer, e->newline, e->newlinelen))
return "buffer overflow";
for (int32_t i = 0; i < e->indent; i++)
if (dst_buffer_push_bytes(e->buffer, e->tab, e->tablen))
return "buffer overflow";
return NULL;
}
static const char *encode_one(Encoder *e, Dst x, int depth) {
switch(dst_type(x)) {
default:
goto badtype;
case DST_NIL:
{
if (dst_buffer_push_cstring(e->buffer, "null"))
goto overflow;
}
break;
case DST_FALSE:
{
if (dst_buffer_push_cstring(e->buffer, "false"))
goto overflow;
}
break;
case DST_TRUE:
{
if (dst_buffer_push_cstring(e->buffer, "true"))
goto overflow;
}
break;
case DST_INTEGER:
{
char cbuf[20];
sprintf(cbuf, "%d", dst_unwrap_integer(x));
if (dst_buffer_push_cstring(e->buffer, cbuf))
goto overflow;
}
break;
case DST_REAL:
{
char cbuf[25];
sprintf(cbuf, "%.17g", dst_unwrap_real(x));
if (dst_buffer_push_cstring(e->buffer, cbuf))
goto overflow;
}
break;
case DST_STRING:
case DST_SYMBOL:
case DST_BUFFER:
{
const uint8_t *bytes;
const uint8_t *c;
const uint8_t *end;
int32_t len;
dst_bytes_view(x, &bytes, &len);
if (dst_buffer_push_u8(e->buffer, '"')) goto overflow;
c = bytes;
end = bytes + len;
while (c < end) {
/* get codepoint */
uint32_t codepoint;
if (*c < 0x80) {
/* one byte */
codepoint = *c++;
} else if (*c < 0xE0) {
/* two bytes */
if (c + 2 > end) goto overflow;
codepoint = ((c[0] & 0x1F) << 6) |
(c[1] & 0x3F);
c += 2;
} else if (*c < 0xF0) {
/* three bytes */
if (c + 3 > end) goto overflow;
codepoint = ((c[0] & 0x0F) << 12) |
((c[1] & 0x3F) << 6) |
(c[2] & 0x3F);
c += 3;
} else if (*c < 0xF8) {
/* four bytes */
if (c + 4 > end) goto overflow;
codepoint = ((c[0] & 0x07) << 18) |
((c[1] & 0x3F) << 12) |
((c[3] & 0x3F) << 6) |
(c[3] & 0x3F);
c += 4;
} else {
/* invalid */
goto invalidutf8;
}
/* write codepoint */
if (codepoint > 0x1F && codepoint < 0x80) {
/* Normal, no escape */
if (codepoint == '\\' || codepoint == '"')
if (dst_buffer_push_u8(e->buffer, '\\'))
goto overflow;
if (dst_buffer_push_u8(e->buffer, (uint8_t) codepoint))
goto overflow;
} else if (codepoint < 0x10000) {
/* One unicode escape */
uint8_t buf[6];
buf[0] = '\\';
buf[1] = 'u';
buf[2] = (codepoint >> 12) & 0xF;
buf[3] = (codepoint >> 8) & 0xF;
buf[4] = (codepoint >> 4) & 0xF;
buf[5] = codepoint & 0xF;
if (dst_buffer_push_bytes(e->buffer, buf, sizeof(buf)))
goto overflow;
} else {
/* Two unicode escapes (surrogate pair) */
uint32_t hi, lo;
uint8_t buf[12];
hi = ((codepoint - 0x10000) >> 10) + 0xD800;
lo = ((codepoint - 0x10000) & 0x3FF) + 0xDC00;
buf[0] = '\\';
buf[1] = 'u';
buf[2] = (hi >> 12) & 0xF;
buf[3] = (hi >> 8) & 0xF;
buf[4] = (hi >> 4) & 0xF;
buf[5] = hi & 0xF;
buf[6] = '\\';
buf[7] = 'u';
buf[8] = (lo >> 12) & 0xF;
buf[9] = (lo >> 8) & 0xF;
buf[10] = (lo >> 4) & 0xF;
buf[11] = lo & 0xF;
if (dst_buffer_push_bytes(e->buffer, buf, sizeof(buf)))
goto overflow;
}
}
if (dst_buffer_push_u8(e->buffer, '"')) goto overflow;
}
break;
case DST_TUPLE:
case DST_ARRAY:
{
const char *err;
const Dst *items;
int32_t len;
dst_indexed_view(x, &items, &len);
if (dst_buffer_push_u8(e->buffer, '[')) goto overflow;
e->indent++;
if ((err = encode_newline(e))) return err;
for (int32_t i = 0; i < len; i++) {
if ((err = encode_newline(e))) return err;
if ((err = encode_one(e, items[i], depth + 1)))
return err;
if (dst_buffer_push_u8(e->buffer, ','))
goto overflow;
}
e->indent--;
if (e->buffer->data[e->buffer->count - 1] == ',') {
e->buffer->count--;
if ((err = encode_newline(e))) return err;
}
if (dst_buffer_push_u8(e->buffer, ']')) goto overflow;
}
break;
case DST_TABLE:
case DST_STRUCT:
{
const char *err;
const DstKV *kvs;
int32_t count, capacity;
dst_dictionary_view(x, &kvs, &count, &capacity);
if (dst_buffer_push_u8(e->buffer, '{')) goto overflow;
e->indent++;
for (int32_t i = 0; i < capacity; i++) {
if (dst_checktype(kvs[i].key, DST_NIL))
continue;
if (!dst_checktype(kvs[i].key, DST_STRING))
return "only strings keys are allowed in objects";
if ((err = encode_newline(e))) return err;
if ((err = encode_one(e, kvs[i].key, depth + 1)))
return err;
const char *sep = e->tablen ? ": " : ":";
if (dst_buffer_push_cstring(e->buffer, sep))
goto overflow;
if ((err = encode_one(e, kvs[i].value, depth + 1)))
return err;
if (dst_buffer_push_u8(e->buffer, ','))
goto overflow;
}
e->indent--;
if (e->buffer->data[e->buffer->count - 1] == ',') {
e->buffer->count--;
if ((err = encode_newline(e))) return err;
}
if (dst_buffer_push_u8(e->buffer, '}')) goto overflow;
}
break;
}
return NULL;
/* Errors */
overflow:
return "buffer overflow";
badtype:
return "type not supported";
invalidutf8:
return "string contains invalid utf-8";
}
static int json_encode(DstArgs args) {
DST_MINARITY(args, 1);
DST_MAXARITY(args, 3);
Encoder e;
e.indent = 0;
e.buffer = dst_buffer(10);
e.tab = NULL;
e.newline = NULL;
e.tablen = 0;
e.newlinelen = 0;
if (args.n >= 2) {
DST_ARG_BYTES(e.tab, e.tablen, args, 1);
if (args.n >= 3) {
DST_ARG_BYTES(e.newline, e.newlinelen, args, 2);
} else {
e.newline = (const uint8_t *)"\n";
e.newlinelen = 1;
}
}
const char *err = encode_one(&e, args.v[0], 0);
if (err) DST_THROW(args, err);
DST_RETURN_BUFFER(args, e.buffer);
}
/****************/
/* Module Entry */
/****************/
static const DstReg cfuns[] = {
{"encode", json_encode},
{"decode", json_decode},
{NULL, NULL}
};
DST_MODULE_ENTRY(DstArgs args) {
DstTable *env = dst_env_arg(args);
dst_env_cfuns(env, cfuns);
return 0;
}