mirror of
https://github.com/Next-Flip/Momentum-Firmware.git
synced 2026-04-28 03:59:58 -07:00
Merge branch 'dev' of https://github.com/flipperdevices/flipperzero-firmware into xfw-dev
This commit is contained in:
@@ -39,6 +39,7 @@ libs = env.BuildModules(
|
||||
"lfrfid",
|
||||
"flipper_application",
|
||||
"music_worker",
|
||||
"mjs",
|
||||
"nanopb",
|
||||
"update_util",
|
||||
"xtreme",
|
||||
|
||||
33
lib/mjs/SConscript
Normal file
33
lib/mjs/SConscript
Normal file
@@ -0,0 +1,33 @@
|
||||
Import("env")
|
||||
|
||||
env.Append(
|
||||
CPPPATH=[
|
||||
"#/lib/mjs",
|
||||
],
|
||||
SDK_HEADERS=[
|
||||
File("mjs_core_public.h"),
|
||||
File("mjs_exec_public.h"),
|
||||
File("mjs_object_public.h"),
|
||||
File("mjs_string_public.h"),
|
||||
File("mjs_array_public.h"),
|
||||
File("mjs_primitive_public.h"),
|
||||
File("mjs_util_public.h"),
|
||||
File("mjs_array_buf_public.h"),
|
||||
],
|
||||
)
|
||||
|
||||
libenv = env.Clone(FW_LIB_NAME="mjs")
|
||||
libenv.ApplyLibFlags()
|
||||
|
||||
libenv.AppendUnique(
|
||||
CCFLAGS=[
|
||||
"-Wno-redundant-decls",
|
||||
"-Wno-unused-function",
|
||||
],
|
||||
)
|
||||
|
||||
sources = libenv.GlobRecursive("*.c*")
|
||||
|
||||
lib = libenv.StaticLibrary("${FW_LIB_NAME}", sources)
|
||||
libenv.Install("${LIB_DIST_DIR}", lib)
|
||||
Return("lib")
|
||||
157
lib/mjs/common/cs_dbg.c
Normal file
157
lib/mjs/common/cs_dbg.c
Normal file
@@ -0,0 +1,157 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_dbg.h"
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "cs_time.h"
|
||||
#include "str_util.h"
|
||||
|
||||
enum cs_log_level cs_log_level WEAK =
|
||||
#if CS_ENABLE_DEBUG
|
||||
LL_VERBOSE_DEBUG;
|
||||
#else
|
||||
LL_ERROR;
|
||||
#endif
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
static char* s_file_level = NULL;
|
||||
|
||||
void cs_log_set_file_level(const char* file_level) WEAK;
|
||||
|
||||
FILE* cs_log_file WEAK = NULL;
|
||||
|
||||
#if CS_LOG_ENABLE_TS_DIFF
|
||||
double cs_log_ts WEAK;
|
||||
#endif
|
||||
|
||||
enum cs_log_level cs_log_cur_msg_level WEAK = LL_NONE;
|
||||
|
||||
void cs_log_set_file_level(const char* file_level) {
|
||||
char* fl = s_file_level;
|
||||
if(file_level != NULL) {
|
||||
s_file_level = strdup(file_level);
|
||||
} else {
|
||||
s_file_level = NULL;
|
||||
}
|
||||
free(fl);
|
||||
}
|
||||
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK;
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) {
|
||||
char prefix[CS_LOG_PREFIX_LEN], *q;
|
||||
const char* p;
|
||||
size_t fl = 0, ll = 0, pl = 0;
|
||||
|
||||
if(level > cs_log_level && s_file_level == NULL) return 0;
|
||||
|
||||
p = file + strlen(file);
|
||||
|
||||
while(p != file) {
|
||||
const char c = *(p - 1);
|
||||
if(c == '/' || c == '\\') break;
|
||||
p--;
|
||||
fl++;
|
||||
}
|
||||
|
||||
ll = (ln < 10000 ? (ln < 1000 ? (ln < 100 ? (ln < 10 ? 1 : 2) : 3) : 4) : 5);
|
||||
if(fl > (sizeof(prefix) - ll - 2)) fl = (sizeof(prefix) - ll - 2);
|
||||
|
||||
pl = fl + 1 + ll;
|
||||
memcpy(prefix, p, fl);
|
||||
q = prefix + pl;
|
||||
memset(q, ' ', sizeof(prefix) - pl);
|
||||
do {
|
||||
*(--q) = '0' + (ln % 10);
|
||||
ln /= 10;
|
||||
} while(ln > 0);
|
||||
*(--q) = ':';
|
||||
|
||||
if(s_file_level != NULL) {
|
||||
enum cs_log_level pll = cs_log_level;
|
||||
struct mg_str fl = mg_mk_str(s_file_level), ps = MG_MK_STR_N(prefix, pl);
|
||||
struct mg_str k, v;
|
||||
while((fl = mg_next_comma_list_entry_n(fl, &k, &v)).p != NULL) {
|
||||
bool yes = !(!mg_str_starts_with(ps, k) || v.len == 0);
|
||||
if(!yes) continue;
|
||||
pll = (enum cs_log_level)(*v.p - '0');
|
||||
break;
|
||||
}
|
||||
if(level > pll) return 0;
|
||||
}
|
||||
|
||||
if(cs_log_file == NULL) cs_log_file = stderr;
|
||||
cs_log_cur_msg_level = level;
|
||||
fwrite(prefix, 1, sizeof(prefix), cs_log_file);
|
||||
#if CS_LOG_ENABLE_TS_DIFF
|
||||
{
|
||||
double now = cs_time();
|
||||
fprintf(cs_log_file, "%7u ", (unsigned int)((now - cs_log_ts) * 1000000));
|
||||
cs_log_ts = now;
|
||||
}
|
||||
#endif
|
||||
return 1;
|
||||
}
|
||||
|
||||
void cs_log_printf(const char* fmt, ...) WEAK;
|
||||
void cs_log_printf(const char* fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
vfprintf(cs_log_file, fmt, ap);
|
||||
va_end(ap);
|
||||
fputc('\n', cs_log_file);
|
||||
fflush(cs_log_file);
|
||||
cs_log_cur_msg_level = LL_NONE;
|
||||
}
|
||||
|
||||
void cs_log_set_file(FILE* file) WEAK;
|
||||
void cs_log_set_file(FILE* file) {
|
||||
cs_log_file = file;
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) WEAK;
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) {
|
||||
(void)level;
|
||||
(void)file;
|
||||
(void)ln;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cs_log_printf(const char* fmt, ...) WEAK;
|
||||
void cs_log_printf(const char* fmt, ...) {
|
||||
(void)fmt;
|
||||
}
|
||||
|
||||
void cs_log_set_file_level(const char* file_level) {
|
||||
(void)file_level;
|
||||
}
|
||||
|
||||
#endif /* CS_ENABLE_STDIO */
|
||||
|
||||
void cs_log_set_level(enum cs_log_level level) WEAK;
|
||||
void cs_log_set_level(enum cs_log_level level) {
|
||||
cs_log_level = level;
|
||||
#if CS_LOG_ENABLE_TS_DIFF && CS_ENABLE_STDIO
|
||||
cs_log_ts = cs_time();
|
||||
#endif
|
||||
}
|
||||
148
lib/mjs/common/cs_dbg.h
Normal file
148
lib/mjs/common/cs_dbg.h
Normal file
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_DBG_H_
|
||||
#define CS_COMMON_CS_DBG_H_
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
#include <stdio.h>
|
||||
#endif
|
||||
|
||||
#ifndef CS_ENABLE_DEBUG
|
||||
#define CS_ENABLE_DEBUG 0
|
||||
#endif
|
||||
|
||||
#ifndef CS_LOG_PREFIX_LEN
|
||||
#define CS_LOG_PREFIX_LEN 24
|
||||
#endif
|
||||
|
||||
#ifndef CS_LOG_ENABLE_TS_DIFF
|
||||
#define CS_LOG_ENABLE_TS_DIFF 0
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Log level; `LL_INFO` is the default. Use `cs_log_set_level()` to change it.
|
||||
*/
|
||||
enum cs_log_level {
|
||||
LL_NONE = -1,
|
||||
LL_ERROR = 0,
|
||||
LL_WARN = 1,
|
||||
LL_INFO = 2,
|
||||
LL_DEBUG = 3,
|
||||
LL_VERBOSE_DEBUG = 4,
|
||||
|
||||
_LL_MIN = -2,
|
||||
_LL_MAX = 5,
|
||||
};
|
||||
|
||||
/*
|
||||
* Set max log level to print; messages with the level above the given one will
|
||||
* not be printed.
|
||||
*/
|
||||
void cs_log_set_level(enum cs_log_level level);
|
||||
|
||||
/*
|
||||
* A comma-separated set of prefix=level.
|
||||
* prefix is matched against the log prefix exactly as printed, including line
|
||||
* number, but partial match is ok. Check stops on first matching entry.
|
||||
* If nothing matches, default level is used.
|
||||
*
|
||||
* Examples:
|
||||
* main.c:=4 - everything from main C at verbose debug level.
|
||||
* mongoose.c=1,mjs.c=1,=4 - everything at verbose debug except mg_* and mjs_*
|
||||
*
|
||||
*/
|
||||
void cs_log_set_file_level(const char* file_level);
|
||||
|
||||
/*
|
||||
* Helper function which prints message prefix with the given `level`.
|
||||
* If message should be printed (according to the current log level
|
||||
* and filter), prints the prefix and returns 1, otherwise returns 0.
|
||||
*
|
||||
* Clients should typically just use `LOG()` macro.
|
||||
*/
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* fname, int line);
|
||||
|
||||
extern enum cs_log_level cs_log_level;
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
|
||||
/*
|
||||
* Set file to write logs into. If `NULL`, logs go to `stderr`.
|
||||
*/
|
||||
void cs_log_set_file(FILE* file);
|
||||
|
||||
/*
|
||||
* Prints log to the current log file, appends "\n" in the end and flushes the
|
||||
* stream.
|
||||
*/
|
||||
void cs_log_printf(const char* fmt, ...) PRINTF_LIKE(1, 2);
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
|
||||
/*
|
||||
* Format and print message `x` with the given level `l`. Example:
|
||||
*
|
||||
* ```c
|
||||
* LOG(LL_INFO, ("my info message: %d", 123));
|
||||
* LOG(LL_DEBUG, ("my debug message: %d", 123));
|
||||
* ```
|
||||
*/
|
||||
#define LOG(l, x) \
|
||||
do { \
|
||||
if(cs_log_print_prefix(l, __FILE__, __LINE__)) { \
|
||||
cs_log_printf x; \
|
||||
} \
|
||||
} while(0)
|
||||
|
||||
#else
|
||||
|
||||
#define LOG(l, x) ((void)l)
|
||||
|
||||
#endif
|
||||
|
||||
#ifndef CS_NDEBUG
|
||||
|
||||
/*
|
||||
* Shortcut for `LOG(LL_VERBOSE_DEBUG, (...))`
|
||||
*/
|
||||
#define DBG(x) LOG(LL_VERBOSE_DEBUG, x)
|
||||
|
||||
#else /* NDEBUG */
|
||||
|
||||
#define DBG(x)
|
||||
|
||||
#endif
|
||||
|
||||
#else /* CS_ENABLE_STDIO */
|
||||
|
||||
#define LOG(l, x)
|
||||
#define DBG(x)
|
||||
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_DBG_H_ */
|
||||
108
lib/mjs/common/cs_dirent.c
Normal file
108
lib/mjs/common/cs_dirent.c
Normal file
@@ -0,0 +1,108 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef EXCLUDE_COMMON
|
||||
|
||||
#include "mg_mem.h"
|
||||
#include "cs_dirent.h"
|
||||
|
||||
/*
|
||||
* This file contains POSIX opendir/closedir/readdir API implementation
|
||||
* for systems which do not natively support it (e.g. Windows).
|
||||
*/
|
||||
|
||||
#ifdef _WIN32
|
||||
struct win32_dir {
|
||||
DIR d;
|
||||
HANDLE handle;
|
||||
WIN32_FIND_DATAW info;
|
||||
struct dirent result;
|
||||
};
|
||||
|
||||
DIR *opendir(const char *name) {
|
||||
struct win32_dir *dir = NULL;
|
||||
wchar_t wpath[MAX_PATH];
|
||||
DWORD attrs;
|
||||
|
||||
if (name == NULL) {
|
||||
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||
} else if ((dir = (struct win32_dir *) MG_MALLOC(sizeof(*dir))) == NULL) {
|
||||
SetLastError(ERROR_NOT_ENOUGH_MEMORY);
|
||||
} else {
|
||||
to_wchar(name, wpath, ARRAY_SIZE(wpath));
|
||||
attrs = GetFileAttributesW(wpath);
|
||||
if (attrs != 0xFFFFFFFF && (attrs & FILE_ATTRIBUTE_DIRECTORY)) {
|
||||
(void) wcscat(wpath, L"\\*");
|
||||
dir->handle = FindFirstFileW(wpath, &dir->info);
|
||||
dir->result.d_name[0] = '\0';
|
||||
} else {
|
||||
MG_FREE(dir);
|
||||
dir = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
return (DIR *) dir;
|
||||
}
|
||||
|
||||
int closedir(DIR *d) {
|
||||
struct win32_dir *dir = (struct win32_dir *) d;
|
||||
int result = 0;
|
||||
|
||||
if (dir != NULL) {
|
||||
if (dir->handle != INVALID_HANDLE_VALUE)
|
||||
result = FindClose(dir->handle) ? 0 : -1;
|
||||
MG_FREE(dir);
|
||||
} else {
|
||||
result = -1;
|
||||
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
struct dirent *readdir(DIR *d) {
|
||||
struct win32_dir *dir = (struct win32_dir *) d;
|
||||
struct dirent *result = NULL;
|
||||
|
||||
if (dir) {
|
||||
memset(&dir->result, 0, sizeof(dir->result));
|
||||
if (dir->handle != INVALID_HANDLE_VALUE) {
|
||||
result = &dir->result;
|
||||
(void) WideCharToMultiByte(CP_UTF8, 0, dir->info.cFileName, -1,
|
||||
result->d_name, sizeof(result->d_name), NULL,
|
||||
NULL);
|
||||
|
||||
if (!FindNextFileW(dir->handle, &dir->info)) {
|
||||
(void) FindClose(dir->handle);
|
||||
dir->handle = INVALID_HANDLE_VALUE;
|
||||
}
|
||||
|
||||
} else {
|
||||
SetLastError(ERROR_FILE_NOT_FOUND);
|
||||
}
|
||||
} else {
|
||||
SetLastError(ERROR_BAD_ARGUMENTS);
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* EXCLUDE_COMMON */
|
||||
|
||||
/* ISO C requires a translation unit to contain at least one declaration */
|
||||
typedef int cs_dirent_dummy;
|
||||
51
lib/mjs/common/cs_dirent.h
Normal file
51
lib/mjs/common/cs_dirent.h
Normal file
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_DIRENT_H_
|
||||
#define CS_COMMON_CS_DIRENT_H_
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#ifdef CS_DEFINE_DIRENT
|
||||
typedef struct { int dummy; } DIR;
|
||||
|
||||
struct dirent {
|
||||
int d_ino;
|
||||
#ifdef _WIN32
|
||||
char d_name[MAX_PATH];
|
||||
#else
|
||||
/* TODO(rojer): Use PATH_MAX but make sure it's sane on every platform */
|
||||
char d_name[256];
|
||||
#endif
|
||||
};
|
||||
|
||||
DIR *opendir(const char *dir_name);
|
||||
int closedir(DIR *dir);
|
||||
struct dirent *readdir(DIR *dir);
|
||||
#endif /* CS_DEFINE_DIRENT */
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_DIRENT_H_ */
|
||||
65
lib/mjs/common/cs_file.c
Normal file
65
lib/mjs/common/cs_file.c
Normal file
@@ -0,0 +1,65 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_file.h"
|
||||
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#ifdef CS_MMAP
|
||||
#include <fcntl.h>
|
||||
#include <sys/mman.h>
|
||||
#include <sys/stat.h>
|
||||
#endif
|
||||
|
||||
#ifdef CS_MMAP
|
||||
char* cs_read_file(const char* path, size_t* size) WEAK;
|
||||
char* cs_read_file(const char* path, size_t* size) {
|
||||
FILE* fp;
|
||||
char* data = NULL;
|
||||
if((fp = fopen(path, "rb")) == NULL) {
|
||||
} else if(fseek(fp, 0, SEEK_END) != 0) {
|
||||
fclose(fp);
|
||||
} else {
|
||||
*size = ftell(fp);
|
||||
data = (char*)malloc(*size + 1);
|
||||
if(data != NULL) {
|
||||
fseek(fp, 0, SEEK_SET); /* Some platforms might not have rewind(), Oo */
|
||||
if(fread(data, 1, *size, fp) != *size) {
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
data[*size] = '\0';
|
||||
}
|
||||
fclose(fp);
|
||||
}
|
||||
return data;
|
||||
}
|
||||
|
||||
char* cs_mmap_file(const char* path, size_t* size) WEAK;
|
||||
char* cs_mmap_file(const char* path, size_t* size) {
|
||||
char* r;
|
||||
int fd = open(path, O_RDONLY, 0);
|
||||
struct stat st;
|
||||
if(fd < 0) return NULL;
|
||||
fstat(fd, &st);
|
||||
*size = (size_t)st.st_size;
|
||||
r = (char*)mmap(NULL, st.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
|
||||
if(r == MAP_FAILED) return NULL;
|
||||
return r;
|
||||
}
|
||||
#endif
|
||||
48
lib/mjs/common/cs_file.h
Normal file
48
lib/mjs/common/cs_file.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_FILE_H_
|
||||
#define CS_COMMON_CS_FILE_H_
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Read whole file `path` in memory. It is responsibility of the caller
|
||||
* to `free()` allocated memory. File content is guaranteed to be
|
||||
* '\0'-terminated. File size is returned in `size` variable, which does not
|
||||
* count terminating `\0`.
|
||||
* Return: allocated memory, or NULL on error.
|
||||
*/
|
||||
char *cs_read_file(const char *path, size_t *size);
|
||||
|
||||
#ifdef CS_MMAP
|
||||
/*
|
||||
* Only on platforms which support mmapping: mmap file `path` to the returned
|
||||
* address. File size is written to `*size`.
|
||||
*/
|
||||
char *cs_mmap_file(const char *path, size_t *size);
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_FILE_H_ */
|
||||
91
lib/mjs/common/cs_time.c
Normal file
91
lib/mjs/common/cs_time.c
Normal file
@@ -0,0 +1,91 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_time.h"
|
||||
|
||||
#if CS_ENABLE_STDIO
|
||||
|
||||
#ifndef _WIN32
|
||||
#include <stddef.h>
|
||||
/*
|
||||
* There is no sys/time.h on ARMCC.
|
||||
*/
|
||||
#if !(defined(__ARMCC_VERSION) || defined(__ICCARM__)) && !defined(__TI_COMPILER_VERSION__) && \
|
||||
(!defined(CS_PLATFORM) || CS_PLATFORM != CS_P_NXP_LPC)
|
||||
#include <sys/time.h>
|
||||
#endif
|
||||
#else
|
||||
#include <windows.h>
|
||||
#endif
|
||||
|
||||
double cs_time(void) WEAK;
|
||||
double cs_time(void) {
|
||||
double now;
|
||||
#ifndef _WIN32
|
||||
struct timeval tv;
|
||||
if(gettimeofday(&tv, NULL /* tz */) != 0) return 0;
|
||||
now = (double)tv.tv_sec + (((double)tv.tv_usec) / (double)1000000.0);
|
||||
#else
|
||||
SYSTEMTIME sysnow;
|
||||
FILETIME ftime;
|
||||
GetLocalTime(&sysnow);
|
||||
SystemTimeToFileTime(&sysnow, &ftime);
|
||||
/*
|
||||
* 1. VC 6.0 doesn't support conversion uint64 -> double, so, using int64
|
||||
* This should not cause a problems in this (21th) century
|
||||
* 2. Windows FILETIME is a number of 100-nanosecond intervals since January
|
||||
* 1, 1601 while time_t is a number of _seconds_ since January 1, 1970 UTC,
|
||||
* thus, we need to convert to seconds and adjust amount (subtract 11644473600
|
||||
* seconds)
|
||||
*/
|
||||
now = (double)(((int64_t)ftime.dwLowDateTime + ((int64_t)ftime.dwHighDateTime << 32)) /
|
||||
10000000.0) -
|
||||
11644473600;
|
||||
#endif /* _WIN32 */
|
||||
return now;
|
||||
}
|
||||
|
||||
double cs_timegm(const struct tm* tm) {
|
||||
/* Month-to-day offset for non-leap-years. */
|
||||
static const int month_day[12] = {0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334};
|
||||
|
||||
/* Most of the calculation is easy; leap years are the main difficulty. */
|
||||
int month = tm->tm_mon % 12;
|
||||
int year = tm->tm_year + tm->tm_mon / 12;
|
||||
int year_for_leap;
|
||||
int64_t rt;
|
||||
|
||||
if(month < 0) { /* Negative values % 12 are still negative. */
|
||||
month += 12;
|
||||
--year;
|
||||
}
|
||||
|
||||
/* This is the number of Februaries since 1900. */
|
||||
year_for_leap = (month > 1) ? year + 1 : year;
|
||||
|
||||
rt = tm->tm_sec /* Seconds */
|
||||
+ 60 * (tm->tm_min /* Minute = 60 seconds */
|
||||
+ 60 * (tm->tm_hour /* Hour = 60 minutes */
|
||||
+ 24 * (month_day[month] + tm->tm_mday - 1 /* Day = 24 hours */
|
||||
+ 365 * (year - 70) /* Year = 365 days */
|
||||
+ (year_for_leap - 69) / 4 /* Every 4 years is leap... */
|
||||
- (year_for_leap - 1) / 100 /* Except centuries... */
|
||||
+ (year_for_leap + 299) / 400))); /* Except 400s. */
|
||||
return rt < 0 ? -1 : (double)rt;
|
||||
}
|
||||
|
||||
#endif
|
||||
42
lib/mjs/common/cs_time.h
Normal file
42
lib/mjs/common/cs_time.h
Normal file
@@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_TIME_H_
|
||||
#define CS_COMMON_CS_TIME_H_
|
||||
|
||||
#include <time.h>
|
||||
|
||||
#include "platform.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* Sub-second granularity time(). */
|
||||
double cs_time(void);
|
||||
|
||||
/*
|
||||
* Similar to (non-standard) timegm, converts broken-down time into the number
|
||||
* of seconds since Unix Epoch.
|
||||
*/
|
||||
double cs_timegm(const struct tm* tm);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_CS_TIME_H_ */
|
||||
76
lib/mjs/common/cs_varint.c
Normal file
76
lib/mjs/common/cs_varint.c
Normal file
@@ -0,0 +1,76 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "cs_varint.h"
|
||||
|
||||
size_t cs_varint_llen(uint64_t num) {
|
||||
size_t llen = 0;
|
||||
|
||||
do {
|
||||
llen++;
|
||||
} while (num >>= 7);
|
||||
|
||||
return llen;
|
||||
}
|
||||
|
||||
size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size) {
|
||||
size_t llen = 0;
|
||||
|
||||
do {
|
||||
uint8_t byte = num & 0x7f;
|
||||
num >>= 7;
|
||||
if (num != 0) byte |= 0x80;
|
||||
if (llen < buf_size) *buf++ = byte;
|
||||
llen++;
|
||||
} while (num != 0);
|
||||
|
||||
return llen;
|
||||
}
|
||||
|
||||
bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num,
|
||||
size_t *llen) {
|
||||
size_t i = 0, shift = 0;
|
||||
uint64_t n = 0;
|
||||
|
||||
do {
|
||||
if (i == buf_size || i == (8 * sizeof(*num) / 7 + 1)) return false;
|
||||
/*
|
||||
* Each byte of varint contains 7 bits, in little endian order.
|
||||
* MSB is a continuation bit: it tells whether next byte is used.
|
||||
*/
|
||||
n |= ((uint64_t)(buf[i] & 0x7f)) << shift;
|
||||
/*
|
||||
* First we increment i, then check whether it is within boundary and
|
||||
* whether decoded byte had continuation bit set.
|
||||
*/
|
||||
i++;
|
||||
shift += 7;
|
||||
} while (shift < sizeof(uint64_t) * 8 && (buf[i - 1] & 0x80));
|
||||
|
||||
*num = n;
|
||||
*llen = i;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen) {
|
||||
uint64_t v;
|
||||
size_t l;
|
||||
cs_varint_decode(buf, ~0, &v, &l);
|
||||
*llen = l;
|
||||
return v;
|
||||
}
|
||||
59
lib/mjs/common/cs_varint.h
Normal file
59
lib/mjs/common/cs_varint.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_CS_VARINT_H_
|
||||
#define CS_COMMON_CS_VARINT_H_
|
||||
|
||||
#if defined(_WIN32) && _MSC_VER < 1700
|
||||
typedef unsigned char uint8_t;
|
||||
typedef unsigned __int64 uint64_t;
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Returns number of bytes required to encode `num`. */
|
||||
size_t cs_varint_llen(uint64_t num);
|
||||
|
||||
/*
|
||||
* Encodes `num` into `buf`.
|
||||
* Returns number of bytes required to encode `num`.
|
||||
* Note: return value may be greater than `buf_size` but the function will only
|
||||
* write `buf_size` bytes.
|
||||
*/
|
||||
size_t cs_varint_encode(uint64_t num, uint8_t *buf, size_t buf_size);
|
||||
|
||||
/*
|
||||
* Decodes varint stored in `buf`.
|
||||
* Stores the number of bytes consumed into `llen`.
|
||||
* If there aren't enough bytes in `buf` to decode a number, returns false.
|
||||
*/
|
||||
bool cs_varint_decode(const uint8_t *buf, size_t buf_size, uint64_t *num,
|
||||
size_t *llen);
|
||||
|
||||
uint64_t cs_varint_decode_unsafe(const uint8_t *buf, int *llen);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_CS_VARINT_H_ */
|
||||
1528
lib/mjs/common/frozen/frozen.c
Normal file
1528
lib/mjs/common/frozen/frozen.c
Normal file
File diff suppressed because it is too large
Load Diff
359
lib/mjs/common/frozen/frozen.h
Normal file
359
lib/mjs/common/frozen/frozen.h
Normal file
@@ -0,0 +1,359 @@
|
||||
/*
|
||||
* Copyright (c) 2004-2013 Sergey Lyubka <valenok@gmail.com>
|
||||
* Copyright (c) 2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_FROZEN_FROZEN_H_
|
||||
#define CS_FROZEN_FROZEN_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stddef.h>
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(_WIN32) && _MSC_VER < 1700
|
||||
typedef int bool;
|
||||
enum { false = 0, true = 1 };
|
||||
#else
|
||||
#include <stdbool.h>
|
||||
#endif
|
||||
|
||||
/* JSON token type */
|
||||
enum json_token_type {
|
||||
JSON_TYPE_INVALID = 0, /* memsetting to 0 should create INVALID value */
|
||||
JSON_TYPE_STRING,
|
||||
JSON_TYPE_NUMBER,
|
||||
JSON_TYPE_TRUE,
|
||||
JSON_TYPE_FALSE,
|
||||
JSON_TYPE_NULL,
|
||||
JSON_TYPE_OBJECT_START,
|
||||
JSON_TYPE_OBJECT_END,
|
||||
JSON_TYPE_ARRAY_START,
|
||||
JSON_TYPE_ARRAY_END,
|
||||
|
||||
JSON_TYPES_CNT
|
||||
};
|
||||
|
||||
/*
|
||||
* Structure containing token type and value. Used in `json_walk()` and
|
||||
* `json_scanf()` with the format specifier `%T`.
|
||||
*/
|
||||
struct json_token {
|
||||
const char* ptr; /* Points to the beginning of the value */
|
||||
int len; /* Value length */
|
||||
enum json_token_type type; /* Type of the token, possible values are above */
|
||||
};
|
||||
|
||||
#define JSON_INVALID_TOKEN \
|
||||
{ 0, 0, JSON_TYPE_INVALID }
|
||||
|
||||
/* Error codes */
|
||||
#define JSON_STRING_INVALID -1
|
||||
#define JSON_STRING_INCOMPLETE -2
|
||||
|
||||
/*
|
||||
* Callback-based SAX-like API.
|
||||
*
|
||||
* Property name and length is given only if it's available: i.e. if current
|
||||
* event is an object's property. In other cases, `name` is `NULL`. For
|
||||
* example, name is never given:
|
||||
* - For the first value in the JSON string;
|
||||
* - For events JSON_TYPE_OBJECT_END and JSON_TYPE_ARRAY_END
|
||||
*
|
||||
* E.g. for the input `{ "foo": 123, "bar": [ 1, 2, { "baz": true } ] }`,
|
||||
* the sequence of callback invocations will be as follows:
|
||||
*
|
||||
* - type: JSON_TYPE_OBJECT_START, name: NULL, path: "", value: NULL
|
||||
* - type: JSON_TYPE_NUMBER, name: "foo", path: ".foo", value: "123"
|
||||
* - type: JSON_TYPE_ARRAY_START, name: "bar", path: ".bar", value: NULL
|
||||
* - type: JSON_TYPE_NUMBER, name: "0", path: ".bar[0]", value: "1"
|
||||
* - type: JSON_TYPE_NUMBER, name: "1", path: ".bar[1]", value: "2"
|
||||
* - type: JSON_TYPE_OBJECT_START, name: "2", path: ".bar[2]", value: NULL
|
||||
* - type: JSON_TYPE_TRUE, name: "baz", path: ".bar[2].baz", value: "true"
|
||||
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: ".bar[2]", value: "{ \"baz\":
|
||||
*true }"
|
||||
* - type: JSON_TYPE_ARRAY_END, name: NULL, path: ".bar", value: "[ 1, 2, {
|
||||
*\"baz\": true } ]"
|
||||
* - type: JSON_TYPE_OBJECT_END, name: NULL, path: "", value: "{ \"foo\": 123,
|
||||
*\"bar\": [ 1, 2, { \"baz\": true } ] }"
|
||||
*/
|
||||
typedef void (*json_walk_callback_t)(
|
||||
void* callback_data,
|
||||
const char* name,
|
||||
size_t name_len,
|
||||
const char* path,
|
||||
const struct json_token* token);
|
||||
|
||||
/*
|
||||
* Parse `json_string`, invoking `callback` in a way similar to SAX parsers;
|
||||
* see `json_walk_callback_t`.
|
||||
* Return number of processed bytes, or a negative error code.
|
||||
*/
|
||||
int json_walk(
|
||||
const char* json_string,
|
||||
int json_string_length,
|
||||
json_walk_callback_t callback,
|
||||
void* callback_data);
|
||||
|
||||
/*
|
||||
* JSON generation API.
|
||||
* struct json_out abstracts output, allowing alternative printing plugins.
|
||||
*/
|
||||
struct json_out {
|
||||
int (*printer)(struct json_out*, const char* str, size_t len);
|
||||
union {
|
||||
struct {
|
||||
char* buf;
|
||||
size_t size;
|
||||
size_t len;
|
||||
} buf;
|
||||
void* data;
|
||||
FILE* fp;
|
||||
} u;
|
||||
};
|
||||
|
||||
extern int json_printer_buf(struct json_out*, const char*, size_t);
|
||||
extern int json_printer_file(struct json_out*, const char*, size_t);
|
||||
|
||||
#define JSON_OUT_BUF(buf, len) \
|
||||
{ \
|
||||
json_printer_buf, { \
|
||||
{ buf, len, 0 } \
|
||||
} \
|
||||
}
|
||||
#define JSON_OUT_FILE(fp) \
|
||||
{ \
|
||||
json_printer_file, { \
|
||||
{ (char*)fp, 0, 0 } \
|
||||
} \
|
||||
}
|
||||
|
||||
typedef int (*json_printf_callback_t)(struct json_out*, va_list* ap);
|
||||
|
||||
/*
|
||||
* Generate formatted output into a given sting buffer.
|
||||
* This is a superset of printf() function, with extra format specifiers:
|
||||
* - `%B` print json boolean, `true` or `false`. Accepts an `int`.
|
||||
* - `%Q` print quoted escaped string or `null`. Accepts a `const char *`.
|
||||
* - `%.*Q` same as `%Q`, but with length. Accepts `int`, `const char *`
|
||||
* - `%V` print quoted base64-encoded string. Accepts a `const char *`, `int`.
|
||||
* - `%H` print quoted hex-encoded string. Accepts a `int`, `const char *`.
|
||||
* - `%M` invokes a json_printf_callback_t function. That callback function
|
||||
* can consume more parameters.
|
||||
*
|
||||
* Return number of bytes printed. If the return value is bigger than the
|
||||
* supplied buffer, that is an indicator of overflow. In the overflow case,
|
||||
* overflown bytes are not printed.
|
||||
*/
|
||||
int json_printf(struct json_out*, const char* fmt, ...);
|
||||
int json_vprintf(struct json_out*, const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Same as json_printf, but prints to a file.
|
||||
* File is created if does not exist. File is truncated if already exists.
|
||||
*/
|
||||
int json_fprintf(const char* file_name, const char* fmt, ...);
|
||||
int json_vfprintf(const char* file_name, const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Print JSON into an allocated 0-terminated string.
|
||||
* Return allocated string, or NULL on error.
|
||||
* Example:
|
||||
*
|
||||
* ```c
|
||||
* char *str = json_asprintf("{a:%H}", 3, "abc");
|
||||
* printf("%s\n", str); // Prints "616263"
|
||||
* free(str);
|
||||
* ```
|
||||
*/
|
||||
char* json_asprintf(const char* fmt, ...);
|
||||
char* json_vasprintf(const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* Helper %M callback that prints contiguous C arrays.
|
||||
* Consumes void *array_ptr, size_t array_size, size_t elem_size, char *fmt
|
||||
* Return number of bytes printed.
|
||||
*/
|
||||
int json_printf_array(struct json_out*, va_list* ap);
|
||||
|
||||
/*
|
||||
* Scan JSON string `str`, performing scanf-like conversions according to `fmt`.
|
||||
* This is a `scanf()` - like function, with following differences:
|
||||
*
|
||||
* 1. Object keys in the format string may be not quoted, e.g. "{key: %d}"
|
||||
* 2. Order of keys in an object is irrelevant.
|
||||
* 3. Several extra format specifiers are supported:
|
||||
* - %B: consumes `int *` (or `char *`, if `sizeof(bool) == sizeof(char)`),
|
||||
* expects boolean `true` or `false`.
|
||||
* - %Q: consumes `char **`, expects quoted, JSON-encoded string. Scanned
|
||||
* string is malloc-ed, caller must free() the string.
|
||||
* - %V: consumes `char **`, `int *`. Expects base64-encoded string.
|
||||
* Result string is base64-decoded, malloced and NUL-terminated.
|
||||
* The length of result string is stored in `int *` placeholder.
|
||||
* Caller must free() the result.
|
||||
* - %H: consumes `int *`, `char **`.
|
||||
* Expects a hex-encoded string, e.g. "fa014f".
|
||||
* Result string is hex-decoded, malloced and NUL-terminated.
|
||||
* The length of the result string is stored in `int *` placeholder.
|
||||
* Caller must free() the result.
|
||||
* - %M: consumes custom scanning function pointer and
|
||||
* `void *user_data` parameter - see json_scanner_t definition.
|
||||
* - %T: consumes `struct json_token *`, fills it out with matched token.
|
||||
*
|
||||
* Return number of elements successfully scanned & converted.
|
||||
* Negative number means scan error.
|
||||
*/
|
||||
int json_scanf(const char* str, int str_len, const char* fmt, ...);
|
||||
int json_vscanf(const char* str, int str_len, const char* fmt, va_list ap);
|
||||
|
||||
/* json_scanf's %M handler */
|
||||
typedef void (*json_scanner_t)(const char* str, int len, void* user_data);
|
||||
|
||||
/*
|
||||
* Helper function to scan array item with given path and index.
|
||||
* Fills `token` with the matched JSON token.
|
||||
* Return -1 if no array element found, otherwise non-negative token length.
|
||||
*/
|
||||
int json_scanf_array_elem(
|
||||
const char* s,
|
||||
int len,
|
||||
const char* path,
|
||||
int index,
|
||||
struct json_token* token);
|
||||
|
||||
/*
|
||||
* Unescape JSON-encoded string src,slen into dst, dlen.
|
||||
* src and dst may overlap.
|
||||
* If destination buffer is too small (or zero-length), result string is not
|
||||
* written but the length is counted nevertheless (similar to snprintf).
|
||||
* Return the length of unescaped string in bytes.
|
||||
*/
|
||||
int json_unescape(const char* src, int slen, char* dst, int dlen);
|
||||
|
||||
/*
|
||||
* Escape a string `str`, `str_len` into the printer `out`.
|
||||
* Return the number of bytes printed.
|
||||
*/
|
||||
int json_escape(struct json_out* out, const char* str, size_t str_len);
|
||||
|
||||
/*
|
||||
* Read the whole file in memory.
|
||||
* Return malloc-ed file content, or NULL on error. The caller must free().
|
||||
*/
|
||||
char* json_fread(const char* file_name);
|
||||
|
||||
/*
|
||||
* Update given JSON string `s,len` by changing the value at given `json_path`.
|
||||
* The result is saved to `out`. If `json_fmt` == NULL, that deletes the key.
|
||||
* If path is not present, missing keys are added. Array path without an
|
||||
* index pushes a value to the end of an array.
|
||||
* Return 1 if the string was changed, 0 otherwise.
|
||||
*
|
||||
* Example: s is a JSON string { "a": 1, "b": [ 2 ] }
|
||||
* json_setf(s, len, out, ".a", "7"); // { "a": 7, "b": [ 2 ] }
|
||||
* json_setf(s, len, out, ".b", "7"); // { "a": 1, "b": 7 }
|
||||
* json_setf(s, len, out, ".b[]", "7"); // { "a": 1, "b": [ 2,7 ] }
|
||||
* json_setf(s, len, out, ".b", NULL); // { "a": 1 }
|
||||
*/
|
||||
int json_setf(
|
||||
const char* s,
|
||||
int len,
|
||||
struct json_out* out,
|
||||
const char* json_path,
|
||||
const char* json_fmt,
|
||||
...);
|
||||
|
||||
int json_vsetf(
|
||||
const char* s,
|
||||
int len,
|
||||
struct json_out* out,
|
||||
const char* json_path,
|
||||
const char* json_fmt,
|
||||
va_list ap);
|
||||
|
||||
/*
|
||||
* Pretty-print JSON string `s,len` into `out`.
|
||||
* Return number of processed bytes in `s`.
|
||||
*/
|
||||
int json_prettify(const char* s, int len, struct json_out* out);
|
||||
|
||||
/*
|
||||
* Prettify JSON file `file_name`.
|
||||
* Return number of processed bytes, or negative number of error.
|
||||
* On error, file content is not modified.
|
||||
*/
|
||||
int json_prettify_file(const char* file_name);
|
||||
|
||||
/*
|
||||
* Iterate over an object at given JSON `path`.
|
||||
* On each iteration, fill the `key` and `val` tokens. It is OK to pass NULL
|
||||
* for `key`, or `val`, in which case they won't be populated.
|
||||
* Return an opaque value suitable for the next iteration, or NULL when done.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* ```c
|
||||
* void *h = NULL;
|
||||
* struct json_token key, val;
|
||||
* while ((h = json_next_key(s, len, h, ".foo", &key, &val)) != NULL) {
|
||||
* printf("[%.*s] -> [%.*s]\n", key.len, key.ptr, val.len, val.ptr);
|
||||
* }
|
||||
* ```
|
||||
*/
|
||||
void* json_next_key(
|
||||
const char* s,
|
||||
int len,
|
||||
void* handle,
|
||||
const char* path,
|
||||
struct json_token* key,
|
||||
struct json_token* val);
|
||||
|
||||
/*
|
||||
* Iterate over an array at given JSON `path`.
|
||||
* Similar to `json_next_key`, but fills array index `idx` instead of `key`.
|
||||
*/
|
||||
void* json_next_elem(
|
||||
const char* s,
|
||||
int len,
|
||||
void* handle,
|
||||
const char* path,
|
||||
int* idx,
|
||||
struct json_token* val);
|
||||
|
||||
#ifndef JSON_MAX_PATH_LEN
|
||||
#define JSON_MAX_PATH_LEN 256
|
||||
#endif
|
||||
|
||||
#ifndef JSON_MINIMAL
|
||||
#define JSON_MINIMAL 0
|
||||
#endif
|
||||
|
||||
#ifndef JSON_ENABLE_BASE64
|
||||
#define JSON_ENABLE_BASE64 !JSON_MINIMAL
|
||||
#endif
|
||||
|
||||
#ifndef JSON_ENABLE_HEX
|
||||
#define JSON_ENABLE_HEX !JSON_MINIMAL
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_FROZEN_FROZEN_H_ */
|
||||
151
lib/mjs/common/mbuf.c
Normal file
151
lib/mjs/common/mbuf.c
Normal file
@@ -0,0 +1,151 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef EXCLUDE_COMMON
|
||||
|
||||
#include <assert.h>
|
||||
#include <string.h>
|
||||
#include "mbuf.h"
|
||||
|
||||
#ifndef MBUF_REALLOC
|
||||
#define MBUF_REALLOC realloc
|
||||
#endif
|
||||
|
||||
#ifndef MBUF_FREE
|
||||
#define MBUF_FREE free
|
||||
#endif
|
||||
|
||||
void mbuf_init(struct mbuf *mbuf, size_t initial_size) WEAK;
|
||||
void mbuf_init(struct mbuf *mbuf, size_t initial_size) {
|
||||
mbuf->len = mbuf->size = 0;
|
||||
mbuf->buf = NULL;
|
||||
mbuf_resize(mbuf, initial_size);
|
||||
}
|
||||
|
||||
void mbuf_free(struct mbuf *mbuf) WEAK;
|
||||
void mbuf_free(struct mbuf *mbuf) {
|
||||
if (mbuf->buf != NULL) {
|
||||
MBUF_FREE(mbuf->buf);
|
||||
mbuf_init(mbuf, 0);
|
||||
}
|
||||
}
|
||||
|
||||
void mbuf_resize(struct mbuf *a, size_t new_size) WEAK;
|
||||
void mbuf_resize(struct mbuf *a, size_t new_size) {
|
||||
if (new_size > a->size || (new_size < a->size && new_size >= a->len)) {
|
||||
char *buf = (char *) MBUF_REALLOC(a->buf, new_size);
|
||||
/*
|
||||
* In case realloc fails, there's not much we can do, except keep things as
|
||||
* they are. Note that NULL is a valid return value from realloc when
|
||||
* size == 0, but that is covered too.
|
||||
*/
|
||||
if (buf == NULL && new_size != 0) return;
|
||||
a->buf = buf;
|
||||
a->size = new_size;
|
||||
}
|
||||
}
|
||||
|
||||
void mbuf_trim(struct mbuf *mbuf) WEAK;
|
||||
void mbuf_trim(struct mbuf *mbuf) {
|
||||
mbuf_resize(mbuf, mbuf->len);
|
||||
}
|
||||
|
||||
size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t) WEAK;
|
||||
size_t mbuf_insert(struct mbuf *a, size_t off, const void *buf, size_t len) {
|
||||
char *p = NULL;
|
||||
|
||||
assert(a != NULL);
|
||||
assert(a->len <= a->size);
|
||||
assert(off <= a->len);
|
||||
|
||||
/* check overflow */
|
||||
if (~(size_t) 0 - (size_t) a->buf < len) return 0;
|
||||
|
||||
if (a->len + len <= a->size) {
|
||||
memmove(a->buf + off + len, a->buf + off, a->len - off);
|
||||
if (buf != NULL) {
|
||||
memcpy(a->buf + off, buf, len);
|
||||
}
|
||||
a->len += len;
|
||||
} else {
|
||||
size_t min_size = (a->len + len);
|
||||
size_t new_size = (size_t)(min_size * MBUF_SIZE_MULTIPLIER);
|
||||
if (new_size - min_size > MBUF_SIZE_MAX_HEADROOM) {
|
||||
new_size = min_size + MBUF_SIZE_MAX_HEADROOM;
|
||||
}
|
||||
p = (char *) MBUF_REALLOC(a->buf, new_size);
|
||||
if (p == NULL && new_size != min_size) {
|
||||
new_size = min_size;
|
||||
p = (char *) MBUF_REALLOC(a->buf, new_size);
|
||||
}
|
||||
if (p != NULL) {
|
||||
a->buf = p;
|
||||
if (off != a->len) {
|
||||
memmove(a->buf + off + len, a->buf + off, a->len - off);
|
||||
}
|
||||
if (buf != NULL) memcpy(a->buf + off, buf, len);
|
||||
a->len += len;
|
||||
a->size = new_size;
|
||||
} else {
|
||||
len = 0;
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) WEAK;
|
||||
size_t mbuf_append(struct mbuf *a, const void *buf, size_t len) {
|
||||
return mbuf_insert(a, a->len, buf, len);
|
||||
}
|
||||
|
||||
size_t mbuf_append_and_free(struct mbuf *a, void *buf, size_t len) WEAK;
|
||||
size_t mbuf_append_and_free(struct mbuf *a, void *data, size_t len) {
|
||||
size_t ret;
|
||||
/* Optimization: if the buffer is currently empty,
|
||||
* take over the user-provided buffer. */
|
||||
if (a->len == 0) {
|
||||
if (a->buf != NULL) free(a->buf);
|
||||
a->buf = (char *) data;
|
||||
a->len = a->size = len;
|
||||
return len;
|
||||
}
|
||||
ret = mbuf_insert(a, a->len, data, len);
|
||||
free(data);
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mbuf_remove(struct mbuf *mb, size_t n) WEAK;
|
||||
void mbuf_remove(struct mbuf *mb, size_t n) {
|
||||
if (n > 0 && n <= mb->len) {
|
||||
memmove(mb->buf, mb->buf + n, mb->len - n);
|
||||
mb->len -= n;
|
||||
}
|
||||
}
|
||||
|
||||
void mbuf_clear(struct mbuf *mb) WEAK;
|
||||
void mbuf_clear(struct mbuf *mb) {
|
||||
mb->len = 0;
|
||||
}
|
||||
|
||||
void mbuf_move(struct mbuf *from, struct mbuf *to) WEAK;
|
||||
void mbuf_move(struct mbuf *from, struct mbuf *to) {
|
||||
memcpy(to, from, sizeof(*to));
|
||||
memset(from, 0, sizeof(*from));
|
||||
}
|
||||
|
||||
#endif /* EXCLUDE_COMMON */
|
||||
111
lib/mjs/common/mbuf.h
Normal file
111
lib/mjs/common/mbuf.h
Normal file
@@ -0,0 +1,111 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
/*
|
||||
* Mbufs are mutable/growing memory buffers, like C++ strings.
|
||||
* Mbuf can append data to the end of a buffer or insert data into arbitrary
|
||||
* position in the middle of a buffer. The buffer grows automatically when
|
||||
* needed.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_MBUF_H_
|
||||
#define CS_COMMON_MBUF_H_
|
||||
|
||||
#include <stdlib.h>
|
||||
#include "platform.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef MBUF_SIZE_MULTIPLIER
|
||||
#define MBUF_SIZE_MULTIPLIER 1.5
|
||||
#endif
|
||||
|
||||
#ifndef MBUF_SIZE_MAX_HEADROOM
|
||||
#ifdef BUFSIZ
|
||||
#define MBUF_SIZE_MAX_HEADROOM BUFSIZ
|
||||
#else
|
||||
#define MBUF_SIZE_MAX_HEADROOM 1024
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/* Memory buffer descriptor */
|
||||
struct mbuf {
|
||||
char *buf; /* Buffer pointer */
|
||||
size_t len; /* Data length. Data is located between offset 0 and len. */
|
||||
size_t size; /* Buffer size allocated by realloc(1). Must be >= len */
|
||||
};
|
||||
|
||||
/*
|
||||
* Initialises an Mbuf.
|
||||
* `initial_capacity` specifies the initial capacity of the mbuf.
|
||||
*/
|
||||
void mbuf_init(struct mbuf *, size_t initial_capacity);
|
||||
|
||||
/* Frees the space allocated for the mbuffer and resets the mbuf structure. */
|
||||
void mbuf_free(struct mbuf *);
|
||||
|
||||
/*
|
||||
* Appends data to the Mbuf.
|
||||
*
|
||||
* Returns the number of bytes appended or 0 if out of memory.
|
||||
*/
|
||||
size_t mbuf_append(struct mbuf *, const void *data, size_t data_size);
|
||||
|
||||
/*
|
||||
* Appends data to the Mbuf and frees it (data must be heap-allocated).
|
||||
*
|
||||
* Returns the number of bytes appended or 0 if out of memory.
|
||||
* data is freed irrespective of return value.
|
||||
*/
|
||||
size_t mbuf_append_and_free(struct mbuf *, void *data, size_t data_size);
|
||||
|
||||
/*
|
||||
* Inserts data at a specified offset in the Mbuf.
|
||||
*
|
||||
* Existing data will be shifted forwards and the buffer will
|
||||
* be grown if necessary.
|
||||
* Returns the number of bytes inserted.
|
||||
*/
|
||||
size_t mbuf_insert(struct mbuf *, size_t, const void *, size_t);
|
||||
|
||||
/* Removes `data_size` bytes from the beginning of the buffer. */
|
||||
void mbuf_remove(struct mbuf *, size_t data_size);
|
||||
|
||||
/*
|
||||
* Resizes an Mbuf.
|
||||
*
|
||||
* If `new_size` is smaller than buffer's `len`, the
|
||||
* resize is not performed.
|
||||
*/
|
||||
void mbuf_resize(struct mbuf *, size_t new_size);
|
||||
|
||||
/* Moves the state from one mbuf to the other. */
|
||||
void mbuf_move(struct mbuf *from, struct mbuf *to);
|
||||
|
||||
/* Removes all the data from mbuf (if any). */
|
||||
void mbuf_clear(struct mbuf *);
|
||||
|
||||
/* Shrinks an Mbuf by resizing its `size` to `len`. */
|
||||
void mbuf_trim(struct mbuf *);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* CS_COMMON_MBUF_H_ */
|
||||
45
lib/mjs/common/mg_mem.h
Normal file
45
lib/mjs/common/mg_mem.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_MG_MEM_H_
|
||||
#define CS_COMMON_MG_MEM_H_
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifndef MG_MALLOC
|
||||
#define MG_MALLOC malloc
|
||||
#endif
|
||||
|
||||
#ifndef MG_CALLOC
|
||||
#define MG_CALLOC calloc
|
||||
#endif
|
||||
|
||||
#ifndef MG_REALLOC
|
||||
#define MG_REALLOC realloc
|
||||
#endif
|
||||
|
||||
#ifndef MG_FREE
|
||||
#define MG_FREE free
|
||||
#endif
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_MG_MEM_H_ */
|
||||
175
lib/mjs/common/mg_str.c
Normal file
175
lib/mjs/common/mg_str.c
Normal file
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#include "mg_mem.h"
|
||||
#include "mg_str.h"
|
||||
#include "platform.h"
|
||||
|
||||
#include <ctype.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK;
|
||||
|
||||
struct mg_str mg_mk_str(const char* s) WEAK;
|
||||
struct mg_str mg_mk_str(const char* s) {
|
||||
struct mg_str ret = {s, 0};
|
||||
if(s != NULL) ret.len = strlen(s);
|
||||
return ret;
|
||||
}
|
||||
|
||||
struct mg_str mg_mk_str_n(const char* s, size_t len) WEAK;
|
||||
struct mg_str mg_mk_str_n(const char* s, size_t len) {
|
||||
struct mg_str ret = {s, len};
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mg_vcmp(const struct mg_str* str1, const char* str2) WEAK;
|
||||
int mg_vcmp(const struct mg_str* str1, const char* str2) {
|
||||
size_t n2 = strlen(str2), n1 = str1->len;
|
||||
int r = strncmp(str1->p, str2, (n1 < n2) ? n1 : n2);
|
||||
if(r == 0) {
|
||||
return n1 - n2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
int mg_vcasecmp(const struct mg_str* str1, const char* str2) WEAK;
|
||||
int mg_vcasecmp(const struct mg_str* str1, const char* str2) {
|
||||
size_t n2 = strlen(str2), n1 = str1->len;
|
||||
int r = mg_ncasecmp(str1->p, str2, (n1 < n2) ? n1 : n2);
|
||||
if(r == 0) {
|
||||
return n1 - n2;
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
static struct mg_str mg_strdup_common(const struct mg_str s, int nul_terminate) {
|
||||
struct mg_str r = {NULL, 0};
|
||||
if(s.len > 0 && s.p != NULL) {
|
||||
char* sc = (char*)MG_MALLOC(s.len + (nul_terminate ? 1 : 0));
|
||||
if(sc != NULL) {
|
||||
memcpy(sc, s.p, s.len);
|
||||
if(nul_terminate) sc[s.len] = '\0';
|
||||
r.p = sc;
|
||||
r.len = s.len;
|
||||
}
|
||||
}
|
||||
return r;
|
||||
}
|
||||
|
||||
struct mg_str mg_strdup(const struct mg_str s) WEAK;
|
||||
struct mg_str mg_strdup(const struct mg_str s) {
|
||||
return mg_strdup_common(s, 0 /* NUL-terminate */);
|
||||
}
|
||||
|
||||
struct mg_str mg_strdup_nul(const struct mg_str s) WEAK;
|
||||
struct mg_str mg_strdup_nul(const struct mg_str s) {
|
||||
return mg_strdup_common(s, 1 /* NUL-terminate */);
|
||||
}
|
||||
|
||||
const char* mg_strchr(const struct mg_str s, int c) WEAK;
|
||||
const char* mg_strchr(const struct mg_str s, int c) {
|
||||
size_t i;
|
||||
for(i = 0; i < s.len; i++) {
|
||||
if(s.p[i] == c) return &s.p[i];
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int mg_strcmp(const struct mg_str str1, const struct mg_str str2) WEAK;
|
||||
int mg_strcmp(const struct mg_str str1, const struct mg_str str2) {
|
||||
size_t i = 0;
|
||||
while(i < str1.len && i < str2.len) {
|
||||
int c1 = str1.p[i];
|
||||
int c2 = str2.p[i];
|
||||
if(c1 < c2) return -1;
|
||||
if(c1 > c2) return 1;
|
||||
i++;
|
||||
}
|
||||
if(i < str1.len) return 1;
|
||||
if(i < str2.len) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int mg_strncmp(const struct mg_str, const struct mg_str, size_t n) WEAK;
|
||||
int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n) {
|
||||
struct mg_str s1 = str1;
|
||||
struct mg_str s2 = str2;
|
||||
|
||||
if(s1.len > n) {
|
||||
s1.len = n;
|
||||
}
|
||||
if(s2.len > n) {
|
||||
s2.len = n;
|
||||
}
|
||||
return mg_strcmp(s1, s2);
|
||||
}
|
||||
|
||||
int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) WEAK;
|
||||
int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2) {
|
||||
size_t i = 0;
|
||||
while(i < str1.len && i < str2.len) {
|
||||
int c1 = tolower((int)str1.p[i]);
|
||||
int c2 = tolower((int)str2.p[i]);
|
||||
if(c1 < c2) return -1;
|
||||
if(c1 > c2) return 1;
|
||||
i++;
|
||||
}
|
||||
if(i < str1.len) return 1;
|
||||
if(i < str2.len) return -1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void mg_strfree(struct mg_str* s) WEAK;
|
||||
void mg_strfree(struct mg_str* s) {
|
||||
char* sp = (char*)s->p;
|
||||
s->p = NULL;
|
||||
s->len = 0;
|
||||
if(sp != NULL) free(sp);
|
||||
}
|
||||
|
||||
const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) WEAK;
|
||||
const char* mg_strstr(const struct mg_str haystack, const struct mg_str needle) {
|
||||
size_t i;
|
||||
if(needle.len > haystack.len) return NULL;
|
||||
for(i = 0; i <= haystack.len - needle.len; i++) {
|
||||
if(memcmp(haystack.p + i, needle.p, needle.len) == 0) {
|
||||
return haystack.p + i;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
struct mg_str mg_strstrip(struct mg_str s) WEAK;
|
||||
struct mg_str mg_strstrip(struct mg_str s) {
|
||||
while(s.len > 0 && isspace((int)*s.p)) {
|
||||
s.p++;
|
||||
s.len--;
|
||||
}
|
||||
while(s.len > 0 && isspace((int)*(s.p + s.len - 1))) {
|
||||
s.len--;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int mg_str_starts_with(struct mg_str s, struct mg_str prefix) WEAK;
|
||||
int mg_str_starts_with(struct mg_str s, struct mg_str prefix) {
|
||||
const struct mg_str sp = MG_MK_STR_N(s.p, prefix.len);
|
||||
if(s.len < prefix.len) return 0;
|
||||
return (mg_strcmp(sp, prefix) == 0);
|
||||
}
|
||||
113
lib/mjs/common/mg_str.h
Normal file
113
lib/mjs/common/mg_str.h
Normal file
@@ -0,0 +1,113 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_MG_STR_H_
|
||||
#define CS_COMMON_MG_STR_H_
|
||||
|
||||
#include <stddef.h>
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/* Describes chunk of memory */
|
||||
struct mg_str {
|
||||
const char *p; /* Memory chunk pointer */
|
||||
size_t len; /* Memory chunk length */
|
||||
};
|
||||
|
||||
/*
|
||||
* Helper function for creating mg_str struct from plain C string.
|
||||
* `NULL` is allowed and becomes `{NULL, 0}`.
|
||||
*/
|
||||
struct mg_str mg_mk_str(const char *s);
|
||||
|
||||
/*
|
||||
* Like `mg_mk_str`, but takes string length explicitly.
|
||||
*/
|
||||
struct mg_str mg_mk_str_n(const char *s, size_t len);
|
||||
|
||||
/* Macro for initializing mg_str. */
|
||||
#define MG_MK_STR(str_literal) \
|
||||
{ str_literal, sizeof(str_literal) - 1 }
|
||||
#define MG_MK_STR_N(str_literal, len) \
|
||||
{ str_literal, len }
|
||||
#define MG_NULL_STR \
|
||||
{ NULL, 0 }
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strcmp()` where where first string is
|
||||
* specified by `struct mg_str`.
|
||||
*/
|
||||
int mg_vcmp(const struct mg_str *str2, const char *str1);
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strncasecmp()` where first string is
|
||||
* specified by `struct mg_str`.
|
||||
*/
|
||||
int mg_vcasecmp(const struct mg_str *str2, const char *str1);
|
||||
|
||||
/* Creates a copy of s (heap-allocated). */
|
||||
struct mg_str mg_strdup(const struct mg_str s);
|
||||
|
||||
/*
|
||||
* Creates a copy of s (heap-allocated).
|
||||
* Resulting string is NUL-terminated (but NUL is not included in len).
|
||||
*/
|
||||
struct mg_str mg_strdup_nul(const struct mg_str s);
|
||||
|
||||
/*
|
||||
* Locates character in a string.
|
||||
*/
|
||||
const char *mg_strchr(const struct mg_str s, int c);
|
||||
|
||||
/*
|
||||
* Compare two `mg_str`s; return value is the same as `strcmp`.
|
||||
*/
|
||||
int mg_strcmp(const struct mg_str str1, const struct mg_str str2);
|
||||
|
||||
/*
|
||||
* Like `mg_strcmp`, but compares at most `n` characters.
|
||||
*/
|
||||
int mg_strncmp(const struct mg_str str1, const struct mg_str str2, size_t n);
|
||||
|
||||
/*
|
||||
* Compare two `mg_str`s ignoreing case; return value is the same as `strcmp`.
|
||||
*/
|
||||
int mg_strcasecmp(const struct mg_str str1, const struct mg_str str2);
|
||||
|
||||
/*
|
||||
* Free the string (assuming it was heap allocated).
|
||||
*/
|
||||
void mg_strfree(struct mg_str *s);
|
||||
|
||||
/*
|
||||
* Finds the first occurrence of a substring `needle` in the `haystack`.
|
||||
*/
|
||||
const char *mg_strstr(const struct mg_str haystack, const struct mg_str needle);
|
||||
|
||||
/* Strip whitespace at the start and the end of s */
|
||||
struct mg_str mg_strstrip(struct mg_str s);
|
||||
|
||||
/* Returns 1 if s starts with the given prefix. */
|
||||
int mg_str_starts_with(struct mg_str s, struct mg_str prefix);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_MG_STR_H_ */
|
||||
89
lib/mjs/common/platform.h
Normal file
89
lib/mjs/common/platform.h
Normal file
@@ -0,0 +1,89 @@
|
||||
#ifndef CS_COMMON_PLATFORM_H_
|
||||
#define CS_COMMON_PLATFORM_H_
|
||||
|
||||
/*
|
||||
* For the "custom" platform, includes and dependencies can be
|
||||
* provided through mg_locals.h.
|
||||
*/
|
||||
#define CS_P_CUSTOM 0
|
||||
#define CS_P_UNIX 1
|
||||
#define CS_P_WINDOWS 2
|
||||
#define CS_P_ESP32 15
|
||||
#define CS_P_ESP8266 3
|
||||
#define CS_P_CC3100 6
|
||||
#define CS_P_CC3200 4
|
||||
#define CS_P_CC3220 17
|
||||
#define CS_P_MSP432 5
|
||||
#define CS_P_TM4C129 14
|
||||
#define CS_P_MBED 7
|
||||
#define CS_P_WINCE 8
|
||||
#define CS_P_NXP_LPC 13
|
||||
#define CS_P_NXP_KINETIS 9
|
||||
#define CS_P_NRF51 12
|
||||
#define CS_P_NRF52 10
|
||||
#define CS_P_PIC32 11
|
||||
#define CS_P_RS14100 18
|
||||
#define CS_P_STM32 16
|
||||
#define CS_P_FLIPPER 19
|
||||
/* Next id: 20 */
|
||||
|
||||
#ifndef CS_PLATFORM
|
||||
#define CS_PLATFORM CS_P_FLIPPER
|
||||
#endif
|
||||
|
||||
#ifndef CS_PLATFORM
|
||||
#error "CS_PLATFORM is not specified and we couldn't guess it."
|
||||
#endif
|
||||
|
||||
#define MG_NET_IF_SOCKET 1
|
||||
#define MG_NET_IF_SIMPLELINK 2
|
||||
#define MG_NET_IF_LWIP_LOW_LEVEL 3
|
||||
#define MG_NET_IF_PIC32 4
|
||||
#define MG_NET_IF_NULL 5
|
||||
|
||||
#define MG_SSL_IF_OPENSSL 1
|
||||
#define MG_SSL_IF_MBEDTLS 2
|
||||
#define MG_SSL_IF_SIMPLELINK 3
|
||||
|
||||
#if CS_PLATFORM == CS_P_FLIPPER
|
||||
#include "platforms/platform_flipper.h"
|
||||
#endif
|
||||
|
||||
/* Common stuff */
|
||||
|
||||
#if !defined(PRINTF_LIKE)
|
||||
#if defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)
|
||||
#define PRINTF_LIKE(f, a) __attribute__((format(printf, f, a)))
|
||||
#else
|
||||
#define PRINTF_LIKE(f, a)
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if !defined(WEAK)
|
||||
#if(defined(__GNUC__) || defined(__clang__) || defined(__TI_COMPILER_VERSION__)) && \
|
||||
!defined(_WIN32)
|
||||
#define WEAK __attribute__((weak))
|
||||
#else
|
||||
#define WEAK
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifdef __GNUC__
|
||||
#define NORETURN __attribute__((noreturn))
|
||||
#define NOINLINE __attribute__((noinline))
|
||||
#define WARN_UNUSED_RESULT __attribute__((warn_unused_result))
|
||||
#define NOINSTR __attribute__((no_instrument_function))
|
||||
#define DO_NOT_WARN_UNUSED __attribute__((unused))
|
||||
#else
|
||||
#define NORETURN
|
||||
#define NOINLINE
|
||||
#define WARN_UNUSED_RESULT
|
||||
#define NOINSTR
|
||||
#define DO_NOT_WARN_UNUSED
|
||||
#endif /* __GNUC__ */
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(array) (sizeof(array) / sizeof(array[0]))
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_PLATFORM_H_ */
|
||||
65
lib/mjs/common/platforms/platform_flipper.c
Normal file
65
lib/mjs/common/platforms/platform_flipper.c
Normal file
@@ -0,0 +1,65 @@
|
||||
#include <furi.h>
|
||||
#include <toolbox/stream/file_stream.h>
|
||||
#include "../cs_dbg.h"
|
||||
#include "../frozen/frozen.h"
|
||||
|
||||
char* cs_read_file(const char* path, size_t* size) {
|
||||
Storage* storage = furi_record_open(RECORD_STORAGE);
|
||||
Stream* stream = file_stream_alloc(storage);
|
||||
char* data = NULL;
|
||||
if(!file_stream_open(stream, path, FSAM_READ, FSOM_OPEN_EXISTING)) {
|
||||
} else {
|
||||
*size = stream_size(stream);
|
||||
data = (char*)malloc(*size + 1);
|
||||
if(data != NULL) {
|
||||
stream_rewind(stream);
|
||||
if(stream_read(stream, (uint8_t*)data, *size) != *size) {
|
||||
file_stream_close(stream);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
stream_free(stream);
|
||||
free(data);
|
||||
return NULL;
|
||||
}
|
||||
data[*size] = '\0';
|
||||
}
|
||||
}
|
||||
file_stream_close(stream);
|
||||
furi_record_close(RECORD_STORAGE);
|
||||
stream_free(stream);
|
||||
return data;
|
||||
}
|
||||
|
||||
char* json_fread(const char* path) {
|
||||
UNUSED(path);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
int json_vfprintf(const char* file_name, const char* fmt, va_list ap) {
|
||||
UNUSED(file_name);
|
||||
UNUSED(fmt);
|
||||
UNUSED(ap);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_prettify_file(const char* file_name) {
|
||||
UNUSED(file_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int json_printer_file(struct json_out* out, const char* buf, size_t len) {
|
||||
UNUSED(out);
|
||||
UNUSED(buf);
|
||||
UNUSED(len);
|
||||
return 0;
|
||||
}
|
||||
|
||||
int cs_log_print_prefix(enum cs_log_level level, const char* file, int ln) {
|
||||
(void)level;
|
||||
(void)file;
|
||||
(void)ln;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cs_log_printf(const char* fmt, ...) {
|
||||
(void)fmt;
|
||||
}
|
||||
48
lib/mjs/common/platforms/platform_flipper.h
Normal file
48
lib/mjs/common/platforms/platform_flipper.h
Normal file
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
#if CS_PLATFORM == CS_P_FLIPPER
|
||||
|
||||
#include <ctype.h>
|
||||
#include <errno.h>
|
||||
#include <fcntl.h>
|
||||
#include <stdint.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdlib.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/time.h>
|
||||
#include <sys/types.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#define to64(x) strtoll(x, NULL, 10)
|
||||
#define INT64_FMT "lld"
|
||||
#define SIZE_T_FMT "u"
|
||||
typedef struct stat cs_stat_t;
|
||||
#define DIRSEP '/'
|
||||
|
||||
#ifndef CS_ENABLE_STDIO
|
||||
#define CS_ENABLE_STDIO 0
|
||||
#endif
|
||||
|
||||
#ifndef MG_ENABLE_FILESYSTEM
|
||||
#define MG_ENABLE_FILESYSTEM 0
|
||||
#endif
|
||||
|
||||
#endif /* CS_PLATFORM == CS_P_FLIPPER */
|
||||
537
lib/mjs/common/str_util.c
Normal file
537
lib/mjs/common/str_util.c
Normal file
@@ -0,0 +1,537 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef EXCLUDE_COMMON
|
||||
|
||||
#include "str_util.h"
|
||||
#include "mg_mem.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifndef C_DISABLE_BUILTIN_SNPRINTF
|
||||
#define C_DISABLE_BUILTIN_SNPRINTF 1
|
||||
#endif
|
||||
|
||||
#include "mg_mem.h"
|
||||
|
||||
size_t c_strnlen(const char* s, size_t maxlen) WEAK;
|
||||
size_t c_strnlen(const char* s, size_t maxlen) {
|
||||
size_t l = 0;
|
||||
for(; l < maxlen && s[l] != '\0'; l++) {
|
||||
}
|
||||
return l;
|
||||
}
|
||||
|
||||
#define C_SNPRINTF_APPEND_CHAR(ch) \
|
||||
do { \
|
||||
if(i < (int)buf_size) buf[i] = ch; \
|
||||
i++; \
|
||||
} while(0)
|
||||
|
||||
#define C_SNPRINTF_FLAG_ZERO 1
|
||||
|
||||
#if C_DISABLE_BUILTIN_SNPRINTF
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK;
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) {
|
||||
return vsnprintf(buf, buf_size, fmt, ap);
|
||||
}
|
||||
#else
|
||||
static int c_itoa(char* buf, size_t buf_size, int64_t num, int base, int flags, int field_width) {
|
||||
char tmp[40];
|
||||
int i = 0, k = 0, neg = 0;
|
||||
|
||||
if(num < 0) {
|
||||
neg++;
|
||||
num = -num;
|
||||
}
|
||||
|
||||
/* Print into temporary buffer - in reverse order */
|
||||
do {
|
||||
int rem = num % base;
|
||||
if(rem < 10) {
|
||||
tmp[k++] = '0' + rem;
|
||||
} else {
|
||||
tmp[k++] = 'a' + (rem - 10);
|
||||
}
|
||||
num /= base;
|
||||
} while(num > 0);
|
||||
|
||||
/* Zero padding */
|
||||
if(flags && C_SNPRINTF_FLAG_ZERO) {
|
||||
while(k < field_width && k < (int)sizeof(tmp) - 1) {
|
||||
tmp[k++] = '0';
|
||||
}
|
||||
}
|
||||
|
||||
/* And sign */
|
||||
if(neg) {
|
||||
tmp[k++] = '-';
|
||||
}
|
||||
|
||||
/* Now output */
|
||||
while(--k >= 0) {
|
||||
C_SNPRINTF_APPEND_CHAR(tmp[k]);
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) WEAK;
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* fmt, va_list ap) {
|
||||
int ch, i = 0, len_mod, flags, precision, field_width;
|
||||
|
||||
while((ch = *fmt++) != '\0') {
|
||||
if(ch != '%') {
|
||||
C_SNPRINTF_APPEND_CHAR(ch);
|
||||
} else {
|
||||
/*
|
||||
* Conversion specification:
|
||||
* zero or more flags (one of: # 0 - <space> + ')
|
||||
* an optional minimum field width (digits)
|
||||
* an optional precision (. followed by digits, or *)
|
||||
* an optional length modifier (one of: hh h l ll L q j z t)
|
||||
* conversion specifier (one of: d i o u x X e E f F g G a A c s p n)
|
||||
*/
|
||||
flags = field_width = precision = len_mod = 0;
|
||||
|
||||
/* Flags. only zero-pad flag is supported. */
|
||||
if(*fmt == '0') {
|
||||
flags |= C_SNPRINTF_FLAG_ZERO;
|
||||
}
|
||||
|
||||
/* Field width */
|
||||
while(*fmt >= '0' && *fmt <= '9') {
|
||||
field_width *= 10;
|
||||
field_width += *fmt++ - '0';
|
||||
}
|
||||
/* Dynamic field width */
|
||||
if(*fmt == '*') {
|
||||
field_width = va_arg(ap, int);
|
||||
fmt++;
|
||||
}
|
||||
|
||||
/* Precision */
|
||||
if(*fmt == '.') {
|
||||
fmt++;
|
||||
if(*fmt == '*') {
|
||||
precision = va_arg(ap, int);
|
||||
fmt++;
|
||||
} else {
|
||||
while(*fmt >= '0' && *fmt <= '9') {
|
||||
precision *= 10;
|
||||
precision += *fmt++ - '0';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Length modifier */
|
||||
switch(*fmt) {
|
||||
case 'h':
|
||||
case 'l':
|
||||
case 'L':
|
||||
case 'I':
|
||||
case 'q':
|
||||
case 'j':
|
||||
case 'z':
|
||||
case 't':
|
||||
len_mod = *fmt++;
|
||||
if(*fmt == 'h') {
|
||||
len_mod = 'H';
|
||||
fmt++;
|
||||
}
|
||||
if(*fmt == 'l') {
|
||||
len_mod = 'q';
|
||||
fmt++;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
ch = *fmt++;
|
||||
if(ch == 's') {
|
||||
const char* s = va_arg(ap, const char*); /* Always fetch parameter */
|
||||
int j;
|
||||
int pad = field_width - (precision >= 0 ? c_strnlen(s, precision) : 0);
|
||||
for(j = 0; j < pad; j++) {
|
||||
C_SNPRINTF_APPEND_CHAR(' ');
|
||||
}
|
||||
|
||||
/* `s` may be NULL in case of %.*s */
|
||||
if(s != NULL) {
|
||||
/* Ignore negative and 0 precisions */
|
||||
for(j = 0; (precision <= 0 || j < precision) && s[j] != '\0'; j++) {
|
||||
C_SNPRINTF_APPEND_CHAR(s[j]);
|
||||
}
|
||||
}
|
||||
} else if(ch == 'c') {
|
||||
ch = va_arg(ap, int); /* Always fetch parameter */
|
||||
C_SNPRINTF_APPEND_CHAR(ch);
|
||||
} else if(ch == 'd' && len_mod == 0) {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, int), 10, flags, field_width);
|
||||
} else if(ch == 'd' && len_mod == 'l') {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, long), 10, flags, field_width);
|
||||
#ifdef SSIZE_MAX
|
||||
} else if(ch == 'd' && len_mod == 'z') {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, ssize_t), 10, flags, field_width);
|
||||
#endif
|
||||
} else if(ch == 'd' && len_mod == 'q') {
|
||||
i += c_itoa(buf + i, buf_size - i, va_arg(ap, int64_t), 10, flags, field_width);
|
||||
} else if((ch == 'x' || ch == 'u') && len_mod == 0) {
|
||||
i += c_itoa(
|
||||
buf + i,
|
||||
buf_size - i,
|
||||
va_arg(ap, unsigned),
|
||||
ch == 'x' ? 16 : 10,
|
||||
flags,
|
||||
field_width);
|
||||
} else if((ch == 'x' || ch == 'u') && len_mod == 'l') {
|
||||
i += c_itoa(
|
||||
buf + i,
|
||||
buf_size - i,
|
||||
va_arg(ap, unsigned long),
|
||||
ch == 'x' ? 16 : 10,
|
||||
flags,
|
||||
field_width);
|
||||
} else if((ch == 'x' || ch == 'u') && len_mod == 'z') {
|
||||
i += c_itoa(
|
||||
buf + i,
|
||||
buf_size - i,
|
||||
va_arg(ap, size_t),
|
||||
ch == 'x' ? 16 : 10,
|
||||
flags,
|
||||
field_width);
|
||||
} else if(ch == 'p') {
|
||||
unsigned long num = (unsigned long)(uintptr_t)va_arg(ap, void*);
|
||||
C_SNPRINTF_APPEND_CHAR('0');
|
||||
C_SNPRINTF_APPEND_CHAR('x');
|
||||
i += c_itoa(buf + i, buf_size - i, num, 16, flags, 0);
|
||||
} else {
|
||||
#ifndef NO_LIBC
|
||||
/*
|
||||
* TODO(lsm): abort is not nice in a library, remove it
|
||||
* Also, ESP8266 SDK doesn't have it
|
||||
*/
|
||||
abort();
|
||||
#endif
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Zero-terminate the result */
|
||||
if(buf_size > 0) {
|
||||
buf[i < (int)buf_size ? i : (int)buf_size - 1] = '\0';
|
||||
}
|
||||
|
||||
return i;
|
||||
}
|
||||
#endif
|
||||
|
||||
int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) WEAK;
|
||||
int c_snprintf(char* buf, size_t buf_size, const char* fmt, ...) {
|
||||
int result;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
result = c_vsnprintf(buf, buf_size, fmt, ap);
|
||||
va_end(ap);
|
||||
return result;
|
||||
}
|
||||
|
||||
#ifdef _WIN32
|
||||
int to_wchar(const char* path, wchar_t* wbuf, size_t wbuf_len) {
|
||||
int ret;
|
||||
char buf[MAX_PATH * 2], buf2[MAX_PATH * 2], *p;
|
||||
|
||||
strncpy(buf, path, sizeof(buf));
|
||||
buf[sizeof(buf) - 1] = '\0';
|
||||
|
||||
/* Trim trailing slashes. Leave backslash for paths like "X:\" */
|
||||
p = buf + strlen(buf) - 1;
|
||||
while(p > buf && p[-1] != ':' && (p[0] == '\\' || p[0] == '/')) *p-- = '\0';
|
||||
|
||||
memset(wbuf, 0, wbuf_len * sizeof(wchar_t));
|
||||
ret = MultiByteToWideChar(CP_UTF8, 0, buf, -1, wbuf, (int)wbuf_len);
|
||||
|
||||
/*
|
||||
* Convert back to Unicode. If doubly-converted string does not match the
|
||||
* original, something is fishy, reject.
|
||||
*/
|
||||
WideCharToMultiByte(CP_UTF8, 0, wbuf, (int)wbuf_len, buf2, sizeof(buf2), NULL, NULL);
|
||||
if(strcmp(buf, buf2) != 0) {
|
||||
wbuf[0] = L'\0';
|
||||
ret = 0;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
#endif /* _WIN32 */
|
||||
|
||||
/* The simplest O(mn) algorithm. Better implementation are GPLed */
|
||||
const char* c_strnstr(const char* s, const char* find, size_t slen) WEAK;
|
||||
const char* c_strnstr(const char* s, const char* find, size_t slen) {
|
||||
size_t find_length = strlen(find);
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < slen; i++) {
|
||||
if(i + find_length > slen) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
if(strncmp(&s[i], find, find_length) == 0) {
|
||||
return &s[i];
|
||||
}
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
#if CS_ENABLE_STRDUP
|
||||
char* strdup(const char* src) WEAK;
|
||||
char* strdup(const char* src) {
|
||||
size_t len = strlen(src) + 1;
|
||||
char* ret = MG_MALLOC(len);
|
||||
if(ret != NULL) {
|
||||
strcpy(ret, src);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
#endif
|
||||
|
||||
void cs_to_hex(char* to, const unsigned char* p, size_t len) WEAK;
|
||||
void cs_to_hex(char* to, const unsigned char* p, size_t len) {
|
||||
static const char* hex = "0123456789abcdef";
|
||||
|
||||
for(; len--; p++) {
|
||||
*to++ = hex[p[0] >> 4];
|
||||
*to++ = hex[p[0] & 0x0f];
|
||||
}
|
||||
*to = '\0';
|
||||
}
|
||||
|
||||
static int fourbit(int ch) {
|
||||
if(ch >= '0' && ch <= '9') {
|
||||
return ch - '0';
|
||||
} else if(ch >= 'a' && ch <= 'f') {
|
||||
return ch - 'a' + 10;
|
||||
} else if(ch >= 'A' && ch <= 'F') {
|
||||
return ch - 'A' + 10;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void cs_from_hex(char* to, const char* p, size_t len) WEAK;
|
||||
void cs_from_hex(char* to, const char* p, size_t len) {
|
||||
size_t i;
|
||||
|
||||
for(i = 0; i < len; i += 2) {
|
||||
*to++ = (fourbit(p[i]) << 4) + fourbit(p[i + 1]);
|
||||
}
|
||||
*to = '\0';
|
||||
}
|
||||
|
||||
#if CS_ENABLE_TO64
|
||||
int64_t cs_to64(const char* s) WEAK;
|
||||
int64_t cs_to64(const char* s) {
|
||||
int64_t result = 0;
|
||||
int64_t neg = 1;
|
||||
while(*s && isspace((unsigned char)*s)) s++;
|
||||
if(*s == '-') {
|
||||
neg = -1;
|
||||
s++;
|
||||
}
|
||||
while(isdigit((unsigned char)*s)) {
|
||||
result *= 10;
|
||||
result += (*s - '0');
|
||||
s++;
|
||||
}
|
||||
return result * neg;
|
||||
}
|
||||
#endif
|
||||
|
||||
static int str_util_lowercase(const char* s) {
|
||||
return tolower(*(const unsigned char*)s);
|
||||
}
|
||||
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len) WEAK;
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len) {
|
||||
int diff = 0;
|
||||
|
||||
if(len > 0) do {
|
||||
diff = str_util_lowercase(s1++) - str_util_lowercase(s2++);
|
||||
} while(diff == 0 && s1[-1] != '\0' && --len > 0);
|
||||
|
||||
return diff;
|
||||
}
|
||||
|
||||
int mg_casecmp(const char* s1, const char* s2) WEAK;
|
||||
int mg_casecmp(const char* s1, const char* s2) {
|
||||
return mg_ncasecmp(s1, s2, (size_t)~0);
|
||||
}
|
||||
|
||||
int mg_asprintf(char** buf, size_t size, const char* fmt, ...) WEAK;
|
||||
int mg_asprintf(char** buf, size_t size, const char* fmt, ...) {
|
||||
int ret;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
ret = mg_avprintf(buf, size, fmt, ap);
|
||||
va_end(ap);
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) WEAK;
|
||||
int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap) {
|
||||
va_list ap_copy;
|
||||
int len;
|
||||
|
||||
va_copy(ap_copy, ap);
|
||||
len = vsnprintf(*buf, size, fmt, ap_copy);
|
||||
va_end(ap_copy);
|
||||
|
||||
if(len < 0) {
|
||||
/* eCos and Windows are not standard-compliant and return -1 when
|
||||
* the buffer is too small. Keep allocating larger buffers until we
|
||||
* succeed or out of memory. */
|
||||
*buf = NULL; /* LCOV_EXCL_START */
|
||||
while(len < 0) {
|
||||
MG_FREE(*buf);
|
||||
if(size == 0) {
|
||||
size = 5;
|
||||
}
|
||||
size *= 2;
|
||||
if((*buf = (char*)MG_MALLOC(size)) == NULL) {
|
||||
len = -1;
|
||||
break;
|
||||
}
|
||||
va_copy(ap_copy, ap);
|
||||
len = vsnprintf(*buf, size - 1, fmt, ap_copy);
|
||||
va_end(ap_copy);
|
||||
}
|
||||
|
||||
/*
|
||||
* Microsoft version of vsnprintf() is not always null-terminated, so put
|
||||
* the terminator manually
|
||||
*/
|
||||
(*buf)[len] = 0;
|
||||
/* LCOV_EXCL_STOP */
|
||||
} else if(len >= (int)size) {
|
||||
/* Standard-compliant code path. Allocate a buffer that is large enough. */
|
||||
if((*buf = (char*)MG_MALLOC(len + 1)) == NULL) {
|
||||
len = -1; /* LCOV_EXCL_LINE */
|
||||
} else { /* LCOV_EXCL_LINE */
|
||||
va_copy(ap_copy, ap);
|
||||
len = vsnprintf(*buf, len + 1, fmt, ap_copy);
|
||||
va_end(ap_copy);
|
||||
}
|
||||
}
|
||||
|
||||
return len;
|
||||
}
|
||||
|
||||
const char* mg_next_comma_list_entry(const char*, struct mg_str*, struct mg_str*) WEAK;
|
||||
const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val) {
|
||||
struct mg_str ret = mg_next_comma_list_entry_n(mg_mk_str(list), val, eq_val);
|
||||
return ret.p;
|
||||
}
|
||||
|
||||
struct mg_str
|
||||
mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) WEAK;
|
||||
struct mg_str
|
||||
mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val) {
|
||||
if(list.len == 0) {
|
||||
/* End of the list */
|
||||
list = mg_mk_str(NULL);
|
||||
} else {
|
||||
const char* chr = NULL;
|
||||
*val = list;
|
||||
|
||||
if((chr = mg_strchr(*val, ',')) != NULL) {
|
||||
/* Comma found. Store length and shift the list ptr */
|
||||
val->len = chr - val->p;
|
||||
chr++;
|
||||
list.len -= (chr - list.p);
|
||||
list.p = chr;
|
||||
} else {
|
||||
/* This value is the last one */
|
||||
list = mg_mk_str_n(list.p + list.len, 0);
|
||||
}
|
||||
|
||||
if(eq_val != NULL) {
|
||||
/* Value has form "x=y", adjust pointers and lengths */
|
||||
/* so that val points to "x", and eq_val points to "y". */
|
||||
eq_val->len = 0;
|
||||
eq_val->p = (const char*)memchr(val->p, '=', val->len);
|
||||
if(eq_val->p != NULL) {
|
||||
eq_val->p++; /* Skip over '=' character */
|
||||
eq_val->len = val->p + val->len - eq_val->p;
|
||||
val->len = (eq_val->p - val->p) - 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return list;
|
||||
}
|
||||
|
||||
size_t mg_match_prefix_n(const struct mg_str, const struct mg_str) WEAK;
|
||||
size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str) {
|
||||
const char* or_str;
|
||||
size_t res = 0, len = 0, i = 0, j = 0;
|
||||
|
||||
if((or_str = (const char*)memchr(pattern.p, '|', pattern.len)) != NULL ||
|
||||
(or_str = (const char*)memchr(pattern.p, ',', pattern.len)) != NULL) {
|
||||
struct mg_str pstr = {pattern.p, (size_t)(or_str - pattern.p)};
|
||||
res = mg_match_prefix_n(pstr, str);
|
||||
if(res > 0) return res;
|
||||
pstr.p = or_str + 1;
|
||||
pstr.len = (pattern.p + pattern.len) - (or_str + 1);
|
||||
return mg_match_prefix_n(pstr, str);
|
||||
}
|
||||
|
||||
for(; i < pattern.len && j < str.len; i++, j++) {
|
||||
if(pattern.p[i] == '?') {
|
||||
continue;
|
||||
} else if(pattern.p[i] == '*') {
|
||||
i++;
|
||||
if(i < pattern.len && pattern.p[i] == '*') {
|
||||
i++;
|
||||
len = str.len - j;
|
||||
} else {
|
||||
len = 0;
|
||||
while(j + len < str.len && str.p[j + len] != '/') len++;
|
||||
}
|
||||
if(i == pattern.len || (pattern.p[i] == '$' && i == pattern.len - 1)) return j + len;
|
||||
do {
|
||||
const struct mg_str pstr = {pattern.p + i, pattern.len - i};
|
||||
const struct mg_str sstr = {str.p + j + len, str.len - j - len};
|
||||
res = mg_match_prefix_n(pstr, sstr);
|
||||
} while(res == 0 && len != 0 && len-- > 0);
|
||||
return res == 0 ? 0 : j + res + len;
|
||||
} else if(str_util_lowercase(&pattern.p[i]) != str_util_lowercase(&str.p[j])) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
if(i < pattern.len && pattern.p[i] == '$') {
|
||||
return j == str.len ? str.len : 0;
|
||||
}
|
||||
return i == pattern.len ? j : 0;
|
||||
}
|
||||
|
||||
size_t mg_match_prefix(const char*, int, const char*) WEAK;
|
||||
size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str) {
|
||||
const struct mg_str pstr = {pattern, (size_t)pattern_len};
|
||||
struct mg_str s = {str, 0};
|
||||
if(str != NULL) s.len = strlen(str);
|
||||
return mg_match_prefix_n(pstr, s);
|
||||
}
|
||||
|
||||
#endif /* EXCLUDE_COMMON */
|
||||
195
lib/mjs/common/str_util.h
Normal file
195
lib/mjs/common/str_util.h
Normal file
@@ -0,0 +1,195 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2018 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the ""License"");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an ""AS IS"" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
#ifndef CS_COMMON_STR_UTIL_H_
|
||||
#define CS_COMMON_STR_UTIL_H_
|
||||
|
||||
#include <stdarg.h>
|
||||
#include <stdlib.h>
|
||||
|
||||
#include "mg_str.h"
|
||||
#include "platform.h"
|
||||
|
||||
#ifndef CS_ENABLE_STRDUP
|
||||
#define CS_ENABLE_STRDUP 0
|
||||
#endif
|
||||
|
||||
#ifndef CS_ENABLE_TO64
|
||||
#define CS_ENABLE_TO64 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Expands to a string representation of its argument: e.g.
|
||||
* `CS_STRINGIFY_LIT(5) expands to "5"`
|
||||
*/
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1900
|
||||
#define CS_STRINGIFY_LIT(...) #__VA_ARGS__
|
||||
#else
|
||||
#define CS_STRINGIFY_LIT(x) #x
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Expands to a string representation of its argument, which is allowed
|
||||
* to be a macro: e.g.
|
||||
*
|
||||
* #define FOO 123
|
||||
* CS_STRINGIFY_MACRO(FOO)
|
||||
*
|
||||
* expands to 123.
|
||||
*/
|
||||
#define CS_STRINGIFY_MACRO(x) CS_STRINGIFY_LIT(x)
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Equivalent of standard `strnlen()`.
|
||||
*/
|
||||
size_t c_strnlen(const char* s, size_t maxlen);
|
||||
|
||||
/*
|
||||
* Equivalent of standard `snprintf()`.
|
||||
*/
|
||||
int c_snprintf(char* buf, size_t buf_size, const char* format, ...) PRINTF_LIKE(3, 4);
|
||||
|
||||
/*
|
||||
* Equivalent of standard `vsnprintf()`.
|
||||
*/
|
||||
int c_vsnprintf(char* buf, size_t buf_size, const char* format, va_list ap);
|
||||
|
||||
/*
|
||||
* Find the first occurrence of find in s, where the search is limited to the
|
||||
* first slen characters of s.
|
||||
*/
|
||||
const char* c_strnstr(const char* s, const char* find, size_t slen);
|
||||
|
||||
/*
|
||||
* Stringify binary data. Output buffer size must be 2 * size_of_input + 1
|
||||
* because each byte of input takes 2 bytes in string representation
|
||||
* plus 1 byte for the terminating \0 character.
|
||||
*/
|
||||
void cs_to_hex(char* to, const unsigned char* p, size_t len);
|
||||
|
||||
/*
|
||||
* Convert stringified binary data back to binary.
|
||||
* Does the reverse of `cs_to_hex()`.
|
||||
*/
|
||||
void cs_from_hex(char* to, const char* p, size_t len);
|
||||
|
||||
#if CS_ENABLE_STRDUP
|
||||
/*
|
||||
* Equivalent of standard `strdup()`, defined if only `CS_ENABLE_STRDUP` is 1.
|
||||
*/
|
||||
char* strdup(const char* src);
|
||||
#endif
|
||||
|
||||
#if CS_ENABLE_TO64
|
||||
#include <stdint.h>
|
||||
/*
|
||||
* Simple string -> int64 conversion routine.
|
||||
*/
|
||||
int64_t cs_to64(const char* s);
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strncasecmp()`.
|
||||
*/
|
||||
int mg_ncasecmp(const char* s1, const char* s2, size_t len);
|
||||
|
||||
/*
|
||||
* Cross-platform version of `strcasecmp()`.
|
||||
*/
|
||||
int mg_casecmp(const char* s1, const char* s2);
|
||||
|
||||
/*
|
||||
* Prints message to the buffer. If the buffer is large enough to hold the
|
||||
* message, it returns buffer. If buffer is to small, it allocates a large
|
||||
* enough buffer on heap and returns allocated buffer.
|
||||
* This is a supposed use case:
|
||||
*
|
||||
* ```c
|
||||
* char buf[5], *p = buf;
|
||||
* mg_avprintf(&p, sizeof(buf), "%s", "hi there");
|
||||
* use_p_somehow(p);
|
||||
* if (p != buf) {
|
||||
* free(p);
|
||||
* }
|
||||
* ```
|
||||
*
|
||||
* The purpose of this is to avoid malloc-ing if generated strings are small.
|
||||
*/
|
||||
int mg_asprintf(char** buf, size_t size, const char* fmt, ...) PRINTF_LIKE(3, 4);
|
||||
|
||||
/* Same as mg_asprintf, but takes varargs list. */
|
||||
int mg_avprintf(char** buf, size_t size, const char* fmt, va_list ap);
|
||||
|
||||
/*
|
||||
* A helper function for traversing a comma separated list of values.
|
||||
* It returns a list pointer shifted to the next value or NULL if the end
|
||||
* of the list found.
|
||||
* The value is stored in a val vector. If the value has a form "x=y", then
|
||||
* eq_val vector is initialised to point to the "y" part, and val vector length
|
||||
* is adjusted to point only to "x".
|
||||
* If the list is just a comma separated list of entries, like "aa,bb,cc" then
|
||||
* `eq_val` will contain zero-length string.
|
||||
*
|
||||
* The purpose of this function is to parse comma separated string without
|
||||
* any copying/memory allocation.
|
||||
*/
|
||||
const char* mg_next_comma_list_entry(const char* list, struct mg_str* val, struct mg_str* eq_val);
|
||||
|
||||
/*
|
||||
* Like `mg_next_comma_list_entry()`, but takes `list` as `struct mg_str`.
|
||||
* NB: Test return value's .p, not .len. On last itreation that yields result
|
||||
* .len will be 0 but .p will not. When finished, .p will be NULL.
|
||||
*/
|
||||
struct mg_str
|
||||
mg_next_comma_list_entry_n(struct mg_str list, struct mg_str* val, struct mg_str* eq_val);
|
||||
|
||||
/*
|
||||
* Matches 0-terminated string (mg_match_prefix) or string with given length
|
||||
* mg_match_prefix_n against a glob pattern. Glob syntax:
|
||||
* ```
|
||||
* - * matches zero or more characters until a slash character /
|
||||
* - ** matches zero or more characters
|
||||
* - ? Matches exactly one character which is not a slash /
|
||||
* - | or , divides alternative patterns
|
||||
* - any other character matches itself
|
||||
* ```
|
||||
* Match is case-insensitive. Return number of bytes matched.
|
||||
* Examples:
|
||||
* ```
|
||||
* mg_match_prefix("a*f", len, "abcdefgh") == 6
|
||||
* mg_match_prefix("a*f", len, "abcdexgh") == 0
|
||||
* mg_match_prefix("a*f|de*,xy", len, "defgh") == 5
|
||||
* mg_match_prefix("?*", len, "abc") == 3
|
||||
* mg_match_prefix("?*", len, "") == 0
|
||||
* ```
|
||||
*/
|
||||
size_t mg_match_prefix(const char* pattern, int pattern_len, const char* str);
|
||||
|
||||
/*
|
||||
* Like `mg_match_prefix()`, but takes `pattern` and `str` as `struct mg_str`.
|
||||
*/
|
||||
size_t mg_match_prefix_n(const struct mg_str pattern, const struct mg_str str);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
|
||||
#endif /* CS_COMMON_STR_UTIL_H_ */
|
||||
553
lib/mjs/ffi/ffi.c
Normal file
553
lib/mjs/ffi/ffi.c
Normal file
@@ -0,0 +1,553 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "ffi.h"
|
||||
|
||||
#define IS_W(arg) ((arg).ctype == FFI_CTYPE_WORD)
|
||||
#define IS_D(arg) ((arg).ctype == FFI_CTYPE_DOUBLE)
|
||||
#define IS_F(arg) ((arg).ctype == FFI_CTYPE_FLOAT)
|
||||
|
||||
#define W(arg) ((ffi_word_t)(arg).v.i)
|
||||
#define D(arg) ((arg).v.d)
|
||||
#define F(arg) ((arg).v.f)
|
||||
|
||||
void ffi_set_word(struct ffi_arg* arg, ffi_word_t v) {
|
||||
arg->ctype = FFI_CTYPE_WORD;
|
||||
arg->v.i = v;
|
||||
}
|
||||
|
||||
void ffi_set_bool(struct ffi_arg* arg, bool v) {
|
||||
arg->ctype = FFI_CTYPE_BOOL;
|
||||
arg->v.i = v;
|
||||
}
|
||||
|
||||
void ffi_set_ptr(struct ffi_arg* arg, void* v) {
|
||||
ffi_set_word(arg, (ffi_word_t)v);
|
||||
}
|
||||
|
||||
void ffi_set_double(struct ffi_arg* arg, double v) {
|
||||
arg->ctype = FFI_CTYPE_DOUBLE;
|
||||
arg->v.d = v;
|
||||
}
|
||||
|
||||
void ffi_set_float(struct ffi_arg* arg, float v) {
|
||||
arg->ctype = FFI_CTYPE_FLOAT;
|
||||
arg->v.f = v;
|
||||
}
|
||||
|
||||
/*
|
||||
* The ARM ABI uses only 4 32-bit registers for paramter passing.
|
||||
* Xtensa call0 calling-convention (as used by Espressif) has 6.
|
||||
*
|
||||
* Focusing only on implementing FFI with registers means we can simplify a lot.
|
||||
*
|
||||
* ARM has some quasi-alignment rules when mixing double and integers as
|
||||
* arguments. Only:
|
||||
* a) double, int32_t, int32_t
|
||||
* b) int32_t, double
|
||||
* would fit in 4 registers. (the same goes for uint64_t).
|
||||
*
|
||||
* In order to simplify further, when a double-width argument is present, we
|
||||
* allow only two arguments.
|
||||
*/
|
||||
|
||||
/*
|
||||
* We need to support x86_64 in order to support local tests.
|
||||
* x86_64 has more and wider registers, but unlike the two main
|
||||
* embedded platforms we target it has a separate register file for
|
||||
* integer values and for floating point values (both for passing args and
|
||||
* return values). E.g. if a double value is passed as a second argument
|
||||
* it gets passed in the first available floating point register.
|
||||
*
|
||||
* I.e, the compiler generates exactly the same code for:
|
||||
*
|
||||
* void foo(int a, double b) {...}
|
||||
*
|
||||
* and
|
||||
*
|
||||
* void foo(double b, int a) {...}
|
||||
*
|
||||
*
|
||||
*/
|
||||
|
||||
typedef ffi_word_t (*w4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*w5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*w6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
|
||||
typedef ffi_word_t (*wdw_t)(double, ffi_word_t);
|
||||
typedef ffi_word_t (*wwd_t)(ffi_word_t, double);
|
||||
typedef ffi_word_t (*wdd_t)(double, double);
|
||||
|
||||
typedef ffi_word_t (*wwwd_t)(ffi_word_t, ffi_word_t, double);
|
||||
typedef ffi_word_t (*wwdw_t)(ffi_word_t, double, ffi_word_t);
|
||||
typedef ffi_word_t (*wwdd_t)(ffi_word_t, double, double);
|
||||
typedef ffi_word_t (*wdww_t)(double, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*wdwd_t)(double, ffi_word_t, double);
|
||||
typedef ffi_word_t (*wddw_t)(double, double, ffi_word_t);
|
||||
typedef ffi_word_t (*wddd_t)(double, double, double);
|
||||
|
||||
typedef ffi_word_t (*wfw_t)(float, ffi_word_t);
|
||||
typedef ffi_word_t (*wwf_t)(ffi_word_t, float);
|
||||
typedef ffi_word_t (*wff_t)(float, float);
|
||||
|
||||
typedef ffi_word_t (*wwwf_t)(ffi_word_t, ffi_word_t, float);
|
||||
typedef ffi_word_t (*wwfw_t)(ffi_word_t, float, ffi_word_t);
|
||||
typedef ffi_word_t (*wwff_t)(ffi_word_t, float, float);
|
||||
typedef ffi_word_t (*wfww_t)(float, ffi_word_t, ffi_word_t);
|
||||
typedef ffi_word_t (*wfwf_t)(float, ffi_word_t, float);
|
||||
typedef ffi_word_t (*wffw_t)(float, float, ffi_word_t);
|
||||
typedef ffi_word_t (*wfff_t)(float, float, float);
|
||||
|
||||
typedef bool (*b4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*b5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*b6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*bdw_t)(double, ffi_word_t);
|
||||
typedef bool (*bwd_t)(ffi_word_t, double);
|
||||
typedef bool (*bdd_t)(double, double);
|
||||
|
||||
typedef bool (*bwwd_t)(ffi_word_t, ffi_word_t, double);
|
||||
typedef bool (*bwdw_t)(ffi_word_t, double, ffi_word_t);
|
||||
typedef bool (*bwdd_t)(ffi_word_t, double, double);
|
||||
typedef bool (*bdww_t)(double, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*bdwd_t)(double, ffi_word_t, double);
|
||||
typedef bool (*bddw_t)(double, double, ffi_word_t);
|
||||
typedef bool (*bddd_t)(double, double, double);
|
||||
|
||||
typedef bool (*bfw_t)(float, ffi_word_t);
|
||||
typedef bool (*bwf_t)(ffi_word_t, float);
|
||||
typedef bool (*bff_t)(float, float);
|
||||
|
||||
typedef bool (*bwwf_t)(ffi_word_t, ffi_word_t, float);
|
||||
typedef bool (*bwfw_t)(ffi_word_t, float, ffi_word_t);
|
||||
typedef bool (*bwff_t)(ffi_word_t, float, float);
|
||||
typedef bool (*bfww_t)(float, ffi_word_t, ffi_word_t);
|
||||
typedef bool (*bfwf_t)(float, ffi_word_t, float);
|
||||
typedef bool (*bffw_t)(float, float, ffi_word_t);
|
||||
typedef bool (*bfff_t)(float, float, float);
|
||||
|
||||
typedef double (*d4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef double (*d5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef double (*d6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef double (*ddw_t)(double, ffi_word_t);
|
||||
typedef double (*dwd_t)(ffi_word_t, double);
|
||||
typedef double (*ddd_t)(double, double);
|
||||
|
||||
typedef double (*dwwd_t)(ffi_word_t, ffi_word_t, double);
|
||||
typedef double (*dwdw_t)(ffi_word_t, double, ffi_word_t);
|
||||
typedef double (*dwdd_t)(ffi_word_t, double, double);
|
||||
typedef double (*ddww_t)(double, ffi_word_t, ffi_word_t);
|
||||
typedef double (*ddwd_t)(double, ffi_word_t, double);
|
||||
typedef double (*dddw_t)(double, double, ffi_word_t);
|
||||
typedef double (*dddd_t)(double, double, double);
|
||||
|
||||
typedef float (*f4w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef float (*f5w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef float (*f6w_t)(ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t, ffi_word_t);
|
||||
typedef float (*ffw_t)(float, ffi_word_t);
|
||||
typedef float (*fwf_t)(ffi_word_t, float);
|
||||
typedef float (*fff_t)(float, float);
|
||||
|
||||
typedef float (*fwwf_t)(ffi_word_t, ffi_word_t, float);
|
||||
typedef float (*fwfw_t)(ffi_word_t, float, ffi_word_t);
|
||||
typedef float (*fwff_t)(ffi_word_t, float, float);
|
||||
typedef float (*ffww_t)(float, ffi_word_t, ffi_word_t);
|
||||
typedef float (*ffwf_t)(float, ffi_word_t, float);
|
||||
typedef float (*fffw_t)(float, float, ffi_word_t);
|
||||
typedef float (*ffff_t)(float, float, float);
|
||||
|
||||
int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args) {
|
||||
int i, doubles = 0, floats = 0;
|
||||
|
||||
if(nargs > 6) return -1;
|
||||
for(i = 0; i < nargs; i++) {
|
||||
doubles += (IS_D(args[i]));
|
||||
floats += (IS_F(args[i]));
|
||||
}
|
||||
|
||||
/* Doubles and floats are not supported together atm */
|
||||
if(doubles > 0 && floats > 0) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
switch(res->ctype) {
|
||||
case FFI_CTYPE_WORD: { /* {{{ */
|
||||
ffi_word_t r;
|
||||
if(doubles == 0) {
|
||||
if(floats == 0) {
|
||||
/*
|
||||
* No double and no float args: we currently support up to 6
|
||||
* word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
w4w_t f = (w4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
w5w_t f = (w5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
w6w_t f = (w6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
/* There are some floats */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_F(args[0]) && IS_F(args[1])) {
|
||||
wff_t f = (wff_t)func;
|
||||
r = f(F(args[0]), F(args[1]));
|
||||
} else if(IS_F(args[0])) {
|
||||
wfw_t f = (wfw_t)func;
|
||||
r = f(F(args[0]), W(args[1]));
|
||||
} else {
|
||||
wwf_t f = (wwf_t)func;
|
||||
r = f(W(args[0]), F(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
wwwf_t f = (wwwf_t)func;
|
||||
r = f(W(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
wwfw_t f = (wwfw_t)func;
|
||||
r = f(W(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
wwff_t f = (wwff_t)func;
|
||||
r = f(W(args[0]), F(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
wfww_t f = (wfww_t)func;
|
||||
r = f(F(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
wfwf_t f = (wfwf_t)func;
|
||||
r = f(F(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
wffw_t f = (wffw_t)func;
|
||||
r = f(F(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
wfff_t f = (wfff_t)func;
|
||||
r = f(F(args[0]), F(args[1]), F(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* There are some doubles */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_D(args[0]) && IS_D(args[1])) {
|
||||
wdd_t f = (wdd_t)func;
|
||||
r = f(D(args[0]), D(args[1]));
|
||||
} else if(IS_D(args[0])) {
|
||||
wdw_t f = (wdw_t)func;
|
||||
r = f(D(args[0]), W(args[1]));
|
||||
} else {
|
||||
wwd_t f = (wwd_t)func;
|
||||
r = f(W(args[0]), D(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
wwwd_t f = (wwwd_t)func;
|
||||
r = f(W(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
wwdw_t f = (wwdw_t)func;
|
||||
r = f(W(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
wwdd_t f = (wwdd_t)func;
|
||||
r = f(W(args[0]), D(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
wdww_t f = (wdww_t)func;
|
||||
r = f(D(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
wdwd_t f = (wdwd_t)func;
|
||||
r = f(D(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
wddw_t f = (wddw_t)func;
|
||||
r = f(D(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
wddd_t f = (wddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]), D(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.i = (uint64_t)r;
|
||||
} break; /* }}} */
|
||||
case FFI_CTYPE_BOOL: { /* {{{ */
|
||||
ffi_word_t r;
|
||||
if(doubles == 0) {
|
||||
if(floats == 0) {
|
||||
/*
|
||||
* No double and no float args: we currently support up to 6
|
||||
* word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
b4w_t f = (b4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
b5w_t f = (b5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
b6w_t f = (b6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
/* There are some floats */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_F(args[0]) && IS_F(args[1])) {
|
||||
bff_t f = (bff_t)func;
|
||||
r = f(F(args[0]), F(args[1]));
|
||||
} else if(IS_F(args[0])) {
|
||||
bfw_t f = (bfw_t)func;
|
||||
r = f(F(args[0]), W(args[1]));
|
||||
} else {
|
||||
bwf_t f = (bwf_t)func;
|
||||
r = f(W(args[0]), F(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
bwwf_t f = (bwwf_t)func;
|
||||
r = f(W(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
bwfw_t f = (bwfw_t)func;
|
||||
r = f(W(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
bwff_t f = (bwff_t)func;
|
||||
r = f(W(args[0]), F(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
bfww_t f = (bfww_t)func;
|
||||
r = f(F(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
bfwf_t f = (bfwf_t)func;
|
||||
r = f(F(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
bffw_t f = (bffw_t)func;
|
||||
r = f(F(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
bfff_t f = (bfff_t)func;
|
||||
r = f(F(args[0]), F(args[1]), F(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
/* There are some doubles */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_D(args[0]) && IS_D(args[1])) {
|
||||
bdd_t f = (bdd_t)func;
|
||||
r = f(D(args[0]), D(args[1]));
|
||||
} else if(IS_D(args[0])) {
|
||||
bdw_t f = (bdw_t)func;
|
||||
r = f(D(args[0]), W(args[1]));
|
||||
} else {
|
||||
bwd_t f = (bwd_t)func;
|
||||
r = f(W(args[0]), D(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
bwwd_t f = (bwwd_t)func;
|
||||
r = f(W(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
bwdw_t f = (bwdw_t)func;
|
||||
r = f(W(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
bwdd_t f = (bwdd_t)func;
|
||||
r = f(W(args[0]), D(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
bdww_t f = (bdww_t)func;
|
||||
r = f(D(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
bdwd_t f = (bdwd_t)func;
|
||||
r = f(D(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
bddw_t f = (bddw_t)func;
|
||||
r = f(D(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
bddd_t f = (bddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]), D(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.i = (uint64_t)r;
|
||||
} break; /* }}} */
|
||||
case FFI_CTYPE_DOUBLE: { /* {{{ */
|
||||
double r;
|
||||
if(doubles == 0) {
|
||||
/* No double args: we currently support up to 6 word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
d4w_t f = (d4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
d5w_t f = (d5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
d6w_t f = (d6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_D(args[0]) && IS_D(args[1])) {
|
||||
ddd_t f = (ddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]));
|
||||
} else if(IS_D(args[0])) {
|
||||
ddw_t f = (ddw_t)func;
|
||||
r = f(D(args[0]), W(args[1]));
|
||||
} else {
|
||||
dwd_t f = (dwd_t)func;
|
||||
r = f(W(args[0]), D(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
dwwd_t f = (dwwd_t)func;
|
||||
r = f(W(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
dwdw_t f = (dwdw_t)func;
|
||||
r = f(W(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
dwdd_t f = (dwdd_t)func;
|
||||
r = f(W(args[0]), D(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
ddww_t f = (ddww_t)func;
|
||||
r = f(D(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_W(args[1]) && IS_D(args[2])) {
|
||||
ddwd_t f = (ddwd_t)func;
|
||||
r = f(D(args[0]), W(args[1]), D(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_W(args[2])) {
|
||||
dddw_t f = (dddw_t)func;
|
||||
r = f(D(args[0]), D(args[1]), W(args[2]));
|
||||
} else if(IS_D(args[0]) && IS_D(args[1]) && IS_D(args[2])) {
|
||||
dddd_t f = (dddd_t)func;
|
||||
r = f(D(args[0]), D(args[1]), D(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.d = r;
|
||||
} break; /* }}} */
|
||||
case FFI_CTYPE_FLOAT: { /* {{{ */
|
||||
double r;
|
||||
if(floats == 0) {
|
||||
/* No float args: we currently support up to 6 word-sized arguments
|
||||
*/
|
||||
if(nargs <= 4) {
|
||||
f4w_t f = (f4w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]));
|
||||
} else if(nargs == 5) {
|
||||
f5w_t f = (f5w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]));
|
||||
} else if(nargs == 6) {
|
||||
f6w_t f = (f6w_t)func;
|
||||
r = f(W(args[0]), W(args[1]), W(args[2]), W(args[3]), W(args[4]), W(args[5]));
|
||||
} else {
|
||||
abort();
|
||||
}
|
||||
} else {
|
||||
/* There are some float args */
|
||||
switch(nargs) {
|
||||
case 0:
|
||||
case 1:
|
||||
case 2:
|
||||
if(IS_F(args[0]) && IS_F(args[1])) {
|
||||
fff_t f = (fff_t)func;
|
||||
r = f(F(args[0]), F(args[1]));
|
||||
} else if(IS_F(args[0])) {
|
||||
ffw_t f = (ffw_t)func;
|
||||
r = f(F(args[0]), W(args[1]));
|
||||
} else {
|
||||
fwf_t f = (fwf_t)func;
|
||||
r = f(W(args[0]), F(args[1]));
|
||||
}
|
||||
break;
|
||||
|
||||
case 3:
|
||||
if(IS_W(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
fwwf_t f = (fwwf_t)func;
|
||||
r = f(W(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
fwfw_t f = (fwfw_t)func;
|
||||
r = f(W(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_W(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
fwff_t f = (fwff_t)func;
|
||||
r = f(W(args[0]), F(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_W(args[2])) {
|
||||
ffww_t f = (ffww_t)func;
|
||||
r = f(F(args[0]), W(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_W(args[1]) && IS_F(args[2])) {
|
||||
ffwf_t f = (ffwf_t)func;
|
||||
r = f(F(args[0]), W(args[1]), F(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_W(args[2])) {
|
||||
fffw_t f = (fffw_t)func;
|
||||
r = f(F(args[0]), F(args[1]), W(args[2]));
|
||||
} else if(IS_F(args[0]) && IS_F(args[1]) && IS_F(args[2])) {
|
||||
ffff_t f = (ffff_t)func;
|
||||
r = f(F(args[0]), F(args[1]), F(args[2]));
|
||||
} else {
|
||||
// The above checks should be exhaustive
|
||||
abort();
|
||||
}
|
||||
break;
|
||||
default:
|
||||
return -1;
|
||||
}
|
||||
}
|
||||
res->v.f = r;
|
||||
} break; /* }}} */
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
53
lib/mjs/ffi/ffi.h
Normal file
53
lib/mjs/ffi/ffi.h
Normal file
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_FFI_FFI_H_
|
||||
#define MJS_FFI_FFI_H_
|
||||
|
||||
#include "../common/platform.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Maximum number of word-sized args to ffi-ed function. If at least one
|
||||
* of the args is double, only 2 args are allowed.
|
||||
*/
|
||||
#define FFI_MAX_ARGS_CNT 6
|
||||
|
||||
typedef void(ffi_fn_t)(void);
|
||||
|
||||
typedef intptr_t ffi_word_t;
|
||||
|
||||
enum ffi_ctype {
|
||||
FFI_CTYPE_WORD,
|
||||
FFI_CTYPE_BOOL,
|
||||
FFI_CTYPE_FLOAT,
|
||||
FFI_CTYPE_DOUBLE,
|
||||
};
|
||||
|
||||
struct ffi_arg {
|
||||
enum ffi_ctype ctype;
|
||||
union {
|
||||
uint64_t i;
|
||||
double d;
|
||||
float f;
|
||||
} v;
|
||||
};
|
||||
|
||||
int ffi_call_mjs(ffi_fn_t* func, int nargs, struct ffi_arg* res, struct ffi_arg* args);
|
||||
|
||||
void ffi_set_word(struct ffi_arg* arg, ffi_word_t v);
|
||||
void ffi_set_bool(struct ffi_arg* arg, bool v);
|
||||
void ffi_set_ptr(struct ffi_arg* arg, void* v);
|
||||
void ffi_set_double(struct ffi_arg* arg, double v);
|
||||
void ffi_set_float(struct ffi_arg* arg, float v);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_FFI_FFI_H_ */
|
||||
232
lib/mjs/mjs_array.c
Normal file
232
lib/mjs/mjs_array.c
Normal file
@@ -0,0 +1,232 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
#include "common/str_util.h"
|
||||
#include "mjs_array.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
|
||||
#define SPLICE_NEW_ITEM_IDX 2
|
||||
|
||||
/* like c_snprintf but returns `size` if write is truncated */
|
||||
static int v_sprintf_s(char* buf, size_t size, const char* fmt, ...) {
|
||||
size_t n;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
n = c_vsnprintf(buf, size, fmt, ap);
|
||||
if(n > size) {
|
||||
return size;
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_array(struct mjs* mjs) {
|
||||
mjs_val_t ret = mjs_mk_object(mjs);
|
||||
/* change the tag to MJS_TAG_ARRAY */
|
||||
ret &= ~MJS_TAG_MASK;
|
||||
ret |= MJS_TAG_ARRAY;
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mjs_is_array(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_array_get(struct mjs* mjs, mjs_val_t arr, unsigned long index) {
|
||||
return mjs_array_get2(mjs, arr, index, NULL);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has) {
|
||||
mjs_val_t res = MJS_UNDEFINED;
|
||||
|
||||
if(has != NULL) {
|
||||
*has = 0;
|
||||
}
|
||||
|
||||
if(mjs_is_object(arr)) {
|
||||
struct mjs_property* p;
|
||||
char buf[20];
|
||||
int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
|
||||
p = mjs_get_own_property(mjs, arr, buf, n);
|
||||
if(p != NULL) {
|
||||
if(has != NULL) {
|
||||
*has = 1;
|
||||
}
|
||||
res = p->value;
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t v) {
|
||||
struct mjs_property* p;
|
||||
unsigned long len = 0;
|
||||
|
||||
if(!mjs_is_object(v)) {
|
||||
len = 0;
|
||||
goto clean;
|
||||
}
|
||||
|
||||
for(p = get_object_struct(v)->properties; p != NULL; p = p->next) {
|
||||
int ok = 0;
|
||||
unsigned long n = 0;
|
||||
str_to_ulong(mjs, p->name, &ok, &n);
|
||||
if(ok && n >= len && n < 0xffffffff) {
|
||||
len = n + 1;
|
||||
}
|
||||
}
|
||||
|
||||
clean:
|
||||
return len;
|
||||
}
|
||||
|
||||
mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v) {
|
||||
mjs_err_t ret = MJS_OK;
|
||||
|
||||
if(mjs_is_object(arr)) {
|
||||
char buf[20];
|
||||
int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
|
||||
ret = mjs_set(mjs, arr, buf, n, v);
|
||||
} else {
|
||||
ret = MJS_TYPE_ERROR;
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index) {
|
||||
char buf[20];
|
||||
int n = v_sprintf_s(buf, sizeof(buf), "%lu", index);
|
||||
mjs_del(mjs, arr, buf, n);
|
||||
}
|
||||
|
||||
mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v) {
|
||||
return mjs_array_set(mjs, arr, mjs_array_length(mjs, arr), v);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs) {
|
||||
mjs_err_t rcode = MJS_OK;
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
int nargs = mjs_nargs(mjs);
|
||||
int i;
|
||||
|
||||
/* Make sure that `this` is an array */
|
||||
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
/* Push all args */
|
||||
for(i = 0; i < nargs; i++) {
|
||||
rcode = mjs_array_push(mjs, mjs->vals.this_obj, mjs_arg(mjs, i));
|
||||
if(rcode != MJS_OK) {
|
||||
mjs_prepend_errorf(mjs, rcode, "");
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
|
||||
/* Return the new array length */
|
||||
ret = mjs_mk_number(mjs, mjs_array_length(mjs, mjs->vals.this_obj));
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
return;
|
||||
}
|
||||
|
||||
static void move_item(struct mjs* mjs, mjs_val_t arr, unsigned long from, unsigned long to) {
|
||||
mjs_val_t cur = mjs_array_get(mjs, arr, from);
|
||||
mjs_array_set(mjs, arr, to, cur);
|
||||
mjs_array_del(mjs, arr, from);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_array_splice(struct mjs* mjs) {
|
||||
int nargs = mjs_nargs(mjs);
|
||||
mjs_err_t rcode = MJS_OK;
|
||||
mjs_val_t ret = mjs_mk_array(mjs);
|
||||
mjs_val_t start_v = MJS_UNDEFINED;
|
||||
mjs_val_t deleteCount_v = MJS_UNDEFINED;
|
||||
int start = 0;
|
||||
int arr_len;
|
||||
int delete_cnt = 0;
|
||||
int new_items_cnt = 0;
|
||||
int delta = 0;
|
||||
int i;
|
||||
|
||||
/* Make sure that `this` is an array */
|
||||
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_OBJECT_ARRAY, NULL)) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
/* Get array length */
|
||||
arr_len = mjs_array_length(mjs, mjs->vals.this_obj);
|
||||
|
||||
/* get start from arg 0 */
|
||||
if(!mjs_check_arg(mjs, 0, "start", MJS_TYPE_NUMBER, &start_v)) {
|
||||
goto clean;
|
||||
}
|
||||
start = mjs_normalize_idx(mjs_get_int(mjs, start_v), arr_len);
|
||||
|
||||
/* Handle deleteCount */
|
||||
if(nargs >= SPLICE_NEW_ITEM_IDX) {
|
||||
/* deleteCount is given; use it */
|
||||
if(!mjs_check_arg(mjs, 1, "deleteCount", MJS_TYPE_NUMBER, &deleteCount_v)) {
|
||||
goto clean;
|
||||
}
|
||||
delete_cnt = mjs_get_int(mjs, deleteCount_v);
|
||||
new_items_cnt = nargs - SPLICE_NEW_ITEM_IDX;
|
||||
} else {
|
||||
/* deleteCount is not given; assume the end of the array */
|
||||
delete_cnt = arr_len - start;
|
||||
}
|
||||
if(delete_cnt > arr_len - start) {
|
||||
delete_cnt = arr_len - start;
|
||||
} else if(delete_cnt < 0) {
|
||||
delete_cnt = 0;
|
||||
}
|
||||
|
||||
/* delta at which subsequent array items should be moved */
|
||||
delta = new_items_cnt - delete_cnt;
|
||||
|
||||
/*
|
||||
* copy items which are going to be deleted to the separate array (will be
|
||||
* returned)
|
||||
*/
|
||||
for(i = 0; i < delete_cnt; i++) {
|
||||
mjs_val_t cur = mjs_array_get(mjs, mjs->vals.this_obj, start + i);
|
||||
rcode = mjs_array_push(mjs, ret, cur);
|
||||
if(rcode != MJS_OK) {
|
||||
mjs_prepend_errorf(mjs, rcode, "");
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
|
||||
/* If needed, move subsequent items */
|
||||
if(delta < 0) {
|
||||
for(i = start; i < arr_len; i++) {
|
||||
if(i >= start - delta) {
|
||||
move_item(mjs, mjs->vals.this_obj, i, i + delta);
|
||||
} else {
|
||||
mjs_array_del(mjs, mjs->vals.this_obj, i);
|
||||
}
|
||||
}
|
||||
} else if(delta > 0) {
|
||||
for(i = arr_len - 1; i >= start; i--) {
|
||||
move_item(mjs, mjs->vals.this_obj, i, i + delta);
|
||||
}
|
||||
}
|
||||
|
||||
/* Set new items to the array */
|
||||
for(i = 0; i < nargs - SPLICE_NEW_ITEM_IDX; i++) {
|
||||
mjs_array_set(mjs, mjs->vals.this_obj, start + i, mjs_arg(mjs, SPLICE_NEW_ITEM_IDX + i));
|
||||
}
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
26
lib/mjs/mjs_array.h
Normal file
26
lib/mjs/mjs_array.h
Normal file
@@ -0,0 +1,26 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_ARRAY_H_
|
||||
#define MJS_ARRAY_H_
|
||||
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_array_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
MJS_PRIVATE mjs_val_t mjs_array_get2(struct mjs* mjs, mjs_val_t arr, unsigned long index, int* has);
|
||||
|
||||
MJS_PRIVATE void mjs_array_splice(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE void mjs_array_push_internal(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_ARRAY_H_ */
|
||||
385
lib/mjs/mjs_array_buf.c
Normal file
385
lib/mjs/mjs_array_buf.c
Normal file
@@ -0,0 +1,385 @@
|
||||
#include "mjs_array_buf.h"
|
||||
#include "common/cs_varint.h"
|
||||
#include "common/mg_str.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_array.h"
|
||||
#include "mjs_util.h"
|
||||
#include "mjs_exec_public.h"
|
||||
|
||||
#ifndef MJS_ARRAY_BUF_RESERVE
|
||||
#define MJS_ARRAY_BUF_RESERVE 100
|
||||
#endif
|
||||
|
||||
#define IS_SIGNED(type) \
|
||||
(type == MJS_DATAVIEW_I8 || type == MJS_DATAVIEW_I16 || type == MJS_DATAVIEW_I32)
|
||||
|
||||
int mjs_is_array_buf(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF;
|
||||
}
|
||||
|
||||
int mjs_is_data_view(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW;
|
||||
}
|
||||
|
||||
int mjs_is_typed_array(mjs_val_t v) {
|
||||
return ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF) ||
|
||||
((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW);
|
||||
}
|
||||
|
||||
char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen) {
|
||||
struct mbuf* m = &mjs->array_buffers;
|
||||
size_t offset = buf & ~MJS_TAG_MASK;
|
||||
char* ptr = m->buf + offset;
|
||||
|
||||
uint64_t len = 0;
|
||||
size_t header_len = 0;
|
||||
if(offset < m->len && cs_varint_decode((uint8_t*)ptr, m->len - offset, &len, &header_len)) {
|
||||
if(bytelen) {
|
||||
*bytelen = len;
|
||||
}
|
||||
return ptr + header_len;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static size_t mjs_dataview_get_element_len(mjs_dataview_type_t type) {
|
||||
size_t len = 1;
|
||||
switch(type) {
|
||||
case MJS_DATAVIEW_U8:
|
||||
case MJS_DATAVIEW_I8:
|
||||
len = 1;
|
||||
break;
|
||||
case MJS_DATAVIEW_U16:
|
||||
case MJS_DATAVIEW_I16:
|
||||
len = 2;
|
||||
break;
|
||||
case MJS_DATAVIEW_U32:
|
||||
case MJS_DATAVIEW_I32:
|
||||
len = 4;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return len;
|
||||
}
|
||||
|
||||
static int64_t get_value(char* buf, mjs_dataview_type_t type) {
|
||||
int64_t value = 0;
|
||||
switch(type) {
|
||||
case MJS_DATAVIEW_U8:
|
||||
value = *(uint8_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_I8:
|
||||
value = *(int8_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_U16:
|
||||
value = *(uint16_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_I16:
|
||||
value = *(int16_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_U32:
|
||||
value = *(uint32_t*)buf;
|
||||
break;
|
||||
case MJS_DATAVIEW_I32:
|
||||
value = *(int32_t*)buf;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
static void set_value(char* buf, int64_t value, mjs_dataview_type_t type) {
|
||||
switch(type) {
|
||||
case MJS_DATAVIEW_U8:
|
||||
*(uint8_t*)buf = (uint8_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_I8:
|
||||
*(int8_t*)buf = (int8_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_U16:
|
||||
*(uint16_t*)buf = (uint16_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_I16:
|
||||
*(int16_t*)buf = (int16_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_U32:
|
||||
*(uint32_t*)buf = (uint32_t)value;
|
||||
break;
|
||||
case MJS_DATAVIEW_I32:
|
||||
*(int32_t*)buf = (int32_t)value;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
static mjs_val_t mjs_dataview_get(struct mjs* mjs, mjs_val_t obj, size_t index) {
|
||||
mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1);
|
||||
|
||||
size_t byte_len = 0;
|
||||
char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len);
|
||||
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
|
||||
if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) {
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
buf += mjs_dataview_get_element_len(type) * index;
|
||||
int64_t value = get_value(buf, type);
|
||||
|
||||
return mjs_mk_number(mjs, value);
|
||||
}
|
||||
|
||||
static mjs_err_t mjs_dataview_set(struct mjs* mjs, mjs_val_t obj, size_t index, int64_t value) {
|
||||
mjs_val_t buf_obj = mjs_get(mjs, obj, "buffer", -1);
|
||||
|
||||
size_t byte_len = 0;
|
||||
char* buf = mjs_array_buf_get_ptr(mjs, buf_obj, &byte_len);
|
||||
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
|
||||
if((mjs_dataview_get_element_len(type) * (index + 1)) > byte_len) {
|
||||
return MJS_TYPE_ERROR;
|
||||
}
|
||||
|
||||
buf += mjs_dataview_get_element_len(type) * index;
|
||||
set_value(buf, value, type);
|
||||
|
||||
return MJS_OK;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
|
||||
if(!mjs_is_number(key)) {
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
int index = mjs_get_int(mjs, key);
|
||||
return mjs_dataview_get(mjs, obj, index);
|
||||
}
|
||||
|
||||
mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val) {
|
||||
if(!mjs_is_number(key)) {
|
||||
return MJS_TYPE_ERROR;
|
||||
}
|
||||
int index = mjs_get_int(mjs, key);
|
||||
int64_t value = 0;
|
||||
|
||||
if(mjs_is_number(val)) {
|
||||
value = mjs_get_double(mjs, val);
|
||||
} else if(mjs_is_boolean(val)) {
|
||||
value = mjs_get_bool(mjs, val) ? (1) : (0);
|
||||
}
|
||||
return mjs_dataview_set(mjs, obj, index, value);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj) {
|
||||
return mjs_get(mjs, obj, "buffer", -1);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj) {
|
||||
size_t bytelen = 0;
|
||||
mjs_array_buf_get_ptr(mjs, mjs_dataview_get_buf(mjs, obj), &bytelen);
|
||||
mjs_dataview_type_t type = mjs_get_int(mjs, mjs_get(mjs, obj, "_t", -1));
|
||||
size_t element_len = mjs_dataview_get_element_len(type);
|
||||
|
||||
return mjs_mk_number(mjs, bytelen / element_len);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len) {
|
||||
struct mbuf* m = &mjs->array_buffers;
|
||||
|
||||
if((m->len + buf_len) > m->size) {
|
||||
char* prev_buf = m->buf;
|
||||
mbuf_resize(m, m->len + buf_len + MJS_ARRAY_BUF_RESERVE);
|
||||
|
||||
if(data >= prev_buf && data < (prev_buf + m->len)) {
|
||||
data += m->buf - prev_buf;
|
||||
}
|
||||
}
|
||||
|
||||
size_t offset = m->len;
|
||||
char* prev_buf = m->buf;
|
||||
|
||||
size_t header_len = cs_varint_llen(buf_len);
|
||||
mbuf_insert(m, offset, NULL, header_len + buf_len);
|
||||
if(data >= prev_buf && data < (prev_buf + m->len)) {
|
||||
data += m->buf - prev_buf;
|
||||
}
|
||||
|
||||
cs_varint_encode(buf_len, (unsigned char*)m->buf + offset, header_len);
|
||||
|
||||
if(data != NULL) {
|
||||
memcpy(m->buf + offset + header_len, data, buf_len);
|
||||
} else {
|
||||
memset(m->buf + offset + header_len, 0, buf_len);
|
||||
}
|
||||
|
||||
return (offset & ~MJS_TAG_MASK) | MJS_TAG_ARRAY_BUF;
|
||||
}
|
||||
|
||||
void mjs_array_buf_slice(struct mjs* mjs) {
|
||||
size_t nargs = mjs_nargs(mjs);
|
||||
mjs_val_t src = mjs_get_this(mjs);
|
||||
|
||||
size_t start = 0;
|
||||
size_t end = 0;
|
||||
char* src_buf = NULL;
|
||||
size_t src_len = 0;
|
||||
|
||||
bool args_correct = false;
|
||||
do {
|
||||
if(!mjs_is_array_buf(src)) {
|
||||
break;
|
||||
}
|
||||
src_buf = mjs_array_buf_get_ptr(mjs, src, &src_len);
|
||||
|
||||
if((nargs == 0) || (nargs > 2)) {
|
||||
break;
|
||||
}
|
||||
|
||||
mjs_val_t start_obj = mjs_arg(mjs, 0);
|
||||
if(!mjs_is_number(start_obj)) {
|
||||
break;
|
||||
}
|
||||
start = mjs_get_int32(mjs, start_obj);
|
||||
|
||||
if(nargs == 2) {
|
||||
mjs_val_t end_obj = mjs_arg(mjs, 1);
|
||||
if(!mjs_is_number(end_obj)) {
|
||||
break;
|
||||
}
|
||||
end = mjs_get_int32(mjs, end_obj);
|
||||
} else {
|
||||
end = src_len - 1;
|
||||
}
|
||||
|
||||
if((start >= src_len) || (end >= src_len) || (start >= end)) {
|
||||
break;
|
||||
}
|
||||
|
||||
args_correct = true;
|
||||
} while(0);
|
||||
|
||||
if(!args_correct) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
return;
|
||||
}
|
||||
|
||||
src_buf += start;
|
||||
mjs_return(mjs, mjs_mk_array_buf(mjs, src_buf, end - start));
|
||||
}
|
||||
|
||||
static mjs_val_t
|
||||
mjs_mk_dataview_from_buf(struct mjs* mjs, mjs_val_t buf, mjs_dataview_type_t type) {
|
||||
size_t len = 0;
|
||||
mjs_array_buf_get_ptr(mjs, buf, &len);
|
||||
if(len % mjs_dataview_get_element_len(type) != 0) {
|
||||
mjs_prepend_errorf(
|
||||
mjs, MJS_BAD_ARGS_ERROR, "Buffer len is not a multiple of element size");
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
mjs_val_t view_obj = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, view_obj, "_t", ~0, mjs_mk_number(mjs, (double)type));
|
||||
mjs_set(mjs, view_obj, "buffer", ~0, buf);
|
||||
|
||||
view_obj &= ~MJS_TAG_MASK;
|
||||
view_obj |= MJS_TAG_ARRAY_BUF_VIEW;
|
||||
|
||||
mjs_dataview_get(mjs, view_obj, 0);
|
||||
|
||||
return view_obj;
|
||||
}
|
||||
|
||||
static mjs_val_t
|
||||
mjs_mk_dataview(struct mjs* mjs, size_t len, mjs_val_t arr, mjs_dataview_type_t type) {
|
||||
size_t elements_nb = 0;
|
||||
if(mjs_is_array(arr)) {
|
||||
if(!mjs_is_number(mjs_array_get(mjs, arr, 0))) {
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
elements_nb = mjs_array_length(mjs, arr);
|
||||
} else {
|
||||
elements_nb = len;
|
||||
}
|
||||
|
||||
size_t element_len = mjs_dataview_get_element_len(type);
|
||||
mjs_val_t buf_obj = mjs_mk_array_buf(mjs, NULL, element_len * elements_nb);
|
||||
|
||||
if(mjs_is_array(arr)) {
|
||||
char* buf_ptr = mjs_array_buf_get_ptr(mjs, buf_obj, NULL);
|
||||
for(uint8_t i = 0; i < elements_nb; i++) {
|
||||
int64_t value = mjs_get_double(mjs, mjs_array_get(mjs, arr, i));
|
||||
set_value(buf_ptr, value, type);
|
||||
buf_ptr += element_len;
|
||||
}
|
||||
}
|
||||
|
||||
return mjs_mk_dataview_from_buf(mjs, buf_obj, type);
|
||||
}
|
||||
|
||||
static void mjs_array_buf_new(struct mjs* mjs) {
|
||||
mjs_val_t len_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t buf_obj = MJS_UNDEFINED;
|
||||
if(!mjs_is_number(len_arg)) {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
} else {
|
||||
int len = mjs_get_int(mjs, len_arg);
|
||||
buf_obj = mjs_mk_array_buf(mjs, NULL, len);
|
||||
}
|
||||
mjs_return(mjs, buf_obj);
|
||||
}
|
||||
|
||||
static void mjs_dataview_new(struct mjs* mjs, mjs_dataview_type_t type) {
|
||||
mjs_val_t view_arg = mjs_arg(mjs, 0);
|
||||
mjs_val_t view_obj = MJS_UNDEFINED;
|
||||
|
||||
if(mjs_is_array_buf(view_arg)) { // Create a view of existing ArrayBuf
|
||||
view_obj = mjs_mk_dataview_from_buf(mjs, view_arg, type);
|
||||
} else if(mjs_is_number(view_arg)) { // Create new typed array
|
||||
int len = mjs_get_int(mjs, view_arg);
|
||||
view_obj = mjs_mk_dataview(mjs, len, MJS_UNDEFINED, type);
|
||||
} else if(mjs_is_array(view_arg)) { // Create new typed array from array
|
||||
view_obj = mjs_mk_dataview(mjs, 0, view_arg, type);
|
||||
} else {
|
||||
mjs_prepend_errorf(mjs, MJS_BAD_ARGS_ERROR, "");
|
||||
}
|
||||
|
||||
mjs_return(mjs, view_obj);
|
||||
}
|
||||
|
||||
static void mjs_new_u8_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_U8);
|
||||
}
|
||||
|
||||
static void mjs_new_i8_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_I8);
|
||||
}
|
||||
|
||||
static void mjs_new_u16_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_U16);
|
||||
}
|
||||
|
||||
static void mjs_new_i16_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_I16);
|
||||
}
|
||||
|
||||
static void mjs_new_u32_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_U32);
|
||||
}
|
||||
|
||||
static void mjs_new_i32_array(struct mjs* mjs) {
|
||||
mjs_dataview_new(mjs, MJS_DATAVIEW_I32);
|
||||
}
|
||||
|
||||
void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj) {
|
||||
mjs_set(mjs, obj, "ArrayBuffer", ~0, MJS_MK_FN(mjs_array_buf_new));
|
||||
mjs_set(mjs, obj, "Uint8Array", ~0, MJS_MK_FN(mjs_new_u8_array));
|
||||
mjs_set(mjs, obj, "Int8Array", ~0, MJS_MK_FN(mjs_new_i8_array));
|
||||
mjs_set(mjs, obj, "Uint16Array", ~0, MJS_MK_FN(mjs_new_u16_array));
|
||||
mjs_set(mjs, obj, "Int16Array", ~0, MJS_MK_FN(mjs_new_i16_array));
|
||||
mjs_set(mjs, obj, "Uint32Array", ~0, MJS_MK_FN(mjs_new_u32_array));
|
||||
mjs_set(mjs, obj, "Int32Array", ~0, MJS_MK_FN(mjs_new_i32_array));
|
||||
}
|
||||
27
lib/mjs/mjs_array_buf.h
Normal file
27
lib/mjs/mjs_array_buf.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_array_buf_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
mjs_val_t mjs_dataview_get_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key);
|
||||
|
||||
mjs_err_t mjs_dataview_set_prop(struct mjs* mjs, mjs_val_t obj, mjs_val_t key, mjs_val_t val);
|
||||
|
||||
void mjs_init_builtin_array_buf(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
mjs_val_t mjs_dataview_get_len(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
void mjs_array_buf_slice(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
37
lib/mjs/mjs_array_buf_public.h
Normal file
37
lib/mjs/mjs_array_buf_public.h
Normal file
@@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
typedef enum {
|
||||
MJS_DATAVIEW_U8,
|
||||
MJS_DATAVIEW_I8,
|
||||
MJS_DATAVIEW_U16,
|
||||
MJS_DATAVIEW_I16,
|
||||
MJS_DATAVIEW_U32,
|
||||
MJS_DATAVIEW_I32,
|
||||
} mjs_dataview_type_t;
|
||||
|
||||
int mjs_is_array_buf(mjs_val_t v);
|
||||
|
||||
int mjs_is_data_view(mjs_val_t v);
|
||||
|
||||
int mjs_is_typed_array(mjs_val_t v);
|
||||
|
||||
mjs_val_t mjs_mk_array_buf(struct mjs* mjs, char* data, size_t buf_len);
|
||||
|
||||
char* mjs_array_buf_get_ptr(struct mjs* mjs, mjs_val_t buf, size_t* bytelen);
|
||||
|
||||
mjs_val_t mjs_dataview_get_buf(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
47
lib/mjs/mjs_array_public.h
Normal file
47
lib/mjs/mjs_array_public.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
/*
|
||||
* === Arrays
|
||||
*/
|
||||
|
||||
#ifndef MJS_ARRAY_PUBLIC_H_
|
||||
#define MJS_ARRAY_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* Make an empty array object */
|
||||
mjs_val_t mjs_mk_array(struct mjs* mjs);
|
||||
|
||||
/* Returns length on an array. If `arr` is not an array, 0 is returned. */
|
||||
unsigned long mjs_array_length(struct mjs* mjs, mjs_val_t arr);
|
||||
|
||||
/* Insert value `v` in array `arr` at the end of the array. */
|
||||
mjs_err_t mjs_array_push(struct mjs* mjs, mjs_val_t arr, mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Return array member at index `index`. If `index` is out of bounds, undefined
|
||||
* is returned.
|
||||
*/
|
||||
mjs_val_t mjs_array_get(struct mjs*, mjs_val_t arr, unsigned long index);
|
||||
|
||||
/* Insert value `v` into `arr` at index `index`. */
|
||||
mjs_err_t mjs_array_set(struct mjs* mjs, mjs_val_t arr, unsigned long index, mjs_val_t v);
|
||||
|
||||
/* Returns true if the given value is an array */
|
||||
int mjs_is_array(mjs_val_t v);
|
||||
|
||||
/* Delete value in array `arr` at index `index`, if it exists. */
|
||||
void mjs_array_del(struct mjs* mjs, mjs_val_t arr, unsigned long index);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_ARRAY_PUBLIC_H_ */
|
||||
147
lib/mjs/mjs_bcode.c
Normal file
147
lib/mjs/mjs_bcode.c
Normal file
@@ -0,0 +1,147 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "common/cs_varint.h"
|
||||
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_bcode.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_tok.h"
|
||||
|
||||
static void add_lineno_map_item(struct pstate* pstate) {
|
||||
if(pstate->last_emitted_line_no < pstate->line_no) {
|
||||
int offset = pstate->cur_idx - pstate->start_bcode_idx;
|
||||
size_t offset_llen = cs_varint_llen(offset);
|
||||
size_t lineno_llen = cs_varint_llen(pstate->line_no);
|
||||
mbuf_resize(
|
||||
&pstate->offset_lineno_map,
|
||||
pstate->offset_lineno_map.size + offset_llen + lineno_llen);
|
||||
|
||||
/* put offset */
|
||||
cs_varint_encode(
|
||||
offset,
|
||||
(uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len,
|
||||
offset_llen);
|
||||
pstate->offset_lineno_map.len += offset_llen;
|
||||
|
||||
/* put line_no */
|
||||
cs_varint_encode(
|
||||
pstate->line_no,
|
||||
(uint8_t*)pstate->offset_lineno_map.buf + pstate->offset_lineno_map.len,
|
||||
lineno_llen);
|
||||
pstate->offset_lineno_map.len += lineno_llen;
|
||||
|
||||
pstate->last_emitted_line_no = pstate->line_no;
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte) {
|
||||
add_lineno_map_item(pstate);
|
||||
mbuf_insert(&pstate->mjs->bcode_gen, pstate->cur_idx, &byte, sizeof(byte));
|
||||
pstate->cur_idx += sizeof(byte);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n) {
|
||||
struct mbuf* b = &pstate->mjs->bcode_gen;
|
||||
size_t llen = cs_varint_llen(n);
|
||||
add_lineno_map_item(pstate);
|
||||
mbuf_insert(b, pstate->cur_idx, NULL, llen);
|
||||
cs_varint_encode(n, (uint8_t*)b->buf + pstate->cur_idx, llen);
|
||||
pstate->cur_idx += llen;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len) {
|
||||
struct mbuf* b = &pstate->mjs->bcode_gen;
|
||||
size_t llen = cs_varint_llen(len);
|
||||
add_lineno_map_item(pstate);
|
||||
mbuf_insert(b, pstate->cur_idx, NULL, llen + len);
|
||||
cs_varint_encode(len, (uint8_t*)b->buf + pstate->cur_idx, llen);
|
||||
memcpy(b->buf + pstate->cur_idx + llen, ptr, len);
|
||||
pstate->cur_idx += llen + len;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int
|
||||
mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v) {
|
||||
int llen = (int)cs_varint_llen(v);
|
||||
int diff = llen - MJS_INIT_OFFSET_SIZE;
|
||||
assert(offset < mjs->bcode_gen.len);
|
||||
if(diff > 0) {
|
||||
mbuf_resize(&mjs->bcode_gen, mjs->bcode_gen.size + diff);
|
||||
}
|
||||
/*
|
||||
* Offset is going to take more than one was reserved, so, move the data
|
||||
* forward
|
||||
*/
|
||||
memmove(
|
||||
mjs->bcode_gen.buf + offset + llen,
|
||||
mjs->bcode_gen.buf + offset + MJS_INIT_OFFSET_SIZE,
|
||||
mjs->bcode_gen.len - offset - MJS_INIT_OFFSET_SIZE);
|
||||
mjs->bcode_gen.len += diff;
|
||||
cs_varint_encode(v, (uint8_t*)mjs->bcode_gen.buf + offset, llen);
|
||||
|
||||
/*
|
||||
* If current parsing index is after the offset at which we've inserted new
|
||||
* varint, the index might need to be adjusted
|
||||
*/
|
||||
if(p->cur_idx >= (int)offset) {
|
||||
p->cur_idx += diff;
|
||||
}
|
||||
return diff;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp) {
|
||||
mbuf_append(&mjs->bcode_parts, bp, sizeof(*bp));
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num) {
|
||||
assert(num < mjs_bcode_parts_cnt(mjs));
|
||||
return (struct mjs_bcode_part*)(mjs->bcode_parts.buf + num * sizeof(struct mjs_bcode_part));
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset) {
|
||||
int i;
|
||||
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
||||
struct mjs_bcode_part* bp = NULL;
|
||||
|
||||
if(offset >= mjs->bcode_len) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
for(i = 0; i < parts_cnt; i++) {
|
||||
bp = mjs_bcode_part_get(mjs, i);
|
||||
if(offset < bp->start_idx + bp->data.len) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* given the non-corrupted data, the needed part must be found */
|
||||
assert(i < parts_cnt);
|
||||
|
||||
return bp;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs) {
|
||||
return mjs->bcode_parts.len / sizeof(struct mjs_bcode_part);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs) {
|
||||
struct mjs_bcode_part bp;
|
||||
memset(&bp, 0, sizeof(bp));
|
||||
|
||||
/* Make sure the bcode doesn't occupy any extra space */
|
||||
mbuf_trim(&mjs->bcode_gen);
|
||||
|
||||
/* Transfer the ownership of the bcode data */
|
||||
bp.data.p = mjs->bcode_gen.buf;
|
||||
bp.data.len = mjs->bcode_gen.len;
|
||||
mbuf_init(&mjs->bcode_gen, 0);
|
||||
|
||||
bp.start_idx = mjs->bcode_len;
|
||||
bp.exec_res = MJS_ERRS_CNT;
|
||||
|
||||
mjs_bcode_part_add(mjs, &bp);
|
||||
|
||||
mjs->bcode_len += bp.data.len;
|
||||
}
|
||||
105
lib/mjs/mjs_bcode.h
Normal file
105
lib/mjs/mjs_bcode.h
Normal file
@@ -0,0 +1,105 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_BCODE_H_
|
||||
#define MJS_BCODE_H_
|
||||
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#include "mjs_core.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
enum mjs_opcode {
|
||||
OP_NOP, /* ( -- ) */
|
||||
OP_DROP, /* ( a -- ) */
|
||||
OP_DUP, /* ( a -- a a ) */
|
||||
OP_SWAP, /* ( a b -- b a ) */
|
||||
OP_JMP, /* ( -- ) */
|
||||
OP_JMP_TRUE, /* ( -- ) */
|
||||
OP_JMP_NEUTRAL_TRUE, /* ( -- ) */
|
||||
OP_JMP_FALSE, /* ( -- ) */
|
||||
OP_JMP_NEUTRAL_FALSE, /* ( -- ) */
|
||||
OP_FIND_SCOPE, /* ( a -- a b ) */
|
||||
OP_PUSH_SCOPE, /* ( -- a ) */
|
||||
OP_PUSH_STR, /* ( -- a ) */
|
||||
OP_PUSH_TRUE, /* ( -- a ) */
|
||||
OP_PUSH_FALSE, /* ( -- a ) */
|
||||
OP_PUSH_INT, /* ( -- a ) */
|
||||
OP_PUSH_DBL, /* ( -- a ) */
|
||||
OP_PUSH_NULL, /* ( -- a ) */
|
||||
OP_PUSH_UNDEF, /* ( -- a ) */
|
||||
OP_PUSH_OBJ, /* ( -- a ) */
|
||||
OP_PUSH_ARRAY, /* ( -- a ) */
|
||||
OP_PUSH_FUNC, /* ( -- a ) */
|
||||
OP_PUSH_THIS, /* ( -- a ) */
|
||||
OP_GET, /* ( key obj -- obj[key] ) */
|
||||
OP_CREATE, /* ( key obj -- ) */
|
||||
OP_EXPR, /* ( ... -- a ) */
|
||||
OP_APPEND, /* ( a b -- ) */
|
||||
OP_SET_ARG, /* ( a -- a ) */
|
||||
OP_NEW_SCOPE, /* ( -- ) */
|
||||
OP_DEL_SCOPE, /* ( -- ) */
|
||||
OP_CALL, /* ( func param1 param2 ... num_params -- result ) */
|
||||
OP_RETURN, /* ( -- ) */
|
||||
OP_LOOP, /* ( -- ) Push break & continue addresses to loop_labels */
|
||||
OP_BREAK, /* ( -- ) */
|
||||
OP_CONTINUE, /* ( -- ) */
|
||||
OP_SETRETVAL, /* ( a -- ) */
|
||||
OP_EXIT, /* ( -- ) */
|
||||
OP_BCODE_HEADER, /* ( -- ) */
|
||||
OP_ARGS, /* ( -- ) Mark the beginning of function call arguments */
|
||||
OP_FOR_IN_NEXT, /* ( name obj iter_ptr -- name obj iter_ptr_next ) */
|
||||
OP_MAX
|
||||
};
|
||||
|
||||
struct pstate;
|
||||
struct mjs;
|
||||
|
||||
MJS_PRIVATE void emit_byte(struct pstate* pstate, uint8_t byte);
|
||||
MJS_PRIVATE void emit_int(struct pstate* pstate, int64_t n);
|
||||
MJS_PRIVATE void emit_str(struct pstate* pstate, const char* ptr, size_t len);
|
||||
|
||||
/*
|
||||
* Inserts provided offset `v` at the offset `offset`.
|
||||
*
|
||||
* Returns delta at which the code was moved; the delta can be any: 0 or
|
||||
* positive or negative.
|
||||
*/
|
||||
MJS_PRIVATE int
|
||||
mjs_bcode_insert_offset(struct pstate* p, struct mjs* mjs, size_t offset, size_t v);
|
||||
|
||||
/*
|
||||
* Adds a new bcode part; does not retain `bp`.
|
||||
*/
|
||||
MJS_PRIVATE void mjs_bcode_part_add(struct mjs* mjs, const struct mjs_bcode_part* bp);
|
||||
|
||||
/*
|
||||
* Returns bcode part by the bcode number
|
||||
*/
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get(struct mjs* mjs, int num);
|
||||
|
||||
/*
|
||||
* Returns bcode part by the global bcode offset
|
||||
*/
|
||||
MJS_PRIVATE struct mjs_bcode_part* mjs_bcode_part_get_by_offset(struct mjs* mjs, size_t offset);
|
||||
|
||||
/*
|
||||
* Returns a number of bcode parts
|
||||
*/
|
||||
MJS_PRIVATE int mjs_bcode_parts_cnt(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* Adds the bcode being generated (mjs->bcode_gen) as a next bcode part
|
||||
*/
|
||||
MJS_PRIVATE void mjs_bcode_commit(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_BCODE_H_ */
|
||||
162
lib/mjs/mjs_builtin.c
Normal file
162
lib/mjs/mjs_builtin.c
Normal file
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mjs_bcode.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_dataview.h"
|
||||
#include "mjs_exec.h"
|
||||
#include "mjs_gc.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_json.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
#include "mjs_array_buf.h"
|
||||
|
||||
/*
|
||||
* If the file with the given filename was already loaded, returns the
|
||||
* corresponding bcode part; otherwise returns NULL.
|
||||
*/
|
||||
static struct mjs_bcode_part* mjs_get_loaded_file_bcode(struct mjs* mjs, const char* filename) {
|
||||
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
||||
int i;
|
||||
|
||||
if(filename == NULL) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for(i = 0; i < parts_cnt; i++) {
|
||||
struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i);
|
||||
const char* cur_fn = mjs_get_bcode_filename(mjs, bp);
|
||||
if(strcmp(filename, cur_fn) == 0) {
|
||||
return bp;
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
static void mjs_load(struct mjs* mjs) {
|
||||
mjs_val_t res = MJS_UNDEFINED;
|
||||
mjs_val_t arg0 = mjs_arg(mjs, 0);
|
||||
mjs_val_t arg1 = mjs_arg(mjs, 1);
|
||||
int custom_global = 0; /* whether the custom global object was provided */
|
||||
|
||||
if(mjs_is_string(arg0)) {
|
||||
const char* path = mjs_get_cstring(mjs, &arg0);
|
||||
struct mjs_bcode_part* bp = NULL;
|
||||
mjs_err_t ret;
|
||||
|
||||
if(mjs_is_object(arg1)) {
|
||||
custom_global = 1;
|
||||
push_mjs_val(&mjs->scopes, arg1);
|
||||
}
|
||||
bp = mjs_get_loaded_file_bcode(mjs, path);
|
||||
if(bp == NULL) {
|
||||
/* File was not loaded before, so, load */
|
||||
ret = mjs_exec_file(mjs, path, &res);
|
||||
} else {
|
||||
/*
|
||||
* File was already loaded before, so if it was evaluated successfully,
|
||||
* then skip the evaluation at all (and assume MJS_OK); otherwise
|
||||
* re-evaluate it again.
|
||||
*
|
||||
* However, if the custom global object was provided, then reevaluate
|
||||
* the file in any case.
|
||||
*/
|
||||
if(bp->exec_res != MJS_OK || custom_global) {
|
||||
ret = mjs_execute(mjs, bp->start_idx, &res);
|
||||
} else {
|
||||
ret = MJS_OK;
|
||||
}
|
||||
}
|
||||
if(ret != MJS_OK) {
|
||||
/*
|
||||
* arg0 and path might be invalidated by executing a file, so refresh
|
||||
* them
|
||||
*/
|
||||
arg0 = mjs_arg(mjs, 0);
|
||||
path = mjs_get_cstring(mjs, &arg0);
|
||||
mjs_prepend_errorf(mjs, ret, "failed to exec file \"%s\"", path);
|
||||
goto clean;
|
||||
}
|
||||
|
||||
clean:
|
||||
if(custom_global) {
|
||||
mjs_pop_val(&mjs->scopes);
|
||||
}
|
||||
}
|
||||
mjs_return(mjs, res);
|
||||
}
|
||||
|
||||
static void mjs_get_mjs(struct mjs* mjs) {
|
||||
mjs_return(mjs, mjs_mk_foreign(mjs, mjs));
|
||||
}
|
||||
|
||||
static void mjs_chr(struct mjs* mjs) {
|
||||
mjs_val_t arg0 = mjs_arg(mjs, 0), res = MJS_NULL;
|
||||
int n = mjs_get_int(mjs, arg0);
|
||||
if(mjs_is_number(arg0) && n >= 0 && n <= 255) {
|
||||
uint8_t s = n;
|
||||
res = mjs_mk_string(mjs, (const char*)&s, sizeof(s), 1);
|
||||
}
|
||||
mjs_return(mjs, res);
|
||||
}
|
||||
|
||||
static void mjs_do_gc(struct mjs* mjs) {
|
||||
mjs_val_t arg0 = mjs_arg(mjs, 0);
|
||||
mjs_gc(mjs, mjs_is_boolean(arg0) ? mjs_get_bool(mjs, arg0) : 0);
|
||||
mjs_return(mjs, arg0);
|
||||
}
|
||||
|
||||
static void mjs_s2o(struct mjs* mjs) {
|
||||
mjs_return(
|
||||
mjs,
|
||||
mjs_struct_to_obj(
|
||||
mjs,
|
||||
mjs_get_ptr(mjs, mjs_arg(mjs, 0)),
|
||||
(const struct mjs_c_struct_member*)mjs_get_ptr(mjs, mjs_arg(mjs, 1))));
|
||||
}
|
||||
|
||||
void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj) {
|
||||
mjs_val_t v;
|
||||
|
||||
mjs_set(mjs, obj, "global", ~0, obj);
|
||||
|
||||
mjs_set(mjs, obj, "load", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_load));
|
||||
mjs_set(mjs, obj, "ffi", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_call));
|
||||
mjs_set(
|
||||
mjs, obj, "ffi_cb_free", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_ffi_cb_free));
|
||||
mjs_set(mjs, obj, "mkstr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_mkstr));
|
||||
mjs_set(mjs, obj, "getMJS", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_get_mjs));
|
||||
mjs_set(mjs, obj, "die", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_die));
|
||||
mjs_set(mjs, obj, "gc", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_do_gc));
|
||||
mjs_set(mjs, obj, "chr", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_chr));
|
||||
mjs_set(mjs, obj, "s2o", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_s2o));
|
||||
|
||||
/*
|
||||
* Populate JSON.parse() and JSON.stringify()
|
||||
*/
|
||||
// v = mjs_mk_object(mjs);
|
||||
// mjs_set(
|
||||
// mjs, v, "stringify", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_stringify));
|
||||
// mjs_set(mjs, v, "parse", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_json_parse));
|
||||
// mjs_set(mjs, obj, "JSON", ~0, v);
|
||||
|
||||
/*
|
||||
* Populate Object.create()
|
||||
*/
|
||||
v = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, v, "create", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_create_object));
|
||||
mjs_set(mjs, obj, "Object", ~0, v);
|
||||
|
||||
/*
|
||||
* Populate numeric stuff
|
||||
*/
|
||||
mjs_set(mjs, obj, "NaN", ~0, MJS_TAG_NAN);
|
||||
mjs_set(mjs, obj, "isNaN", ~0, mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)mjs_op_isnan));
|
||||
|
||||
mjs_init_builtin_array_buf(mjs, obj);
|
||||
}
|
||||
22
lib/mjs/mjs_builtin.h
Normal file
22
lib/mjs/mjs_builtin.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_BUILTIN_H_
|
||||
#define MJS_BUILTIN_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
void mjs_init_builtin(struct mjs* mjs, mjs_val_t obj);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_BUILTIN_H_ */
|
||||
422
lib/mjs/mjs_core.c
Normal file
422
lib/mjs/mjs_core.c
Normal file
@@ -0,0 +1,422 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "common/cs_varint.h"
|
||||
#include "common/str_util.h"
|
||||
|
||||
#include "mjs_bcode.h"
|
||||
#include "mjs_builtin.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_exec.h"
|
||||
#include "mjs_ffi.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
|
||||
#ifndef MJS_OBJECT_ARENA_SIZE
|
||||
#define MJS_OBJECT_ARENA_SIZE 20
|
||||
#endif
|
||||
#ifndef MJS_PROPERTY_ARENA_SIZE
|
||||
#define MJS_PROPERTY_ARENA_SIZE 20
|
||||
#endif
|
||||
#ifndef MJS_FUNC_FFI_ARENA_SIZE
|
||||
#define MJS_FUNC_FFI_ARENA_SIZE 20
|
||||
#endif
|
||||
|
||||
#ifndef MJS_OBJECT_ARENA_INC_SIZE
|
||||
#define MJS_OBJECT_ARENA_INC_SIZE 10
|
||||
#endif
|
||||
#ifndef MJS_PROPERTY_ARENA_INC_SIZE
|
||||
#define MJS_PROPERTY_ARENA_INC_SIZE 10
|
||||
#endif
|
||||
#ifndef MJS_FUNC_FFI_ARENA_INC_SIZE
|
||||
#define MJS_FUNC_FFI_ARENA_INC_SIZE 10
|
||||
#endif
|
||||
|
||||
void mjs_destroy(struct mjs* mjs) {
|
||||
{
|
||||
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
||||
int i;
|
||||
for(i = 0; i < parts_cnt; i++) {
|
||||
struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i);
|
||||
if(!bp->in_rom) {
|
||||
free((void*)bp->data.p);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
mbuf_free(&mjs->bcode_gen);
|
||||
mbuf_free(&mjs->bcode_parts);
|
||||
mbuf_free(&mjs->stack);
|
||||
mbuf_free(&mjs->call_stack);
|
||||
mbuf_free(&mjs->arg_stack);
|
||||
mbuf_free(&mjs->owned_strings);
|
||||
mbuf_free(&mjs->foreign_strings);
|
||||
mbuf_free(&mjs->owned_values);
|
||||
mbuf_free(&mjs->scopes);
|
||||
mbuf_free(&mjs->loop_addresses);
|
||||
mbuf_free(&mjs->json_visited_stack);
|
||||
mbuf_free(&mjs->array_buffers);
|
||||
free(mjs->error_msg);
|
||||
free(mjs->stack_trace);
|
||||
mjs_ffi_args_free_list(mjs);
|
||||
gc_arena_destroy(mjs, &mjs->object_arena);
|
||||
gc_arena_destroy(mjs, &mjs->property_arena);
|
||||
gc_arena_destroy(mjs, &mjs->ffi_sig_arena);
|
||||
free(mjs);
|
||||
}
|
||||
|
||||
struct mjs* mjs_create(void* context) {
|
||||
mjs_val_t global_object;
|
||||
struct mjs* mjs = calloc(1, sizeof(*mjs));
|
||||
mjs->context = context;
|
||||
mbuf_init(&mjs->stack, 0);
|
||||
mbuf_init(&mjs->call_stack, 0);
|
||||
mbuf_init(&mjs->arg_stack, 0);
|
||||
mbuf_init(&mjs->owned_strings, 0);
|
||||
mbuf_init(&mjs->foreign_strings, 0);
|
||||
mbuf_init(&mjs->bcode_gen, 0);
|
||||
mbuf_init(&mjs->bcode_parts, 0);
|
||||
mbuf_init(&mjs->owned_values, 0);
|
||||
mbuf_init(&mjs->scopes, 0);
|
||||
mbuf_init(&mjs->loop_addresses, 0);
|
||||
mbuf_init(&mjs->json_visited_stack, 0);
|
||||
mbuf_init(&mjs->array_buffers, 0);
|
||||
|
||||
mjs->bcode_len = 0;
|
||||
|
||||
/*
|
||||
* The compacting GC exploits the null terminator of the previous string as a
|
||||
* marker.
|
||||
*/
|
||||
{
|
||||
char z = 0;
|
||||
mbuf_append(&mjs->owned_strings, &z, 1);
|
||||
}
|
||||
|
||||
gc_arena_init(
|
||||
&mjs->object_arena,
|
||||
sizeof(struct mjs_object),
|
||||
MJS_OBJECT_ARENA_SIZE,
|
||||
MJS_OBJECT_ARENA_INC_SIZE);
|
||||
gc_arena_init(
|
||||
&mjs->property_arena,
|
||||
sizeof(struct mjs_property),
|
||||
MJS_PROPERTY_ARENA_SIZE,
|
||||
MJS_PROPERTY_ARENA_INC_SIZE);
|
||||
gc_arena_init(
|
||||
&mjs->ffi_sig_arena,
|
||||
sizeof(struct mjs_ffi_sig),
|
||||
MJS_FUNC_FFI_ARENA_SIZE,
|
||||
MJS_FUNC_FFI_ARENA_INC_SIZE);
|
||||
mjs->ffi_sig_arena.destructor = mjs_ffi_sig_destructor;
|
||||
|
||||
global_object = mjs_mk_object(mjs);
|
||||
mjs_init_builtin(mjs, global_object);
|
||||
mjs_set_ffi_resolver(mjs, NULL, NULL);
|
||||
push_mjs_val(&mjs->scopes, global_object);
|
||||
mjs->vals.this_obj = MJS_UNDEFINED;
|
||||
mjs->vals.dataview_proto = MJS_UNDEFINED;
|
||||
|
||||
return mjs;
|
||||
}
|
||||
|
||||
mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) {
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
free(mjs->error_msg);
|
||||
mjs->error_msg = NULL;
|
||||
mjs->error = err;
|
||||
if(fmt != NULL) {
|
||||
mg_avprintf(&mjs->error_msg, 0, fmt, ap);
|
||||
}
|
||||
va_end(ap);
|
||||
return err;
|
||||
}
|
||||
|
||||
void mjs_exit(struct mjs* mjs) {
|
||||
free(mjs->error_msg);
|
||||
mjs->error_msg = NULL;
|
||||
mjs->error = MJS_NEED_EXIT;
|
||||
}
|
||||
|
||||
void mjs_set_exec_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller) {
|
||||
mjs->exec_flags_poller = poller;
|
||||
}
|
||||
|
||||
void* mjs_get_context(struct mjs* mjs) {
|
||||
return mjs->context;
|
||||
}
|
||||
|
||||
mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...) {
|
||||
char* old_error_msg = mjs->error_msg;
|
||||
char* new_error_msg = NULL;
|
||||
va_list ap;
|
||||
va_start(ap, fmt);
|
||||
|
||||
/* err should never be MJS_OK here */
|
||||
assert(err != MJS_OK);
|
||||
|
||||
mjs->error_msg = NULL;
|
||||
/* set error if only it wasn't already set to some error */
|
||||
if(mjs->error == MJS_OK) {
|
||||
mjs->error = err;
|
||||
}
|
||||
mg_avprintf(&new_error_msg, 0, fmt, ap);
|
||||
va_end(ap);
|
||||
|
||||
if(old_error_msg != NULL) {
|
||||
mg_asprintf(&mjs->error_msg, 0, "%s: %s", new_error_msg, old_error_msg);
|
||||
free(new_error_msg);
|
||||
free(old_error_msg);
|
||||
} else {
|
||||
mjs->error_msg = new_error_msg;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
||||
void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack_trace) {
|
||||
(void)fp;
|
||||
|
||||
if(print_stack_trace && mjs->stack_trace != NULL) {
|
||||
// fprintf(fp, "%s", mjs->stack_trace);
|
||||
}
|
||||
|
||||
if(msg == NULL) {
|
||||
msg = "MJS error";
|
||||
}
|
||||
|
||||
// fprintf(fp, "%s: %s\n", msg, mjs_strerror(mjs, mjs->error));
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_die(struct mjs* mjs) {
|
||||
mjs_val_t msg_v = MJS_UNDEFINED;
|
||||
const char* msg = NULL;
|
||||
size_t msg_len = 0;
|
||||
|
||||
/* get idx from arg 0 */
|
||||
if(!mjs_check_arg(mjs, 0, "msg", MJS_TYPE_STRING, &msg_v)) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
msg = mjs_get_string(mjs, &msg_v, &msg_len);
|
||||
|
||||
/* TODO(dfrank): take error type as an argument */
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "%.*s", (int)msg_len, msg);
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, MJS_UNDEFINED);
|
||||
}
|
||||
|
||||
const char* mjs_strerror(struct mjs* mjs, enum mjs_err err) {
|
||||
const char* err_names[] = {
|
||||
"NO_ERROR",
|
||||
"SYNTAX_ERROR",
|
||||
"REFERENCE_ERROR",
|
||||
"TYPE_ERROR",
|
||||
"OUT_OF_MEMORY",
|
||||
"INTERNAL_ERROR",
|
||||
"NOT_IMPLEMENTED",
|
||||
"FILE_OPEN_ERROR",
|
||||
"BAD_ARGUMENTS"};
|
||||
return mjs->error_msg == NULL || mjs->error_msg[0] == '\0' ? err_names[err] : mjs->error_msg;
|
||||
}
|
||||
|
||||
const char* mjs_get_stack_trace(struct mjs* mjs) {
|
||||
return mjs->stack_trace;
|
||||
}
|
||||
|
||||
MJS_PRIVATE size_t mjs_get_func_addr(mjs_val_t v) {
|
||||
return v & ~MJS_TAG_MASK;
|
||||
}
|
||||
|
||||
MJS_PRIVATE enum mjs_type mjs_get_type(mjs_val_t v) {
|
||||
int tag;
|
||||
if(mjs_is_number(v)) {
|
||||
return MJS_TYPE_NUMBER;
|
||||
}
|
||||
tag = (v & MJS_TAG_MASK) >> 48;
|
||||
switch(tag) {
|
||||
case MJS_TAG_FOREIGN >> 48:
|
||||
return MJS_TYPE_FOREIGN;
|
||||
case MJS_TAG_UNDEFINED >> 48:
|
||||
return MJS_TYPE_UNDEFINED;
|
||||
case MJS_TAG_OBJECT >> 48:
|
||||
return MJS_TYPE_OBJECT_GENERIC;
|
||||
case MJS_TAG_ARRAY >> 48:
|
||||
return MJS_TYPE_OBJECT_ARRAY;
|
||||
case MJS_TAG_FUNCTION >> 48:
|
||||
return MJS_TYPE_OBJECT_FUNCTION;
|
||||
case MJS_TAG_STRING_I >> 48:
|
||||
case MJS_TAG_STRING_O >> 48:
|
||||
case MJS_TAG_STRING_F >> 48:
|
||||
case MJS_TAG_STRING_D >> 48:
|
||||
case MJS_TAG_STRING_5 >> 48:
|
||||
return MJS_TYPE_STRING;
|
||||
case MJS_TAG_BOOLEAN >> 48:
|
||||
return MJS_TYPE_BOOLEAN;
|
||||
case MJS_TAG_NULL >> 48:
|
||||
return MJS_TYPE_NULL;
|
||||
case MJS_TAG_ARRAY_BUF >> 48:
|
||||
return MJS_TYPE_ARRAY_BUF;
|
||||
case MJS_TAG_ARRAY_BUF_VIEW >> 48:
|
||||
return MJS_TYPE_ARRAY_BUF_VIEW;
|
||||
default:
|
||||
abort();
|
||||
return MJS_TYPE_UNDEFINED;
|
||||
}
|
||||
}
|
||||
|
||||
mjs_val_t mjs_get_global(struct mjs* mjs) {
|
||||
return *vptr(&mjs->scopes, 0);
|
||||
}
|
||||
|
||||
static void mjs_append_stack_trace_line(struct mjs* mjs, size_t offset) {
|
||||
if(offset != MJS_BCODE_OFFSET_EXIT) {
|
||||
const char* filename = mjs_get_bcode_filename_by_offset(mjs, offset);
|
||||
int line_no = mjs_get_lineno_by_offset(mjs, offset);
|
||||
char* new_line = NULL;
|
||||
const char* fmt = "at %s:%d\n";
|
||||
if(filename == NULL) {
|
||||
// fprintf(
|
||||
// stderr,
|
||||
// "ERROR during stack trace generation: wrong bcode offset %d\n",
|
||||
// (int)offset);
|
||||
filename = "<unknown-filename>";
|
||||
}
|
||||
mg_asprintf(&new_line, 0, fmt, filename, line_no);
|
||||
|
||||
if(mjs->stack_trace != NULL) {
|
||||
char* old = mjs->stack_trace;
|
||||
mg_asprintf(&mjs->stack_trace, 0, "%s%s", mjs->stack_trace, new_line);
|
||||
free(old);
|
||||
free(new_line);
|
||||
} else {
|
||||
mjs->stack_trace = new_line;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_gen_stack_trace(struct mjs* mjs, size_t offset) {
|
||||
mjs_append_stack_trace_line(mjs, offset);
|
||||
while(mjs->call_stack.len >= sizeof(mjs_val_t) * CALL_STACK_FRAME_ITEMS_CNT) {
|
||||
int i;
|
||||
|
||||
/* set current offset to it to the offset stored in the frame */
|
||||
offset = mjs_get_int(mjs, *vptr(&mjs->call_stack, -1 - CALL_STACK_FRAME_ITEM_RETURN_ADDR));
|
||||
|
||||
/* pop frame from the call stack */
|
||||
for(i = 0; i < CALL_STACK_FRAME_ITEMS_CNT; i++) {
|
||||
mjs_pop_val(&mjs->call_stack);
|
||||
}
|
||||
|
||||
mjs_append_stack_trace_line(mjs, offset);
|
||||
}
|
||||
}
|
||||
|
||||
void mjs_own(struct mjs* mjs, mjs_val_t* v) {
|
||||
mbuf_append(&mjs->owned_values, &v, sizeof(v));
|
||||
}
|
||||
|
||||
int mjs_disown(struct mjs* mjs, mjs_val_t* v) {
|
||||
mjs_val_t** vp = (mjs_val_t**)(mjs->owned_values.buf + mjs->owned_values.len - sizeof(v));
|
||||
|
||||
for(; (char*)vp >= mjs->owned_values.buf; vp--) {
|
||||
if(*vp == v) {
|
||||
*vp = *(mjs_val_t**)(mjs->owned_values.buf + mjs->owned_values.len - sizeof(v));
|
||||
mjs->owned_values.len -= sizeof(v);
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns position in the data stack at which the called function is located,
|
||||
* and which should be later replaced with the returned value.
|
||||
*/
|
||||
MJS_PRIVATE int mjs_getretvalpos(struct mjs* mjs) {
|
||||
int pos;
|
||||
mjs_val_t* ppos = vptr(&mjs->call_stack, -1);
|
||||
// LOG(LL_INFO, ("ppos: %p %d", ppos, mjs_stack_size(&mjs->call_stack)));
|
||||
assert(ppos != NULL && mjs_is_number(*ppos));
|
||||
pos = mjs_get_int(mjs, *ppos) - 1;
|
||||
assert(pos < (int)mjs_stack_size(&mjs->stack));
|
||||
return pos;
|
||||
}
|
||||
|
||||
int mjs_nargs(struct mjs* mjs) {
|
||||
int top = mjs_stack_size(&mjs->stack);
|
||||
int pos = mjs_getretvalpos(mjs) + 1;
|
||||
// LOG(LL_INFO, ("top: %d pos: %d", top, pos));
|
||||
return pos > 0 && pos < top ? top - pos : 0;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_arg(struct mjs* mjs, int arg_index) {
|
||||
mjs_val_t res = MJS_UNDEFINED;
|
||||
int top = mjs_stack_size(&mjs->stack);
|
||||
int pos = mjs_getretvalpos(mjs) + 1;
|
||||
// LOG(LL_INFO, ("idx %d pos: %d", arg_index, pos));
|
||||
if(pos > 0 && pos + arg_index < top) {
|
||||
res = *vptr(&mjs->stack, pos + arg_index);
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
void mjs_return(struct mjs* mjs, mjs_val_t v) {
|
||||
int pos = mjs_getretvalpos(mjs);
|
||||
// LOG(LL_INFO, ("pos: %d", pos));
|
||||
mjs->stack.len = sizeof(mjs_val_t) * pos;
|
||||
mjs_push(mjs, v);
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_val_t vtop(struct mbuf* m) {
|
||||
size_t size = mjs_stack_size(m);
|
||||
return size > 0 ? *vptr(m, size - 1) : MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
MJS_PRIVATE size_t mjs_stack_size(const struct mbuf* m) {
|
||||
return m->len / sizeof(mjs_val_t);
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_val_t* vptr(struct mbuf* m, int idx) {
|
||||
int size = mjs_stack_size(m);
|
||||
if(idx < 0) idx = size + idx;
|
||||
return idx >= 0 && idx < size ? &((mjs_val_t*)m->buf)[idx] : NULL;
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_val_t mjs_pop(struct mjs* mjs) {
|
||||
if(mjs->stack.len == 0) {
|
||||
mjs_set_errorf(mjs, MJS_INTERNAL_ERROR, "stack underflow");
|
||||
return MJS_UNDEFINED;
|
||||
} else {
|
||||
return mjs_pop_val(&mjs->stack);
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE void push_mjs_val(struct mbuf* m, mjs_val_t v) {
|
||||
mbuf_append(m, &v, sizeof(v));
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_val_t mjs_pop_val(struct mbuf* m) {
|
||||
mjs_val_t v = MJS_UNDEFINED;
|
||||
assert(m->len >= sizeof(v));
|
||||
if(m->len >= sizeof(v)) {
|
||||
memcpy(&v, m->buf + m->len - sizeof(v), sizeof(v));
|
||||
m->len -= sizeof(v);
|
||||
}
|
||||
return v;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_push(struct mjs* mjs, mjs_val_t v) {
|
||||
push_mjs_val(&mjs->stack, v);
|
||||
}
|
||||
|
||||
void mjs_set_generate_jsc(struct mjs* mjs, int generate_jsc) {
|
||||
mjs->generate_jsc = generate_jsc;
|
||||
}
|
||||
137
lib/mjs/mjs_core.h
Normal file
137
lib/mjs/mjs_core.h
Normal file
@@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_CORE_H
|
||||
#define MJS_CORE_H
|
||||
|
||||
#include "mjs_ffi.h"
|
||||
#include "mjs_gc.h"
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#define JUMP_INSTRUCTION_SIZE 2
|
||||
|
||||
enum mjs_call_stack_frame_item {
|
||||
CALL_STACK_FRAME_ITEM_RETVAL_STACK_IDX, /* TOS */
|
||||
CALL_STACK_FRAME_ITEM_LOOP_ADDR_IDX,
|
||||
CALL_STACK_FRAME_ITEM_SCOPE_IDX,
|
||||
CALL_STACK_FRAME_ITEM_RETURN_ADDR,
|
||||
CALL_STACK_FRAME_ITEM_THIS,
|
||||
|
||||
CALL_STACK_FRAME_ITEMS_CNT
|
||||
};
|
||||
|
||||
struct mjs_vals {
|
||||
/* Current `this` value */
|
||||
mjs_val_t this_obj;
|
||||
mjs_val_t dataview_proto;
|
||||
|
||||
/*
|
||||
* The object against which the last `OP_GET` was invoked. Needed for
|
||||
* "method invocation pattern".
|
||||
*/
|
||||
mjs_val_t last_getprop_obj;
|
||||
};
|
||||
|
||||
struct mjs_bcode_part {
|
||||
/* Global index of the bcode part */
|
||||
size_t start_idx;
|
||||
|
||||
/* Actual bcode data */
|
||||
struct {
|
||||
const char* p; /* Memory chunk pointer */
|
||||
size_t len; /* Memory chunk length */
|
||||
} data;
|
||||
|
||||
/*
|
||||
* Result of evaluation (not parsing: if there is an error during parsing,
|
||||
* the bcode is not even committed). It is used to determine whether we
|
||||
* need to evaluate the file: if file was already evaluated, and the result
|
||||
* was MJS_OK, then we won't evaluate it again. Otherwise, we will.
|
||||
*/
|
||||
mjs_err_t exec_res : 4;
|
||||
|
||||
/* If set, bcode data does not need to be freed */
|
||||
unsigned in_rom : 1;
|
||||
};
|
||||
|
||||
struct mjs {
|
||||
struct mbuf bcode_gen;
|
||||
struct mbuf bcode_parts;
|
||||
size_t bcode_len;
|
||||
struct mbuf stack;
|
||||
struct mbuf call_stack;
|
||||
struct mbuf arg_stack;
|
||||
struct mbuf scopes; /* Scope objects */
|
||||
struct mbuf loop_addresses; /* Addresses for breaks & continues */
|
||||
struct mbuf owned_strings; /* Sequence of (varint len, char data[]) */
|
||||
struct mbuf foreign_strings; /* Sequence of (varint len, char *data) */
|
||||
struct mbuf owned_values;
|
||||
struct mbuf json_visited_stack;
|
||||
struct mbuf array_buffers;
|
||||
struct mjs_vals vals;
|
||||
char* error_msg;
|
||||
char* stack_trace;
|
||||
enum mjs_err error;
|
||||
mjs_ffi_resolver_t* dlsym; /* Symbol resolver function for FFI */
|
||||
void* dlsym_handle;
|
||||
ffi_cb_args_t* ffi_cb_args; /* List of FFI args descriptors */
|
||||
size_t cur_bcode_offset;
|
||||
mjs_flags_poller_t exec_flags_poller;
|
||||
void* context;
|
||||
|
||||
struct gc_arena object_arena;
|
||||
struct gc_arena property_arena;
|
||||
struct gc_arena ffi_sig_arena;
|
||||
|
||||
unsigned inhibit_gc : 1;
|
||||
unsigned need_gc : 1;
|
||||
unsigned generate_jsc : 1;
|
||||
};
|
||||
|
||||
/*
|
||||
* Bcode header: type of the items, and item numbers.
|
||||
*/
|
||||
typedef uint32_t mjs_header_item_t;
|
||||
enum mjs_header_items {
|
||||
MJS_HDR_ITEM_TOTAL_SIZE, /* Total size of the bcode (not counting the
|
||||
OP_BCODE_HEADER byte) */
|
||||
MJS_HDR_ITEM_BCODE_OFFSET, /* Offset to the start of the actual bcode (not
|
||||
counting the OP_BCODE_HEADER byte) */
|
||||
MJS_HDR_ITEM_MAP_OFFSET, /* Offset to the start of offset-to-line_no mapping
|
||||
k*/
|
||||
|
||||
MJS_HDR_ITEMS_CNT
|
||||
};
|
||||
|
||||
MJS_PRIVATE size_t mjs_get_func_addr(mjs_val_t v);
|
||||
|
||||
MJS_PRIVATE int mjs_getretvalpos(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE enum mjs_type mjs_get_type(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Prints stack trace starting from the given bcode offset; other offsets
|
||||
* (if any) will be fetched from the call_stack.
|
||||
*/
|
||||
MJS_PRIVATE void mjs_gen_stack_trace(struct mjs* mjs, size_t offset);
|
||||
|
||||
MJS_PRIVATE mjs_val_t vtop(struct mbuf* m);
|
||||
MJS_PRIVATE size_t mjs_stack_size(const struct mbuf* m);
|
||||
MJS_PRIVATE mjs_val_t* vptr(struct mbuf* m, int idx);
|
||||
MJS_PRIVATE void push_mjs_val(struct mbuf* m, mjs_val_t v);
|
||||
MJS_PRIVATE mjs_val_t mjs_pop_val(struct mbuf* m);
|
||||
MJS_PRIVATE mjs_val_t mjs_pop(struct mjs* mjs);
|
||||
MJS_PRIVATE void mjs_push(struct mjs* mjs, mjs_val_t v);
|
||||
MJS_PRIVATE void mjs_die(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_CORE_H */
|
||||
286
lib/mjs/mjs_core_public.h
Normal file
286
lib/mjs/mjs_core_public.h
Normal file
@@ -0,0 +1,286 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_CORE_PUBLIC_H_
|
||||
#define MJS_CORE_PUBLIC_H_
|
||||
|
||||
#if !defined(_MSC_VER) || _MSC_VER >= 1700
|
||||
#include <stdint.h>
|
||||
#else
|
||||
typedef unsigned __int64 uint64_t;
|
||||
typedef int int32_t;
|
||||
typedef unsigned char uint8_t;
|
||||
#endif
|
||||
#include <stdio.h>
|
||||
#include <stddef.h>
|
||||
#include "mjs_features.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#ifndef MJS_ENABLE_DEBUG
|
||||
#define MJS_ENABLE_DEBUG 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Double-precision floating-point number, IEEE 754
|
||||
*
|
||||
* 64 bit (8 bytes) in total
|
||||
* 1 bit sign
|
||||
* 11 bits exponent
|
||||
* 52 bits mantissa
|
||||
* 7 6 5 4 3 2 1 0
|
||||
* seeeeeee|eeeemmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm|mmmmmmmm
|
||||
*
|
||||
* If an exponent is all-1 and mantissa is all-0, then it is an INFINITY:
|
||||
* 11111111|11110000|00000000|00000000|00000000|00000000|00000000|00000000
|
||||
*
|
||||
* If an exponent is all-1 and mantissa's MSB is 1, it is a quiet NaN:
|
||||
* 11111111|11111000|00000000|00000000|00000000|00000000|00000000|00000000
|
||||
*
|
||||
* MJS NaN-packing:
|
||||
* sign and exponent is 0xfff
|
||||
* 4 bits specify type (tag), must be non-zero
|
||||
* 48 bits specify value
|
||||
*
|
||||
* 11111111|1111tttt|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv|vvvvvvvv
|
||||
* NaN marker |type| 48-bit placeholder for values: pointers, strings
|
||||
*
|
||||
* On 64-bit platforms, pointers are really 48 bit only, so they can fit,
|
||||
* provided they are sign extended
|
||||
*/
|
||||
|
||||
typedef uint64_t mjs_val_t;
|
||||
|
||||
/*
|
||||
* A tag is made of the sign bit and the 4 lower order bits of byte 6.
|
||||
* So in total we have 32 possible tags.
|
||||
*
|
||||
* Tag (1,0) however cannot hold a zero payload otherwise it's interpreted as an
|
||||
* INFINITY; for simplicity we're just not going to use that combination.
|
||||
*/
|
||||
#define MAKE_TAG(s, t) ((uint64_t)(s) << 63 | (uint64_t)0x7ff0 << 48 | (uint64_t)(t) << 48)
|
||||
|
||||
#define MJS_TAG_OBJECT MAKE_TAG(1, 1)
|
||||
#define MJS_TAG_FOREIGN MAKE_TAG(1, 2)
|
||||
#define MJS_TAG_UNDEFINED MAKE_TAG(1, 3)
|
||||
#define MJS_TAG_BOOLEAN MAKE_TAG(1, 4)
|
||||
#define MJS_TAG_NAN MAKE_TAG(1, 5)
|
||||
#define MJS_TAG_STRING_I MAKE_TAG(1, 6) /* Inlined string len < 5 */
|
||||
#define MJS_TAG_STRING_5 MAKE_TAG(1, 7) /* Inlined string len 5 */
|
||||
#define MJS_TAG_STRING_O MAKE_TAG(1, 8) /* Owned string */
|
||||
#define MJS_TAG_STRING_F MAKE_TAG(1, 9) /* Foreign string */
|
||||
#define MJS_TAG_STRING_C MAKE_TAG(1, 10) /* String chunk */
|
||||
#define MJS_TAG_STRING_D MAKE_TAG(1, 11) /* Dictionary string */
|
||||
#define MJS_TAG_ARRAY MAKE_TAG(1, 12)
|
||||
#define MJS_TAG_FUNCTION MAKE_TAG(1, 13)
|
||||
#define MJS_TAG_FUNCTION_FFI MAKE_TAG(1, 14)
|
||||
#define MJS_TAG_NULL MAKE_TAG(1, 15)
|
||||
|
||||
#define MJS_TAG_ARRAY_BUF MAKE_TAG(0, 1) /* ArrayBuffer */
|
||||
#define MJS_TAG_ARRAY_BUF_VIEW MAKE_TAG(0, 2) /* DataView */
|
||||
|
||||
#define MJS_TAG_MASK MAKE_TAG(1, 15)
|
||||
|
||||
/* This if-0 is a dirty workaround to force etags to pick `struct mjs` */
|
||||
#if 0
|
||||
/* Opaque structure. MJS engine context. */
|
||||
struct mjs {
|
||||
/* ... */
|
||||
};
|
||||
#endif
|
||||
|
||||
struct mjs;
|
||||
|
||||
enum mjs_type {
|
||||
/* Primitive types */
|
||||
MJS_TYPE_UNDEFINED,
|
||||
MJS_TYPE_NULL,
|
||||
MJS_TYPE_BOOLEAN,
|
||||
MJS_TYPE_NUMBER,
|
||||
MJS_TYPE_STRING,
|
||||
MJS_TYPE_FOREIGN,
|
||||
MJS_TYPE_ARRAY_BUF,
|
||||
MJS_TYPE_ARRAY_BUF_VIEW,
|
||||
|
||||
/* Different classes of Object type */
|
||||
MJS_TYPE_OBJECT_GENERIC,
|
||||
MJS_TYPE_OBJECT_ARRAY,
|
||||
MJS_TYPE_OBJECT_FUNCTION,
|
||||
/*
|
||||
* TODO(dfrank): if we support prototypes, need to add items for them here
|
||||
*/
|
||||
|
||||
MJS_TYPES_CNT
|
||||
};
|
||||
|
||||
typedef enum mjs_err {
|
||||
MJS_OK,
|
||||
MJS_SYNTAX_ERROR,
|
||||
MJS_REFERENCE_ERROR,
|
||||
MJS_TYPE_ERROR,
|
||||
MJS_OUT_OF_MEMORY,
|
||||
MJS_INTERNAL_ERROR,
|
||||
MJS_NOT_IMPLEMENTED_ERROR,
|
||||
MJS_FILE_READ_ERROR,
|
||||
MJS_BAD_ARGS_ERROR,
|
||||
|
||||
MJS_NEED_EXIT,
|
||||
|
||||
MJS_ERRS_CNT
|
||||
} mjs_err_t;
|
||||
|
||||
typedef void (*mjs_flags_poller_t)(struct mjs* mjs);
|
||||
|
||||
struct mjs;
|
||||
|
||||
/* Create MJS instance */
|
||||
struct mjs* mjs_create(void* context);
|
||||
|
||||
/* Destroy MJS instance */
|
||||
void mjs_destroy(struct mjs* mjs);
|
||||
|
||||
mjs_val_t mjs_get_global(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* Tells the GC about an MJS value variable/field owned by C code.
|
||||
*
|
||||
* The user's C code should own mjs_val_t variables if the value's lifetime
|
||||
* crosses any invocation of `mjs_exec()` and friends, including `mjs_call()`.
|
||||
*
|
||||
* The registration of the variable prevents the GC from mistakenly treat the
|
||||
* object as garbage.
|
||||
*
|
||||
* User code should also explicitly disown the variables with `mjs_disown()`
|
||||
* once it goes out of scope or the structure containing the mjs_val_t field is
|
||||
* freed.
|
||||
*
|
||||
* Consider the following examples:
|
||||
*
|
||||
* Correct (owning is not necessary):
|
||||
* ```c
|
||||
* mjs_val_t res;
|
||||
* mjs_exec(mjs, "....some script", &res);
|
||||
* // ... use res somehow
|
||||
*
|
||||
* mjs_val_t res;
|
||||
* mjs_exec(mjs, "....some script2", &res);
|
||||
* // ... use new res somehow
|
||||
* ```
|
||||
*
|
||||
* WRONG:
|
||||
* ```c
|
||||
* mjs_val_t res1;
|
||||
* mjs_exec(mjs, "....some script", &res1);
|
||||
*
|
||||
* mjs_val_t res2;
|
||||
* mjs_exec(mjs, "....some script2", &res2);
|
||||
*
|
||||
* // ... use res1 (WRONG!) and res2
|
||||
* ```
|
||||
*
|
||||
* The code above is wrong, because after the second invocation of
|
||||
* `mjs_exec()`, the value of `res1` is invalidated.
|
||||
*
|
||||
* Correct (res1 is owned)
|
||||
* ```c
|
||||
* mjs_val_t res1 = MJS_UNDEFINED;
|
||||
* mjs_own(mjs, &res1);
|
||||
* mjs_exec(mjs, "....some script", &res1);
|
||||
*
|
||||
* mjs_val_t res2 = MJS_UNDEFINED;
|
||||
* mjs_exec(mjs, "....some script2", &res2);
|
||||
*
|
||||
* // ... use res1 and res2
|
||||
* mjs_disown(mjs, &res1);
|
||||
* ```
|
||||
*
|
||||
* NOTE that we explicly initialized `res1` to a valid value before owning it
|
||||
* (in this case, the value is `MJS_UNDEFINED`). Owning an uninitialized
|
||||
* variable is an undefined behaviour.
|
||||
*
|
||||
* Of course, it's not an error to own a variable even if it's not mandatory:
|
||||
* e.g. in the last example we could own both `res1` and `res2`. Probably it
|
||||
* would help us in the future, when we refactor the code so that `res2` has to
|
||||
* be owned, and we could forget to do that.
|
||||
*
|
||||
* Also, if the user code has some C function called from MJS, and in this C
|
||||
* function some MJS value (`mjs_val_t`) needs to be stored somewhere and to
|
||||
* stay alive after the C function has returned, it also needs to be properly
|
||||
* owned.
|
||||
*/
|
||||
void mjs_own(struct mjs* mjs, mjs_val_t* v);
|
||||
|
||||
/*
|
||||
* Disowns the value previously owned by `mjs_own()`.
|
||||
*
|
||||
* Returns 1 if value is found, 0 otherwise.
|
||||
*/
|
||||
int mjs_disown(struct mjs* mjs, mjs_val_t* v);
|
||||
|
||||
mjs_err_t mjs_set_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...);
|
||||
|
||||
void mjs_exit(struct mjs* mjs);
|
||||
|
||||
void mjs_set_exec_flags_poller(struct mjs* mjs, mjs_flags_poller_t poller);
|
||||
|
||||
void* mjs_get_context(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* If there is no error message already set, then it's equal to
|
||||
* `mjs_set_errorf()`.
|
||||
*
|
||||
* Otherwise, an old message gets prepended with the new one, followed by a
|
||||
* colon. (the previously set error code is kept)
|
||||
*/
|
||||
mjs_err_t mjs_prepend_errorf(struct mjs* mjs, mjs_err_t err, const char* fmt, ...);
|
||||
|
||||
/*
|
||||
* Print the last error details. If print_stack_trace is non-zero, also
|
||||
* print stack trace. `msg` is the message which gets prepended to the actual
|
||||
* error message, if it's NULL, then "MJS error" is used.
|
||||
*/
|
||||
void mjs_print_error(struct mjs* mjs, FILE* fp, const char* msg, int print_stack_trace);
|
||||
|
||||
/*
|
||||
* return a string representation of an error.
|
||||
* the error string might be overwritten by calls to `mjs_set_errorf`.
|
||||
*/
|
||||
const char* mjs_strerror(struct mjs* mjs, enum mjs_err err);
|
||||
|
||||
const char* mjs_get_stack_trace(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* Sets whether *.jsc files are generated when *.js file is executed. By
|
||||
* default it's 0.
|
||||
*
|
||||
* If either `MJS_GENERATE_JSC` or `CS_MMAP` is off, then this function has no
|
||||
* effect.
|
||||
*/
|
||||
void mjs_set_generate_jsc(struct mjs* mjs, int generate_jsc);
|
||||
|
||||
/*
|
||||
* When invoked from a cfunction, returns number of arguments passed to the
|
||||
* current JS function call.
|
||||
*/
|
||||
int mjs_nargs(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* When invoked from a cfunction, returns n-th argument to the current JS
|
||||
* function call.
|
||||
*/
|
||||
mjs_val_t mjs_arg(struct mjs* mjs, int n);
|
||||
|
||||
/*
|
||||
* Sets return value for the current JS function call.
|
||||
*/
|
||||
void mjs_return(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_CORE_PUBLIC_H_ */
|
||||
86
lib/mjs/mjs_dataview.c
Normal file
86
lib/mjs/mjs_dataview.c
Normal file
@@ -0,0 +1,86 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mjs_exec_public.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_util.h"
|
||||
|
||||
void* mjs_mem_to_ptr(unsigned val) {
|
||||
return (void*)(uintptr_t)val;
|
||||
}
|
||||
|
||||
void* mjs_mem_get_ptr(void* base, int offset) {
|
||||
return (char*)base + offset;
|
||||
}
|
||||
|
||||
void mjs_mem_set_ptr(void* ptr, void* val) {
|
||||
*(void**)ptr = val;
|
||||
}
|
||||
|
||||
double mjs_mem_get_dbl(void* ptr) {
|
||||
double v;
|
||||
memcpy(&v, ptr, sizeof(v));
|
||||
return v;
|
||||
}
|
||||
|
||||
void mjs_mem_set_dbl(void* ptr, double val) {
|
||||
memcpy(ptr, &val, sizeof(val));
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(dfrank): add support for unsigned ints to ffi and use
|
||||
* unsigned int here
|
||||
*/
|
||||
double mjs_mem_get_uint(void* ptr, int size, int bigendian) {
|
||||
uint8_t* p = (uint8_t*)ptr;
|
||||
int i, inc = bigendian ? 1 : -1;
|
||||
unsigned int res = 0;
|
||||
p += bigendian ? 0 : size - 1;
|
||||
for(i = 0; i < size; i++, p += inc) {
|
||||
res <<= 8;
|
||||
res |= *p;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(dfrank): add support for unsigned ints to ffi and use
|
||||
* unsigned int here
|
||||
*/
|
||||
double mjs_mem_get_int(void* ptr, int size, int bigendian) {
|
||||
uint8_t* p = (uint8_t*)ptr;
|
||||
int i, inc = bigendian ? 1 : -1;
|
||||
int res = 0;
|
||||
p += bigendian ? 0 : size - 1;
|
||||
|
||||
for(i = 0; i < size; i++, p += inc) {
|
||||
res <<= 8;
|
||||
res |= *p;
|
||||
}
|
||||
|
||||
/* sign-extend */
|
||||
{
|
||||
int extra = sizeof(res) - size;
|
||||
for(i = 0; i < extra; i++) res <<= 8;
|
||||
for(i = 0; i < extra; i++) res >>= 8;
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
void mjs_mem_set_uint(void* ptr, unsigned int val, int size, int bigendian) {
|
||||
uint8_t* p = (uint8_t*)ptr + (bigendian ? size - 1 : 0);
|
||||
int i, inc = bigendian ? -1 : 1;
|
||||
for(i = 0; i < size; i++, p += inc) {
|
||||
*p = val & 0xff;
|
||||
val >>= 8;
|
||||
}
|
||||
}
|
||||
|
||||
void mjs_mem_set_int(void* ptr, int val, int size, int bigendian) {
|
||||
mjs_mem_set_uint(ptr, val, size, bigendian);
|
||||
}
|
||||
32
lib/mjs/mjs_dataview.h
Normal file
32
lib/mjs/mjs_dataview.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_DATAVIEW_H_
|
||||
#define MJS_DATAVIEW_H_
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Functions for memory introspection.
|
||||
* These are supposed to be FFI-ed and used from the JS environment.
|
||||
*/
|
||||
|
||||
void* mjs_mem_to_ptr(unsigned int val);
|
||||
void* mjs_mem_get_ptr(void* base, int offset);
|
||||
void mjs_mem_set_ptr(void* ptr, void* val);
|
||||
double mjs_mem_get_dbl(void* ptr);
|
||||
void mjs_mem_set_dbl(void* ptr, double val);
|
||||
double mjs_mem_get_uint(void* ptr, int size, int bigendian);
|
||||
double mjs_mem_get_int(void* ptr, int size, int bigendian);
|
||||
void mjs_mem_set_uint(void* ptr, unsigned int val, int size, int bigendian);
|
||||
void mjs_mem_set_int(void* ptr, int val, int size, int bigendian);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_DATAVIEW_H_ */
|
||||
1252
lib/mjs/mjs_exec.c
Normal file
1252
lib/mjs/mjs_exec.c
Normal file
File diff suppressed because it is too large
Load Diff
27
lib/mjs/mjs_exec.h
Normal file
27
lib/mjs/mjs_exec.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_EXEC_H_
|
||||
#define MJS_EXEC_H_
|
||||
|
||||
#include "mjs_exec_public.h"
|
||||
|
||||
/*
|
||||
* A special bcode offset value which causes mjs_execute() to exit immediately;
|
||||
* used in mjs_apply().
|
||||
*/
|
||||
#define MJS_BCODE_OFFSET_EXIT ((size_t)0x7fffffff)
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
MJS_PRIVATE mjs_err_t mjs_execute(struct mjs* mjs, size_t off, mjs_val_t* res);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_EXEC_H_ */
|
||||
34
lib/mjs/mjs_exec_public.h
Normal file
34
lib/mjs/mjs_exec_public.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_EXEC_PUBLIC_H_
|
||||
#define MJS_EXEC_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
mjs_err_t mjs_exec(struct mjs*, const char* src, mjs_val_t* res);
|
||||
|
||||
mjs_err_t mjs_exec_file(struct mjs* mjs, const char* path, mjs_val_t* res);
|
||||
mjs_err_t mjs_apply(
|
||||
struct mjs* mjs,
|
||||
mjs_val_t* res,
|
||||
mjs_val_t func,
|
||||
mjs_val_t this_val,
|
||||
int nargs,
|
||||
mjs_val_t* args);
|
||||
mjs_err_t
|
||||
mjs_call(struct mjs* mjs, mjs_val_t* res, mjs_val_t func, mjs_val_t this_val, int nargs, ...);
|
||||
mjs_val_t mjs_get_this(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_EXEC_PUBLIC_H_ */
|
||||
33
lib/mjs/mjs_features.h
Normal file
33
lib/mjs/mjs_features.h
Normal file
@@ -0,0 +1,33 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_FEATURES_H_
|
||||
#define MJS_FEATURES_H_
|
||||
|
||||
#if !defined(MJS_AGGRESSIVE_GC)
|
||||
#define MJS_AGGRESSIVE_GC 0
|
||||
#endif
|
||||
|
||||
#if !defined(MJS_MEMORY_STATS)
|
||||
#define MJS_MEMORY_STATS 0
|
||||
#endif
|
||||
|
||||
/*
|
||||
* MJS_GENERATE_JSC: if enabled, and if mmapping is also enabled (CS_MMAP),
|
||||
* then execution of any .js file will result in creation of a .jsc file with
|
||||
* precompiled bcode, and this .jsc file will be mmapped, instead of keeping
|
||||
* bcode in RAM.
|
||||
*
|
||||
* By default it's enabled (provided that CS_MMAP is defined)
|
||||
*/
|
||||
#if !defined(MJS_GENERATE_JSC)
|
||||
#if defined(CS_MMAP)
|
||||
#define MJS_GENERATE_JSC 1
|
||||
#else
|
||||
#define MJS_GENERATE_JSC 0
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#endif /* MJS_FEATURES_H_ */
|
||||
1223
lib/mjs/mjs_ffi.c
Normal file
1223
lib/mjs/mjs_ffi.c
Normal file
File diff suppressed because it is too large
Load Diff
135
lib/mjs/mjs_ffi.h
Normal file
135
lib/mjs/mjs_ffi.h
Normal file
@@ -0,0 +1,135 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_FFI_H_
|
||||
#define MJS_FFI_H_
|
||||
|
||||
#include "ffi/ffi.h"
|
||||
#include "mjs_ffi_public.h"
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#define MJS_CB_ARGS_MAX_CNT 6
|
||||
#define MJS_CB_SIGNATURE_MAX_SIZE (MJS_CB_ARGS_MAX_CNT + 1 /* return type */)
|
||||
|
||||
typedef uint8_t mjs_ffi_ctype_t;
|
||||
|
||||
enum ffi_sig_type {
|
||||
FFI_SIG_FUNC,
|
||||
FFI_SIG_CALLBACK,
|
||||
};
|
||||
|
||||
/*
|
||||
* Parsed FFI signature
|
||||
*/
|
||||
struct mjs_ffi_sig {
|
||||
/*
|
||||
* Callback signature, corresponds to the arg of type MJS_FFI_CTYPE_CALLBACK
|
||||
* TODO(dfrank): probably we'll need to support multiple callback/userdata
|
||||
* pairs
|
||||
*
|
||||
* NOTE(dfrank): instances of this structure are grouped into GC arenas and
|
||||
* managed by GC, and for the GC mark to work, the first element should be
|
||||
* a pointer (so that the two LSBs are not used).
|
||||
*/
|
||||
struct mjs_ffi_sig* cb_sig;
|
||||
|
||||
/*
|
||||
* The first item is the return value type (for `void`, `MJS_FFI_CTYPE_NONE`
|
||||
* is used); the rest are arguments. If some argument is
|
||||
* `MJS_FFI_CTYPE_NONE`, it means that there are no more arguments.
|
||||
*/
|
||||
mjs_ffi_ctype_t val_types[MJS_CB_SIGNATURE_MAX_SIZE];
|
||||
|
||||
/*
|
||||
* Function to call. If `is_callback` is not set, then it's the function
|
||||
* obtained by dlsym; otherwise it's a pointer to the appropriate callback
|
||||
* implementation.
|
||||
*/
|
||||
ffi_fn_t* fn;
|
||||
|
||||
/* Number of arguments in the signature */
|
||||
int8_t args_cnt;
|
||||
|
||||
/*
|
||||
* If set, then the signature represents the callback (as opposed to a normal
|
||||
* function), and `fn` points to the suitable callback implementation.
|
||||
*/
|
||||
unsigned is_callback : 1;
|
||||
unsigned is_valid : 1;
|
||||
};
|
||||
typedef struct mjs_ffi_sig mjs_ffi_sig_t;
|
||||
|
||||
/* Initialize new FFI signature */
|
||||
MJS_PRIVATE void mjs_ffi_sig_init(mjs_ffi_sig_t* sig);
|
||||
/* Copy existing FFI signature */
|
||||
MJS_PRIVATE void mjs_ffi_sig_copy(mjs_ffi_sig_t* to, const mjs_ffi_sig_t* from);
|
||||
/* Free FFI signature. NOTE: the pointer `sig` itself is not freed */
|
||||
MJS_PRIVATE void mjs_ffi_sig_free(mjs_ffi_sig_t* sig);
|
||||
|
||||
/*
|
||||
* Creates a new FFI signature from the GC arena, and return mjs_val_t which
|
||||
* wraps it.
|
||||
*/
|
||||
MJS_PRIVATE mjs_val_t mjs_mk_ffi_sig(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* Checks whether the given value is a FFI signature.
|
||||
*/
|
||||
MJS_PRIVATE int mjs_is_ffi_sig(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Wraps FFI signature structure into mjs_val_t value.
|
||||
*/
|
||||
MJS_PRIVATE mjs_val_t mjs_ffi_sig_to_value(struct mjs_ffi_sig* psig);
|
||||
|
||||
/*
|
||||
* Extracts a pointer to the FFI signature struct from the mjs_val_t value.
|
||||
*/
|
||||
MJS_PRIVATE struct mjs_ffi_sig* mjs_get_ffi_sig_struct(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* A wrapper for mjs_ffi_sig_free() suitable to use as a GC cell destructor.
|
||||
*/
|
||||
MJS_PRIVATE void mjs_ffi_sig_destructor(struct mjs* mjs, void* psig);
|
||||
|
||||
MJS_PRIVATE int mjs_ffi_sig_set_val_type(mjs_ffi_sig_t* sig, int idx, mjs_ffi_ctype_t type);
|
||||
MJS_PRIVATE int
|
||||
mjs_ffi_sig_validate(struct mjs* mjs, mjs_ffi_sig_t* sig, enum ffi_sig_type sig_type);
|
||||
MJS_PRIVATE int mjs_ffi_is_regular_word(mjs_ffi_ctype_t type);
|
||||
MJS_PRIVATE int mjs_ffi_is_regular_word_or_void(mjs_ffi_ctype_t type);
|
||||
|
||||
struct mjs_ffi_cb_args {
|
||||
struct mjs_ffi_cb_args* next;
|
||||
struct mjs* mjs;
|
||||
mjs_ffi_sig_t sig;
|
||||
mjs_val_t func;
|
||||
mjs_val_t userdata;
|
||||
};
|
||||
typedef struct mjs_ffi_cb_args ffi_cb_args_t;
|
||||
|
||||
/*
|
||||
* cfunction:
|
||||
* Parses the FFI signature string and returns a value wrapping mjs_ffi_sig_t.
|
||||
*/
|
||||
MJS_PRIVATE mjs_err_t mjs_ffi_call(struct mjs* mjs);
|
||||
|
||||
/*
|
||||
* cfunction:
|
||||
* Performs the FFI signature call.
|
||||
*/
|
||||
MJS_PRIVATE mjs_err_t mjs_ffi_call2(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE void mjs_ffi_cb_free(struct mjs*);
|
||||
MJS_PRIVATE void mjs_ffi_args_free_list(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_FFI_H_ */
|
||||
40
lib/mjs/mjs_ffi_public.h
Normal file
40
lib/mjs/mjs_ffi_public.h
Normal file
@@ -0,0 +1,40 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_FFI_PUBLIC_H_
|
||||
#define MJS_FFI_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
enum mjs_ffi_ctype {
|
||||
MJS_FFI_CTYPE_NONE,
|
||||
MJS_FFI_CTYPE_USERDATA,
|
||||
MJS_FFI_CTYPE_CALLBACK,
|
||||
MJS_FFI_CTYPE_INT,
|
||||
MJS_FFI_CTYPE_BOOL,
|
||||
MJS_FFI_CTYPE_DOUBLE,
|
||||
MJS_FFI_CTYPE_FLOAT,
|
||||
MJS_FFI_CTYPE_CHAR_PTR,
|
||||
MJS_FFI_CTYPE_VOID_PTR,
|
||||
MJS_FFI_CTYPE_STRUCT_MG_STR_PTR,
|
||||
MJS_FFI_CTYPE_STRUCT_MG_STR,
|
||||
MJS_FFI_CTYPE_INVALID,
|
||||
};
|
||||
|
||||
typedef void*(mjs_ffi_resolver_t)(void* handle, const char* symbol);
|
||||
|
||||
void mjs_set_ffi_resolver(struct mjs* mjs, mjs_ffi_resolver_t* dlsym, void* handle);
|
||||
|
||||
void* mjs_ffi_resolve(struct mjs* mjs, const char* symbol);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_FFI_PUBLIC_H_ */
|
||||
535
lib/mjs/mjs_gc.c
Normal file
535
lib/mjs/mjs_gc.c
Normal file
@@ -0,0 +1,535 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#include "common/cs_varint.h"
|
||||
#include "common/mbuf.h"
|
||||
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_ffi.h"
|
||||
#include "mjs_gc.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
|
||||
/*
|
||||
* Macros for marking reachable things: use bit 0.
|
||||
*/
|
||||
#define MARK(p) (((struct gc_cell*)(p))->head.word |= 1)
|
||||
#define UNMARK(p) (((struct gc_cell*)(p))->head.word &= ~1)
|
||||
#define MARKED(p) (((struct gc_cell*)(p))->head.word & 1)
|
||||
|
||||
/*
|
||||
* Similar to `MARK()` / `UNMARK()` / `MARKED()`, but `.._FREE` counterparts
|
||||
* are intended to mark free cells (as opposed to used ones), so they use
|
||||
* bit 1.
|
||||
*/
|
||||
#define MARK_FREE(p) (((struct gc_cell*)(p))->head.word |= 2)
|
||||
#define UNMARK_FREE(p) (((struct gc_cell*)(p))->head.word &= ~2)
|
||||
#define MARKED_FREE(p) (((struct gc_cell*)(p))->head.word & 2)
|
||||
|
||||
/*
|
||||
* When each arena has that or less free cells, GC will be scheduled
|
||||
*/
|
||||
#define GC_ARENA_CELLS_RESERVE 2
|
||||
|
||||
static struct gc_block* gc_new_block(struct gc_arena* a, size_t size);
|
||||
static void gc_free_block(struct gc_block* b);
|
||||
static void gc_mark_mbuf_pt(struct mjs* mjs, const struct mbuf* mbuf);
|
||||
|
||||
MJS_PRIVATE struct mjs_object* new_object(struct mjs* mjs) {
|
||||
return (struct mjs_object*)gc_alloc_cell(mjs, &mjs->object_arena);
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_property* new_property(struct mjs* mjs) {
|
||||
return (struct mjs_property*)gc_alloc_cell(mjs, &mjs->property_arena);
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_ffi_sig* new_ffi_sig(struct mjs* mjs) {
|
||||
return (struct mjs_ffi_sig*)gc_alloc_cell(mjs, &mjs->ffi_sig_arena);
|
||||
}
|
||||
|
||||
/* Initializes a new arena. */
|
||||
MJS_PRIVATE void gc_arena_init(
|
||||
struct gc_arena* a,
|
||||
size_t cell_size,
|
||||
size_t initial_size,
|
||||
size_t size_increment) {
|
||||
assert(cell_size >= sizeof(uintptr_t));
|
||||
|
||||
memset(a, 0, sizeof(*a));
|
||||
a->cell_size = cell_size;
|
||||
a->size_increment = size_increment;
|
||||
a->blocks = gc_new_block(a, initial_size);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void gc_arena_destroy(struct mjs* mjs, struct gc_arena* a) {
|
||||
struct gc_block* b;
|
||||
|
||||
if(a->blocks != NULL) {
|
||||
gc_sweep(mjs, a, 0);
|
||||
for(b = a->blocks; b != NULL;) {
|
||||
struct gc_block* tmp;
|
||||
tmp = b;
|
||||
b = b->next;
|
||||
gc_free_block(tmp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void gc_free_block(struct gc_block* b) {
|
||||
free(b->base);
|
||||
free(b);
|
||||
}
|
||||
|
||||
static struct gc_block* gc_new_block(struct gc_arena* a, size_t size) {
|
||||
struct gc_cell* cur;
|
||||
struct gc_block* b;
|
||||
|
||||
b = (struct gc_block*)calloc(1, sizeof(*b));
|
||||
if(b == NULL) abort();
|
||||
|
||||
b->size = size;
|
||||
b->base = (struct gc_cell*)calloc(a->cell_size, b->size);
|
||||
if(b->base == NULL) abort();
|
||||
|
||||
for(cur = GC_CELL_OP(a, b->base, +, 0); cur < GC_CELL_OP(a, b->base, +, b->size);
|
||||
cur = GC_CELL_OP(a, cur, +, 1)) {
|
||||
cur->head.link = a->free;
|
||||
a->free = cur;
|
||||
}
|
||||
|
||||
return b;
|
||||
}
|
||||
|
||||
/*
|
||||
* Returns whether the given arena has GC_ARENA_CELLS_RESERVE or less free
|
||||
* cells
|
||||
*/
|
||||
static int gc_arena_is_gc_needed(struct gc_arena* a) {
|
||||
struct gc_cell* r = a->free;
|
||||
int i;
|
||||
|
||||
for(i = 0; i <= GC_ARENA_CELLS_RESERVE; i++, r = r->head.link) {
|
||||
if(r == NULL) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int gc_strings_is_gc_needed(struct mjs* mjs) {
|
||||
struct mbuf* m = &mjs->owned_strings;
|
||||
return (double)m->len / (double)m->size > (double)0.9;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void* gc_alloc_cell(struct mjs* mjs, struct gc_arena* a) {
|
||||
struct gc_cell* r;
|
||||
|
||||
if(a->free == NULL) {
|
||||
struct gc_block* b = gc_new_block(a, a->size_increment);
|
||||
b->next = a->blocks;
|
||||
a->blocks = b;
|
||||
}
|
||||
r = a->free;
|
||||
|
||||
UNMARK(r);
|
||||
|
||||
a->free = r->head.link;
|
||||
|
||||
#if MJS_MEMORY_STATS
|
||||
a->allocations++;
|
||||
a->alive++;
|
||||
#endif
|
||||
|
||||
/* Schedule GC if needed */
|
||||
if(gc_arena_is_gc_needed(a)) {
|
||||
mjs->need_gc = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* TODO(mkm): minor opt possible since most of the fields
|
||||
* are overwritten downstream, but not worth the yak shave time
|
||||
* when fields are added to GC-able structures */
|
||||
memset(r, 0, a->cell_size);
|
||||
return (void*)r;
|
||||
}
|
||||
|
||||
/*
|
||||
* Scans the arena and add all unmarked cells to the free list.
|
||||
*
|
||||
* Empty blocks get deallocated. The head of the free list will contais cells
|
||||
* from the last (oldest) block. Cells will thus be allocated in block order.
|
||||
*/
|
||||
void gc_sweep(struct mjs* mjs, struct gc_arena* a, size_t start) {
|
||||
struct gc_block* b;
|
||||
struct gc_cell* cur;
|
||||
struct gc_block** prevp = &a->blocks;
|
||||
#if MJS_MEMORY_STATS
|
||||
a->alive = 0;
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Before we sweep, we should mark all free cells in a way that is
|
||||
* distinguishable from marked used cells.
|
||||
*/
|
||||
{
|
||||
struct gc_cell* next;
|
||||
for(cur = a->free; cur != NULL; cur = next) {
|
||||
next = cur->head.link;
|
||||
MARK_FREE(cur);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* We'll rebuild the whole `free` list, so initially we just reset it
|
||||
*/
|
||||
a->free = NULL;
|
||||
|
||||
for(b = a->blocks; b != NULL;) {
|
||||
size_t freed_in_block = 0;
|
||||
/*
|
||||
* if it turns out that this block is 100% garbage
|
||||
* we can release the whole block, but the addition
|
||||
* of it's cells to the free list has to be undone.
|
||||
*/
|
||||
struct gc_cell* prev_free = a->free;
|
||||
|
||||
for(cur = GC_CELL_OP(a, b->base, +, start); cur < GC_CELL_OP(a, b->base, +, b->size);
|
||||
cur = GC_CELL_OP(a, cur, +, 1)) {
|
||||
if(MARKED(cur)) {
|
||||
/* The cell is used and marked */
|
||||
UNMARK(cur);
|
||||
#if MJS_MEMORY_STATS
|
||||
a->alive++;
|
||||
#endif
|
||||
} else {
|
||||
/*
|
||||
* The cell is either:
|
||||
* - free
|
||||
* - garbage that's about to be freed
|
||||
*/
|
||||
|
||||
if(MARKED_FREE(cur)) {
|
||||
/* The cell is free, so, just unmark it */
|
||||
UNMARK_FREE(cur);
|
||||
} else {
|
||||
/*
|
||||
* The cell is used and should be freed: call the destructor and
|
||||
* reset the memory
|
||||
*/
|
||||
if(a->destructor != NULL) {
|
||||
a->destructor(mjs, cur);
|
||||
}
|
||||
memset(cur, 0, a->cell_size);
|
||||
}
|
||||
|
||||
/* Add this cell to the `free` list */
|
||||
cur->head.link = a->free;
|
||||
a->free = cur;
|
||||
freed_in_block++;
|
||||
#if MJS_MEMORY_STATS
|
||||
a->garbage++;
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* don't free the initial block, which is at the tail
|
||||
* because it has a special size aimed at reducing waste
|
||||
* and simplifying initial startup. TODO(mkm): improve
|
||||
* */
|
||||
if(b->next != NULL && freed_in_block == b->size) {
|
||||
*prevp = b->next;
|
||||
gc_free_block(b);
|
||||
b = *prevp;
|
||||
a->free = prev_free;
|
||||
} else {
|
||||
prevp = &b->next;
|
||||
b = b->next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/* Mark an FFI signature */
|
||||
static void gc_mark_ffi_sig(struct mjs* mjs, mjs_val_t* v) {
|
||||
struct mjs_ffi_sig* psig;
|
||||
|
||||
assert(mjs_is_ffi_sig(*v));
|
||||
|
||||
psig = mjs_get_ffi_sig_struct(*v);
|
||||
|
||||
/*
|
||||
* we treat all object like things like objects but they might be functions,
|
||||
* gc_check_val checks the appropriate arena per actual value type.
|
||||
*/
|
||||
if(!gc_check_val(mjs, *v)) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if(MARKED(psig)) return;
|
||||
|
||||
MARK(psig);
|
||||
}
|
||||
|
||||
/* Mark an object */
|
||||
static void gc_mark_object(struct mjs* mjs, mjs_val_t* v) {
|
||||
struct mjs_object* obj_base;
|
||||
struct mjs_property* prop;
|
||||
struct mjs_property* next;
|
||||
|
||||
assert(mjs_is_object_based(*v));
|
||||
|
||||
obj_base = get_object_struct(*v);
|
||||
|
||||
/*
|
||||
* we treat all object like things like objects but they might be functions,
|
||||
* gc_check_val checks the appropriate arena per actual value type.
|
||||
*/
|
||||
if(!gc_check_val(mjs, *v)) {
|
||||
abort();
|
||||
}
|
||||
|
||||
if(MARKED(obj_base)) return;
|
||||
|
||||
/* mark object itself, and its properties */
|
||||
for((prop = obj_base->properties), MARK(obj_base); prop != NULL; prop = next) {
|
||||
if(!gc_check_ptr(&mjs->property_arena, prop)) {
|
||||
abort();
|
||||
}
|
||||
|
||||
gc_mark(mjs, &prop->name);
|
||||
gc_mark(mjs, &prop->value);
|
||||
|
||||
next = prop->next;
|
||||
MARK(prop);
|
||||
}
|
||||
|
||||
/* mark object's prototype */
|
||||
/*
|
||||
* We dropped support for object prototypes in MJS.
|
||||
* If we ever bring it back, don't forget to mark it
|
||||
*/
|
||||
/* gc_mark(mjs, mjs_get_proto(mjs, v)); */
|
||||
}
|
||||
|
||||
/* Mark a string value */
|
||||
static void gc_mark_string(struct mjs* mjs, mjs_val_t* v) {
|
||||
mjs_val_t h, tmp = 0;
|
||||
char* s;
|
||||
|
||||
/* clang-format off */
|
||||
|
||||
/*
|
||||
* If a value points to an unmarked string we shall:
|
||||
* 1. save the first 6 bytes of the string
|
||||
* since we need to be able to distinguish real values from
|
||||
* the saved first 6 bytes of the string, we need to tag the chunk
|
||||
* as MJS_TAG_STRING_C
|
||||
* 2. encode value's address (v) into the first 6 bytes of the string.
|
||||
* 3. put the saved 8 bytes (tag + chunk) back into the value.
|
||||
* 4. mark the string by putting '\1' in the NUL terminator of the previous
|
||||
* string chunk.
|
||||
*
|
||||
* If a value points to an already marked string we shall:
|
||||
* (0, <6 bytes of a pointer to a mjs_val_t>), hence we have to skip
|
||||
* the first byte. We tag the value pointer as a MJS_TAG_FOREIGN
|
||||
* so that it won't be followed during recursive mark.
|
||||
*
|
||||
* ... the rest is the same
|
||||
*
|
||||
* Note: 64-bit pointers can be represented with 48-bits
|
||||
*/
|
||||
|
||||
/* clang-format on */
|
||||
|
||||
assert((*v & MJS_TAG_MASK) == MJS_TAG_STRING_O);
|
||||
|
||||
s = mjs->owned_strings.buf + gc_string_mjs_val_to_offset(*v);
|
||||
assert(s < mjs->owned_strings.buf + mjs->owned_strings.len);
|
||||
if(s[-1] == '\0') {
|
||||
memcpy(&tmp, s, sizeof(tmp) - 2);
|
||||
tmp |= MJS_TAG_STRING_C;
|
||||
} else {
|
||||
memcpy(&tmp, s, sizeof(tmp) - 2);
|
||||
tmp |= MJS_TAG_FOREIGN;
|
||||
}
|
||||
|
||||
h = (mjs_val_t)(uintptr_t)v;
|
||||
s[-1] = 1;
|
||||
memcpy(s, &h, sizeof(h) - 2);
|
||||
memcpy(v, &tmp, sizeof(tmp));
|
||||
}
|
||||
|
||||
MJS_PRIVATE void gc_mark(struct mjs* mjs, mjs_val_t* v) {
|
||||
if(mjs_is_object_based(*v)) {
|
||||
gc_mark_object(mjs, v);
|
||||
}
|
||||
if(mjs_is_ffi_sig(*v)) {
|
||||
gc_mark_ffi_sig(mjs, v);
|
||||
}
|
||||
if((*v & MJS_TAG_MASK) == MJS_TAG_STRING_O) {
|
||||
gc_mark_string(mjs, v);
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE uint64_t gc_string_mjs_val_to_offset(mjs_val_t v) {
|
||||
return (((uint64_t)(uintptr_t)get_ptr(v)) & ~MJS_TAG_MASK);
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_val_t gc_string_val_from_offset(uint64_t s) {
|
||||
return s | MJS_TAG_STRING_O;
|
||||
}
|
||||
|
||||
void gc_compact_strings(struct mjs* mjs) {
|
||||
char* p = mjs->owned_strings.buf + 1;
|
||||
uint64_t h, next, head = 1;
|
||||
int len, llen;
|
||||
|
||||
while(p < mjs->owned_strings.buf + mjs->owned_strings.len) {
|
||||
if(p[-1] == '\1') {
|
||||
/* relocate and update ptrs */
|
||||
h = 0;
|
||||
memcpy(&h, p, sizeof(h) - 2);
|
||||
|
||||
/*
|
||||
* relocate pointers until we find the tail.
|
||||
* The tail is marked with MJS_TAG_STRING_C,
|
||||
* while mjs_val_t link pointers are tagged with MJS_TAG_FOREIGN
|
||||
*/
|
||||
for(; (h & MJS_TAG_MASK) != MJS_TAG_STRING_C; h = next) {
|
||||
h &= ~MJS_TAG_MASK;
|
||||
memcpy(&next, (char*)(uintptr_t)h, sizeof(h));
|
||||
|
||||
*(mjs_val_t*)(uintptr_t)h = gc_string_val_from_offset(head);
|
||||
}
|
||||
h &= ~MJS_TAG_MASK;
|
||||
|
||||
/*
|
||||
* the tail contains the first 6 bytes we stole from
|
||||
* the actual string.
|
||||
*/
|
||||
len = cs_varint_decode_unsafe((unsigned char*)&h, &llen);
|
||||
len += llen + 1;
|
||||
|
||||
/*
|
||||
* restore the saved 6 bytes
|
||||
* TODO(mkm): think about endianness
|
||||
*/
|
||||
memcpy(p, &h, sizeof(h) - 2);
|
||||
|
||||
/*
|
||||
* and relocate the string data by packing it to the left.
|
||||
*/
|
||||
memmove(mjs->owned_strings.buf + head, p, len);
|
||||
mjs->owned_strings.buf[head - 1] = 0x0;
|
||||
p += len;
|
||||
head += len;
|
||||
} else {
|
||||
len = cs_varint_decode_unsafe((unsigned char*)p, &llen);
|
||||
len += llen + 1;
|
||||
|
||||
p += len;
|
||||
}
|
||||
}
|
||||
|
||||
mjs->owned_strings.len = head;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int maybe_gc(struct mjs* mjs) {
|
||||
if(!mjs->inhibit_gc) {
|
||||
mjs_gc(mjs, 0);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* mark an array of `mjs_val_t` values (*not pointers* to them)
|
||||
*/
|
||||
static void gc_mark_val_array(struct mjs* mjs, mjs_val_t* vals, size_t len) {
|
||||
mjs_val_t* vp;
|
||||
for(vp = vals; vp < vals + len; vp++) {
|
||||
gc_mark(mjs, vp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* mark an mbuf containing *pointers* to `mjs_val_t` values
|
||||
*/
|
||||
static void gc_mark_mbuf_pt(struct mjs* mjs, const struct mbuf* mbuf) {
|
||||
mjs_val_t** vp;
|
||||
for(vp = (mjs_val_t**)mbuf->buf; (char*)vp < mbuf->buf + mbuf->len; vp++) {
|
||||
gc_mark(mjs, *vp);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* mark an mbuf containing `mjs_val_t` values (*not pointers* to them)
|
||||
*/
|
||||
static void gc_mark_mbuf_val(struct mjs* mjs, const struct mbuf* mbuf) {
|
||||
gc_mark_val_array(mjs, (mjs_val_t*)mbuf->buf, mbuf->len / sizeof(mjs_val_t));
|
||||
}
|
||||
|
||||
static void gc_mark_ffi_cbargs_list(struct mjs* mjs, ffi_cb_args_t* cbargs) {
|
||||
for(; cbargs != NULL; cbargs = cbargs->next) {
|
||||
gc_mark(mjs, &cbargs->func);
|
||||
gc_mark(mjs, &cbargs->userdata);
|
||||
}
|
||||
}
|
||||
|
||||
/* Perform garbage collection */
|
||||
void mjs_gc(struct mjs* mjs, int full) {
|
||||
gc_mark_val_array(mjs, (mjs_val_t*)&mjs->vals, sizeof(mjs->vals) / sizeof(mjs_val_t));
|
||||
|
||||
gc_mark_mbuf_pt(mjs, &mjs->owned_values);
|
||||
gc_mark_mbuf_val(mjs, &mjs->scopes);
|
||||
gc_mark_mbuf_val(mjs, &mjs->stack);
|
||||
gc_mark_mbuf_val(mjs, &mjs->call_stack);
|
||||
|
||||
gc_mark_ffi_cbargs_list(mjs, mjs->ffi_cb_args);
|
||||
|
||||
gc_compact_strings(mjs);
|
||||
|
||||
gc_sweep(mjs, &mjs->object_arena, 0);
|
||||
gc_sweep(mjs, &mjs->property_arena, 0);
|
||||
gc_sweep(mjs, &mjs->ffi_sig_arena, 0);
|
||||
|
||||
if(full) {
|
||||
/*
|
||||
* In case of full GC, we also resize strings buffer, but we still leave
|
||||
* some extra space (at most, `_MJS_STRING_BUF_RESERVE`) in order to avoid
|
||||
* frequent reallocations
|
||||
*/
|
||||
size_t trimmed_size = mjs->owned_strings.len + _MJS_STRING_BUF_RESERVE;
|
||||
if(trimmed_size < mjs->owned_strings.size) {
|
||||
mbuf_resize(&mjs->owned_strings, trimmed_size);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE int gc_check_val(struct mjs* mjs, mjs_val_t v) {
|
||||
if(mjs_is_object_based(v)) {
|
||||
return gc_check_ptr(&mjs->object_arena, get_object_struct(v));
|
||||
}
|
||||
if(mjs_is_ffi_sig(v)) {
|
||||
return gc_check_ptr(&mjs->ffi_sig_arena, mjs_get_ffi_sig_struct(v));
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int gc_check_ptr(const struct gc_arena* a, const void* ptr) {
|
||||
const struct gc_cell* p = (const struct gc_cell*)ptr;
|
||||
struct gc_block* b;
|
||||
for(b = a->blocks; b != NULL; b = b->next) {
|
||||
if(p >= b->base && p < GC_CELL_OP(a, b->base, +, b->size)) {
|
||||
return 1;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
60
lib/mjs/mjs_gc.h
Normal file
60
lib/mjs/mjs_gc.h
Normal file
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_GC_H_
|
||||
#define MJS_GC_H_
|
||||
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_mm.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_gc_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* performs arithmetics on gc_cell pointers as if they were arena->cell_size
|
||||
* bytes wide
|
||||
*/
|
||||
#define GC_CELL_OP(arena, cell, op, arg) \
|
||||
((struct gc_cell*)(((char*)(cell))op((arg) * (arena)->cell_size)))
|
||||
|
||||
struct gc_cell {
|
||||
union {
|
||||
struct gc_cell* link;
|
||||
uintptr_t word;
|
||||
} head;
|
||||
};
|
||||
|
||||
MJS_PRIVATE int gc_strings_is_gc_needed(struct mjs* mjs);
|
||||
|
||||
/* perform gc if not inhibited */
|
||||
MJS_PRIVATE int maybe_gc(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE struct mjs_object* new_object(struct mjs*);
|
||||
MJS_PRIVATE struct mjs_property* new_property(struct mjs*);
|
||||
MJS_PRIVATE struct mjs_ffi_sig* new_ffi_sig(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE void gc_mark(struct mjs* mjs, mjs_val_t* val);
|
||||
|
||||
MJS_PRIVATE void gc_arena_init(struct gc_arena*, size_t, size_t, size_t);
|
||||
MJS_PRIVATE void gc_arena_destroy(struct mjs*, struct gc_arena* a);
|
||||
MJS_PRIVATE void gc_sweep(struct mjs*, struct gc_arena*, size_t);
|
||||
MJS_PRIVATE void* gc_alloc_cell(struct mjs*, struct gc_arena*);
|
||||
|
||||
MJS_PRIVATE uint64_t gc_string_mjs_val_to_offset(mjs_val_t v);
|
||||
|
||||
/* return 0 if v is an object/function with a bad pointer */
|
||||
MJS_PRIVATE int gc_check_val(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
/* checks whether a pointer is within the ranges of an arena */
|
||||
MJS_PRIVATE int gc_check_ptr(const struct gc_arena* a, const void* p);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_GC_H_ */
|
||||
25
lib/mjs/mjs_gc_public.h
Normal file
25
lib/mjs/mjs_gc_public.h
Normal file
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright (c) 2014 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_GC_PUBLIC_H_
|
||||
#define MJS_GC_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Perform garbage collection.
|
||||
* Pass true to full in order to reclaim unused heap back to the OS.
|
||||
*/
|
||||
void mjs_gc(struct mjs* mjs, int full);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_GC_PUBLIC_H_ */
|
||||
92
lib/mjs/mjs_internal.h
Normal file
92
lib/mjs/mjs_internal.h
Normal file
@@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_INTERNAL_H_
|
||||
#define MJS_INTERNAL_H_
|
||||
|
||||
#include <assert.h>
|
||||
#include <ctype.h>
|
||||
#include <math.h>
|
||||
#include <stdarg.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
#ifndef FAST
|
||||
#define FAST
|
||||
#endif
|
||||
|
||||
#ifndef STATIC
|
||||
#define STATIC
|
||||
#endif
|
||||
|
||||
#ifndef ENDL
|
||||
#define ENDL "\n"
|
||||
#endif
|
||||
|
||||
#ifndef MJS_EXPOSE_PRIVATE
|
||||
#define MJS_EXPOSE_PRIVATE 1
|
||||
#endif
|
||||
|
||||
#if MJS_EXPOSE_PRIVATE
|
||||
#define MJS_PRIVATE
|
||||
#else
|
||||
#define MJS_PRIVATE static
|
||||
#endif
|
||||
|
||||
#ifndef ARRAY_SIZE
|
||||
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
|
||||
#endif
|
||||
|
||||
#if !defined(WEAK)
|
||||
#if(defined(__GNUC__) || defined(__TI_COMPILER_VERSION__)) && !defined(_WIN32)
|
||||
#define WEAK __attribute__((weak))
|
||||
#else
|
||||
#define WEAK
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#ifndef CS_ENABLE_STDIO
|
||||
#define CS_ENABLE_STDIO 1
|
||||
#endif
|
||||
|
||||
#include "common/cs_dbg.h"
|
||||
#include "common/cs_file.h"
|
||||
#include "common/mbuf.h"
|
||||
|
||||
#if defined(_WIN32) && _MSC_VER < 1700
|
||||
typedef signed char int8_t;
|
||||
typedef unsigned char uint8_t;
|
||||
typedef int int32_t;
|
||||
typedef unsigned int uint32_t;
|
||||
typedef short int16_t;
|
||||
typedef unsigned short uint16_t;
|
||||
typedef __int64 int64_t;
|
||||
typedef unsigned long uintptr_t;
|
||||
#define STRX(x) #x
|
||||
#define STR(x) STRX(x)
|
||||
#define __func__ __FILE__ ":" STR(__LINE__)
|
||||
// #define snprintf _snprintf
|
||||
#define vsnprintf _vsnprintf
|
||||
#define isnan(x) _isnan(x)
|
||||
#define va_copy(x, y) (x) = (y)
|
||||
#define CS_DEFINE_DIRENT
|
||||
#include <windows.h>
|
||||
#else
|
||||
#if defined(__unix__) || defined(__APPLE__)
|
||||
#include <dlfcn.h>
|
||||
#endif
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Number of bytes reserved for the jump offset initially. The most practical
|
||||
* value is 1, but for testing it's useful to set it to 0 and to some large
|
||||
* value as well (like, 4), to make sure that the code behaves correctly under
|
||||
* all circumstances.
|
||||
*/
|
||||
#ifndef MJS_INIT_OFFSET_SIZE
|
||||
#define MJS_INIT_OFFSET_SIZE 1
|
||||
#endif
|
||||
|
||||
#endif /* MJS_INTERNAL_H_ */
|
||||
523
lib/mjs/mjs_json.c
Normal file
523
lib/mjs/mjs_json.c
Normal file
@@ -0,0 +1,523 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "common/str_util.h"
|
||||
#include "common/frozen/frozen.h"
|
||||
#include "mjs_array.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util_public.h"
|
||||
|
||||
#define BUF_LEFT(size, used) (((size_t)(used) < (size)) ? ((size) - (used)) : 0)
|
||||
|
||||
/*
|
||||
* Returns whether the value of given type should be skipped when generating
|
||||
* JSON output
|
||||
*
|
||||
* So far it always returns 0, but we might add some logic later, if we
|
||||
* implement some non-jsonnable objects
|
||||
*/
|
||||
static int should_skip_for_json(enum mjs_type type) {
|
||||
int ret;
|
||||
switch(type) {
|
||||
/* All permitted values */
|
||||
case MJS_TYPE_NULL:
|
||||
case MJS_TYPE_BOOLEAN:
|
||||
case MJS_TYPE_NUMBER:
|
||||
case MJS_TYPE_STRING:
|
||||
case MJS_TYPE_ARRAY_BUF:
|
||||
case MJS_TYPE_ARRAY_BUF_VIEW:
|
||||
case MJS_TYPE_OBJECT_GENERIC:
|
||||
case MJS_TYPE_OBJECT_ARRAY:
|
||||
ret = 0;
|
||||
break;
|
||||
default:
|
||||
ret = 1;
|
||||
break;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
static const char* hex_digits = "0123456789abcdef";
|
||||
static char* append_hex(char* buf, char* limit, uint8_t c) {
|
||||
if(buf < limit) *buf++ = 'u';
|
||||
if(buf < limit) *buf++ = '0';
|
||||
if(buf < limit) *buf++ = '0';
|
||||
if(buf < limit) *buf++ = hex_digits[(int)((c >> 4) % 0xf)];
|
||||
if(buf < limit) *buf++ = hex_digits[(int)(c & 0xf)];
|
||||
return buf;
|
||||
}
|
||||
|
||||
/*
|
||||
* Appends quoted s to buf. Any double quote contained in s will be escaped.
|
||||
* Returns the number of characters that would have been added,
|
||||
* like snprintf.
|
||||
* If size is zero it doesn't output anything but keeps counting.
|
||||
*/
|
||||
static int snquote(char* buf, size_t size, const char* s, size_t len) {
|
||||
char* limit = buf + size;
|
||||
const char* end;
|
||||
/*
|
||||
* String single character escape sequence:
|
||||
* http://www.ecma-international.org/ecma-262/6.0/index.html#table-34
|
||||
*
|
||||
* 0x8 -> \b
|
||||
* 0x9 -> \t
|
||||
* 0xa -> \n
|
||||
* 0xb -> \v
|
||||
* 0xc -> \f
|
||||
* 0xd -> \r
|
||||
*/
|
||||
const char* specials = "btnvfr";
|
||||
size_t i = 0;
|
||||
|
||||
i++;
|
||||
if(buf < limit) *buf++ = '"';
|
||||
|
||||
for(end = s + len; s < end; s++) {
|
||||
if(*s == '"' || *s == '\\') {
|
||||
i++;
|
||||
if(buf < limit) *buf++ = '\\';
|
||||
} else if(*s >= '\b' && *s <= '\r') {
|
||||
i += 2;
|
||||
if(buf < limit) *buf++ = '\\';
|
||||
if(buf < limit) *buf++ = specials[*s - '\b'];
|
||||
continue;
|
||||
} else if((unsigned char)*s < '\b' || (*s > '\r' && *s < ' ')) {
|
||||
i += 6 /* \uXX XX */;
|
||||
if(buf < limit) *buf++ = '\\';
|
||||
buf = append_hex(buf, limit, (uint8_t)*s);
|
||||
continue;
|
||||
}
|
||||
i++;
|
||||
if(buf < limit) *buf++ = *s;
|
||||
}
|
||||
|
||||
i++;
|
||||
if(buf < limit) *buf++ = '"';
|
||||
|
||||
if(buf < limit) {
|
||||
*buf = '\0';
|
||||
} else if(size != 0) {
|
||||
/*
|
||||
* There is no room for the NULL char, but the size wasn't zero, so we can
|
||||
* safely put NULL in the previous byte
|
||||
*/
|
||||
*(buf - 1) = '\0';
|
||||
}
|
||||
return i;
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_err_t to_json_or_debug(
|
||||
struct mjs* mjs,
|
||||
mjs_val_t v,
|
||||
char* buf,
|
||||
size_t size,
|
||||
size_t* res_len,
|
||||
uint8_t is_debug) {
|
||||
mjs_val_t el;
|
||||
char* vp;
|
||||
mjs_err_t rcode = MJS_OK;
|
||||
size_t len = 0;
|
||||
/*
|
||||
* TODO(dfrank) : also push all `mjs_val_t`s that are declared below
|
||||
*/
|
||||
|
||||
if(size > 0) *buf = '\0';
|
||||
|
||||
if(!is_debug && should_skip_for_json(mjs_get_type(v))) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
for(vp = mjs->json_visited_stack.buf;
|
||||
vp < mjs->json_visited_stack.buf + mjs->json_visited_stack.len;
|
||||
vp += sizeof(mjs_val_t)) {
|
||||
if(*(mjs_val_t*)vp == v) {
|
||||
strncpy(buf, "[Circular]", size);
|
||||
len = 10;
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
|
||||
switch(mjs_get_type(v)) {
|
||||
case MJS_TYPE_NULL:
|
||||
case MJS_TYPE_BOOLEAN:
|
||||
case MJS_TYPE_NUMBER:
|
||||
case MJS_TYPE_UNDEFINED:
|
||||
case MJS_TYPE_FOREIGN:
|
||||
case MJS_TYPE_ARRAY_BUF:
|
||||
case MJS_TYPE_ARRAY_BUF_VIEW:
|
||||
/* For those types, regular `mjs_to_string()` works */
|
||||
{
|
||||
/* refactor: mjs_to_string allocates memory every time */
|
||||
char* p = NULL;
|
||||
int need_free = 0;
|
||||
rcode = mjs_to_string(mjs, &v, &p, &len, &need_free);
|
||||
c_snprintf(buf, size, "%.*s", (int)len, p);
|
||||
if(need_free) {
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
goto clean;
|
||||
|
||||
case MJS_TYPE_STRING: {
|
||||
/*
|
||||
* For strings we can't just use `primitive_to_str()`, because we need
|
||||
* quoted value
|
||||
*/
|
||||
size_t n;
|
||||
const char* str = mjs_get_string(mjs, &v, &n);
|
||||
len = snquote(buf, size, str, n);
|
||||
goto clean;
|
||||
}
|
||||
|
||||
case MJS_TYPE_OBJECT_FUNCTION:
|
||||
case MJS_TYPE_OBJECT_GENERIC: {
|
||||
char* b = buf;
|
||||
struct mjs_property* prop = NULL;
|
||||
struct mjs_object* o = NULL;
|
||||
|
||||
mbuf_append(&mjs->json_visited_stack, (char*)&v, sizeof(v));
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), "{");
|
||||
o = get_object_struct(v);
|
||||
for(prop = o->properties; prop != NULL; prop = prop->next) {
|
||||
size_t n;
|
||||
const char* s;
|
||||
if(!is_debug && should_skip_for_json(mjs_get_type(prop->value))) {
|
||||
continue;
|
||||
}
|
||||
if(b - buf != 1) { /* Not the first property to be printed */
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), ",");
|
||||
}
|
||||
s = mjs_get_string(mjs, &prop->name, &n);
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), "\"%.*s\":", (int)n, s);
|
||||
{
|
||||
size_t tmp = 0;
|
||||
rcode =
|
||||
to_json_or_debug(mjs, prop->value, b, BUF_LEFT(size, b - buf), &tmp, is_debug);
|
||||
if(rcode != MJS_OK) {
|
||||
goto clean_iter;
|
||||
}
|
||||
b += tmp;
|
||||
}
|
||||
}
|
||||
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), "}");
|
||||
mjs->json_visited_stack.len -= sizeof(v);
|
||||
|
||||
clean_iter:
|
||||
len = b - buf;
|
||||
goto clean;
|
||||
}
|
||||
case MJS_TYPE_OBJECT_ARRAY: {
|
||||
int has;
|
||||
char* b = buf;
|
||||
size_t i, alen = mjs_array_length(mjs, v);
|
||||
mbuf_append(&mjs->json_visited_stack, (char*)&v, sizeof(v));
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), "[");
|
||||
for(i = 0; i < alen; i++) {
|
||||
el = mjs_array_get2(mjs, v, i, &has);
|
||||
if(has) {
|
||||
size_t tmp = 0;
|
||||
if(!is_debug && should_skip_for_json(mjs_get_type(el))) {
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), "null");
|
||||
} else {
|
||||
rcode = to_json_or_debug(mjs, el, b, BUF_LEFT(size, b - buf), &tmp, is_debug);
|
||||
if(rcode != MJS_OK) {
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
b += tmp;
|
||||
} else {
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), "null");
|
||||
}
|
||||
if(i != alen - 1) {
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), ",");
|
||||
}
|
||||
}
|
||||
b += c_snprintf(b, BUF_LEFT(size, b - buf), "]");
|
||||
mjs->json_visited_stack.len -= sizeof(v);
|
||||
len = b - buf;
|
||||
goto clean;
|
||||
}
|
||||
|
||||
case MJS_TYPES_CNT:
|
||||
abort();
|
||||
}
|
||||
|
||||
abort();
|
||||
|
||||
len = 0; /* for compilers that don't know about abort() */
|
||||
goto clean;
|
||||
|
||||
clean:
|
||||
if(rcode != MJS_OK) {
|
||||
len = 0;
|
||||
}
|
||||
if(res_len != NULL) {
|
||||
*res_len = len;
|
||||
}
|
||||
return rcode;
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_err_t
|
||||
mjs_json_stringify(struct mjs* mjs, mjs_val_t v, char* buf, size_t size, char** res) {
|
||||
mjs_err_t rcode = MJS_OK;
|
||||
char* p = buf;
|
||||
size_t len;
|
||||
|
||||
to_json_or_debug(mjs, v, buf, size, &len, 0);
|
||||
|
||||
if(len >= size) {
|
||||
/* Buffer is not large enough. Allocate a bigger one */
|
||||
p = (char*)malloc(len + 1);
|
||||
rcode = mjs_json_stringify(mjs, v, p, len + 1, res);
|
||||
assert(*res == p);
|
||||
goto clean;
|
||||
} else {
|
||||
*res = p;
|
||||
goto clean;
|
||||
}
|
||||
|
||||
clean:
|
||||
/*
|
||||
* If we're going to return an error, and we allocated a buffer, then free
|
||||
* it. Otherwise, caller should free it.
|
||||
*/
|
||||
if(rcode != MJS_OK && p != buf) {
|
||||
free(p);
|
||||
}
|
||||
return rcode;
|
||||
}
|
||||
|
||||
/*
|
||||
* JSON parsing frame: a separate frame is allocated for each nested
|
||||
* object/array during parsing
|
||||
*/
|
||||
struct json_parse_frame {
|
||||
mjs_val_t val;
|
||||
struct json_parse_frame* up;
|
||||
};
|
||||
|
||||
/*
|
||||
* Context for JSON parsing by means of json_walk()
|
||||
*/
|
||||
struct json_parse_ctx {
|
||||
struct mjs* mjs;
|
||||
mjs_val_t result;
|
||||
struct json_parse_frame* frame;
|
||||
enum mjs_err rcode;
|
||||
};
|
||||
|
||||
/* Allocate JSON parse frame */
|
||||
static struct json_parse_frame* alloc_json_frame(struct json_parse_ctx* ctx, mjs_val_t v) {
|
||||
struct json_parse_frame* frame =
|
||||
(struct json_parse_frame*)calloc(sizeof(struct json_parse_frame), 1);
|
||||
frame->val = v;
|
||||
mjs_own(ctx->mjs, &frame->val);
|
||||
return frame;
|
||||
}
|
||||
|
||||
/* Free JSON parse frame, return the previous one (which may be NULL) */
|
||||
static struct json_parse_frame*
|
||||
free_json_frame(struct json_parse_ctx* ctx, struct json_parse_frame* frame) {
|
||||
struct json_parse_frame* up = frame->up;
|
||||
mjs_disown(ctx->mjs, &frame->val);
|
||||
free(frame);
|
||||
return up;
|
||||
}
|
||||
|
||||
/* Callback for json_walk() */
|
||||
static void frozen_cb(
|
||||
void* data,
|
||||
const char* name,
|
||||
size_t name_len,
|
||||
const char* path,
|
||||
const struct json_token* token) {
|
||||
struct json_parse_ctx* ctx = (struct json_parse_ctx*)data;
|
||||
mjs_val_t v = MJS_UNDEFINED;
|
||||
|
||||
(void)path;
|
||||
|
||||
mjs_own(ctx->mjs, &v);
|
||||
|
||||
switch(token->type) {
|
||||
case JSON_TYPE_STRING: {
|
||||
char* dst;
|
||||
if(token->len > 0 && (dst = malloc(token->len)) != NULL) {
|
||||
int len = json_unescape(token->ptr, token->len, dst, token->len);
|
||||
if(len < 0) {
|
||||
mjs_prepend_errorf(ctx->mjs, MJS_TYPE_ERROR, "invalid JSON string");
|
||||
break;
|
||||
}
|
||||
v = mjs_mk_string(ctx->mjs, dst, len, 1 /* copy */);
|
||||
free(dst);
|
||||
} else {
|
||||
/*
|
||||
* This branch is for 0-len strings, and for malloc errors
|
||||
* TODO(lsm): on malloc error, propagate the error upstream
|
||||
*/
|
||||
v = mjs_mk_string(ctx->mjs, "", 0, 1 /* copy */);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case JSON_TYPE_NUMBER:
|
||||
v = mjs_mk_number(ctx->mjs, strtod(token->ptr, NULL));
|
||||
break;
|
||||
case JSON_TYPE_TRUE:
|
||||
v = mjs_mk_boolean(ctx->mjs, 1);
|
||||
break;
|
||||
case JSON_TYPE_FALSE:
|
||||
v = mjs_mk_boolean(ctx->mjs, 0);
|
||||
break;
|
||||
case JSON_TYPE_NULL:
|
||||
v = MJS_NULL;
|
||||
break;
|
||||
case JSON_TYPE_OBJECT_START:
|
||||
v = mjs_mk_object(ctx->mjs);
|
||||
break;
|
||||
case JSON_TYPE_ARRAY_START:
|
||||
v = mjs_mk_array(ctx->mjs);
|
||||
break;
|
||||
|
||||
case JSON_TYPE_OBJECT_END:
|
||||
case JSON_TYPE_ARRAY_END: {
|
||||
/* Object or array has finished: deallocate its frame */
|
||||
ctx->frame = free_json_frame(ctx, ctx->frame);
|
||||
} break;
|
||||
|
||||
default:
|
||||
LOG(LL_ERROR, ("Wrong token type %d\n", token->type));
|
||||
break;
|
||||
}
|
||||
|
||||
if(!mjs_is_undefined(v)) {
|
||||
if(name != NULL && name_len != 0) {
|
||||
/* Need to define a property on the current object/array */
|
||||
if(mjs_is_object(ctx->frame->val)) {
|
||||
mjs_set(ctx->mjs, ctx->frame->val, name, name_len, v);
|
||||
} else if(mjs_is_array(ctx->frame->val)) {
|
||||
/*
|
||||
* TODO(dfrank): consult name_len. Currently it's not a problem due to
|
||||
* the implementation details of frozen, but it might change
|
||||
*/
|
||||
int idx = (int)strtod(name, NULL);
|
||||
mjs_array_set(ctx->mjs, ctx->frame->val, idx, v);
|
||||
} else {
|
||||
LOG(LL_ERROR, ("Current value is neither object nor array\n"));
|
||||
}
|
||||
} else {
|
||||
/* This is a root value */
|
||||
assert(ctx->frame == NULL);
|
||||
|
||||
/*
|
||||
* This value will also be the overall result of JSON parsing
|
||||
* (it's already owned by the `mjs_alt_json_parse()`)
|
||||
*/
|
||||
ctx->result = v;
|
||||
}
|
||||
|
||||
if(token->type == JSON_TYPE_OBJECT_START || token->type == JSON_TYPE_ARRAY_START) {
|
||||
/* New object or array has just started, so we need to allocate a frame
|
||||
* for it */
|
||||
struct json_parse_frame* new_frame = alloc_json_frame(ctx, v);
|
||||
new_frame->up = ctx->frame;
|
||||
ctx->frame = new_frame;
|
||||
}
|
||||
}
|
||||
|
||||
mjs_disown(ctx->mjs, &v);
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_err_t mjs_json_parse(struct mjs* mjs, const char* str, size_t len, mjs_val_t* res) {
|
||||
struct json_parse_ctx* ctx = (struct json_parse_ctx*)calloc(sizeof(struct json_parse_ctx), 1);
|
||||
int json_res;
|
||||
enum mjs_err rcode = MJS_OK;
|
||||
|
||||
ctx->mjs = mjs;
|
||||
ctx->result = MJS_UNDEFINED;
|
||||
ctx->frame = NULL;
|
||||
ctx->rcode = MJS_OK;
|
||||
|
||||
mjs_own(mjs, &ctx->result);
|
||||
|
||||
{
|
||||
/*
|
||||
* We have to reallocate the buffer before invoking json_walk, because
|
||||
* frozen_cb can create new strings, which can result in the reallocation
|
||||
* of mjs string mbuf, invalidating the `str` pointer.
|
||||
*/
|
||||
char* stmp = malloc(len);
|
||||
memcpy(stmp, str, len);
|
||||
json_res = json_walk(stmp, len, frozen_cb, ctx);
|
||||
free(stmp);
|
||||
stmp = NULL;
|
||||
|
||||
/* str might have been invalidated, so null it out */
|
||||
str = NULL;
|
||||
}
|
||||
|
||||
if(ctx->rcode != MJS_OK) {
|
||||
rcode = ctx->rcode;
|
||||
mjs_prepend_errorf(mjs, rcode, "invalid JSON string");
|
||||
} else if(json_res < 0) {
|
||||
/* There was an error during parsing */
|
||||
rcode = MJS_TYPE_ERROR;
|
||||
mjs_prepend_errorf(mjs, rcode, "invalid JSON string");
|
||||
} else {
|
||||
/* Expression is parsed successfully */
|
||||
*res = ctx->result;
|
||||
|
||||
/* There should be no allocated frames */
|
||||
assert(ctx->frame == NULL);
|
||||
}
|
||||
|
||||
if(rcode != MJS_OK) {
|
||||
/* There might be some allocated frames in case of malformed JSON */
|
||||
while(ctx->frame != NULL) {
|
||||
ctx->frame = free_json_frame(ctx, ctx->frame);
|
||||
}
|
||||
}
|
||||
|
||||
mjs_disown(mjs, &ctx->result);
|
||||
free(ctx);
|
||||
|
||||
return rcode;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_op_json_stringify(struct mjs* mjs) {
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
mjs_val_t val = mjs_arg(mjs, 0);
|
||||
|
||||
if(mjs_nargs(mjs) < 1) {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing a value to stringify");
|
||||
} else {
|
||||
char* p = NULL;
|
||||
if(mjs_json_stringify(mjs, val, NULL, 0, &p) == MJS_OK) {
|
||||
ret = mjs_mk_string(mjs, p, ~0, 1 /* copy */);
|
||||
free(p);
|
||||
}
|
||||
}
|
||||
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_op_json_parse(struct mjs* mjs) {
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
mjs_val_t arg0 = mjs_arg(mjs, 0);
|
||||
|
||||
if(mjs_is_string(arg0)) {
|
||||
size_t len;
|
||||
const char* str = mjs_get_string(mjs, &arg0, &len);
|
||||
mjs_json_parse(mjs, str, len, &ret);
|
||||
} else {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "string argument required");
|
||||
}
|
||||
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
32
lib/mjs/mjs_json.h
Normal file
32
lib/mjs/mjs_json.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_JSON_H_
|
||||
#define MJS_JSON_H_
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
MJS_PRIVATE mjs_err_t to_json_or_debug(
|
||||
struct mjs* mjs,
|
||||
mjs_val_t v,
|
||||
char* buf,
|
||||
size_t size,
|
||||
size_t* res_len,
|
||||
uint8_t is_debug);
|
||||
|
||||
MJS_PRIVATE mjs_err_t
|
||||
mjs_json_stringify(struct mjs* mjs, mjs_val_t v, char* buf, size_t size, char** res);
|
||||
MJS_PRIVATE void mjs_op_json_stringify(struct mjs* mjs);
|
||||
MJS_PRIVATE void mjs_op_json_parse(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE mjs_err_t mjs_json_parse(struct mjs* mjs, const char* str, size_t len, mjs_val_t* res);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_JSON_H_ */
|
||||
17
lib/mjs/mjs_license.h
Normal file
17
lib/mjs/mjs_license.h
Normal file
@@ -0,0 +1,17 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*
|
||||
* This software is dual-licensed: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License version 2 as
|
||||
* published by the Free Software Foundation. For the terms of this
|
||||
* license, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
* You are free to use this software under the terms of the GNU General
|
||||
* Public License, but WITHOUT ANY WARRANTY; without even the implied
|
||||
* warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
|
||||
* See the GNU General Public License for more details.
|
||||
*
|
||||
* Alternatively, you can license this software under a commercial
|
||||
* license, as set out in <https://www.cesanta.com/license>.
|
||||
*/
|
||||
44
lib/mjs/mjs_mm.h
Normal file
44
lib/mjs/mjs_mm.h
Normal file
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
* Copyright (c) 2014-2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_MM_H_
|
||||
#define MJS_MM_H_
|
||||
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
struct mjs;
|
||||
|
||||
typedef void (*gc_cell_destructor_t)(struct mjs* mjs, void*);
|
||||
|
||||
struct gc_block {
|
||||
struct gc_block* next;
|
||||
struct gc_cell* base;
|
||||
size_t size;
|
||||
};
|
||||
|
||||
struct gc_arena {
|
||||
struct gc_block* blocks;
|
||||
size_t size_increment;
|
||||
struct gc_cell* free; /* head of free list */
|
||||
size_t cell_size;
|
||||
|
||||
#if MJS_MEMORY_STATS
|
||||
unsigned long allocations; /* cumulative counter of allocations */
|
||||
unsigned long garbage; /* cumulative counter of garbage */
|
||||
unsigned long alive; /* number of living cells */
|
||||
#endif
|
||||
|
||||
gc_cell_destructor_t destructor;
|
||||
};
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_MM_H_ */
|
||||
399
lib/mjs/mjs_object.c
Normal file
399
lib/mjs/mjs_object.c
Normal file
@@ -0,0 +1,399 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
|
||||
#include "common/mg_str.h"
|
||||
|
||||
MJS_PRIVATE mjs_val_t mjs_object_to_value(struct mjs_object* o) {
|
||||
if(o == NULL) {
|
||||
return MJS_NULL;
|
||||
} else {
|
||||
return mjs_legit_pointer_to_value(o) | MJS_TAG_OBJECT;
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v) {
|
||||
struct mjs_object* ret = NULL;
|
||||
if(mjs_is_null(v)) {
|
||||
ret = NULL;
|
||||
} else {
|
||||
assert(mjs_is_object_based(v));
|
||||
ret = (struct mjs_object*)get_ptr(v);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_object(struct mjs* mjs) {
|
||||
struct mjs_object* o = new_object(mjs);
|
||||
if(o == NULL) {
|
||||
return MJS_NULL;
|
||||
}
|
||||
(void)mjs;
|
||||
o->properties = NULL;
|
||||
return mjs_object_to_value(o);
|
||||
}
|
||||
|
||||
int mjs_is_object(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_OBJECT || (v & MJS_TAG_MASK) == MJS_TAG_ARRAY;
|
||||
}
|
||||
|
||||
int mjs_is_object_based(mjs_val_t v) {
|
||||
return ((v & MJS_TAG_MASK) == MJS_TAG_OBJECT) || ((v & MJS_TAG_MASK) == MJS_TAG_ARRAY) ||
|
||||
((v & MJS_TAG_MASK) == MJS_TAG_ARRAY_BUF_VIEW);
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_property*
|
||||
mjs_get_own_property(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) {
|
||||
struct mjs_property* p;
|
||||
struct mjs_object* o;
|
||||
|
||||
if(!mjs_is_object_based(obj)) {
|
||||
return NULL;
|
||||
}
|
||||
|
||||
o = get_object_struct(obj);
|
||||
|
||||
if(len <= 5) {
|
||||
mjs_val_t ss = mjs_mk_string(mjs, name, len, 1);
|
||||
for(p = o->properties; p != NULL; p = p->next) {
|
||||
if(p->name == ss) return p;
|
||||
}
|
||||
} else {
|
||||
for(p = o->properties; p != NULL; p = p->next) {
|
||||
if(mjs_strcmp(mjs, &p->name, name, len) == 0) return p;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_property*
|
||||
mjs_get_own_property_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
|
||||
size_t n;
|
||||
char* s = NULL;
|
||||
int need_free = 0;
|
||||
struct mjs_property* p = NULL;
|
||||
mjs_err_t err = mjs_to_string(mjs, &key, &s, &n, &need_free);
|
||||
if(err == MJS_OK) {
|
||||
p = mjs_get_own_property(mjs, obj, s, n);
|
||||
}
|
||||
if(need_free) free(s);
|
||||
return p;
|
||||
}
|
||||
|
||||
MJS_PRIVATE struct mjs_property*
|
||||
mjs_mk_property(struct mjs* mjs, mjs_val_t name, mjs_val_t value) {
|
||||
struct mjs_property* p = new_property(mjs);
|
||||
p->next = NULL;
|
||||
p->name = name;
|
||||
p->value = value;
|
||||
return p;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_get(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len) {
|
||||
struct mjs_property* p;
|
||||
|
||||
if(name_len == (size_t)~0) {
|
||||
name_len = strlen(name);
|
||||
}
|
||||
|
||||
p = mjs_get_own_property(mjs, obj, name, name_len);
|
||||
if(p == NULL) {
|
||||
return MJS_UNDEFINED;
|
||||
} else {
|
||||
return p->value;
|
||||
}
|
||||
}
|
||||
|
||||
mjs_val_t mjs_get_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name) {
|
||||
size_t n;
|
||||
char* s = NULL;
|
||||
int need_free = 0;
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
|
||||
mjs_err_t err = mjs_to_string(mjs, &name, &s, &n, &need_free);
|
||||
|
||||
if(err == MJS_OK) {
|
||||
/* Successfully converted name value to string: get the property */
|
||||
ret = mjs_get(mjs, obj, s, n);
|
||||
}
|
||||
|
||||
if(need_free) {
|
||||
free(s);
|
||||
s = NULL;
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_get_v_proto(struct mjs* mjs, mjs_val_t obj, mjs_val_t key) {
|
||||
struct mjs_property* p;
|
||||
mjs_val_t pn = mjs_mk_string(mjs, MJS_PROTO_PROP_NAME, ~0, 1);
|
||||
if((p = mjs_get_own_property_v(mjs, obj, key)) != NULL) return p->value;
|
||||
if((p = mjs_get_own_property_v(mjs, obj, pn)) == NULL) return MJS_UNDEFINED;
|
||||
return mjs_get_v_proto(mjs, p->value, key);
|
||||
}
|
||||
|
||||
mjs_err_t
|
||||
mjs_set(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len, mjs_val_t val) {
|
||||
return mjs_set_internal(mjs, obj, MJS_UNDEFINED, (char*)name, name_len, val);
|
||||
}
|
||||
|
||||
mjs_err_t mjs_set_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val) {
|
||||
return mjs_set_internal(mjs, obj, name, NULL, 0, val);
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_err_t mjs_set_internal(
|
||||
struct mjs* mjs,
|
||||
mjs_val_t obj,
|
||||
mjs_val_t name_v,
|
||||
char* name,
|
||||
size_t name_len,
|
||||
mjs_val_t val) {
|
||||
mjs_err_t rcode = MJS_OK;
|
||||
|
||||
struct mjs_property* p;
|
||||
|
||||
int need_free = 0;
|
||||
|
||||
if(name == NULL) {
|
||||
/* Pointer was not provided, so obtain one from the name_v. */
|
||||
rcode = mjs_to_string(mjs, &name_v, &name, &name_len, &need_free);
|
||||
if(rcode != MJS_OK) {
|
||||
goto clean;
|
||||
}
|
||||
} else {
|
||||
/*
|
||||
* Pointer was provided, so we ignore name_v. Here we set it to undefined,
|
||||
* and the actual value will be calculated later if needed.
|
||||
*/
|
||||
name_v = MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
p = mjs_get_own_property(mjs, obj, name, name_len);
|
||||
|
||||
if(p == NULL) {
|
||||
struct mjs_object* o;
|
||||
if(!mjs_is_object_based(obj)) {
|
||||
return MJS_REFERENCE_ERROR;
|
||||
}
|
||||
|
||||
/*
|
||||
* name_v might be not a string here. In this case, we need to create a new
|
||||
* `name_v`, which will be a string.
|
||||
*/
|
||||
if(!mjs_is_string(name_v)) {
|
||||
name_v = mjs_mk_string(mjs, name, name_len, 1);
|
||||
}
|
||||
|
||||
p = mjs_mk_property(mjs, name_v, val);
|
||||
|
||||
o = get_object_struct(obj);
|
||||
p->next = o->properties;
|
||||
o->properties = p;
|
||||
}
|
||||
|
||||
p->value = val;
|
||||
|
||||
clean:
|
||||
if(need_free) {
|
||||
free(name);
|
||||
name = NULL;
|
||||
}
|
||||
return rcode;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_destroy_property(struct mjs_property** p) {
|
||||
*p = NULL;
|
||||
}
|
||||
|
||||
/*
|
||||
* See comments in `object_public.h`
|
||||
*/
|
||||
int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len) {
|
||||
struct mjs_property *prop, *prev;
|
||||
|
||||
if(!mjs_is_object_based(obj)) {
|
||||
return -1;
|
||||
}
|
||||
if(len == (size_t)~0) {
|
||||
len = strlen(name);
|
||||
}
|
||||
for(prev = NULL, prop = get_object_struct(obj)->properties; prop != NULL;
|
||||
prev = prop, prop = prop->next) {
|
||||
size_t n;
|
||||
const char* s = mjs_get_string(mjs, &prop->name, &n);
|
||||
if(n == len && strncmp(s, name, len) == 0) {
|
||||
if(prev) {
|
||||
prev->next = prop->next;
|
||||
} else {
|
||||
get_object_struct(obj)->properties = prop->next;
|
||||
}
|
||||
mjs_destroy_property(&prop);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return -1;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator) {
|
||||
struct mjs_property* p = NULL;
|
||||
mjs_val_t key = MJS_UNDEFINED;
|
||||
|
||||
if(*iterator == MJS_UNDEFINED) {
|
||||
struct mjs_object* o = get_object_struct(obj);
|
||||
p = o->properties;
|
||||
} else {
|
||||
p = ((struct mjs_property*)get_ptr(*iterator))->next;
|
||||
}
|
||||
|
||||
if(p == NULL) {
|
||||
*iterator = MJS_UNDEFINED;
|
||||
} else {
|
||||
key = p->name;
|
||||
*iterator = mjs_mk_foreign(mjs, p);
|
||||
}
|
||||
|
||||
return key;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs) {
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
mjs_val_t proto_v = mjs_arg(mjs, 0);
|
||||
|
||||
if(!mjs_check_arg(mjs, 0, "proto", MJS_TYPE_OBJECT_GENERIC, &proto_v)) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
ret = mjs_mk_object(mjs);
|
||||
mjs_set(mjs, ret, MJS_PROTO_PROP_NAME, ~0, proto_v);
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
mjs_val_t
|
||||
mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* defs) {
|
||||
mjs_val_t obj;
|
||||
const struct mjs_c_struct_member* def = defs;
|
||||
if(base == NULL || def == NULL) return MJS_UNDEFINED;
|
||||
obj = mjs_mk_object(mjs);
|
||||
/* Pin the object while it is being built */
|
||||
mjs_own(mjs, &obj);
|
||||
/*
|
||||
* Because mjs inserts new properties at the head of the list,
|
||||
* start from the end so the constructed object more closely resembles
|
||||
* the definition.
|
||||
*/
|
||||
while(def->name != NULL) def++;
|
||||
for(def--; def >= defs; def--) {
|
||||
mjs_val_t v = MJS_UNDEFINED;
|
||||
const char* ptr = (const char*)base + def->offset;
|
||||
switch(def->type) {
|
||||
case MJS_STRUCT_FIELD_TYPE_STRUCT: {
|
||||
const void* sub_base = (const void*)ptr;
|
||||
const struct mjs_c_struct_member* sub_def =
|
||||
(const struct mjs_c_struct_member*)def->arg;
|
||||
v = mjs_struct_to_obj(mjs, sub_base, sub_def);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_STRUCT_PTR: {
|
||||
const void** sub_base = (const void**)ptr;
|
||||
const struct mjs_c_struct_member* sub_def =
|
||||
(const struct mjs_c_struct_member*)def->arg;
|
||||
if(*sub_base != NULL) {
|
||||
v = mjs_struct_to_obj(mjs, *sub_base, sub_def);
|
||||
} else {
|
||||
v = MJS_NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_INT: {
|
||||
double value = (double)(*(int*)ptr);
|
||||
v = mjs_mk_number(mjs, value);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_BOOL: {
|
||||
v = mjs_mk_boolean(mjs, *(bool*)ptr);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_DOUBLE: {
|
||||
v = mjs_mk_number(mjs, *(double*)ptr);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_FLOAT: {
|
||||
float value = *(float*)ptr;
|
||||
v = mjs_mk_number(mjs, value);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_CHAR_PTR: {
|
||||
const char* value = *(const char**)ptr;
|
||||
v = mjs_mk_string(mjs, value, ~0, 1);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_VOID_PTR: {
|
||||
v = mjs_mk_foreign(mjs, *(void**)ptr);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_MG_STR_PTR: {
|
||||
const struct mg_str* s = *(const struct mg_str**)ptr;
|
||||
if(s != NULL) {
|
||||
v = mjs_mk_string(mjs, s->p, s->len, 1);
|
||||
} else {
|
||||
v = MJS_NULL;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_MG_STR: {
|
||||
const struct mg_str* s = (const struct mg_str*)ptr;
|
||||
v = mjs_mk_string(mjs, s->p, s->len, 1);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_DATA: {
|
||||
const char* dptr = (const char*)ptr;
|
||||
const intptr_t dlen = (intptr_t)def->arg;
|
||||
v = mjs_mk_string(mjs, dptr, dlen, 1);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_INT8: {
|
||||
double value = (double)(*(int8_t*)ptr);
|
||||
v = mjs_mk_number(mjs, value);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_INT16: {
|
||||
double value = (double)(*(int16_t*)ptr);
|
||||
v = mjs_mk_number(mjs, value);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_UINT8: {
|
||||
double value = (double)(*(uint8_t*)ptr);
|
||||
v = mjs_mk_number(mjs, value);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_UINT16: {
|
||||
double value = (double)(*(uint16_t*)ptr);
|
||||
v = mjs_mk_number(mjs, value);
|
||||
break;
|
||||
}
|
||||
case MJS_STRUCT_FIELD_TYPE_CUSTOM: {
|
||||
mjs_val_t (*fptr)(struct mjs*, const void*) =
|
||||
(mjs_val_t(*)(struct mjs*, const void*))def->arg;
|
||||
v = fptr(mjs, ptr);
|
||||
}
|
||||
default: {
|
||||
break;
|
||||
}
|
||||
}
|
||||
mjs_set(mjs, obj, def->name, ~0, v);
|
||||
}
|
||||
mjs_disown(mjs, &obj);
|
||||
return obj;
|
||||
}
|
||||
59
lib/mjs/mjs_object.h
Normal file
59
lib/mjs/mjs_object.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_OBJECT_H_
|
||||
#define MJS_OBJECT_H_
|
||||
|
||||
#include "mjs_object_public.h"
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
struct mjs;
|
||||
|
||||
struct mjs_property {
|
||||
struct mjs_property* next; /* Linkage in struct mjs_object::properties */
|
||||
mjs_val_t name; /* Property name (a string) */
|
||||
mjs_val_t value; /* Property value */
|
||||
};
|
||||
|
||||
struct mjs_object {
|
||||
struct mjs_property* properties;
|
||||
};
|
||||
|
||||
MJS_PRIVATE struct mjs_object* get_object_struct(mjs_val_t v);
|
||||
MJS_PRIVATE struct mjs_property*
|
||||
mjs_get_own_property(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len);
|
||||
|
||||
MJS_PRIVATE struct mjs_property*
|
||||
mjs_get_own_property_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t key);
|
||||
|
||||
/*
|
||||
* A worker function for `mjs_set()` and `mjs_set_v()`: it takes name as both
|
||||
* ptr+len and mjs_val_t. If `name` pointer is not NULL, it takes precedence
|
||||
* over `name_v`.
|
||||
*/
|
||||
MJS_PRIVATE mjs_err_t mjs_set_internal(
|
||||
struct mjs* mjs,
|
||||
mjs_val_t obj,
|
||||
mjs_val_t name_v,
|
||||
char* name,
|
||||
size_t name_len,
|
||||
mjs_val_t val);
|
||||
|
||||
/*
|
||||
* Implementation of `Object.create(proto)`
|
||||
*/
|
||||
MJS_PRIVATE void mjs_op_create_object(struct mjs* mjs);
|
||||
|
||||
#define MJS_PROTO_PROP_NAME "__p" /* Make it < 5 chars */
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_OBJECT_H_ */
|
||||
126
lib/mjs/mjs_object_public.h
Normal file
126
lib/mjs/mjs_object_public.h
Normal file
@@ -0,0 +1,126 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_OBJECT_PUBLIC_H_
|
||||
#define MJS_OBJECT_PUBLIC_H_
|
||||
|
||||
#include <stddef.h>
|
||||
#include "mjs_core_public.h"
|
||||
#include "mjs_ffi_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Returns true if the given value is an object or array.
|
||||
*/
|
||||
int mjs_is_object(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Returns true if the given value type is object-based (object, array, dataview).
|
||||
*/
|
||||
int mjs_is_object_based(mjs_val_t v);
|
||||
|
||||
/* Make an empty object */
|
||||
mjs_val_t mjs_mk_object(struct mjs* mjs);
|
||||
|
||||
/* Field types for struct-object conversion. */
|
||||
enum mjs_struct_field_type {
|
||||
MJS_STRUCT_FIELD_TYPE_INVALID,
|
||||
MJS_STRUCT_FIELD_TYPE_STRUCT, /* Struct, arg points to def. */
|
||||
MJS_STRUCT_FIELD_TYPE_STRUCT_PTR, /* Ptr to struct, arg points to def. */
|
||||
MJS_STRUCT_FIELD_TYPE_INT,
|
||||
MJS_STRUCT_FIELD_TYPE_BOOL,
|
||||
MJS_STRUCT_FIELD_TYPE_DOUBLE,
|
||||
MJS_STRUCT_FIELD_TYPE_FLOAT,
|
||||
MJS_STRUCT_FIELD_TYPE_CHAR_PTR, /* NUL-terminated string. */
|
||||
MJS_STRUCT_FIELD_TYPE_VOID_PTR, /* Converted to foreign ptr. */
|
||||
MJS_STRUCT_FIELD_TYPE_MG_STR_PTR, /* Converted to string. */
|
||||
MJS_STRUCT_FIELD_TYPE_MG_STR, /* Converted to string. */
|
||||
MJS_STRUCT_FIELD_TYPE_DATA, /* Data, arg is length, becomes string. */
|
||||
MJS_STRUCT_FIELD_TYPE_INT8,
|
||||
MJS_STRUCT_FIELD_TYPE_INT16,
|
||||
MJS_STRUCT_FIELD_TYPE_UINT8,
|
||||
MJS_STRUCT_FIELD_TYPE_UINT16,
|
||||
/*
|
||||
* User-provided function. Arg is a pointer to function that takes void *
|
||||
* (pointer to field within the struct) and returns mjs_val_t:
|
||||
* mjs_val_t field_value(struct mjs *mjs, const void *field_ptr) { ... }
|
||||
*/
|
||||
MJS_STRUCT_FIELD_TYPE_CUSTOM,
|
||||
};
|
||||
|
||||
/* C structure layout descriptor - needed by mjs_struct_to_obj */
|
||||
struct mjs_c_struct_member {
|
||||
const char* name;
|
||||
int offset;
|
||||
enum mjs_struct_field_type type;
|
||||
const void* arg; /* Additional argument, used for some types. */
|
||||
};
|
||||
|
||||
/* Create flat JS object from a C memory descriptor */
|
||||
mjs_val_t
|
||||
mjs_struct_to_obj(struct mjs* mjs, const void* base, const struct mjs_c_struct_member* members);
|
||||
|
||||
/*
|
||||
* Lookup property `name` in object `obj`. If `obj` holds no such property,
|
||||
* an `undefined` value is returned.
|
||||
*
|
||||
* If `name_len` is ~0, `name` is assumed to be NUL-terminated and
|
||||
* `strlen(name)` is used.
|
||||
*/
|
||||
mjs_val_t mjs_get(struct mjs* mjs, mjs_val_t obj, const char* name, size_t name_len);
|
||||
|
||||
/*
|
||||
* Like mjs_get but with a JS string.
|
||||
*/
|
||||
mjs_val_t mjs_get_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name);
|
||||
|
||||
/*
|
||||
* Like mjs_get_v but lookup the prototype chain.
|
||||
*/
|
||||
mjs_val_t mjs_get_v_proto(struct mjs* mjs, mjs_val_t obj, mjs_val_t key);
|
||||
|
||||
/*
|
||||
* Set object property. Behaves just like JavaScript assignment.
|
||||
*/
|
||||
mjs_err_t mjs_set(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len, mjs_val_t val);
|
||||
|
||||
/*
|
||||
* Like mjs_set but the name is already a JS string.
|
||||
*/
|
||||
mjs_err_t mjs_set_v(struct mjs* mjs, mjs_val_t obj, mjs_val_t name, mjs_val_t val);
|
||||
|
||||
/*
|
||||
* Delete own property `name` of the object `obj`. Does not follow the
|
||||
* prototype chain.
|
||||
*
|
||||
* If `name_len` is ~0, `name` is assumed to be NUL-terminated and
|
||||
* `strlen(name)` is used.
|
||||
*
|
||||
* Returns 0 on success, -1 on error.
|
||||
*/
|
||||
int mjs_del(struct mjs* mjs, mjs_val_t obj, const char* name, size_t len);
|
||||
|
||||
/*
|
||||
* Iterate over `obj` properties.
|
||||
* First call should set `iterator` to MJS_UNDEFINED.
|
||||
* Return object's key (a string), or MJS_UNDEFINED when no more keys left.
|
||||
* Do not mutate the object during iteration.
|
||||
*
|
||||
* Example:
|
||||
* mjs_val_t key, iter = MJS_UNDEFINED;
|
||||
* while ((key = mjs_next(mjs, obj, &iter)) != MJS_UNDEFINED) {
|
||||
* // Do something with the obj/key ...
|
||||
* }
|
||||
*/
|
||||
mjs_val_t mjs_next(struct mjs* mjs, mjs_val_t obj, mjs_val_t* iterator);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_OBJECT_PUBLIC_H_ */
|
||||
1033
lib/mjs/mjs_parser.c
Normal file
1033
lib/mjs/mjs_parser.c
Normal file
File diff suppressed because it is too large
Load Diff
21
lib/mjs/mjs_parser.h
Normal file
21
lib/mjs/mjs_parser.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_PARSER_H
|
||||
#define MJS_PARSER_H
|
||||
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
MJS_PRIVATE mjs_err_t mjs_parse(const char* path, const char* buf, struct mjs*);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_PARSER_H */
|
||||
160
lib/mjs/mjs_primitive.c
Normal file
160
lib/mjs/mjs_primitive.c
Normal file
@@ -0,0 +1,160 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_primitive.h"
|
||||
|
||||
mjs_val_t mjs_mk_null(void) {
|
||||
return MJS_NULL;
|
||||
}
|
||||
|
||||
int mjs_is_null(mjs_val_t v) {
|
||||
return v == MJS_NULL;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_undefined(void) {
|
||||
return MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
int mjs_is_undefined(mjs_val_t v) {
|
||||
return v == MJS_UNDEFINED;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_number(struct mjs* mjs, double v) {
|
||||
mjs_val_t res;
|
||||
(void)mjs;
|
||||
/* not every NaN is a JS NaN */
|
||||
if(isnan(v)) {
|
||||
res = MJS_TAG_NAN;
|
||||
} else {
|
||||
union {
|
||||
double d;
|
||||
mjs_val_t r;
|
||||
} u;
|
||||
u.d = v;
|
||||
res = u.r;
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
static double get_double(mjs_val_t v) {
|
||||
union {
|
||||
double d;
|
||||
mjs_val_t v;
|
||||
} u;
|
||||
u.v = v;
|
||||
/* Due to NaN packing, any non-numeric value is already a valid NaN value */
|
||||
return u.d;
|
||||
}
|
||||
|
||||
double mjs_get_double(struct mjs* mjs, mjs_val_t v) {
|
||||
(void)mjs;
|
||||
return get_double(v);
|
||||
}
|
||||
|
||||
int mjs_get_int(struct mjs* mjs, mjs_val_t v) {
|
||||
(void)mjs;
|
||||
/*
|
||||
* NOTE(dfrank): without double cast, all numbers >= 0x80000000 are always
|
||||
* converted to exactly 0x80000000.
|
||||
*/
|
||||
return (int)(unsigned int)get_double(v);
|
||||
}
|
||||
|
||||
int32_t mjs_get_int32(struct mjs* mjs, mjs_val_t v) {
|
||||
(void)mjs;
|
||||
return (int32_t)get_double(v);
|
||||
}
|
||||
|
||||
int mjs_is_number(mjs_val_t v) {
|
||||
return v == MJS_TAG_NAN || !isnan(get_double(v));
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_boolean(struct mjs* mjs, int v) {
|
||||
(void)mjs;
|
||||
return (v ? 1 : 0) | MJS_TAG_BOOLEAN;
|
||||
}
|
||||
|
||||
int mjs_get_bool(struct mjs* mjs, mjs_val_t v) {
|
||||
(void)mjs;
|
||||
if(mjs_is_boolean(v)) {
|
||||
return v & 1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
int mjs_is_boolean(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_BOOLEAN;
|
||||
}
|
||||
|
||||
#define MJS_IS_POINTER_LEGIT(n) \
|
||||
(((n)&MJS_TAG_MASK) == 0 || ((n)&MJS_TAG_MASK) == (~0 & MJS_TAG_MASK))
|
||||
|
||||
MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p) {
|
||||
uint64_t n = ((uint64_t)(uintptr_t)p);
|
||||
|
||||
if(!MJS_IS_POINTER_LEGIT(n)) {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "invalid pointer value: %p", p);
|
||||
}
|
||||
return n & ~MJS_TAG_MASK;
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_val_t mjs_legit_pointer_to_value(void* p) {
|
||||
uint64_t n = ((uint64_t)(uintptr_t)p);
|
||||
|
||||
assert(MJS_IS_POINTER_LEGIT(n));
|
||||
return n & ~MJS_TAG_MASK;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void* get_ptr(mjs_val_t v) {
|
||||
return (void*)(uintptr_t)(v & 0xFFFFFFFFFFFFUL);
|
||||
}
|
||||
|
||||
void* mjs_get_ptr(struct mjs* mjs, mjs_val_t v) {
|
||||
(void)mjs;
|
||||
if(!mjs_is_foreign(v)) {
|
||||
return NULL;
|
||||
}
|
||||
return get_ptr(v);
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_foreign(struct mjs* mjs, void* p) {
|
||||
(void)mjs;
|
||||
return mjs_pointer_to_value(mjs, p) | MJS_TAG_FOREIGN;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_foreign_func(struct mjs* mjs, mjs_func_ptr_t fn) {
|
||||
union {
|
||||
mjs_func_ptr_t fn;
|
||||
void* p;
|
||||
} u;
|
||||
u.fn = fn;
|
||||
(void)mjs;
|
||||
return mjs_pointer_to_value(mjs, u.p) | MJS_TAG_FOREIGN;
|
||||
}
|
||||
|
||||
int mjs_is_foreign(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_FOREIGN;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_function(struct mjs* mjs, size_t off) {
|
||||
(void)mjs;
|
||||
return (mjs_val_t)off | MJS_TAG_FUNCTION;
|
||||
}
|
||||
|
||||
int mjs_is_function(mjs_val_t v) {
|
||||
return (v & MJS_TAG_MASK) == MJS_TAG_FUNCTION;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs) {
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
mjs_val_t val = mjs_arg(mjs, 0);
|
||||
|
||||
ret = mjs_mk_boolean(mjs, val == MJS_TAG_NAN);
|
||||
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
41
lib/mjs/mjs_primitive.h
Normal file
41
lib/mjs/mjs_primitive.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_PRIMITIVE_H
|
||||
#define MJS_PRIMITIVE_H
|
||||
|
||||
#include "mjs_primitive_public.h"
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Convert a pointer to mjs_val_t. If pointer is not valid, mjs crashes.
|
||||
*/
|
||||
MJS_PRIVATE mjs_val_t mjs_legit_pointer_to_value(void* p);
|
||||
|
||||
/*
|
||||
* Convert a pointer to mjs_val_t. If pointer is not valid, error is set
|
||||
* in the mjs context.
|
||||
*/
|
||||
MJS_PRIVATE mjs_val_t mjs_pointer_to_value(struct mjs* mjs, void* p);
|
||||
|
||||
/*
|
||||
* Extracts a pointer from the mjs_val_t value.
|
||||
*/
|
||||
MJS_PRIVATE void* get_ptr(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Implementation for JS isNaN()
|
||||
*/
|
||||
MJS_PRIVATE void mjs_op_isnan(struct mjs* mjs);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_PRIMITIVE_H */
|
||||
123
lib/mjs/mjs_primitive_public.h
Normal file
123
lib/mjs/mjs_primitive_public.h
Normal file
@@ -0,0 +1,123 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_PRIMITIVE_PUBLIC_H_
|
||||
#define MJS_PRIMITIVE_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/* JavaScript `null` value */
|
||||
#define MJS_NULL MJS_TAG_NULL
|
||||
|
||||
/* JavaScript `undefined` value */
|
||||
#define MJS_UNDEFINED MJS_TAG_UNDEFINED
|
||||
|
||||
#define MJS_MK_FN(fn) mjs_mk_foreign_func(mjs, (mjs_func_ptr_t)fn)
|
||||
|
||||
/* Function pointer type used in `mjs_mk_foreign_func`. */
|
||||
typedef void (*mjs_func_ptr_t)(void);
|
||||
|
||||
/*
|
||||
* Make `null` primitive value.
|
||||
*
|
||||
* NOTE: this function is deprecated and will be removed in future releases.
|
||||
* Use `MJS_NULL` instead.
|
||||
*/
|
||||
mjs_val_t mjs_mk_null(void);
|
||||
|
||||
/* Returns true if given value is a primitive `null` value */
|
||||
int mjs_is_null(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Make `undefined` primitive value.
|
||||
*
|
||||
* NOTE: this function is deprecated and will be removed in future releases.
|
||||
* Use `MJS_UNDEFINED` instead.
|
||||
*/
|
||||
mjs_val_t mjs_mk_undefined(void);
|
||||
|
||||
/* Returns true if given value is a primitive `undefined` value */
|
||||
int mjs_is_undefined(mjs_val_t v);
|
||||
|
||||
/* Make numeric primitive value */
|
||||
mjs_val_t mjs_mk_number(struct mjs* mjs, double num);
|
||||
|
||||
/*
|
||||
* Returns number value stored in `mjs_val_t` as `double`.
|
||||
*
|
||||
* Returns NaN for non-numbers.
|
||||
*/
|
||||
double mjs_get_double(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Returns number value stored in `mjs_val_t` as `int`. If the number value is
|
||||
* not an integer, the fraction part will be discarded.
|
||||
*
|
||||
* If the given value is a non-number, or NaN, the result is undefined.
|
||||
*/
|
||||
int mjs_get_int(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Like mjs_get_int but ensures that the returned type
|
||||
* is a 32-bit signed integer.
|
||||
*/
|
||||
int32_t mjs_get_int32(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
/* Returns true if given value is a primitive number value */
|
||||
int mjs_is_number(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Make JavaScript value that holds C/C++ `void *` pointer.
|
||||
*
|
||||
* A foreign value is completely opaque and JS code cannot do anything useful
|
||||
* with it except holding it in properties and passing it around.
|
||||
* It behaves like a sealed object with no properties.
|
||||
*
|
||||
* NOTE:
|
||||
* Only valid pointers (as defined by each supported architecture) will fully
|
||||
* preserved. In particular, all supported 64-bit architectures (x86_64, ARM-64)
|
||||
* actually define a 48-bit virtual address space.
|
||||
* Foreign values will be sign-extended as required, i.e creating a foreign
|
||||
* value of something like `(void *) -1` will work as expected. This is
|
||||
* important because in some 64-bit OSs (e.g. Solaris) the user stack grows
|
||||
* downwards from the end of the address space.
|
||||
*
|
||||
* If you need to store exactly sizeof(void*) bytes of raw data where
|
||||
* `sizeof(void*)` >= 8, please use byte arrays instead.
|
||||
*/
|
||||
mjs_val_t mjs_mk_foreign(struct mjs* mjs, void* ptr);
|
||||
|
||||
/*
|
||||
* Make JavaScript value that holds C/C++ function pointer, similarly to
|
||||
* `mjs_mk_foreign`.
|
||||
*/
|
||||
mjs_val_t mjs_mk_foreign_func(struct mjs* mjs, mjs_func_ptr_t fn);
|
||||
|
||||
/*
|
||||
* Returns `void *` pointer stored in `mjs_val_t`.
|
||||
*
|
||||
* Returns NULL if the value is not a foreign pointer.
|
||||
*/
|
||||
void* mjs_get_ptr(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
/* Returns true if given value holds `void *` pointer */
|
||||
int mjs_is_foreign(mjs_val_t v);
|
||||
|
||||
mjs_val_t mjs_mk_boolean(struct mjs* mjs, int v);
|
||||
int mjs_get_bool(struct mjs* mjs, mjs_val_t v);
|
||||
int mjs_is_boolean(mjs_val_t v);
|
||||
|
||||
mjs_val_t mjs_mk_function(struct mjs* mjs, size_t off);
|
||||
int mjs_is_function(mjs_val_t v);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_PRIMITIVE_PUBLIC_H_ */
|
||||
601
lib/mjs/mjs_string.c
Normal file
601
lib/mjs/mjs_string.c
Normal file
@@ -0,0 +1,601 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "mjs_string.h"
|
||||
#include "common/cs_varint.h"
|
||||
#include "common/mg_str.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_util.h"
|
||||
|
||||
// No UTF
|
||||
typedef unsigned short Rune;
|
||||
static int chartorune(Rune* rune, const char* str) {
|
||||
*rune = *(unsigned char*)str;
|
||||
return 1;
|
||||
}
|
||||
static int runetochar(char* str, Rune* rune) {
|
||||
str[0] = (char)*rune;
|
||||
return 1;
|
||||
}
|
||||
|
||||
#ifndef MJS_STRING_BUF_RESERVE
|
||||
#define MJS_STRING_BUF_RESERVE 100
|
||||
#endif
|
||||
|
||||
MJS_PRIVATE size_t unescape(const char* s, size_t len, char* to);
|
||||
|
||||
MJS_PRIVATE void embed_string(
|
||||
struct mbuf* m,
|
||||
size_t offset,
|
||||
const char* p,
|
||||
size_t len,
|
||||
uint8_t /*enum embstr_flags*/ flags);
|
||||
|
||||
/* TODO(lsm): NaN payload location depends on endianness, make crossplatform */
|
||||
#define GET_VAL_NAN_PAYLOAD(v) ((char*)&(v))
|
||||
|
||||
int mjs_is_string(mjs_val_t v) {
|
||||
uint64_t t = v & MJS_TAG_MASK;
|
||||
return t == MJS_TAG_STRING_I || t == MJS_TAG_STRING_F || t == MJS_TAG_STRING_O ||
|
||||
t == MJS_TAG_STRING_5 || t == MJS_TAG_STRING_D;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_mk_string(struct mjs* mjs, const char* p, size_t len, int copy) {
|
||||
struct mbuf* m;
|
||||
mjs_val_t offset, tag = MJS_TAG_STRING_F;
|
||||
if(len == 0) {
|
||||
/*
|
||||
* Zero length for foreign string has a special meaning (that the foreign
|
||||
* string is not inlined into mjs_val_t), so when creating a zero-length
|
||||
* string, we always assume it'll be owned. Since the length is zero, it
|
||||
* doesn't matter anyway.
|
||||
*/
|
||||
copy = 1;
|
||||
}
|
||||
m = copy ? &mjs->owned_strings : &mjs->foreign_strings;
|
||||
offset = m->len;
|
||||
|
||||
if(len == ~((size_t)0)) len = strlen(p);
|
||||
|
||||
if(copy) {
|
||||
/* owned string */
|
||||
if(len <= 4) {
|
||||
char* s = GET_VAL_NAN_PAYLOAD(offset) + 1;
|
||||
offset = 0;
|
||||
if(p != 0) {
|
||||
memcpy(s, p, len);
|
||||
}
|
||||
s[-1] = len;
|
||||
tag = MJS_TAG_STRING_I;
|
||||
} else if(len == 5) {
|
||||
char* s = GET_VAL_NAN_PAYLOAD(offset);
|
||||
offset = 0;
|
||||
if(p != 0) {
|
||||
memcpy(s, p, len);
|
||||
}
|
||||
tag = MJS_TAG_STRING_5;
|
||||
// } else if ((dict_index = v_find_string_in_dictionary(p, len)) >= 0) {
|
||||
// offset = 0;
|
||||
// GET_VAL_NAN_PAYLOAD(offset)[0] = dict_index;
|
||||
// tag = MJS_TAG_STRING_D;
|
||||
} else {
|
||||
if(gc_strings_is_gc_needed(mjs)) {
|
||||
mjs->need_gc = 1;
|
||||
}
|
||||
|
||||
/*
|
||||
* Before embedding new string, check if the reallocation is needed. If
|
||||
* so, perform the reallocation by calling `mbuf_resize` manually, since
|
||||
* we need to preallocate some extra space (`MJS_STRING_BUF_RESERVE`)
|
||||
*/
|
||||
if((m->len + len) > m->size) {
|
||||
char* prev_buf = m->buf;
|
||||
mbuf_resize(m, m->len + len + MJS_STRING_BUF_RESERVE);
|
||||
|
||||
/*
|
||||
* There is a corner case: when the source pointer is located within
|
||||
* the mbuf. In this case, we should adjust the pointer, because it
|
||||
* might have just been reallocated.
|
||||
*/
|
||||
if(p >= prev_buf && p < (prev_buf + m->len)) {
|
||||
p += (m->buf - prev_buf);
|
||||
}
|
||||
}
|
||||
|
||||
embed_string(m, m->len, p, len, EMBSTR_ZERO_TERM);
|
||||
tag = MJS_TAG_STRING_O;
|
||||
}
|
||||
} else {
|
||||
/* foreign string */
|
||||
if(sizeof(void*) <= 4 && len <= (1 << 15)) {
|
||||
/* small foreign strings can fit length and ptr in the mjs_val_t */
|
||||
offset = (uint64_t)len << 32 | (uint64_t)(uintptr_t)p;
|
||||
} else {
|
||||
/* bigger strings need indirection that uses ram */
|
||||
size_t pos = m->len;
|
||||
size_t llen = cs_varint_llen(len);
|
||||
|
||||
/* allocate space for len and ptr */
|
||||
mbuf_insert(m, pos, NULL, llen + sizeof(p));
|
||||
|
||||
cs_varint_encode(len, (uint8_t*)(m->buf + pos), llen);
|
||||
memcpy(m->buf + pos + llen, &p, sizeof(p));
|
||||
}
|
||||
tag = MJS_TAG_STRING_F;
|
||||
}
|
||||
|
||||
/* NOTE(lsm): don't use pointer_to_value, 32-bit ptrs will truncate */
|
||||
return (offset & ~MJS_TAG_MASK) | tag;
|
||||
}
|
||||
|
||||
/* Get a pointer to string and string length. */
|
||||
const char* mjs_get_string(struct mjs* mjs, mjs_val_t* v, size_t* sizep) {
|
||||
uint64_t tag = v[0] & MJS_TAG_MASK;
|
||||
const char* p = NULL;
|
||||
size_t size = 0, llen;
|
||||
|
||||
if(!mjs_is_string(*v)) {
|
||||
goto clean;
|
||||
}
|
||||
|
||||
if(tag == MJS_TAG_STRING_I) {
|
||||
p = GET_VAL_NAN_PAYLOAD(*v) + 1;
|
||||
size = p[-1];
|
||||
} else if(tag == MJS_TAG_STRING_5) {
|
||||
p = GET_VAL_NAN_PAYLOAD(*v);
|
||||
size = 5;
|
||||
// } else if (tag == MJS_TAG_STRING_D) {
|
||||
// int index = ((unsigned char *) GET_VAL_NAN_PAYLOAD(*v))[0];
|
||||
// size = v_dictionary_strings[index].len;
|
||||
// p = v_dictionary_strings[index].p;
|
||||
} else if(tag == MJS_TAG_STRING_O) {
|
||||
size_t offset = (size_t)gc_string_mjs_val_to_offset(*v);
|
||||
char* s = mjs->owned_strings.buf + offset;
|
||||
uint64_t v = 0;
|
||||
if(offset < mjs->owned_strings.len &&
|
||||
cs_varint_decode((uint8_t*)s, mjs->owned_strings.len - offset, &v, &llen)) {
|
||||
size = v;
|
||||
p = s + llen;
|
||||
} else {
|
||||
goto clean;
|
||||
}
|
||||
} else if(tag == MJS_TAG_STRING_F) {
|
||||
/*
|
||||
* short foreign strings on <=32-bit machines can be encoded in a compact
|
||||
* form:
|
||||
*
|
||||
* 7 6 5 4 3 2 1 0
|
||||
* 11111111|1111tttt|llllllll|llllllll|ssssssss|ssssssss|ssssssss|ssssssss
|
||||
*
|
||||
* Strings longer than 2^26 will be indireceted through the foreign_strings
|
||||
* mbuf.
|
||||
*
|
||||
* We don't use a different tag to represent those two cases. Instead, all
|
||||
* foreign strings represented with the help of the foreign_strings mbuf
|
||||
* will have the upper 16-bits of the payload set to zero. This allows us to
|
||||
* represent up to 477 million foreign strings longer than 64k.
|
||||
*/
|
||||
uint16_t len = (*v >> 32) & 0xFFFF;
|
||||
if(sizeof(void*) <= 4 && len != 0) {
|
||||
size = (size_t)len;
|
||||
p = (const char*)(uintptr_t)*v;
|
||||
} else {
|
||||
size_t offset = (size_t)gc_string_mjs_val_to_offset(*v);
|
||||
char* s = mjs->foreign_strings.buf + offset;
|
||||
uint64_t v = 0;
|
||||
if(offset < mjs->foreign_strings.len &&
|
||||
cs_varint_decode((uint8_t*)s, mjs->foreign_strings.len - offset, &v, &llen)) {
|
||||
size = v;
|
||||
memcpy((char**)&p, s + llen, sizeof(p));
|
||||
} else {
|
||||
goto clean;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
assert(0);
|
||||
}
|
||||
|
||||
clean:
|
||||
if(sizep != NULL) {
|
||||
*sizep = size;
|
||||
}
|
||||
return p;
|
||||
}
|
||||
|
||||
const char* mjs_get_cstring(struct mjs* mjs, mjs_val_t* value) {
|
||||
size_t size;
|
||||
const char* s = mjs_get_string(mjs, value, &size);
|
||||
if(s == NULL) return NULL;
|
||||
if(s[size] != 0 || strlen(s) != size) {
|
||||
return NULL;
|
||||
}
|
||||
return s;
|
||||
}
|
||||
|
||||
int mjs_strcmp(struct mjs* mjs, mjs_val_t* a, const char* b, size_t len) {
|
||||
size_t n;
|
||||
const char* s;
|
||||
if(len == (size_t)~0) len = strlen(b);
|
||||
s = mjs_get_string(mjs, a, &n);
|
||||
if(n != len) {
|
||||
return n - len;
|
||||
}
|
||||
return strncmp(s, b, len);
|
||||
}
|
||||
|
||||
MJS_PRIVATE unsigned long cstr_to_ulong(const char* s, size_t len, int* ok) {
|
||||
char* e;
|
||||
unsigned long res = strtoul(s, &e, 10);
|
||||
*ok = (e == s + len) && len != 0;
|
||||
return res;
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_err_t str_to_ulong(struct mjs* mjs, mjs_val_t v, int* ok, unsigned long* res) {
|
||||
enum mjs_err ret = MJS_OK;
|
||||
size_t len = 0;
|
||||
const char* p = mjs_get_string(mjs, &v, &len);
|
||||
*res = cstr_to_ulong(p, len, ok);
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int s_cmp(struct mjs* mjs, mjs_val_t a, mjs_val_t b) {
|
||||
size_t a_len, b_len;
|
||||
const char *a_ptr, *b_ptr;
|
||||
|
||||
a_ptr = mjs_get_string(mjs, &a, &a_len);
|
||||
b_ptr = mjs_get_string(mjs, &b, &b_len);
|
||||
|
||||
if(a_len == b_len) {
|
||||
return memcmp(a_ptr, b_ptr, a_len);
|
||||
}
|
||||
if(a_len > b_len) {
|
||||
return 1;
|
||||
} else if(a_len < b_len) {
|
||||
return -1;
|
||||
} else {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b) {
|
||||
size_t a_len, b_len, res_len;
|
||||
const char *a_ptr, *b_ptr, *res_ptr;
|
||||
mjs_val_t res;
|
||||
|
||||
/* Find out lengths of both srtings */
|
||||
a_ptr = mjs_get_string(mjs, &a, &a_len);
|
||||
b_ptr = mjs_get_string(mjs, &b, &b_len);
|
||||
|
||||
/* Create a placeholder string */
|
||||
res = mjs_mk_string(mjs, NULL, a_len + b_len, 1);
|
||||
|
||||
/* mjs_mk_string() may have reallocated mbuf - revalidate pointers */
|
||||
a_ptr = mjs_get_string(mjs, &a, &a_len);
|
||||
b_ptr = mjs_get_string(mjs, &b, &b_len);
|
||||
|
||||
/* Copy strings into the placeholder */
|
||||
res_ptr = mjs_get_string(mjs, &res, &res_len);
|
||||
memcpy((char*)res_ptr, a_ptr, a_len);
|
||||
memcpy((char*)res_ptr + a_len, b_ptr, b_len);
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_string_slice(struct mjs* mjs) {
|
||||
int nargs = mjs_nargs(mjs);
|
||||
mjs_val_t ret = mjs_mk_number(mjs, 0);
|
||||
mjs_val_t beginSlice_v = MJS_UNDEFINED;
|
||||
mjs_val_t endSlice_v = MJS_UNDEFINED;
|
||||
int beginSlice = 0;
|
||||
int endSlice = 0;
|
||||
size_t size;
|
||||
const char* s = NULL;
|
||||
|
||||
/* get string from `this` */
|
||||
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) {
|
||||
goto clean;
|
||||
}
|
||||
s = mjs_get_string(mjs, &mjs->vals.this_obj, &size);
|
||||
|
||||
/* get idx from arg 0 */
|
||||
if(!mjs_check_arg(mjs, 0, "beginSlice", MJS_TYPE_NUMBER, &beginSlice_v)) {
|
||||
goto clean;
|
||||
}
|
||||
beginSlice = mjs_normalize_idx(mjs_get_int(mjs, beginSlice_v), size);
|
||||
|
||||
if(nargs >= 2) {
|
||||
/* endSlice is given; use it */
|
||||
/* get idx from arg 0 */
|
||||
if(!mjs_check_arg(mjs, 1, "endSlice", MJS_TYPE_NUMBER, &endSlice_v)) {
|
||||
goto clean;
|
||||
}
|
||||
endSlice = mjs_normalize_idx(mjs_get_int(mjs, endSlice_v), size);
|
||||
} else {
|
||||
/* endSlice is not given; assume the end of the string */
|
||||
endSlice = size;
|
||||
}
|
||||
|
||||
if(endSlice < beginSlice) {
|
||||
endSlice = beginSlice;
|
||||
}
|
||||
|
||||
ret = mjs_mk_string(mjs, s + beginSlice, endSlice - beginSlice, 1);
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs) {
|
||||
mjs_val_t ret = mjs_mk_number(mjs, -1);
|
||||
mjs_val_t substr_v = MJS_UNDEFINED;
|
||||
mjs_val_t idx_v = MJS_UNDEFINED;
|
||||
int idx = 0;
|
||||
const char *str = NULL, *substr = NULL;
|
||||
size_t str_len = 0, substr_len = 0;
|
||||
|
||||
if(!mjs_check_arg(mjs, -1 /* this */, "this", MJS_TYPE_STRING, NULL)) {
|
||||
goto clean;
|
||||
}
|
||||
str = mjs_get_string(mjs, &mjs->vals.this_obj, &str_len);
|
||||
|
||||
if(!mjs_check_arg(mjs, 0, "searchValue", MJS_TYPE_STRING, &substr_v)) {
|
||||
goto clean;
|
||||
}
|
||||
substr = mjs_get_string(mjs, &substr_v, &substr_len);
|
||||
if(mjs_nargs(mjs) > 1) {
|
||||
if(!mjs_check_arg(mjs, 1, "fromIndex", MJS_TYPE_NUMBER, &idx_v)) {
|
||||
goto clean;
|
||||
}
|
||||
idx = mjs_get_int(mjs, idx_v);
|
||||
if(idx < 0) idx = 0;
|
||||
if((size_t)idx > str_len) idx = str_len;
|
||||
}
|
||||
{
|
||||
const char* substr_p;
|
||||
struct mg_str mgstr, mgsubstr;
|
||||
mgstr.p = str + idx;
|
||||
mgstr.len = str_len - idx;
|
||||
mgsubstr.p = substr;
|
||||
mgsubstr.len = substr_len;
|
||||
substr_p = mg_strstr(mgstr, mgsubstr);
|
||||
if(substr_p != NULL) {
|
||||
ret = mjs_mk_number(mjs, (int)(substr_p - str));
|
||||
}
|
||||
}
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs) {
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
mjs_val_t idx_v = MJS_UNDEFINED;
|
||||
int idx = 0;
|
||||
size_t size;
|
||||
const char* s = NULL;
|
||||
|
||||
/* get string from `this` */
|
||||
if(!mjs_check_arg(mjs, -1 /*this*/, "this", MJS_TYPE_STRING, NULL)) {
|
||||
goto clean;
|
||||
}
|
||||
s = mjs_get_string(mjs, &mjs->vals.this_obj, &size);
|
||||
|
||||
/* get idx from arg 0 */
|
||||
if(!mjs_check_arg(mjs, 0, "index", MJS_TYPE_NUMBER, &idx_v)) {
|
||||
goto clean;
|
||||
}
|
||||
idx = mjs_normalize_idx(mjs_get_int(mjs, idx_v), size);
|
||||
if(idx >= 0 && idx < (int)size) {
|
||||
ret = mjs_mk_number(mjs, ((unsigned char*)s)[idx]);
|
||||
}
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
MJS_PRIVATE void mjs_mkstr(struct mjs* mjs) {
|
||||
int nargs = mjs_nargs(mjs);
|
||||
mjs_val_t ret = MJS_UNDEFINED;
|
||||
|
||||
char* ptr = NULL;
|
||||
int offset = 0;
|
||||
int len = 0;
|
||||
int copy = 0;
|
||||
|
||||
mjs_val_t ptr_v = MJS_UNDEFINED;
|
||||
mjs_val_t offset_v = MJS_UNDEFINED;
|
||||
mjs_val_t len_v = MJS_UNDEFINED;
|
||||
mjs_val_t copy_v = MJS_UNDEFINED;
|
||||
|
||||
if(nargs == 2) {
|
||||
ptr_v = mjs_arg(mjs, 0);
|
||||
len_v = mjs_arg(mjs, 1);
|
||||
} else if(nargs == 3) {
|
||||
ptr_v = mjs_arg(mjs, 0);
|
||||
offset_v = mjs_arg(mjs, 1);
|
||||
len_v = mjs_arg(mjs, 2);
|
||||
} else if(nargs == 4) {
|
||||
ptr_v = mjs_arg(mjs, 0);
|
||||
offset_v = mjs_arg(mjs, 1);
|
||||
len_v = mjs_arg(mjs, 2);
|
||||
copy_v = mjs_arg(mjs, 3);
|
||||
} else {
|
||||
mjs_prepend_errorf(
|
||||
mjs,
|
||||
MJS_TYPE_ERROR,
|
||||
"mkstr takes 2, 3 or 4 arguments: (ptr, len), (ptr, "
|
||||
"offset, len) or (ptr, offset, len, copy)");
|
||||
goto clean;
|
||||
}
|
||||
|
||||
if(!mjs_is_foreign(ptr_v)) {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "ptr should be a foreign pointer");
|
||||
goto clean;
|
||||
}
|
||||
|
||||
if(offset_v != MJS_UNDEFINED && !mjs_is_number(offset_v)) {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "offset should be a number");
|
||||
goto clean;
|
||||
}
|
||||
|
||||
if(!mjs_is_number(len_v)) {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "len should be a number");
|
||||
goto clean;
|
||||
}
|
||||
|
||||
copy = mjs_is_truthy(mjs, copy_v);
|
||||
|
||||
/* all arguments are fine */
|
||||
|
||||
ptr = (char*)mjs_get_ptr(mjs, ptr_v);
|
||||
if(offset_v != MJS_UNDEFINED) {
|
||||
offset = mjs_get_int(mjs, offset_v);
|
||||
}
|
||||
len = mjs_get_int(mjs, len_v);
|
||||
|
||||
ret = mjs_mk_string(mjs, ptr + offset, len, copy);
|
||||
|
||||
clean:
|
||||
mjs_return(mjs, ret);
|
||||
}
|
||||
|
||||
enum unescape_error {
|
||||
SLRE_INVALID_HEX_DIGIT,
|
||||
SLRE_INVALID_ESC_CHAR,
|
||||
SLRE_UNTERM_ESC_SEQ,
|
||||
};
|
||||
|
||||
static int hex(int c) {
|
||||
if(c >= '0' && c <= '9') return c - '0';
|
||||
if(c >= 'a' && c <= 'f') return c - 'a' + 10;
|
||||
if(c >= 'A' && c <= 'F') return c - 'A' + 10;
|
||||
return -SLRE_INVALID_HEX_DIGIT;
|
||||
}
|
||||
|
||||
static int nextesc(const char** p) {
|
||||
const unsigned char* s = (unsigned char*)(*p)++;
|
||||
switch(*s) {
|
||||
case 0:
|
||||
return -SLRE_UNTERM_ESC_SEQ;
|
||||
case 'c':
|
||||
++*p;
|
||||
return *s & 31;
|
||||
case 'b':
|
||||
return '\b';
|
||||
case 't':
|
||||
return '\t';
|
||||
case 'n':
|
||||
return '\n';
|
||||
case 'v':
|
||||
return '\v';
|
||||
case 'f':
|
||||
return '\f';
|
||||
case 'r':
|
||||
return '\r';
|
||||
case '\\':
|
||||
return '\\';
|
||||
case 'u':
|
||||
if(isxdigit(s[1]) && isxdigit(s[2]) && isxdigit(s[3]) && isxdigit(s[4])) {
|
||||
(*p) += 4;
|
||||
return hex(s[1]) << 12 | hex(s[2]) << 8 | hex(s[3]) << 4 | hex(s[4]);
|
||||
}
|
||||
return -SLRE_INVALID_HEX_DIGIT;
|
||||
case 'x':
|
||||
if(isxdigit(s[1]) && isxdigit(s[2])) {
|
||||
(*p) += 2;
|
||||
return (hex(s[1]) << 4) | hex(s[2]);
|
||||
}
|
||||
return -SLRE_INVALID_HEX_DIGIT;
|
||||
default:
|
||||
return -SLRE_INVALID_ESC_CHAR;
|
||||
}
|
||||
}
|
||||
|
||||
MJS_PRIVATE size_t unescape(const char* s, size_t len, char* to) {
|
||||
const char* end = s + len;
|
||||
size_t n = 0;
|
||||
char tmp[4];
|
||||
Rune r;
|
||||
|
||||
while(s < end) {
|
||||
s += chartorune(&r, s);
|
||||
if(r == '\\' && s < end) {
|
||||
switch(*s) {
|
||||
case '"':
|
||||
s++, r = '"';
|
||||
break;
|
||||
case '\'':
|
||||
s++, r = '\'';
|
||||
break;
|
||||
case '\n':
|
||||
s++, r = '\n';
|
||||
break;
|
||||
default: {
|
||||
const char* tmp_s = s;
|
||||
int i = nextesc(&s);
|
||||
switch(i) {
|
||||
case -SLRE_INVALID_ESC_CHAR:
|
||||
r = '\\';
|
||||
s = tmp_s;
|
||||
n += runetochar(to == NULL ? tmp : to + n, &r);
|
||||
s += chartorune(&r, s);
|
||||
break;
|
||||
case -SLRE_INVALID_HEX_DIGIT:
|
||||
default:
|
||||
r = i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
n += runetochar(to == NULL ? tmp : to + n, &r);
|
||||
}
|
||||
|
||||
return n;
|
||||
}
|
||||
|
||||
MJS_PRIVATE void embed_string(
|
||||
struct mbuf* m,
|
||||
size_t offset,
|
||||
const char* p,
|
||||
size_t len,
|
||||
uint8_t /*enum embstr_flags*/ flags) {
|
||||
char* old_base = m->buf;
|
||||
uint8_t p_backed_by_mbuf = p >= old_base && p < old_base + m->len;
|
||||
size_t n = (flags & EMBSTR_UNESCAPE) ? unescape(p, len, NULL) : len;
|
||||
|
||||
/* Calculate how many bytes length takes */
|
||||
size_t k = cs_varint_llen(n);
|
||||
|
||||
/* total length: varing length + string len + zero-term */
|
||||
size_t tot_len = k + n + !!(flags & EMBSTR_ZERO_TERM);
|
||||
|
||||
/* Allocate buffer */
|
||||
mbuf_insert(m, offset, NULL, tot_len);
|
||||
|
||||
/* Fixup p if it was relocated by mbuf_insert() above */
|
||||
if(p_backed_by_mbuf) {
|
||||
p += m->buf - old_base;
|
||||
}
|
||||
|
||||
/* Write length */
|
||||
cs_varint_encode(n, (unsigned char*)m->buf + offset, k);
|
||||
|
||||
/* Write string */
|
||||
if(p != 0) {
|
||||
if(flags & EMBSTR_UNESCAPE) {
|
||||
unescape(p, len, m->buf + offset + k);
|
||||
} else {
|
||||
memcpy(m->buf + offset + k, p, len);
|
||||
}
|
||||
}
|
||||
|
||||
/* add NULL-terminator if needed */
|
||||
if(flags & EMBSTR_ZERO_TERM) {
|
||||
m->buf[offset + tot_len - 1] = '\0';
|
||||
}
|
||||
}
|
||||
47
lib/mjs/mjs_string.h
Normal file
47
lib/mjs/mjs_string.h
Normal file
@@ -0,0 +1,47 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_STRING_H_
|
||||
#define MJS_STRING_H_
|
||||
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_string_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Size of the extra space for strings mbuf that is needed to avoid frequent
|
||||
* reallocations
|
||||
*/
|
||||
#define _MJS_STRING_BUF_RESERVE 100
|
||||
|
||||
MJS_PRIVATE unsigned long cstr_to_ulong(const char* s, size_t len, int* ok);
|
||||
MJS_PRIVATE mjs_err_t str_to_ulong(struct mjs* mjs, mjs_val_t v, int* ok, unsigned long* res);
|
||||
MJS_PRIVATE int s_cmp(struct mjs* mjs, mjs_val_t a, mjs_val_t b);
|
||||
MJS_PRIVATE mjs_val_t s_concat(struct mjs* mjs, mjs_val_t a, mjs_val_t b);
|
||||
|
||||
MJS_PRIVATE void embed_string(
|
||||
struct mbuf* m,
|
||||
size_t offset,
|
||||
const char* p,
|
||||
size_t len,
|
||||
uint8_t /*enum embstr_flags*/ flags);
|
||||
|
||||
MJS_PRIVATE void mjs_mkstr(struct mjs* mjs);
|
||||
|
||||
MJS_PRIVATE void mjs_string_slice(struct mjs* mjs);
|
||||
MJS_PRIVATE void mjs_string_index_of(struct mjs* mjs);
|
||||
MJS_PRIVATE void mjs_string_char_code_at(struct mjs* mjs);
|
||||
|
||||
#define EMBSTR_ZERO_TERM 1
|
||||
#define EMBSTR_UNESCAPE 2
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_STRING_H_ */
|
||||
78
lib/mjs/mjs_string_public.h
Normal file
78
lib/mjs/mjs_string_public.h
Normal file
@@ -0,0 +1,78 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_STRING_PUBLIC_H_
|
||||
#define MJS_STRING_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
|
||||
#define MJS_STRING_LITERAL_MAX_LEN 128
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
/*
|
||||
* Creates a string primitive value.
|
||||
* `str` must point to the utf8 string of length `len`.
|
||||
* If `len` is ~0, `str` is assumed to be NUL-terminated and `strlen(str)` is
|
||||
* used.
|
||||
*
|
||||
* If `copy` is non-zero, the string data is copied and owned by the GC. The
|
||||
* caller can free the string data afterwards. Otherwise (`copy` is zero), the
|
||||
* caller owns the string data, and is responsible for not freeing it while it
|
||||
* is used.
|
||||
*/
|
||||
mjs_val_t mjs_mk_string(struct mjs* mjs, const char* str, size_t len, int copy);
|
||||
|
||||
/* Returns true if given value is a primitive string value */
|
||||
int mjs_is_string(mjs_val_t v);
|
||||
|
||||
/*
|
||||
* Returns a pointer to the string stored in `mjs_val_t`.
|
||||
*
|
||||
* String length returned in `len`, which is allowed to be NULL. Returns NULL
|
||||
* if the value is not a string.
|
||||
*
|
||||
* JS strings can contain embedded NUL chars and may or may not be NUL
|
||||
* terminated.
|
||||
*
|
||||
* CAUTION: creating new JavaScript object, array, or string may kick in a
|
||||
* garbage collector, which in turn may relocate string data and invalidate
|
||||
* pointer returned by `mjs_get_string()`.
|
||||
*
|
||||
* Short JS strings are embedded inside the `mjs_val_t` value itself. This
|
||||
* is why a pointer to a `mjs_val_t` is required. It also means that the string
|
||||
* data will become invalid once that `mjs_val_t` value goes out of scope.
|
||||
*/
|
||||
const char* mjs_get_string(struct mjs* mjs, mjs_val_t* v, size_t* len);
|
||||
|
||||
/*
|
||||
* Returns a pointer to the string stored in `mjs_val_t`.
|
||||
*
|
||||
* Returns NULL if the value is not a string or if the string is not compatible
|
||||
* with a C string.
|
||||
*
|
||||
* C compatible strings contain exactly one NUL char, in terminal position.
|
||||
*
|
||||
* All strings owned by the MJS engine (see `mjs_mk_string()`) are guaranteed to
|
||||
* be NUL terminated. Out of these, those that don't include embedded NUL chars
|
||||
* are guaranteed to be C compatible.
|
||||
*/
|
||||
const char* mjs_get_cstring(struct mjs* mjs, mjs_val_t* v);
|
||||
|
||||
/*
|
||||
* Returns the standard strcmp comparison code after comparing a JS string a
|
||||
* with a possibly non null-terminated string b. NOTE: the strings are equal
|
||||
* only if their length is equal, i.e. the len field doesn't imply strncmp
|
||||
* behaviour.
|
||||
*/
|
||||
int mjs_strcmp(struct mjs* mjs, mjs_val_t* a, const char* b, size_t len);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_STRING_PUBLIC_H_ */
|
||||
247
lib/mjs/mjs_tok.c
Normal file
247
lib/mjs/mjs_tok.c
Normal file
@@ -0,0 +1,247 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
#include "common/cs_dbg.h"
|
||||
#include "mjs_tok.h"
|
||||
|
||||
MJS_PRIVATE void pinit(const char* file_name, const char* buf, struct pstate* p) {
|
||||
memset(p, 0, sizeof(*p));
|
||||
p->line_no = 1;
|
||||
p->last_emitted_line_no = 1;
|
||||
p->file_name = file_name;
|
||||
p->buf = p->pos = buf;
|
||||
mbuf_init(&p->offset_lineno_map, 0);
|
||||
}
|
||||
|
||||
// We're not relying on the target libc ctype, as it may incorrectly
|
||||
// handle negative arguments, e.g. isspace(-1).
|
||||
static int mjs_is_space(int c) {
|
||||
return c == ' ' || c == '\r' || c == '\n' || c == '\t' || c == '\f' || c == '\v';
|
||||
}
|
||||
|
||||
MJS_PRIVATE int mjs_is_digit(int c) {
|
||||
return c >= '0' && c <= '9';
|
||||
}
|
||||
|
||||
static int mjs_is_alpha(int c) {
|
||||
return (c >= 'a' && c <= 'z') || (c >= 'A' && c <= 'Z');
|
||||
}
|
||||
|
||||
MJS_PRIVATE int mjs_is_ident(int c) {
|
||||
return c == '_' || c == '$' || mjs_is_alpha(c);
|
||||
}
|
||||
|
||||
// Try to parse a token that can take one or two chars.
|
||||
static int longtok(struct pstate* p, const char* first_chars, const char* second_chars) {
|
||||
if(strchr(first_chars, p->pos[0]) == NULL) return TOK_EOF;
|
||||
if(p->pos[1] != '\0' && strchr(second_chars, p->pos[1]) != NULL) {
|
||||
p->tok.len++;
|
||||
p->pos++;
|
||||
return p->pos[-1] << 8 | p->pos[0];
|
||||
}
|
||||
return p->pos[0];
|
||||
}
|
||||
|
||||
// Try to parse a token that takes exactly 3 chars.
|
||||
static int longtok3(struct pstate* p, char a, char b, char c) {
|
||||
if(p->pos[0] == a && p->pos[1] == b && p->pos[2] == c) {
|
||||
p->tok.len += 2;
|
||||
p->pos += 2;
|
||||
return p->pos[-2] << 16 | p->pos[-1] << 8 | p->pos[0];
|
||||
}
|
||||
return TOK_EOF;
|
||||
}
|
||||
|
||||
// Try to parse a token that takes exactly 4 chars.
|
||||
static int longtok4(struct pstate* p, char a, char b, char c, char d) {
|
||||
if(p->pos[0] == a && p->pos[1] == b && p->pos[2] == c && p->pos[3] == d) {
|
||||
p->tok.len += 3;
|
||||
p->pos += 3;
|
||||
return p->pos[-3] << 24 | p->pos[-2] << 16 | p->pos[-1] << 8 | p->pos[0];
|
||||
}
|
||||
return TOK_EOF;
|
||||
}
|
||||
|
||||
static int getnum(struct pstate* p) {
|
||||
if(p->pos[0] == '0' && p->pos[1] == 'x') {
|
||||
// MSVC6 strtod cannot parse 0x... numbers, thus this ugly workaround.
|
||||
strtoul(p->pos + 2, (char**)&p->pos, 16);
|
||||
} else {
|
||||
strtod(p->pos, (char**)&p->pos);
|
||||
}
|
||||
p->tok.len = p->pos - p->tok.ptr;
|
||||
p->pos--;
|
||||
return TOK_NUM;
|
||||
}
|
||||
|
||||
static int is_reserved_word_token(const char* s, int len) {
|
||||
const char* reserved[] = {"break", "case", "catch", "continue", "debugger", "default",
|
||||
"delete", "do", "else", "false", "finally", "for",
|
||||
"function", "if", "in", "instanceof", "new", "null",
|
||||
"return", "switch", "this", "throw", "true", "try",
|
||||
"typeof", "var", "void", "while", "with", "let",
|
||||
"undefined", NULL};
|
||||
int i;
|
||||
if(!mjs_is_alpha(s[0])) return 0;
|
||||
for(i = 0; reserved[i] != NULL; i++) {
|
||||
if(len == (int)strlen(reserved[i]) && strncmp(s, reserved[i], len) == 0) return i + 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
static int getident(struct pstate* p) {
|
||||
while(mjs_is_ident(p->pos[0]) || mjs_is_digit(p->pos[0])) p->pos++;
|
||||
p->tok.len = p->pos - p->tok.ptr;
|
||||
p->pos--;
|
||||
return TOK_IDENT;
|
||||
}
|
||||
|
||||
static int getstr(struct pstate* p) {
|
||||
int quote = *p->pos++;
|
||||
p->tok.ptr++;
|
||||
while(p->pos[0] != '\0' && p->pos[0] != quote) {
|
||||
if(p->pos[0] == '\\' && p->pos[1] != '\0' &&
|
||||
(p->pos[1] == quote || strchr("bfnrtv\\", p->pos[1]) != NULL)) {
|
||||
p->pos += 2;
|
||||
} else {
|
||||
p->pos++;
|
||||
}
|
||||
}
|
||||
p->tok.len = p->pos - p->tok.ptr;
|
||||
return TOK_STR;
|
||||
}
|
||||
|
||||
static void skip_spaces_and_comments(struct pstate* p) {
|
||||
const char* pos;
|
||||
do {
|
||||
pos = p->pos;
|
||||
while(mjs_is_space(p->pos[0])) {
|
||||
if(p->pos[0] == '\n') p->line_no++;
|
||||
p->pos++;
|
||||
}
|
||||
if(p->pos[0] == '/' && p->pos[1] == '/') {
|
||||
while(p->pos[0] != '\0' && p->pos[0] != '\n') p->pos++;
|
||||
}
|
||||
if(p->pos[0] == '/' && p->pos[1] == '*') {
|
||||
p->pos += 2;
|
||||
while(p->pos[0] != '\0') {
|
||||
if(p->pos[0] == '\n') p->line_no++;
|
||||
if(p->pos[0] == '*' && p->pos[1] == '/') {
|
||||
p->pos += 2;
|
||||
break;
|
||||
}
|
||||
p->pos++;
|
||||
}
|
||||
}
|
||||
} while(pos < p->pos);
|
||||
}
|
||||
|
||||
static int ptranslate(int tok) {
|
||||
#define DT(a, b) ((a) << 8 | (b))
|
||||
#define TT(a, b, c) ((a) << 16 | (b) << 8 | (c))
|
||||
#define QT(a, b, c, d) ((a) << 24 | (b) << 16 | (c) << 8 | (d))
|
||||
/* Map token ID produced by mjs_tok.c to token ID produced by lemon */
|
||||
/* clang-format off */
|
||||
switch (tok) {
|
||||
case ':': return TOK_COLON;
|
||||
case ';': return TOK_SEMICOLON;
|
||||
case ',': return TOK_COMMA;
|
||||
case '=': return TOK_ASSIGN;
|
||||
case '{': return TOK_OPEN_CURLY;
|
||||
case '}': return TOK_CLOSE_CURLY;
|
||||
case '(': return TOK_OPEN_PAREN;
|
||||
case ')': return TOK_CLOSE_PAREN;
|
||||
case '[': return TOK_OPEN_BRACKET;
|
||||
case ']': return TOK_CLOSE_BRACKET;
|
||||
case '*': return TOK_MUL;
|
||||
case '+': return TOK_PLUS;
|
||||
case '-': return TOK_MINUS;
|
||||
case '/': return TOK_DIV;
|
||||
case '%': return TOK_REM;
|
||||
case '&': return TOK_AND;
|
||||
case '|': return TOK_OR;
|
||||
case '^': return TOK_XOR;
|
||||
case '.': return TOK_DOT;
|
||||
case '?': return TOK_QUESTION;
|
||||
case '!': return TOK_NOT;
|
||||
case '~': return TOK_TILDA;
|
||||
case '<': return TOK_LT;
|
||||
case '>': return TOK_GT;
|
||||
case DT('<','<'): return TOK_LSHIFT;
|
||||
case DT('>','>'): return TOK_RSHIFT;
|
||||
case DT('-','-'): return TOK_MINUS_MINUS;
|
||||
case DT('+','+'): return TOK_PLUS_PLUS;
|
||||
case DT('+','='): return TOK_PLUS_ASSIGN;
|
||||
case DT('-','='): return TOK_MINUS_ASSIGN;
|
||||
case DT('*','='): return TOK_MUL_ASSIGN;
|
||||
case DT('/','='): return TOK_DIV_ASSIGN;
|
||||
case DT('&','='): return TOK_AND_ASSIGN;
|
||||
case DT('|','='): return TOK_OR_ASSIGN;
|
||||
case DT('%','='): return TOK_REM_ASSIGN;
|
||||
case DT('^','='): return TOK_XOR_ASSIGN;
|
||||
case DT('=','='): return TOK_EQ;
|
||||
case DT('!','='): return TOK_NE;
|
||||
case DT('<','='): return TOK_LE;
|
||||
case DT('>','='): return TOK_GE;
|
||||
case DT('&','&'): return TOK_LOGICAL_AND;
|
||||
case DT('|','|'): return TOK_LOGICAL_OR;
|
||||
case TT('=','=','='): return TOK_EQ_EQ;
|
||||
case TT('!','=','='): return TOK_NE_NE;
|
||||
case TT('<','<','='): return TOK_LSHIFT_ASSIGN;
|
||||
case TT('>','>','='): return TOK_RSHIFT_ASSIGN;
|
||||
case TT('>','>','>'): return TOK_URSHIFT;
|
||||
case QT('>','>','>','='): return TOK_URSHIFT_ASSIGN;
|
||||
}
|
||||
/* clang-format on */
|
||||
return tok;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int pnext(struct pstate* p) {
|
||||
int tmp, tok = TOK_INVALID;
|
||||
|
||||
skip_spaces_and_comments(p);
|
||||
p->tok.ptr = p->pos;
|
||||
p->tok.len = 1;
|
||||
|
||||
if(p->pos[0] == '\0') {
|
||||
tok = TOK_EOF;
|
||||
} else if(mjs_is_digit(p->pos[0])) {
|
||||
tok = getnum(p);
|
||||
} else if(p->pos[0] == '\'' || p->pos[0] == '"') {
|
||||
tok = getstr(p);
|
||||
} else if(mjs_is_ident(p->pos[0])) {
|
||||
tok = getident(p);
|
||||
/*
|
||||
* NOTE: getident() has side effects on `p`, and `is_reserved_word_token()`
|
||||
* relies on them. Since in C the order of evaluation of the operands is
|
||||
* undefined, `is_reserved_word_token()` should be called in a separate
|
||||
* statement.
|
||||
*/
|
||||
tok += is_reserved_word_token(p->tok.ptr, p->tok.len);
|
||||
} else if(strchr(",.:;{}[]()?", p->pos[0]) != NULL) {
|
||||
tok = p->pos[0];
|
||||
} else if(
|
||||
(tmp = longtok3(p, '<', '<', '=')) != TOK_EOF ||
|
||||
(tmp = longtok3(p, '>', '>', '=')) != TOK_EOF ||
|
||||
(tmp = longtok4(p, '>', '>', '>', '=')) != TOK_EOF ||
|
||||
(tmp = longtok3(p, '>', '>', '>')) != TOK_EOF ||
|
||||
(tmp = longtok3(p, '=', '=', '=')) != TOK_EOF ||
|
||||
(tmp = longtok3(p, '!', '=', '=')) != TOK_EOF ||
|
||||
(tmp = longtok(p, "&", "&=")) != TOK_EOF || (tmp = longtok(p, "|", "|=")) != TOK_EOF ||
|
||||
(tmp = longtok(p, "<", "<=")) != TOK_EOF || (tmp = longtok(p, ">", ">=")) != TOK_EOF ||
|
||||
(tmp = longtok(p, "-", "-=")) != TOK_EOF || (tmp = longtok(p, "+", "+=")) != TOK_EOF) {
|
||||
tok = tmp;
|
||||
} else if((tmp = longtok(p, "^~+-%/*<>=!|&", "=")) != TOK_EOF) {
|
||||
tok = tmp;
|
||||
}
|
||||
if(p->pos[0] != '\0') p->pos++;
|
||||
LOG(LL_VERBOSE_DEBUG, (" --> %d [%.*s]", tok, p->tok.len, p->tok.ptr));
|
||||
p->prev_tok = p->tok.tok;
|
||||
p->tok.tok = ptranslate(tok);
|
||||
return p->tok.tok;
|
||||
}
|
||||
141
lib/mjs/mjs_tok.h
Normal file
141
lib/mjs/mjs_tok.h
Normal file
@@ -0,0 +1,141 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_TOK_H_
|
||||
#define MJS_TOK_H_
|
||||
|
||||
#include "mjs_internal.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
struct tok {
|
||||
int tok;
|
||||
int len;
|
||||
const char* ptr;
|
||||
};
|
||||
|
||||
struct pstate {
|
||||
const char* file_name; /* Source code file name */
|
||||
const char* buf; /* Nul-terminated source code buffer */
|
||||
const char* pos; /* Current position */
|
||||
int line_no; /* Line number */
|
||||
int last_emitted_line_no;
|
||||
struct mbuf offset_lineno_map;
|
||||
int prev_tok; /* Previous token, for prefix increment / decrement */
|
||||
struct tok tok; /* Parsed token */
|
||||
struct mjs* mjs;
|
||||
int start_bcode_idx; /* Index in mjs->bcode at which parsing was started */
|
||||
int cur_idx; /* Index in mjs->bcode at which newly generated code is inserted
|
||||
*/
|
||||
int depth;
|
||||
};
|
||||
|
||||
enum {
|
||||
TOK_EOF,
|
||||
TOK_INVALID,
|
||||
|
||||
TOK_COLON,
|
||||
TOK_SEMICOLON,
|
||||
TOK_COMMA,
|
||||
TOK_ASSIGN,
|
||||
TOK_OPEN_CURLY,
|
||||
TOK_CLOSE_CURLY,
|
||||
TOK_OPEN_PAREN,
|
||||
TOK_CLOSE_PAREN,
|
||||
TOK_OPEN_BRACKET,
|
||||
TOK_CLOSE_BRACKET,
|
||||
TOK_MUL,
|
||||
TOK_PLUS,
|
||||
TOK_MINUS,
|
||||
TOK_DIV,
|
||||
TOK_REM,
|
||||
TOK_AND,
|
||||
TOK_OR,
|
||||
TOK_XOR,
|
||||
TOK_DOT,
|
||||
TOK_QUESTION,
|
||||
TOK_NOT,
|
||||
TOK_TILDA,
|
||||
TOK_LT,
|
||||
TOK_GT,
|
||||
TOK_LSHIFT,
|
||||
TOK_RSHIFT,
|
||||
TOK_MINUS_MINUS,
|
||||
TOK_PLUS_PLUS,
|
||||
TOK_PLUS_ASSIGN,
|
||||
TOK_MINUS_ASSIGN,
|
||||
TOK_MUL_ASSIGN,
|
||||
TOK_DIV_ASSIGN,
|
||||
TOK_AND_ASSIGN,
|
||||
TOK_OR_ASSIGN,
|
||||
TOK_REM_ASSIGN,
|
||||
TOK_XOR_ASSIGN,
|
||||
TOK_EQ,
|
||||
TOK_NE,
|
||||
TOK_LE,
|
||||
TOK_GE,
|
||||
TOK_LOGICAL_AND,
|
||||
TOK_LOGICAL_OR,
|
||||
TOK_EQ_EQ,
|
||||
TOK_NE_NE,
|
||||
TOK_LSHIFT_ASSIGN,
|
||||
TOK_RSHIFT_ASSIGN,
|
||||
TOK_URSHIFT,
|
||||
TOK_URSHIFT_ASSIGN,
|
||||
|
||||
TOK_UNARY_PLUS,
|
||||
TOK_UNARY_MINUS,
|
||||
TOK_POSTFIX_PLUS,
|
||||
TOK_POSTFIX_MINUS,
|
||||
|
||||
TOK_NUM = 200, /* Make sure they don't clash with ascii '+', '{', etc */
|
||||
TOK_STR,
|
||||
TOK_IDENT,
|
||||
TOK_KEYWORD_BREAK,
|
||||
TOK_KEYWORD_CASE,
|
||||
TOK_KEYWORD_CATCH,
|
||||
TOK_KEYWORD_CONTINUE,
|
||||
TOK_KEYWORD_DEBUGGER,
|
||||
TOK_KEYWORD_DEFAULT,
|
||||
TOK_KEYWORD_DELETE,
|
||||
TOK_KEYWORD_DO,
|
||||
TOK_KEYWORD_ELSE,
|
||||
TOK_KEYWORD_FALSE,
|
||||
TOK_KEYWORD_FINALLY,
|
||||
TOK_KEYWORD_FOR,
|
||||
TOK_KEYWORD_FUNCTION,
|
||||
TOK_KEYWORD_IF,
|
||||
TOK_KEYWORD_IN,
|
||||
TOK_KEYWORD_INSTANCEOF,
|
||||
TOK_KEYWORD_NEW,
|
||||
TOK_KEYWORD_NULL,
|
||||
TOK_KEYWORD_RETURN,
|
||||
TOK_KEYWORD_SWITCH,
|
||||
TOK_KEYWORD_THIS,
|
||||
TOK_KEYWORD_THROW,
|
||||
TOK_KEYWORD_TRUE,
|
||||
TOK_KEYWORD_TRY,
|
||||
TOK_KEYWORD_TYPEOF,
|
||||
TOK_KEYWORD_VAR,
|
||||
TOK_KEYWORD_VOID,
|
||||
TOK_KEYWORD_WHILE,
|
||||
TOK_KEYWORD_WITH,
|
||||
TOK_KEYWORD_LET,
|
||||
TOK_KEYWORD_UNDEFINED,
|
||||
TOK_MAX
|
||||
};
|
||||
|
||||
MJS_PRIVATE void pinit(const char* file_name, const char* buf, struct pstate*);
|
||||
MJS_PRIVATE int pnext(struct pstate*);
|
||||
MJS_PRIVATE int mjs_is_ident(int c);
|
||||
MJS_PRIVATE int mjs_is_digit(int c);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_TOK_H_ */
|
||||
567
lib/mjs/mjs_util.c
Normal file
567
lib/mjs/mjs_util.c
Normal file
@@ -0,0 +1,567 @@
|
||||
/*
|
||||
* Copyright (c) 2017 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#include "common/cs_varint.h"
|
||||
#include "common/frozen/frozen.h"
|
||||
#include "mjs_array.h"
|
||||
#include "mjs_bcode.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_internal.h"
|
||||
#include "mjs_object.h"
|
||||
#include "mjs_primitive.h"
|
||||
#include "mjs_string.h"
|
||||
#include "mjs_util.h"
|
||||
#include "mjs_tok.h"
|
||||
#include "mjs_array_buf.h"
|
||||
#include <furi.h>
|
||||
|
||||
const char* mjs_typeof(mjs_val_t v) {
|
||||
return mjs_stringify_type(mjs_get_type(v));
|
||||
}
|
||||
|
||||
MJS_PRIVATE const char* mjs_stringify_type(enum mjs_type t) {
|
||||
switch(t) {
|
||||
case MJS_TYPE_NUMBER:
|
||||
return "number";
|
||||
case MJS_TYPE_BOOLEAN:
|
||||
return "boolean";
|
||||
case MJS_TYPE_STRING:
|
||||
return "string";
|
||||
case MJS_TYPE_OBJECT_ARRAY:
|
||||
return "array";
|
||||
case MJS_TYPE_OBJECT_GENERIC:
|
||||
return "object";
|
||||
case MJS_TYPE_FOREIGN:
|
||||
return "foreign_ptr";
|
||||
case MJS_TYPE_OBJECT_FUNCTION:
|
||||
return "function";
|
||||
case MJS_TYPE_NULL:
|
||||
return "null";
|
||||
case MJS_TYPE_UNDEFINED:
|
||||
return "undefined";
|
||||
case MJS_TYPE_ARRAY_BUF:
|
||||
return "array_buf";
|
||||
case MJS_TYPE_ARRAY_BUF_VIEW:
|
||||
return "data_view";
|
||||
default:
|
||||
return "???";
|
||||
}
|
||||
}
|
||||
|
||||
void mjs_jprintf(mjs_val_t v, struct mjs* mjs, struct json_out* out) {
|
||||
if(mjs_is_number(v)) {
|
||||
double iv, d = mjs_get_double(mjs, v);
|
||||
if(modf(d, &iv) == 0) {
|
||||
json_printf(out, "%" INT64_FMT, (int64_t)d);
|
||||
} else {
|
||||
json_printf(out, "%f", mjs_get_double(mjs, v));
|
||||
}
|
||||
} else if(mjs_is_boolean(v)) {
|
||||
json_printf(out, "%s", mjs_get_bool(mjs, v) ? "true" : "false");
|
||||
} else if(mjs_is_string(v)) {
|
||||
size_t i, size;
|
||||
const char* s = mjs_get_string(mjs, &v, &size);
|
||||
for(i = 0; i < size; i++) {
|
||||
int ch = ((unsigned char*)s)[i];
|
||||
if(isprint(ch)) {
|
||||
json_printf(out, "%c", ch);
|
||||
} else {
|
||||
json_printf(out, "%s%02x", "\\x", ch);
|
||||
}
|
||||
}
|
||||
} else if(mjs_is_array(v)) {
|
||||
json_printf(out, "%s", "<array>");
|
||||
} else if(mjs_is_object(v)) {
|
||||
json_printf(out, "%s", "<object>");
|
||||
} else if(mjs_is_foreign(v)) {
|
||||
json_printf(
|
||||
out, "%s%lx%s", "<foreign_ptr@", (unsigned long)(uintptr_t)mjs_get_ptr(mjs, v), ">");
|
||||
} else if(mjs_is_function(v)) {
|
||||
json_printf(out, "%s%d%s", "<function@", (int)mjs_get_func_addr(v), ">");
|
||||
} else if(mjs_is_null(v)) {
|
||||
json_printf(out, "%s", "null");
|
||||
} else if(mjs_is_undefined(v)) {
|
||||
json_printf(out, "%s", "undefined");
|
||||
} else {
|
||||
json_printf(out, "%s%" INT64_FMT "%s", "<???", (int64_t)v, ">");
|
||||
}
|
||||
}
|
||||
|
||||
void mjs_sprintf(mjs_val_t v, struct mjs* mjs, char* buf, size_t n) {
|
||||
struct json_out out = JSON_OUT_BUF(buf, n);
|
||||
mjs_jprintf(v, mjs, &out);
|
||||
}
|
||||
|
||||
void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp) {
|
||||
struct json_out out = JSON_OUT_FILE(fp);
|
||||
mjs_jprintf(v, mjs, &out);
|
||||
}
|
||||
|
||||
MJS_PRIVATE const char* opcodetostr(uint8_t opcode) {
|
||||
static const char* names[] = {
|
||||
"NOP",
|
||||
"DROP",
|
||||
"DUP",
|
||||
"SWAP",
|
||||
"JMP",
|
||||
"JMP_TRUE",
|
||||
"JMP_NEUTRAL_TRUE",
|
||||
"JMP_FALSE",
|
||||
"JMP_NEUTRAL_FALSE",
|
||||
"FIND_SCOPE",
|
||||
"PUSH_SCOPE",
|
||||
"PUSH_STR",
|
||||
"PUSH_TRUE",
|
||||
"PUSH_FALSE",
|
||||
"PUSH_INT",
|
||||
"PUSH_DBL",
|
||||
"PUSH_NULL",
|
||||
"PUSH_UNDEF",
|
||||
"PUSH_OBJ",
|
||||
"PUSH_ARRAY",
|
||||
"PUSH_FUNC",
|
||||
"PUSH_THIS",
|
||||
"GET",
|
||||
"CREATE",
|
||||
"EXPR",
|
||||
"APPEND",
|
||||
"SET_ARG",
|
||||
"NEW_SCOPE",
|
||||
"DEL_SCOPE",
|
||||
"CALL",
|
||||
"RETURN",
|
||||
"LOOP",
|
||||
"BREAK",
|
||||
"CONTINUE",
|
||||
"SETRETVAL",
|
||||
"EXIT",
|
||||
"BCODE_HDR",
|
||||
"ARGS",
|
||||
"FOR_IN_NEXT",
|
||||
};
|
||||
const char* name = "???";
|
||||
assert(ARRAY_SIZE(names) == OP_MAX);
|
||||
if(opcode < ARRAY_SIZE(names)) name = names[opcode];
|
||||
return name;
|
||||
}
|
||||
|
||||
MJS_PRIVATE size_t
|
||||
mjs_disasm_single(const uint8_t* code, size_t i, MjsPrintCallback print_cb, void* print_ctx) {
|
||||
char buf[40];
|
||||
size_t start_i = i;
|
||||
size_t llen;
|
||||
uint64_t n;
|
||||
|
||||
furi_assert(print_cb);
|
||||
|
||||
snprintf(buf, sizeof(buf), "%-3u\t%-8s", (unsigned)i, opcodetostr(code[i]));
|
||||
|
||||
switch(code[i]) {
|
||||
case OP_PUSH_FUNC: {
|
||||
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
||||
print_cb(print_ctx, "%s %04u", buf, (unsigned)(i - n));
|
||||
i += llen;
|
||||
break;
|
||||
}
|
||||
case OP_PUSH_INT: {
|
||||
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
||||
print_cb(print_ctx, "%s\t%lu", buf, (unsigned long)n);
|
||||
i += llen;
|
||||
break;
|
||||
}
|
||||
case OP_SET_ARG: {
|
||||
size_t llen2;
|
||||
uint64_t arg_no;
|
||||
cs_varint_decode(&code[i + 1], ~0, &arg_no, &llen);
|
||||
cs_varint_decode(&code[i + llen + 1], ~0, &n, &llen2);
|
||||
print_cb(
|
||||
print_ctx, "%s\t[%.*s] %u", buf, (int)n, code + i + 1 + llen + llen2, (unsigned)arg_no);
|
||||
i += llen + llen2 + n;
|
||||
break;
|
||||
}
|
||||
case OP_PUSH_STR:
|
||||
case OP_PUSH_DBL: {
|
||||
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
||||
print_cb(print_ctx, "%s\t[%.*s]", buf, (int)n, code + i + 1 + llen);
|
||||
i += llen + n;
|
||||
break;
|
||||
}
|
||||
case OP_JMP:
|
||||
case OP_JMP_TRUE:
|
||||
case OP_JMP_NEUTRAL_TRUE:
|
||||
case OP_JMP_FALSE:
|
||||
case OP_JMP_NEUTRAL_FALSE: {
|
||||
cs_varint_decode(&code[i + 1], ~0, &n, &llen);
|
||||
print_cb(
|
||||
print_ctx,
|
||||
"%s\t%u",
|
||||
buf,
|
||||
(unsigned)(i + n + llen + 1 /* becaue i will be incremented on the usual terms */));
|
||||
i += llen;
|
||||
break;
|
||||
}
|
||||
case OP_LOOP: {
|
||||
size_t l1, l2;
|
||||
uint64_t n1, n2;
|
||||
cs_varint_decode(&code[i + 1], ~0, &n1, &l1);
|
||||
cs_varint_decode(&code[i + l1 + 1], ~0, &n2, &l2);
|
||||
print_cb(
|
||||
print_ctx,
|
||||
"%s\tB:%lu C:%lu (%d)",
|
||||
buf,
|
||||
(unsigned long)(i + 1 /* OP_LOOP */ + l1 + n1),
|
||||
(unsigned long)(i + 1 /* OP_LOOP */ + l1 + l2 + n2),
|
||||
(int)i);
|
||||
i += l1 + l2;
|
||||
break;
|
||||
}
|
||||
case OP_EXPR: {
|
||||
int op = code[i + 1];
|
||||
const char* name = "???";
|
||||
/* clang-format off */
|
||||
switch (op) {
|
||||
case TOK_DOT: name = "."; break;
|
||||
case TOK_MINUS: name = "-"; break;
|
||||
case TOK_PLUS: name = "+"; break;
|
||||
case TOK_MUL: name = "*"; break;
|
||||
case TOK_DIV: name = "/"; break;
|
||||
case TOK_REM: name = "%"; break;
|
||||
case TOK_XOR: name = "^"; break;
|
||||
case TOK_AND: name = "&"; break;
|
||||
case TOK_OR: name = "|"; break;
|
||||
case TOK_LSHIFT: name = "<<"; break;
|
||||
case TOK_RSHIFT: name = ">>"; break;
|
||||
case TOK_URSHIFT: name = ">>>"; break;
|
||||
case TOK_UNARY_MINUS: name = "- (unary)"; break;
|
||||
case TOK_UNARY_PLUS: name = "+ (unary)"; break;
|
||||
case TOK_NOT: name = "!"; break;
|
||||
case TOK_TILDA: name = "~"; break;
|
||||
case TOK_EQ: name = "=="; break;
|
||||
case TOK_NE: name = "!="; break;
|
||||
case TOK_EQ_EQ: name = "==="; break;
|
||||
case TOK_NE_NE: name = "!=="; break;
|
||||
case TOK_LT: name = "<"; break;
|
||||
case TOK_GT: name = ">"; break;
|
||||
case TOK_LE: name = "<="; break;
|
||||
case TOK_GE: name = ">="; break;
|
||||
case TOK_ASSIGN: name = "="; break;
|
||||
case TOK_POSTFIX_PLUS: name = "++ (postfix)"; break;
|
||||
case TOK_POSTFIX_MINUS: name = "-- (postfix)"; break;
|
||||
case TOK_MINUS_MINUS: name = "--"; break;
|
||||
case TOK_PLUS_PLUS: name = "++"; break;
|
||||
case TOK_LOGICAL_AND: name = "&&"; break;
|
||||
case TOK_LOGICAL_OR: name = "||"; break;
|
||||
case TOK_KEYWORD_TYPEOF: name = "typeof"; break;
|
||||
case TOK_PLUS_ASSIGN: name = "+="; break;
|
||||
case TOK_MINUS_ASSIGN: name = "-="; break;
|
||||
case TOK_MUL_ASSIGN: name = "*="; break;
|
||||
case TOK_DIV_ASSIGN: name = "/="; break;
|
||||
case TOK_REM_ASSIGN: name = "%="; break;
|
||||
case TOK_XOR_ASSIGN: name = "^="; break;
|
||||
case TOK_AND_ASSIGN: name = "&="; break;
|
||||
case TOK_OR_ASSIGN: name = "|="; break;
|
||||
case TOK_LSHIFT_ASSIGN: name = "<<="; break;
|
||||
case TOK_RSHIFT_ASSIGN: name = ">>="; break;
|
||||
case TOK_URSHIFT_ASSIGN: name = ">>>="; break;
|
||||
}
|
||||
/* clang-format on */
|
||||
print_cb(print_ctx, "%s\t%s", buf, name);
|
||||
i++;
|
||||
break;
|
||||
}
|
||||
case OP_BCODE_HEADER: {
|
||||
size_t start = 0;
|
||||
mjs_header_item_t map_offset = 0, total_size = 0;
|
||||
start = i;
|
||||
memcpy(&total_size, &code[i + 1], sizeof(total_size));
|
||||
memcpy(
|
||||
&map_offset,
|
||||
&code[i + 1 + MJS_HDR_ITEM_MAP_OFFSET * sizeof(total_size)],
|
||||
sizeof(map_offset));
|
||||
i += sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT;
|
||||
print_cb(
|
||||
print_ctx,
|
||||
"%s\t[%s] end:%lu map_offset: %lu",
|
||||
buf,
|
||||
&code[i + 1],
|
||||
(unsigned long)start + total_size,
|
||||
(unsigned long)start + map_offset);
|
||||
i += strlen((char*)(code + i + 1)) + 1;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
print_cb(print_ctx, "%s", buf);
|
||||
break;
|
||||
}
|
||||
return i - start_i;
|
||||
}
|
||||
|
||||
void mjs_disasm(const uint8_t* code, size_t len, MjsPrintCallback print_cb, void* print_ctx) {
|
||||
size_t i, start = 0;
|
||||
mjs_header_item_t map_offset = 0, total_size = 0;
|
||||
|
||||
for(i = 0; i < len; i++) {
|
||||
size_t delta = mjs_disasm_single(code, i, print_cb, print_ctx);
|
||||
if(code[i] == OP_BCODE_HEADER) {
|
||||
start = i;
|
||||
memcpy(&total_size, &code[i + 1], sizeof(total_size));
|
||||
memcpy(
|
||||
&map_offset,
|
||||
&code[i + 1 + MJS_HDR_ITEM_MAP_OFFSET * sizeof(total_size)],
|
||||
sizeof(map_offset));
|
||||
}
|
||||
|
||||
i += delta;
|
||||
|
||||
if(map_offset > 0 && i == start + map_offset) {
|
||||
i = start + total_size - 1;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void mjs_disasm_all(struct mjs* mjs, MjsPrintCallback print_cb, void* print_ctx) {
|
||||
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
||||
for(int i = 0; i < parts_cnt; i++) {
|
||||
struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i);
|
||||
mjs_disasm((uint8_t*)bp->data.p, bp->data.len, print_cb, print_ctx);
|
||||
}
|
||||
}
|
||||
|
||||
static void mjs_dump_obj_stack(
|
||||
struct mjs* mjs,
|
||||
const char* name,
|
||||
const struct mbuf* m,
|
||||
MjsPrintCallback print_cb,
|
||||
void* print_ctx) {
|
||||
char buf[50];
|
||||
size_t i, n;
|
||||
n = mjs_stack_size(m);
|
||||
print_cb(print_ctx, "%12s (%d elems): ", name, (int)n);
|
||||
for(i = 0; i < n; i++) {
|
||||
mjs_sprintf(((mjs_val_t*)m->buf)[i], mjs, buf, sizeof(buf));
|
||||
print_cb(print_ctx, "%34s", buf);
|
||||
}
|
||||
}
|
||||
|
||||
void mjs_dump(struct mjs* mjs, int do_disasm, MjsPrintCallback print_cb, void* print_ctx) {
|
||||
print_cb(print_ctx, "------- MJS VM DUMP BEGIN");
|
||||
mjs_dump_obj_stack(mjs, "DATA_STACK", &mjs->stack, print_cb, print_ctx);
|
||||
mjs_dump_obj_stack(mjs, "CALL_STACK", &mjs->call_stack, print_cb, print_ctx);
|
||||
mjs_dump_obj_stack(mjs, "SCOPES", &mjs->scopes, print_cb, print_ctx);
|
||||
mjs_dump_obj_stack(mjs, "LOOP_OFFSETS", &mjs->loop_addresses, print_cb, print_ctx);
|
||||
mjs_dump_obj_stack(mjs, "ARG_STACK", &mjs->arg_stack, print_cb, print_ctx);
|
||||
if(do_disasm) {
|
||||
int parts_cnt = mjs_bcode_parts_cnt(mjs);
|
||||
int i;
|
||||
print_cb(print_ctx, "%23s", "CODE:");
|
||||
for(i = 0; i < parts_cnt; i++) {
|
||||
struct mjs_bcode_part* bp = mjs_bcode_part_get(mjs, i);
|
||||
mjs_disasm((uint8_t*)bp->data.p, bp->data.len, print_cb, print_ctx);
|
||||
}
|
||||
}
|
||||
print_cb(print_ctx, "------- MJS VM DUMP END");
|
||||
}
|
||||
|
||||
MJS_PRIVATE int mjs_check_arg(
|
||||
struct mjs* mjs,
|
||||
int arg_num,
|
||||
const char* arg_name,
|
||||
enum mjs_type expected_type,
|
||||
mjs_val_t* parg) {
|
||||
mjs_val_t arg = MJS_UNDEFINED;
|
||||
enum mjs_type actual_type;
|
||||
|
||||
if(arg_num >= 0) {
|
||||
int nargs = mjs_nargs(mjs);
|
||||
if(nargs < arg_num + 1) {
|
||||
mjs_prepend_errorf(mjs, MJS_TYPE_ERROR, "missing argument %s", arg_name);
|
||||
return 0;
|
||||
}
|
||||
|
||||
arg = mjs_arg(mjs, arg_num);
|
||||
} else {
|
||||
/* use `this` */
|
||||
arg = mjs->vals.this_obj;
|
||||
}
|
||||
|
||||
actual_type = mjs_get_type(arg);
|
||||
if(actual_type != expected_type) {
|
||||
mjs_prepend_errorf(
|
||||
mjs,
|
||||
MJS_TYPE_ERROR,
|
||||
"%s should be a %s, %s given",
|
||||
arg_name,
|
||||
mjs_stringify_type(expected_type),
|
||||
mjs_stringify_type(actual_type));
|
||||
return 0;
|
||||
}
|
||||
|
||||
if(parg != NULL) {
|
||||
*parg = arg;
|
||||
}
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
MJS_PRIVATE int mjs_normalize_idx(int idx, int size) {
|
||||
if(idx < 0) {
|
||||
idx = size + idx;
|
||||
if(idx < 0) {
|
||||
idx = 0;
|
||||
}
|
||||
}
|
||||
if(idx > size) {
|
||||
idx = size;
|
||||
}
|
||||
return idx;
|
||||
}
|
||||
|
||||
MJS_PRIVATE const char* mjs_get_bcode_filename(struct mjs* mjs, struct mjs_bcode_part* bp) {
|
||||
(void)mjs;
|
||||
return bp->data.p + 1 /* OP_BCODE_HEADER */ + sizeof(mjs_header_item_t) * MJS_HDR_ITEMS_CNT;
|
||||
}
|
||||
|
||||
const char* mjs_get_bcode_filename_by_offset(struct mjs* mjs, int offset) {
|
||||
const char* ret = NULL;
|
||||
struct mjs_bcode_part* bp = mjs_bcode_part_get_by_offset(mjs, offset);
|
||||
if(bp != NULL) {
|
||||
ret = mjs_get_bcode_filename(mjs, bp);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mjs_get_lineno_by_offset(struct mjs* mjs, int offset) {
|
||||
size_t llen;
|
||||
uint64_t map_len;
|
||||
int prev_line_no, ret = 1;
|
||||
struct mjs_bcode_part* bp = mjs_bcode_part_get_by_offset(mjs, offset);
|
||||
uint8_t *p, *pe;
|
||||
if(bp != NULL) {
|
||||
mjs_header_item_t map_offset, bcode_offset;
|
||||
memcpy(
|
||||
&map_offset,
|
||||
bp->data.p + 1 /* OP_BCODE_HEADER */ +
|
||||
sizeof(mjs_header_item_t) * MJS_HDR_ITEM_MAP_OFFSET,
|
||||
sizeof(map_offset));
|
||||
|
||||
memcpy(
|
||||
&bcode_offset,
|
||||
bp->data.p + 1 /* OP_BCODE_HEADER */ +
|
||||
sizeof(mjs_header_item_t) * MJS_HDR_ITEM_BCODE_OFFSET,
|
||||
sizeof(bcode_offset));
|
||||
|
||||
offset -= (1 /* OP_BCODE_HEADER */ + bcode_offset) + bp->start_idx;
|
||||
|
||||
/* get pointer to the length of the map followed by the map itself */
|
||||
p = (uint8_t*)bp->data.p + 1 /* OP_BCODE_HEADER */ + map_offset;
|
||||
|
||||
cs_varint_decode(p, ~0, &map_len, &llen);
|
||||
p += llen;
|
||||
pe = p + map_len;
|
||||
|
||||
prev_line_no = 1;
|
||||
while(p < pe) {
|
||||
uint64_t cur_offset, line_no;
|
||||
cs_varint_decode(p, ~0, &cur_offset, &llen);
|
||||
p += llen;
|
||||
cs_varint_decode(p, ~0, &line_no, &llen);
|
||||
p += llen;
|
||||
|
||||
if(cur_offset >= (uint64_t)offset) {
|
||||
ret = prev_line_no;
|
||||
break;
|
||||
}
|
||||
prev_line_no = line_no;
|
||||
}
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num) {
|
||||
int ret = -1;
|
||||
if(cf_num == 0) {
|
||||
/* Return current bcode offset */
|
||||
ret = mjs->cur_bcode_offset;
|
||||
} else if(
|
||||
cf_num > 0 &&
|
||||
mjs->call_stack.len >= sizeof(mjs_val_t) * CALL_STACK_FRAME_ITEMS_CNT * cf_num) {
|
||||
/* Get offset from the call_stack */
|
||||
int pos = CALL_STACK_FRAME_ITEM_RETURN_ADDR + CALL_STACK_FRAME_ITEMS_CNT * (cf_num - 1);
|
||||
mjs_val_t val = *vptr(&mjs->call_stack, -1 - pos);
|
||||
ret = mjs_get_int(mjs, val);
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
mjs_err_t mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free) {
|
||||
mjs_err_t ret = MJS_OK;
|
||||
|
||||
*p = NULL;
|
||||
*sizep = 0;
|
||||
*need_free = 0;
|
||||
|
||||
if(mjs_is_string(*v)) {
|
||||
*p = (char*)mjs_get_string(mjs, v, sizep);
|
||||
} else if(mjs_is_number(*v)) {
|
||||
char buf[50] = "";
|
||||
struct json_out out = JSON_OUT_BUF(buf, sizeof(buf));
|
||||
mjs_jprintf(*v, mjs, &out);
|
||||
*sizep = strlen(buf);
|
||||
*p = malloc(*sizep + 1);
|
||||
if(*p == NULL) {
|
||||
ret = MJS_OUT_OF_MEMORY;
|
||||
goto clean;
|
||||
}
|
||||
memmove(*p, buf, *sizep + 1);
|
||||
*need_free = 1;
|
||||
} else if(mjs_is_boolean(*v)) {
|
||||
if(mjs_get_bool(mjs, *v)) {
|
||||
*p = "true";
|
||||
*sizep = 4;
|
||||
} else {
|
||||
*p = "false";
|
||||
*sizep = 5;
|
||||
}
|
||||
} else if(mjs_is_undefined(*v)) {
|
||||
*p = "undefined";
|
||||
*sizep = 9;
|
||||
} else if(mjs_is_null(*v)) {
|
||||
*p = "null";
|
||||
*sizep = 4;
|
||||
} else if(mjs_is_object(*v)) {
|
||||
ret = MJS_TYPE_ERROR;
|
||||
mjs_set_errorf(mjs, ret, "conversion from object to string is not supported");
|
||||
} else if(mjs_is_foreign(*v)) {
|
||||
*p = "TODO_foreign";
|
||||
*sizep = 12;
|
||||
} else if(mjs_is_typed_array(*v)) {
|
||||
*p = "TODO_typed_array";
|
||||
*sizep = 16;
|
||||
} else {
|
||||
ret = MJS_TYPE_ERROR;
|
||||
mjs_set_errorf(mjs, ret, "unknown type to convert to string");
|
||||
}
|
||||
|
||||
clean:
|
||||
return ret;
|
||||
}
|
||||
|
||||
mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v) {
|
||||
size_t len;
|
||||
int is_truthy;
|
||||
|
||||
is_truthy = ((mjs_is_boolean(v) && mjs_get_bool(mjs, v)) ||
|
||||
(mjs_is_number(v) && mjs_get_double(mjs, v) != (double)0.0) ||
|
||||
(mjs_is_string(v) && mjs_get_string(mjs, &v, &len) && len > 0) ||
|
||||
(mjs_is_function(v)) || (mjs_is_foreign(v)) || (mjs_is_object(v))) &&
|
||||
v != MJS_TAG_NAN;
|
||||
|
||||
return mjs_mk_boolean(mjs, is_truthy);
|
||||
}
|
||||
|
||||
int mjs_is_truthy(struct mjs* mjs, mjs_val_t v) {
|
||||
return mjs_get_bool(mjs, mjs_to_boolean_v(mjs, v));
|
||||
}
|
||||
57
lib/mjs/mjs_util.h
Normal file
57
lib/mjs/mjs_util.h
Normal file
@@ -0,0 +1,57 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_UTIL_H_
|
||||
#define MJS_UTIL_H_
|
||||
|
||||
#include "common/frozen/frozen.h"
|
||||
#include "mjs_core.h"
|
||||
#include "mjs_util_public.h"
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
struct mjs_bcode_part;
|
||||
|
||||
#if MJS_ENABLE_DEBUG
|
||||
MJS_PRIVATE const char* opcodetostr(uint8_t opcode);
|
||||
MJS_PRIVATE size_t mjs_disasm_single(const uint8_t* code, size_t i);
|
||||
#endif
|
||||
|
||||
MJS_PRIVATE const char* mjs_stringify_type(enum mjs_type t);
|
||||
|
||||
/*
|
||||
* Checks that the given argument is provided, and checks its type. If check
|
||||
* fails, sets error in the mjs context, and returns 0; otherwise returns 1.
|
||||
*
|
||||
* If `arg_num` >= 0, checks argument; otherwise (`arg_num` is negative) checks
|
||||
* `this`. `arg_name` is used for the error message only. If `parg` is not
|
||||
* NULL, writes resulting value at this location in case of success.
|
||||
*/
|
||||
MJS_PRIVATE int mjs_check_arg(
|
||||
struct mjs* mjs,
|
||||
int arg_num,
|
||||
const char* arg_name,
|
||||
enum mjs_type expected_type,
|
||||
mjs_val_t* parg);
|
||||
|
||||
/*
|
||||
* mjs_normalize_idx takes and index in the string and the string size, and
|
||||
* returns the index which is >= 0 and <= size. Negative index is interpreted
|
||||
* as size + index.
|
||||
*/
|
||||
MJS_PRIVATE int mjs_normalize_idx(int idx, int size);
|
||||
|
||||
MJS_PRIVATE const char* mjs_get_bcode_filename(struct mjs* mjs, struct mjs_bcode_part* bp);
|
||||
|
||||
/* Print JS value `v` to the JSON stream `out`. */
|
||||
void mjs_jprintf(mjs_val_t v, struct mjs* mjs, struct json_out* out);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_UTIL_H_ */
|
||||
67
lib/mjs/mjs_util_public.h
Normal file
67
lib/mjs/mjs_util_public.h
Normal file
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (c) 2016 Cesanta Software Limited
|
||||
* All rights reserved
|
||||
*/
|
||||
|
||||
#ifndef MJS_UTIL_PUBLIC_H_
|
||||
#define MJS_UTIL_PUBLIC_H_
|
||||
|
||||
#include "mjs_core_public.h"
|
||||
#include <stdio.h>
|
||||
|
||||
#if defined(__cplusplus)
|
||||
extern "C" {
|
||||
#endif /* __cplusplus */
|
||||
|
||||
typedef void (*MjsPrintCallback)(void* ctx, const char* format, ...);
|
||||
|
||||
const char* mjs_typeof(mjs_val_t v);
|
||||
|
||||
void mjs_fprintf(mjs_val_t v, struct mjs* mjs, FILE* fp);
|
||||
void mjs_sprintf(mjs_val_t v, struct mjs* mjs, char* buf, size_t buflen);
|
||||
|
||||
void mjs_disasm_all(struct mjs* mjs, MjsPrintCallback print_cb, void* print_ctx);
|
||||
void mjs_dump(struct mjs* mjs, int do_disasm, MjsPrintCallback print_cb, void* print_ctx);
|
||||
|
||||
/*
|
||||
* Returns the filename corresponding to the given bcode offset.
|
||||
*/
|
||||
const char* mjs_get_bcode_filename_by_offset(struct mjs* mjs, int offset);
|
||||
|
||||
/*
|
||||
* Returns the line number corresponding to the given bcode offset.
|
||||
*/
|
||||
int mjs_get_lineno_by_offset(struct mjs* mjs, int offset);
|
||||
|
||||
/*
|
||||
* Returns bcode offset of the corresponding call frame cf_num, where 0 means
|
||||
* the currently executing function, 1 means the first return address, etc.
|
||||
*
|
||||
* If given cf_num is too large, -1 is returned.
|
||||
*/
|
||||
int mjs_get_offset_by_call_frame_num(struct mjs* mjs, int cf_num);
|
||||
|
||||
/*
|
||||
* Tries to convert `mjs_val_t` to a string, returns MJS_OK if successful.
|
||||
* String is returned as a pair of pointers: `char **p, size_t *sizep`.
|
||||
*
|
||||
* Caller must also provide a non-null `need_free`, and if it is non-zero,
|
||||
* then the string `*p` should be freed by the caller.
|
||||
*
|
||||
* MJS does not support `toString()` and `valueOf()`, so, passing an object
|
||||
* always results in `MJS_TYPE_ERROR`.
|
||||
*/
|
||||
mjs_err_t mjs_to_string(struct mjs* mjs, mjs_val_t* v, char** p, size_t* sizep, int* need_free);
|
||||
|
||||
/*
|
||||
* Converts value to boolean as in the expression `if (v)`.
|
||||
*/
|
||||
mjs_val_t mjs_to_boolean_v(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
int mjs_is_truthy(struct mjs* mjs, mjs_val_t v);
|
||||
|
||||
#if defined(__cplusplus)
|
||||
}
|
||||
#endif /* __cplusplus */
|
||||
|
||||
#endif /* MJS_UTIL_PUBLIC_H_ */
|
||||
Reference in New Issue
Block a user