diff --git a/Makefile b/Makefile
index b7ffd9c4..74ad7c78 100644
--- a/Makefile
+++ b/Makefile
@@ -1,3 +1,4 @@
+PKG_CONFIG = PKG_CONFIG_ALLOW_SYSTEM_CFLAGS=1 pkg-config
COMPILER_DIR = src/compiler/
RUNTIME_DIR = src/runtime/
SHARED_DIR = src/shared/
@@ -5,6 +6,9 @@ UTILS_DIR = src/utils/
OPT_DIR = src/optionals/
GRAVITY_SRC = src/cli/gravity.c
+DOCKER_DIR = docker
+DOCKERFILE_DIRS = $(notdir $(wildcard $(DOCKER_DIR)/*))
+
CC ?= gcc
SRC = $(wildcard $(COMPILER_DIR)*.c) \
$(wildcard $(RUNTIME_DIR)/*.c) \
@@ -12,6 +16,15 @@ SRC = $(wildcard $(COMPILER_DIR)*.c) \
$(wildcard $(UTILS_DIR)/*.c) \
$(wildcard $(OPT_DIR)/*.c)
+OPENSSL_ENABLED := true
+OPENSSL_INSTALLED = $(shell $(PKG_CONFIG) --exists openssl && echo true || echo false)
+$(info OPENSSL_ENABLED: $(OPENSSL_ENABLED))
+$(info OPENSSL_INSTALLED: $(OPENSSL_INSTALLED))
+ifeq ($(OPENSSL_ENABLED),true)
+ ifeq ($(OPENSSL_INSTALLED),false)
+ $(error OpenSSL is enabled, but not installed)
+ endif
+endif
INCLUDE = -I$(COMPILER_DIR) -I$(RUNTIME_DIR) -I$(SHARED_DIR) -I$(UTILS_DIR) -I$(OPT_DIR)
CFLAGS = $(INCLUDE) -std=gnu99 -fgnu89-inline -fPIC -DBUILD_GRAVITY_API
OBJ = $(SRC:.c=.o)
@@ -26,6 +39,10 @@ else
# MacOS
LIBTARGET = libgravity.dylib
LDFLAGS = -lm
+ ifeq ($(OPENSSL_ENABLED),true)
+ CFLAGS += $(shell $(PKG_CONFIG) --cflags openssl) -DGRAVITY_OPENSSL_ENABLED
+ LDFLAGS += $(shell $(PKG_CONFIG) --libs openssl)
+ endif
else ifeq ($(UNAME_S),OpenBSD)
# OpenBSD
CFLAGS += -D_WITH_GETLINE
@@ -46,6 +63,10 @@ else
# Linux
LIBTARGET = libgravity.so
LDFLAGS = -lm -lrt
+ ifeq ($(OPENSSL_ENABLED),true)
+ CFLAGS += $(shell $(PKG_CONFIG) --cflags openssl) -DGRAVITY_OPENSSL_ENABLED
+ LDFLAGS += $(shell $(PKG_CONFIG) --libs openssl)
+ endif
endif
endif
@@ -67,3 +88,18 @@ lib: gravity
clean:
rm -f $(OBJ) gravity libgravity.so gravity.dll
+
+docker.%.base:
+ docker build --no-cache -t gravity-$*-base -f $(DOCKER_DIR)/$*/base/Dockerfile .
+
+docker.build:
+ docker-compose build
+
+docker.up: docker.$(DOCKERFILE_DIRS).base docker.build
+ $(info building gravity on: $(DOCKERFILE_DIRS))
+ docker-compose up
+
+docker.down:
+ docker-compose down
+ docker-compose stop
+ docker-compose rm
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
new file mode 100644
index 00000000..209988bc
--- /dev/null
+++ b/docker-compose.yaml
@@ -0,0 +1,6 @@
+version: '3'
+services:
+ ubuntu_openssl_enabled:
+ build: docker/ubuntu/openssl_enabled
+ ubuntu_openssl_disabled:
+ build: docker/ubuntu/openssl_disabled
\ No newline at end of file
diff --git a/docker/ubuntu/base/Dockerfile b/docker/ubuntu/base/Dockerfile
new file mode 100644
index 00000000..9ca45855
--- /dev/null
+++ b/docker/ubuntu/base/Dockerfile
@@ -0,0 +1,8 @@
+FROM ubuntu:latest
+
+RUN apt update
+RUN apt install -y make pkg-config
+
+COPY . /app
+WORKDIR /app
+ENV PATH="/app:${PATH}"
\ No newline at end of file
diff --git a/docker/ubuntu/openssl_disabled/Dockerfile b/docker/ubuntu/openssl_disabled/Dockerfile
new file mode 100644
index 00000000..612d73db
--- /dev/null
+++ b/docker/ubuntu/openssl_disabled/Dockerfile
@@ -0,0 +1,6 @@
+FROM gravity-ubuntu-base
+
+ENV OPENSSL_ENABLED=false
+
+ENTRYPOINT [ "/bin/bash" ]
+CMD [ "-c", "./examples/http/run.sh" ]
\ No newline at end of file
diff --git a/docker/ubuntu/openssl_enabled/Dockerfile b/docker/ubuntu/openssl_enabled/Dockerfile
new file mode 100644
index 00000000..39e316f2
--- /dev/null
+++ b/docker/ubuntu/openssl_enabled/Dockerfile
@@ -0,0 +1,8 @@
+FROM gravity-ubuntu-base
+
+RUN apt install -y libssl-dev
+
+ENV OPENSSL_ENABLED=true
+
+ENTRYPOINT [ "/bin/bash" ]
+CMD [ "-c", "./examples/http/run.sh" ]
\ No newline at end of file
diff --git a/examples/http/main.gravity b/examples/http/main.gravity
new file mode 100644
index 00000000..621c8777
--- /dev/null
+++ b/examples/http/main.gravity
@@ -0,0 +1,40 @@
+func main() {
+ var get_tests = [
+ [
+ "host": "github.com"
+ ],
+ [
+ "host": "https://reddit.com"
+ ],
+ [
+ "host": "https://api.github.com",
+ "path": "/users/jamesalbert"
+ ],
+ [
+ "host": "https://httpbin.org",
+ "path": "/get"
+ ]
+ ]
+
+ get_tests.loop(func (request) {
+ var resp = Http.get(request)
+ System.print("=== " + resp.Hostname)
+ System.print(resp)
+ })
+
+ var post_tests = [
+ [
+ "host": "https://httpbin.org/post",
+ "path": "/post",
+ "data": [
+ "pet": "cat"
+ ]
+ ]
+ ]
+
+ post_tests.loop(func (request) {
+ var resp = Http.post(request)
+ System.print("=== " + resp.Hostname)
+ System.print(resp)
+ })
+}
diff --git a/examples/http/run.sh b/examples/http/run.sh
new file mode 100755
index 00000000..6a334db1
--- /dev/null
+++ b/examples/http/run.sh
@@ -0,0 +1,9 @@
+#!/usr/bin/env bash
+
+# make gravity
+make clean gravity OPENSSL_ENABLED=${OPENSSL_ENABLED}
+
+# run example
+rm -f examples/http/main.json
+gravity -c examples/http/main.gravity -o examples/http/main.json
+gravity -x examples/http/main.json
\ No newline at end of file
diff --git a/gravity_visualstudio/exports.def b/gravity_visualstudio/exports.def
index ce34b62f..db2b6e43 100644
--- a/gravity_visualstudio/exports.def
+++ b/gravity_visualstudio/exports.def
@@ -174,6 +174,7 @@ EXPORTS
gravity_list_from_array
gravity_list_free
gravity_list_append_list
+ gravity_list_append_value
gravity_list_blacken
gravity_list_size
diff --git a/gravity_visualstudio/gravity.vcxproj b/gravity_visualstudio/gravity.vcxproj
index 5665e388..49e91b79 100644
--- a/gravity_visualstudio/gravity.vcxproj
+++ b/gravity_visualstudio/gravity.vcxproj
@@ -33,6 +33,7 @@
+
@@ -63,6 +64,7 @@
+
diff --git a/gravity_visualstudio/gravity.vcxproj.filters b/gravity_visualstudio/gravity.vcxproj.filters
index 9b16f52e..b0348786 100644
--- a/gravity_visualstudio/gravity.vcxproj.filters
+++ b/gravity_visualstudio/gravity.vcxproj.filters
@@ -120,6 +120,9 @@
src\opt
+
+ src\opt
+
src\opt
@@ -185,6 +188,9 @@
src\opt
+
+ src\opt
+
src\compiler
diff --git a/src/compiler/gravity_parser.c b/src/compiler/gravity_parser.c
index 0e11f0a9..0b5632ba 100644
--- a/src/compiler/gravity_parser.c
+++ b/src/compiler/gravity_parser.c
@@ -2516,6 +2516,11 @@ static void parser_register_optional_classes (gravity_parser_t *parser) {
gnode_array_push(decls, decl2);
#endif
+ #ifdef GRAVITY_INCLUDE_HTTP
+ gnode_t *decl3 = gnode_variable_create(NO_TOKEN, string_dup(GRAVITY_HTTP_NAME()), NULL, NULL, LAST_DECLARATION(), NULL);
+ gnode_array_push(decls, decl3);
+ #endif
+
// check if optional classes callback is registered
if (parser->delegate && parser->delegate->optional_classes) {
const char **list = parser->delegate->optional_classes();
diff --git a/src/optionals/gravity_http.c b/src/optionals/gravity_http.c
new file mode 100644
index 00000000..060ffb3d
--- /dev/null
+++ b/src/optionals/gravity_http.c
@@ -0,0 +1,501 @@
+//
+// gravity_http.c
+// gravity
+//
+// Created by Marco Bambini on 14/08/2017.
+// Copyright © 2017 CreoLabs. All rights reserved.
+//
+// Based on: https://www.w3schools.com/jsref/jsref_obj_http.asp
+
+/* Generic */
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+/* Network */
+#include
+#include
+#include "gravity_vm.h"
+#include "gravity_http.h"
+#include "gravity_core.h"
+#include "gravity_hash.h"
+#include "gravity_utils.h"
+#include "gravity_macros.h"
+#include "gravity_vmmacros.h"
+
+#ifdef GRAVITY_OPENSSL_ENABLED
+#include
+#endif
+
+#define HTTP_CLASS_NAME "Http"
+#define HTTP_MIN_RESPONSE_BODY_SIZE 1024
+#define HTTP_MAX_HEADERS_SIZE 60
+#define HTTP_MAX_HOSTNAME_SIZE 60
+#define HTTP_MAX_SCHEME_SIZE 25
+#define HTTP_MAX_PORT_SIZE 6
+#define HTTP_MAX_REQUEST_BODY_SIZE 1024 * 1024
+#define HTTP_MAX_BUF_SIZE 1024 * 1024
+
+
+static gravity_class_t *gravity_class_http = NULL;
+static uint32_t refcount = 0;
+
+// MARK: - Implementation -
+
+static Request *http_request_new(gravity_vm *vm, char *hostname, char *path, int port, char *method, gravity_map_t *data) {
+ Request *req = mem_alloc(vm, sizeof(Request));
+ if (strstr(hostname, "://") != NULL) {
+ char *bk = hostname;
+ char *scheme_prefix = strtok_r(hostname, "://", &bk);
+ req->scheme = mem_alloc(NULL, strlen(scheme_prefix) + 3 * sizeof(char));
+ sprintf(req->scheme, "%s://", scheme_prefix);
+ req->hostname = strtok_r(NULL, "//", &bk);
+ } else {
+ req->scheme = "";
+ req->hostname = hostname;
+ }
+ req->path = path;
+ req->port = port;
+ req->method = method;
+ req->data = data;
+ bool trying_ssl = strstr(req->scheme, "https://") != NULL || req->port == 443;
+ #ifdef GRAVITY_OPENSSL_ENABLED
+ req->use_ssl = trying_ssl;
+ #else
+ if (trying_ssl) {
+ fprintf(stderr, "Trying to use ssl when openssl is not installed\n");
+ req->error = 1;
+ return req;
+ }
+ req->use_ssl = false;
+ #endif
+ req->body = mem_alloc(NULL, HTTP_MAX_REQUEST_BODY_SIZE * sizeof(char));
+ req->error = 0;
+ return req;
+}
+
+static void http_request_free(Request *req) {
+ if (req != NULL)
+ mem_free(req);
+}
+
+#ifdef GRAVITY_OPENSSL_ENABLED
+static void http_request_ssl_fd(Request *req) {
+ int fd;
+ struct hostent *host;
+ struct sockaddr_in addr;
+
+ if ((host = gethostbyname(req->hostname)) == NULL) {
+ perror(req->hostname);
+ abort();
+ }
+
+ fd = socket(PF_INET, SOCK_STREAM, 0);
+ bzero(&addr, sizeof(addr));
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(req->port);
+ addr.sin_addr.s_addr = *(long*)(host->h_addr);
+
+ if (connect(fd, (struct sockaddr*)&addr, sizeof(addr)) != 0) {
+ close(fd);
+ perror(req->hostname);
+ abort();
+ }
+
+ req->fd = fd;
+}
+
+static void http_request_ssl_init_ctx(Request *req) {
+ const SSL_METHOD *method;
+ OpenSSL_add_all_algorithms();
+ SSL_load_error_strings();
+ method = SSLv23_client_method();
+ if ((req->ctx = SSL_CTX_new(method)) == NULL) {
+ perror("ssl_ctx");
+ abort();
+ }
+}
+
+static void http_request_connect_ssl(Request *req) {
+ SSL_library_init();
+ http_request_ssl_fd(req);
+ http_request_ssl_init_ctx(req);
+ if ((req->conn = SSL_new(req->ctx)) == NULL) {
+ perror("Unable to create ssl connection");
+ abort();
+ }
+
+ SSL_set_fd(req->conn, req->fd);
+ if (SSL_connect(req->conn) != 1) {
+ perror("connect_ssl");
+ }
+}
+#endif
+
+// Get host information (used to http_request_connect_tcp)
+static struct addrinfo *http_request_host_info(char *host, int port) {
+ int r;
+ struct addrinfo hints, *getaddrinfo_res;
+ memset(&hints, 0, sizeof(hints));
+ hints.ai_family = AF_INET;
+ hints.ai_socktype = SOCK_STREAM;
+ int length = (int)floor(log10((double)port)) + 1;
+ char port_string[length];
+ sprintf(port_string, "%d", port);
+ if ((r = getaddrinfo(host, port_string, &hints, &getaddrinfo_res))) {
+ fprintf(stderr, "[http_request_host_info:getaddrinfo] %s\n", gai_strerror(r));
+ perror("getaddrinfo");
+ return NULL;
+ }
+ return getaddrinfo_res;
+}
+
+static void http_request_connect_tcp(Request *req) {
+ int fd;
+ struct addrinfo *info = http_request_host_info(req->hostname, req->port);
+ if (info == NULL) {
+ perror("Unable to get host information");
+ return;
+ }
+
+ for (;info != NULL; info = info->ai_next) {
+ if ((fd = socket(info->ai_family,
+ info->ai_socktype,
+ info->ai_protocol)) < 0) {
+ perror("Unable to obtain file descriptor");
+ continue;
+ }
+
+ // HTTP
+ if (connect(fd, info->ai_addr, info->ai_addrlen) < 0) {
+ close(fd);
+ perror("Unable to connect");
+ continue;
+ }
+
+ freeaddrinfo(info);
+ req->fd = fd;
+ return;
+ }
+}
+
+// Establish connection with host
+static Response *http_request_connect(gravity_vm *vm, char *hostname, char *path, int port, char *method, gravity_map_t *data) {
+ Request *req = http_request_new(NULL, hostname, path, port, method, data);
+ if (req->error != 0)
+ return http_response_new(vm, req);
+ #ifdef GRAVITY_OPENSSL_ENABLED
+ if (req->use_ssl)
+ http_request_connect_ssl(req);
+ else
+ #else
+ if (true)
+ #endif
+ http_request_connect_tcp(req);
+
+ if (req->fd == -1)
+ perror("Failed to connect");
+
+ http_request_send(vm, req);
+ Response *resp = http_response_receive(vm, req);
+ http_request_free(req);
+ return resp;
+}
+
+static bool http_request_validate_options(gravity_vm *vm, gravity_map_t **options, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+ if(!VALUE_ISA_MAP(args[1])) {
+ RETURN_ERROR("Data must be a map.");
+ }
+ *options = VALUE_AS_MAP(args[1]);
+ return true;
+}
+
+static bool http_request_validate_args(gravity_vm *vm, gravity_map_t **options, uint32_t rindex) {
+ // validate host
+ // - required
+ // - must be a string
+ gravity_value_t *host = gravity_hash_lookup_cstring((*options)->hash, "host");
+ if (host == NULL)
+ RETURN_ERROR("Host must be specified.");
+ if (!VALUE_ISA_STRING(((gravity_value_t)(*host))))
+ RETURN_ERROR("Host must be a string.");
+
+ // validate path
+ // - not required, defaults to '/'
+ // - must be a string
+ gravity_value_t *path = gravity_hash_lookup_cstring((*options)->hash, "path");
+ if (path == NULL)
+ gravity_map_insert(vm, *options, gravity_string_to_value(vm, "path", 4), gravity_string_to_value(vm, "/", 1));
+ else if (!VALUE_ISA_STRING(((gravity_value_t)(*path))))
+ RETURN_ERROR("Path must be a string.");
+
+ // validate port
+ // - not required, defaults to "80"
+ // - must be a string
+ gravity_value_t *port = gravity_hash_lookup_cstring((*options)->hash, "port");
+ if (port == NULL)
+ if (string_starts_with(VALUE_AS_CSTRING(*host), "https://"))
+ gravity_map_insert(vm, *options, VALUE_FROM_CSTRING(vm, "port"), VALUE_FROM_INT(443));
+ else
+ gravity_map_insert(vm, *options, VALUE_FROM_CSTRING(vm, "port"), VALUE_FROM_INT(80));
+ else if (!VALUE_ISA_INT(((gravity_value_t)(*port))))
+ RETURN_ERROR("Port must be an integer.");
+
+ // validate method
+ // - not required, defaults to "GET"
+ // - must be a string
+ gravity_value_t *method = gravity_hash_lookup_cstring((*options)->hash, "method");
+ if (method == NULL)
+ gravity_map_insert(vm, *options, gravity_string_to_value(vm, "method", 6), gravity_string_to_value(vm, "GET", 3));
+ else if (!VALUE_ISA_STRING(((gravity_value_t)(*method))))
+ RETURN_ERROR("Method must be a string.");
+
+ // validate data
+ // - not required, defaults to empty map
+ // - must be a map
+ gravity_value_t *data = gravity_hash_lookup_cstring((*options)->hash, "data");
+ if (data == NULL)
+ gravity_map_insert(vm, *options, gravity_string_to_value(vm, "data", 4), gravity_map_to_value(vm, gravity_map_new(vm, 32)));
+ else if (!VALUE_ISA_MAP(((gravity_value_t)(*data))))
+ RETURN_ERROR("Data must be a map.");
+
+ return true;
+}
+
+static Response *http_response_new(gravity_vm *vm, Request *req) {
+ Response *resp = mem_alloc(vm, sizeof(Response));
+ resp->headers = mem_alloc(vm, 60 * sizeof(Header)); // max of 60 headers; 48 standard, 12 non-standard
+ resp->body = mem_alloc(vm, HTTP_MIN_RESPONSE_BODY_SIZE);
+ resp->hostname = mem_alloc(vm, strlen(req->hostname));
+ memcpy(resp->hostname, req->hostname, strlen(req->hostname) + 1);
+ resp->headercount = 0;
+ resp->error = req->error;
+ return resp;
+}
+
+static void http_response_free(Response *resp) {
+ if (resp != NULL && resp->headers != NULL)
+ mem_free(resp->headers);
+ if (resp != NULL)
+ mem_free(resp);
+}
+
+static void http_response_parse_header(Response *resp, char *line) {
+ char *bk = line;
+ resp->headers[resp->headercount].name = strtok_r(line, ": ", &bk);
+ resp->headers[resp->headercount++].value = strtok_r(NULL, "\r\n", &bk);
+}
+
+// Parses status_code and status_message; e.g. HTTP/1.0 403 Forbidden
+static void http_response_parse_status(Response *resp, char *line) {
+ char *bk = line;
+ strtok_r(line, " ", &bk);
+ resp->status_code = atoi(strtok_r(NULL, " ", &bk));
+ resp->status_message = strtok_r(NULL, "\r\n", &bk);
+}
+
+static int8_t http_response_parse_line(Response *resp, char *line) {
+ if (line == NULL)
+ return 1;
+
+ // get ready for body
+ if (string_starts_with(line, "\r"))
+ return 3;
+
+ // check for opening HTTP response
+ if (string_starts_with(line, "HTTP")) {
+ http_response_parse_status(resp, line);
+ return 0;
+ }
+
+ // check if header
+ if (strstr(line, ":") != NULL) {
+ http_response_parse_header(resp, line);
+ return 0;
+ }
+
+ return 2;
+}
+
+static void http_response_slurp_body(Response *resp, char **bk) {
+ char *token = NULL;
+ while ((token = strtok_r(NULL, "\n", bk)) != NULL)
+ strcat(resp->body, token);
+}
+
+static void http_response_parse(Response *resp, char *source) {
+ char *token = NULL;
+ char *bk = source;
+ token = strtok_r(source, "\n", &bk);
+ while (token) {
+ int8_t status = http_response_parse_line(resp, token);
+ if (status == 3)
+ http_response_slurp_body(resp, &bk);
+ token = strtok_r(NULL, "\n", &bk);
+ }
+}
+
+static ssize_t http_request_send(gravity_vm *vm, Request *req) {
+ sprintf(req->body, "%s %s HTTP/1.1\r\n", req->method, req->path);
+ strcat(req->body, "Host: ");
+ strcat(req->body, req->hostname);
+ strcat(req->body, "\r\n");
+ strcat(req->body, "User-Agent: Gravity\r\n");
+ bool posting_data = strcmp(req->method, "POST") == 0;
+ if (posting_data && req->data != NULL) {
+ char *json = gravity_map_to_string(vm, req->data);
+ char content_length_header[40];
+ sprintf(content_length_header, "Content-Length: %lu\r\n", strlen(json));
+ strcat(req->body, content_length_header);
+ strcat(req->body, "Content-Type: application/json\r\n");
+ strcat(req->body, "\r\n");
+ strcat(req->body, json);
+ } else {
+ strcat(req->body, "Accept: */*\r\n");
+ strcat(req->body, "Accept-Language: en-US,en;q=0.9\r\n");
+ }
+
+ strcat(req->body, "\r\n");
+ ssize_t bytes;
+ #ifdef GRAVITY_OPENSSL_ENABLED
+ if (req->use_ssl) {
+ bytes = SSL_write(req->conn, req->body, strlen(req->body));
+ } else {
+ #else
+ if (true) {
+ #endif
+ bytes = send(req->fd, req->body, strlen(req->body), 0);
+ }
+ if (req->body != NULL)
+ mem_free(req->body);
+ return bytes;
+}
+
+static Response *http_response_receive(gravity_vm *vm, Request *req) {
+ Response *resp = http_response_new(vm, req);
+ char *buf = mem_alloc(vm, HTTP_MAX_BUF_SIZE * sizeof(char));
+
+ #ifdef GRAVITY_OPENSSL_ENABLED
+ if (req->use_ssl) {
+ int bytes = SSL_read(req->conn, buf, HTTP_MAX_BUF_SIZE);
+ if (bytes <= 0) {
+ SSL_get_error(req->conn, bytes);
+ }
+ } else {
+ #else
+ if (true) {
+ #endif
+ while (recv(req->fd, buf, HTTP_MAX_BUF_SIZE, MSG_WAITALL) > 0);
+ }
+ close(req->fd);
+ #ifdef GRAVITY_OPENSSL_ENABLED
+ SSL_CTX_free(req->ctx);
+ #endif
+ http_response_parse(resp, buf);
+ mem_free(buf);
+ return resp;
+}
+
+static bool http_request (gravity_vm *vm, gravity_map_t *options, uint32_t rindex) {
+ bool valid_args = http_request_validate_args(vm, &options, rindex);
+ if (!valid_args)
+ return valid_args;
+
+ int fd;
+ char *hostname = VALUE_AS_CSTRING(*gravity_hash_lookup_cstring(options->hash, "host"));
+ char *path = VALUE_AS_CSTRING(*gravity_hash_lookup_cstring(options->hash, "path"));
+ int port = VALUE_AS_INT(*gravity_hash_lookup_cstring(options->hash, "port"));
+ char *method = VALUE_AS_CSTRING(*gravity_hash_lookup_cstring(options->hash, "method"));
+ gravity_map_t *data = VALUE_AS_MAP(*gravity_hash_lookup_cstring(options->hash, "data"));
+
+ Response *resp = http_request_connect(vm, hostname, path, port, method, data);
+
+ // build response map
+ gravity_map_t *response = gravity_map_new(vm, 32);
+ gravity_map_insert(vm, response, VALUE_FROM_CSTRING(vm, "Hostname"), VALUE_FROM_CSTRING(vm, resp->hostname));
+ if (resp->error != 0)
+ gravity_map_insert(vm, response, VALUE_FROM_CSTRING(vm, "Error"), VALUE_FROM_CSTRING(vm, "Failed to make request"));
+ else {
+ gravity_map_t *headers = gravity_map_new(vm, HTTP_MAX_HEADERS_SIZE);
+ for (int i = 0; i < resp->headercount; i++)
+ gravity_map_insert(vm, headers,
+ VALUE_FROM_CSTRING(vm, resp->headers[i].name),
+ VALUE_FROM_CSTRING(vm, resp->headers[i].value));
+ gravity_map_insert(vm, response, VALUE_FROM_CSTRING(vm, "Headers"), VALUE_FROM_OBJECT(headers));
+ gravity_map_insert(vm, response, VALUE_FROM_CSTRING(vm, "Body"), VALUE_FROM_CSTRING(vm, resp->body));
+ gravity_map_insert(vm, response, VALUE_FROM_CSTRING(vm, "StatusCode"), VALUE_FROM_INT(resp->status_code));
+ gravity_map_insert(vm, response, VALUE_FROM_CSTRING(vm, "StatusMessage"), VALUE_FROM_CSTRING(vm, resp->status_message));
+ }
+
+ // free allocated
+ http_response_free(resp);
+ RETURN_VALUE(VALUE_FROM_OBJECT(response), rindex);
+}
+
+static bool http_get (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+ #pragma unused(vm, nargs)
+ gravity_map_t *options;
+ bool valid_options = http_request_validate_options(vm, &options, args, nargs, rindex);
+ if (!valid_options)
+ return valid_options;
+ gravity_map_insert(vm, options,
+ gravity_string_to_value(vm, "method", strlen("method")),
+ gravity_string_to_value(vm, "GET", strlen("GET")));
+ return http_request(vm, options, rindex);
+}
+
+static bool http_post (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex) {
+ #pragma unused(vm, nargs)
+ gravity_map_t *options;
+ bool valid_options = http_request_validate_options(vm, &options, args, nargs, rindex);
+ if (!valid_options)
+ return valid_options;
+ gravity_map_insert(vm, options,
+ gravity_string_to_value(vm, "method", strlen("method")),
+ gravity_string_to_value(vm, "POST", strlen("POST")));
+ return http_request(vm, options, rindex);
+}
+
+// MARK: - Internals -
+
+static void create_optional_class (void) {
+ gravity_class_http = gravity_class_new_pair(NULL, HTTP_CLASS_NAME, NULL, 0, 0);
+ gravity_class_t *meta = gravity_class_get_meta(gravity_class_http);
+ gravity_class_bind(meta, "get", NEW_CLOSURE_VALUE(http_get));
+ gravity_class_bind(meta, "post", NEW_CLOSURE_VALUE(http_post));
+ gravity_closure_t *closure = NULL;
+ SETMETA_INITED(gravity_class_http);
+}
+
+// MARK: - Commons -
+
+bool gravity_ishttp_class (gravity_class_t *c) {
+ return (c == gravity_class_http);
+}
+
+const char *gravity_http_name (void) {
+ return HTTP_CLASS_NAME;
+}
+
+void gravity_http_register (gravity_vm *vm) {
+ if (!gravity_class_http) create_optional_class();
+ ++refcount;
+
+ if (!vm || gravity_vm_ismini(vm)) return;
+ gravity_vm_setvalue(vm, HTTP_CLASS_NAME, VALUE_FROM_OBJECT(gravity_class_http));
+}
+
+void gravity_http_free (void) {
+ if (!gravity_class_http) return;
+ if (--refcount) return;
+
+ gravity_class_t *meta = gravity_class_get_meta(gravity_class_http);
+ gravity_class_free_core(NULL, meta);
+ gravity_class_free_core(NULL, gravity_class_http);
+
+ gravity_class_http = NULL;
+}
+
diff --git a/src/optionals/gravity_http.h b/src/optionals/gravity_http.h
new file mode 100644
index 00000000..275d823c
--- /dev/null
+++ b/src/optionals/gravity_http.h
@@ -0,0 +1,71 @@
+//
+// gravity_http.h
+// gravity
+//
+// Created by Marco Bambini on 14/08/2017.
+// Copyright © 2017 CreoLabs. All rights reserved.
+//
+
+#ifndef __GRAVITY_HTTP__
+#define __GRAVITY_HTTP__
+
+#include "gravity_value.h"
+
+
+typedef struct {
+ const char *name; // name of header; e.g. Last-Modified
+ const char *value; // value of header
+} Header;
+
+typedef struct {
+ Header *headers;
+ char *body;
+ char *hostname;
+ int status_code;
+ char *status_message;
+ int headercount;
+ int error;
+} Response;
+
+typedef struct {
+ char *body;
+ char *scheme;
+ char *hostname;
+ char *path;
+ int port;
+ char *method;
+ gravity_map_t *data;
+ bool use_ssl;
+ int fd;
+ void *conn;
+ void *ctx;
+ int error;
+} Request;
+
+// http library
+static Request *http_request_new(gravity_vm *vm, char *hostname, char *path, int port, char *method, gravity_map_t *data);
+static void http_request_ssl_init_ctx(Request *req);
+static void http_request_connect_ssl(Request *req);
+static struct addrinfo *http_get_host_info(char *host, int port);
+static void http_request_connect_tcp(Request *req);
+static Response *http_request_connect(gravity_vm *vm, char *hostname, char *path, int port, char *method, gravity_map_t *data);
+static Response *http_response_new(gravity_vm *vm, Request *req);
+static void http_response_free(Response *resp);
+static void http_response_parse_header(Response *resp, char *header);
+static int8_t http_response_parse_line(Response *resp, char *line);
+static void http_response_slurp_body(Response *resp, char **bk);
+static void http_response_parse(Response *resp, char *source);
+static ssize_t http_request_send(gravity_vm *vm, Request *req);
+static Response *http_response_receive(gravity_vm *vm, Request *req);
+static bool http_request (gravity_vm *vm, gravity_map_t *options, uint32_t rindex);
+static bool http_get (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex);
+static bool http_post (gravity_vm *vm, gravity_value_t *args, uint16_t nargs, uint32_t rindex);
+
+
+// gravity bindings
+void gravity_http_register (gravity_vm *vm);
+void gravity_http_free (void);
+bool gravity_ishttp_class (gravity_class_t *c);
+const char *gravity_http_name (void);
+
+#endif
diff --git a/src/optionals/gravity_optionals.h b/src/optionals/gravity_optionals.h
index 7fc23d33..1d9b6fc3 100644
--- a/src/optionals/gravity_optionals.h
+++ b/src/optionals/gravity_optionals.h
@@ -26,6 +26,24 @@
#define GRAVITY_ISMATH_CLASS(_c) false
#endif
+
+#ifndef GRAVITY_INCLUDE_HTTP
+#define GRAVITY_INCLUDE_HTTP
+#endif
+
+#ifdef GRAVITY_INCLUDE_HTTP
+#define GRAVITY_HTTP_REGISTER(_vm) gravity_http_register(_vm)
+#define GRAVITY_HTTP_FREE() gravity_http_free()
+#define GRAVITY_HTTP_NAME() gravity_http_name()
+#define GRAVITY_ISHTTP_CLASS(_c) gravity_ishttp_class(_c)
+#include "gravity_http.h"
+#else
+#define GRAVITY_HTTP_REGISTER(_vm)
+#define GRAVITY_HTTP_FREE()
+#define GRAVITY_HTTP_NAME() NULL
+#define GRAVITY_ISHTTP_CLASS(_c) false
+#endif
+
#ifndef GRAVITY_INCLUDE_ENV
#define GRAVITY_INCLUDE_ENV
#endif
diff --git a/src/runtime/gravity_vm.c b/src/runtime/gravity_vm.c
index 3d9c2a4c..864f1c86 100644
--- a/src/runtime/gravity_vm.c
+++ b/src/runtime/gravity_vm.c
@@ -356,11 +356,13 @@ static void gravity_vm_loadclass (gravity_vm *vm, gravity_class_t *c) {
void gravity_opt_register (gravity_vm *vm) {
GRAVITY_MATH_REGISTER(vm);
+ GRAVITY_HTTP_REGISTER(vm);
GRAVITY_ENV_REGISTER(vm);
}
void gravity_opt_free() {
GRAVITY_MATH_FREE();
+ GRAVITY_HTTP_FREE();
GRAVITY_ENV_FREE();
}
diff --git a/src/shared/gravity_hash.c b/src/shared/gravity_hash.c
index 4051d13c..eae020d8 100644
--- a/src/shared/gravity_hash.c
+++ b/src/shared/gravity_hash.c
@@ -174,6 +174,21 @@ gravity_hash_t *gravity_hash_create (uint32_t size, gravity_hash_compute_fn comp
return hashtable;
}
+gravity_hash_t *gravity_hash_dup (gravity_hash_t *from_hash) {
+ if (from_hash->size < GRAVITYHASH_DEFAULT_SIZE) from_hash->size = GRAVITYHASH_DEFAULT_SIZE;
+
+ gravity_hash_t *hashtable = (gravity_hash_t *)mem_alloc(NULL, sizeof(gravity_hash_t));
+ if (!hashtable) return NULL;
+ if (!(hashtable->nodes = mem_calloc(NULL, from_hash->size, sizeof(hash_node_t*)))) {mem_free(hashtable); return NULL;}
+
+ hashtable->compute_fn = from_hash->compute_fn;
+ hashtable->isequal_fn = from_hash->isequal_fn;
+ hashtable->free_fn = from_hash->free_fn;
+ hashtable->data = from_hash->data;
+ hashtable->size = from_hash->size;
+ return hashtable;
+}
+
void gravity_hash_free (gravity_hash_t *hashtable) {
if (!hashtable) return;
gravity_hash_iterate_fn free_fn = hashtable->free_fn;
diff --git a/src/shared/gravity_hash.h b/src/shared/gravity_hash.h
index a0ef3ffa..7f3e8fc3 100644
--- a/src/shared/gravity_hash.h
+++ b/src/shared/gravity_hash.h
@@ -39,6 +39,7 @@ typedef bool (*gravity_hash_compare_fn) (gravity_value_t value1, gravity_
// PUBLIC functions
GRAVITY_API gravity_hash_t *gravity_hash_create (uint32_t size, gravity_hash_compute_fn compute, gravity_hash_isequal_fn isequal, gravity_hash_iterate_fn free, void *data);
+GRAVITY_API gravity_hash_t *gravity_hash_dup (gravity_hash_t *from_hash);
GRAVITY_API void gravity_hash_free (gravity_hash_t *hashtable);
GRAVITY_API bool gravity_hash_isempty (gravity_hash_t *hashtable);
GRAVITY_API bool gravity_hash_remove (gravity_hash_t *hashtable, gravity_value_t key);
diff --git a/src/shared/gravity_value.c b/src/shared/gravity_value.c
index cd6f15ac..334847fe 100644
--- a/src/shared/gravity_value.c
+++ b/src/shared/gravity_value.c
@@ -119,11 +119,11 @@ void gravity_module_free (gravity_vm *vm, gravity_module_t *m) {
uint32_t gravity_module_size (gravity_vm *vm, gravity_module_t *m) {
SET_OBJECT_VISITED_FLAG(m, true);
-
+
uint32_t hash_size = 0;
gravity_hash_iterate2(m->htable, gravity_hash_internalsize, (void*)&hash_size, (void*)vm);
uint32_t module_size = (sizeof(gravity_module_t)) + string_size(m->identifier) + hash_size + gravity_hash_memsize(m->htable);
-
+
SET_OBJECT_VISITED_FLAG(m, false);
return module_size;
}
@@ -202,7 +202,7 @@ gravity_class_t *gravity_class_new_pair (gravity_vm *vm, const char *identifier,
char buffer[512];
snprintf(buffer, sizeof(buffer), "%s meta", identifier);
-
+
// ivar count/grow is managed by gravity_class_setsuper
gravity_class_t *meta = gravity_class_new_single(vm, buffer, nsvar);
meta->objclass = gravity_class_object;
@@ -255,7 +255,7 @@ void gravity_class_serialize (gravity_class_t *c, json_t *json) {
json_begin_object(json, c->identifier);
json_add_cstring(json, GRAVITY_JSON_LABELTYPE, GRAVITY_JSON_CLASS); // MANDATORY 1st FIELD
json_add_cstring(json, GRAVITY_JSON_LABELIDENTIFIER, c->identifier); // MANDATORY 2nd FIELD
-
+
// avoid write superclass name if it is the default Object one
if ((c->superclass) && (c->superclass->identifier) && (strcmp(c->superclass->identifier, GRAVITY_CLASS_OBJECT_NAME) != 0)) {
json_add_cstring(json, GRAVITY_JSON_LABELSUPER, c->superclass->identifier);
@@ -399,7 +399,7 @@ static void gravity_class_free_internal (gravity_vm *vm, gravity_class_t *c, boo
if (c->identifier) mem_free((void *)c->identifier);
if (c->superlook) mem_free((void *)c->superlook);
-
+
if (!skip_base) {
// base classes have functions not registered inside VM so manually free all of them
gravity_hash_iterate(c->htable, gravity_hash_finteralfree, NULL);
@@ -456,7 +456,7 @@ inline gravity_closure_t *gravity_class_lookup_constructor (gravity_class_t *c,
uint32_t gravity_class_size (gravity_vm *vm, gravity_class_t *c) {
SET_OBJECT_VISITED_FLAG(c, true);
-
+
uint32_t class_size = sizeof(gravity_class_t) + (c->nivars * sizeof(gravity_value_t)) + string_size(c->identifier);
uint32_t hash_size = 0;
@@ -590,59 +590,59 @@ static void gravity_function_array_dump (gravity_function_t *f, gravity_value_r
printf("%05zu\tNULL\n", i);
continue;
}
-
+
if (VALUE_ISA_UNDEFINED(v)) {
printf("%05zu\tUNDEFINED\n", i);
continue;
}
-
+
if (VALUE_ISA_BOOL(v)) {
printf("%05zu\tBOOL: %d\n", i, (v.n == 0) ? 0 : 1);
continue;
}
-
+
if (VALUE_ISA_INT(v)) {
printf("%05zu\tINT: %" PRId64 "\n", i, (int64_t)v.n);
continue;
}
-
+
if (VALUE_ISA_FLOAT(v)) {
printf("%05zu\tFLOAT: %f\n", i, (double)v.f);
continue;
}
-
+
if (VALUE_ISA_FUNCTION(v)) {
gravity_function_t *vf = VALUE_AS_FUNCTION(v);
printf("%05zu\tFUNC: %s\n", i, (vf->identifier) ? vf->identifier : "$anon");
continue;
}
-
+
if (VALUE_ISA_CLASS(v)) {
gravity_class_t *c = VALUE_AS_CLASS(v);
printf("%05zu\tCLASS: %s\n", i, (c->identifier) ? c->identifier: "$anon");
continue;
-
+
}
-
+
if (VALUE_ISA_STRING(v)) {
printf("%05zu\tSTRING: %s\n", i, VALUE_AS_CSTRING(v));
continue;
}
-
+
if (VALUE_ISA_LIST(v)) {
gravity_list_t *value = VALUE_AS_LIST(v);
size_t count = marray_size(value->array);
printf("%05zu\tLIST: %zu items\n", i, count);
continue;
-
+
}
-
+
if (VALUE_ISA_MAP(v)) {
gravity_map_t *map = VALUE_AS_MAP(v);
printf("%05zu\tMAP: %u items\n", i, gravity_hash_count(map->hash));
continue;
}
-
+
// should never reach this point
assert(0);
}
@@ -670,23 +670,23 @@ static void gravity_function_bytecode_serialize (gravity_function_t *f, json_t *
json_add_string(json, GRAVITY_JSON_LABELBYTECODE, (const char *)hexchar, length);
mem_free(hexchar);
-
+
// debug lineno
if (!f->lineno) return;
-
+
ninst = f->ninsts;
length = ninst * 2 * sizeof(uint32_t);
hexchar = (uint8_t*) mem_alloc(NULL, sizeof(uint8_t) * length);
-
+
for (uint32_t k=0, i=0; i < ninst; ++i) {
uint32_t value = f->lineno[i];
-
+
for (int32_t j=2*sizeof(value)-1; j>=0; --j) {
uint8_t c = "0123456789ABCDEF"[((value >> (j*4)) & 0xF)];
hexchar[k++] = c;
}
}
-
+
json_add_string(json, GRAVITY_JSON_LABELLINENO, (const char *)hexchar, length);
mem_free(hexchar);
}
@@ -740,10 +740,10 @@ void gravity_function_dump (gravity_function_t *f, code_dump_function codef) {
if (f->tag == EXEC_TYPE_NATIVE) {
if (marray_size(f->cpool)) printf("======= CONST POOL =======\n");
gravity_function_array_dump(f, f->cpool);
-
+
if (marray_size(f->pname)) printf("======= PARAM NAMES =======\n");
gravity_function_array_dump(f, f->pname);
-
+
if (marray_size(f->pvalue)) printf("======= PARAM VALUES =======\n");
gravity_function_array_dump(f, f->pvalue);
@@ -817,7 +817,7 @@ void gravity_function_serialize (gravity_function_t *f, json_t *json) {
json_begin_array(json, GRAVITY_JSON_LABELPOOL);
gravity_function_array_serialize(f, json, f->cpool);
json_end_array(json);
-
+
// default values (if any)
if (marray_size(f->pvalue)) {
json_begin_array(json, GRAVITY_JSON_LABELPVALUES);
@@ -832,7 +832,7 @@ void gravity_function_serialize (gravity_function_t *f, json_t *json) {
json_end_array(json);
}
}
-
+
if (identifier) json_end_object(json);
}
@@ -978,7 +978,7 @@ gravity_function_t *gravity_function_deserialize (gravity_vm *vm, json_value *js
if (string_casencmp(label, GRAVITY_JSON_LABELLINENO, label_size) == 0) {
if (value->type == json_string) f->lineno = gravity_bytecode_deserialize(value->u.string.ptr, value->u.string.length, &f->ninsts);
}
-
+
// arguments names
if (string_casencmp(label, GRAVITY_JSON_LABELPNAMES, label_size) == 0) {
if (value->type != json_array) goto abort_load;
@@ -990,12 +990,12 @@ gravity_function_t *gravity_function_deserialize (gravity_vm *vm, json_value *js
marray_push(gravity_value_t, f->pname, VALUE_FROM_STRING(NULL, r->u.string.ptr, r->u.string.length));
}
}
-
+
// arguments default values
if (string_casencmp(label, GRAVITY_JSON_LABELPVALUES, label_size) == 0) {
if (value->type != json_array) goto abort_load;
if (f->tag != EXEC_TYPE_NATIVE) goto abort_load;
-
+
uint32_t m = value->u.array.length;
for (uint32_t j=0; ju.array.values[j];
@@ -1003,27 +1003,27 @@ gravity_function_t *gravity_function_deserialize (gravity_vm *vm, json_value *js
case json_integer:
marray_push(gravity_value_t, f->pvalue, VALUE_FROM_INT((gravity_int_t)r->u.integer));
break;
-
+
case json_double:
marray_push(gravity_value_t, f->pvalue, VALUE_FROM_FLOAT((gravity_float_t)r->u.dbl));
break;
-
+
case json_boolean:
marray_push(gravity_value_t, f->pvalue, VALUE_FROM_BOOL(r->u.boolean));
break;
-
+
case json_string:
marray_push(gravity_value_t, f->pvalue, VALUE_FROM_STRING(NULL, r->u.string.ptr, r->u.string.length));
break;
-
+
case json_object:
marray_push(gravity_value_t, f->pvalue, VALUE_FROM_UNDEFINED);
break;
-
+
case json_null:
marray_push(gravity_value_t, f->pvalue, VALUE_FROM_NULL);
break;
-
+
case json_none:
case json_array:
marray_push(gravity_value_t, f->pvalue, VALUE_FROM_NULL);
@@ -1031,7 +1031,7 @@ gravity_function_t *gravity_function_deserialize (gravity_vm *vm, json_value *js
}
}
}
-
+
// cpool
if (string_casencmp(label, GRAVITY_JSON_LABELPOOL, label_size) == 0) {
if (value->type != json_array) goto abort_load;
@@ -1121,7 +1121,7 @@ void gravity_function_free (gravity_vm *vm, gravity_function_t *f) {
if (f->tag == EXEC_TYPE_NATIVE) {
if (f->bytecode) mem_free((void *)f->bytecode);
if (f->lineno) mem_free((void *)f->lineno);
-
+
// FREE EACH DEFAULT value
size_t n = marray_size(f->pvalue);
for (size_t i=0; ipvalue);
-
+
// FREE EACH PARAM name
n = marray_size(f->pname);
for (size_t i=0; ipname);
-
+
// DO NOT FREE EACH INDIVIDUAL CPOOL ITEM HERE
marray_destroy(f->cpool);
}
@@ -1146,7 +1146,7 @@ void gravity_function_free (gravity_vm *vm, gravity_function_t *f) {
uint32_t gravity_function_size (gravity_vm *vm, gravity_function_t *f) {
SET_OBJECT_VISITED_FLAG(f, true);
-
+
uint32_t func_size = sizeof(gravity_function_t) + string_size(f->identifier);
if (f->tag == EXEC_TYPE_NATIVE) {
@@ -1224,7 +1224,7 @@ uint32_t gravity_closure_size (gravity_vm *vm, gravity_closure_t *closure) {
closure_size += sizeof(gravity_upvalue_t*);
++upvalue;
}
-
+
SET_OBJECT_VISITED_FLAG(closure, false);
return closure_size;
}
@@ -1241,7 +1241,7 @@ void gravity_closure_blacken (gravity_vm *vm, gravity_closure_t *closure) {
gravity_gray_object(vm, (gravity_object_t*)upvalue[0]);
++upvalue;
}
-
+
// mark context (if any)
if (closure->context) gravity_gray_object(vm, closure->context);
}
@@ -1262,18 +1262,18 @@ gravity_upvalue_t *gravity_upvalue_new (gravity_vm *vm, gravity_value_t *value)
void gravity_upvalue_free(gravity_vm *vm, gravity_upvalue_t *upvalue) {
#pragma unused(vm)
-
+
DEBUG_FREE("FREE %s", gravity_object_debug((gravity_object_t *)upvalue, true));
mem_free(upvalue);
}
uint32_t gravity_upvalue_size (gravity_vm *vm, gravity_upvalue_t *upvalue) {
#pragma unused(vm, upvalue)
-
+
SET_OBJECT_VISITED_FLAG(upvalue, true);
uint32_t upvalue_size = sizeof(gravity_upvalue_t);
SET_OBJECT_VISITED_FLAG(upvalue, false);
-
+
return upvalue_size;
}
@@ -1316,7 +1316,7 @@ gravity_fiber_t *gravity_fiber_new (gravity_vm *vm, gravity_closure_t *closure,
// replace self with fiber instance
frame->stackstart[0] = VALUE_FROM_OBJECT(fiber);
-
+
if (vm) gravity_vm_transfer(vm, (gravity_object_t*) fiber);
return fiber;
}
@@ -1361,7 +1361,7 @@ void gravity_fiber_seterror (gravity_fiber_t *fiber, const char *error) {
uint32_t gravity_fiber_size (gravity_vm *vm, gravity_fiber_t *fiber) {
SET_OBJECT_VISITED_FLAG(fiber, true);
-
+
// internal size
uint32_t fiber_size = sizeof(gravity_fiber_t);
fiber_size += fiber->stackalloc * sizeof(gravity_value_t);
@@ -1381,7 +1381,7 @@ uint32_t gravity_fiber_size (gravity_vm *vm, gravity_fiber_t *fiber) {
void gravity_fiber_blacken (gravity_vm *vm, gravity_fiber_t *fiber) {
gravity_vm_memupdate(vm, gravity_fiber_size(vm, fiber));
-
+
// gray call frame functions
for (uint32_t i=0; i < fiber->nframes; ++i) {
gravity_gray_object(vm, (gravity_object_t *)fiber->frames[i].closure);
@@ -1543,10 +1543,10 @@ void gravity_object_free (gravity_vm *vm, gravity_object_t *obj) {
uint32_t gravity_object_size (gravity_vm *vm, gravity_object_t *obj) {
if ((!obj) || (!OBJECT_IS_VALID(obj))) return 0;
-
+
// check if object has already been visited (to avoid infinite loop)
if (obj->gc.visited) return 0;
-
+
if (OBJECT_ISA_CLASS(obj)) return gravity_class_size(vm, (gravity_class_t *)obj);
else if (OBJECT_ISA_FUNCTION(obj)) return gravity_function_size(vm, (gravity_function_t *)obj);
else if (OBJECT_ISA_CLOSURE(obj)) return gravity_closure_size(vm, (gravity_closure_t *)obj);
@@ -1585,10 +1585,10 @@ gravity_instance_t *gravity_instance_new (gravity_vm *vm, gravity_class_t *c) {
instance->isa = gravity_class_instance;
instance->objclass = c;
-
+
if (c->nivars) instance->ivars = (gravity_value_t *)mem_alloc(NULL, c->nivars * sizeof(gravity_value_t));
for (uint32_t i=0; inivars; ++i) instance->ivars[i] = VALUE_FROM_NULL;
-
+
if (vm) gravity_vm_transfer(vm, (gravity_object_t*) instance);
return instance;
}
@@ -1599,16 +1599,16 @@ gravity_instance_t *gravity_instance_clone (gravity_vm *vm, gravity_instance_t *
gravity_instance_t *instance = (gravity_instance_t *)mem_alloc(NULL, sizeof(gravity_instance_t));
instance->isa = gravity_class_instance;
instance->objclass = c;
-
+
// if c is an anonymous class then it must be deeply copied
if (gravity_class_is_anon(c)) {
// clone class c and all its closures
}
-
+
gravity_delegate_t *delegate = gravity_vm_delegate(vm);
instance->xdata = (src_instance->xdata && delegate->bridge_clone) ? delegate->bridge_clone(vm, src_instance->xdata) : NULL;
-
+
if (c->nivars) instance->ivars = (gravity_value_t *)mem_alloc(NULL, c->nivars * sizeof(gravity_value_t));
for (uint32_t i=0; inivars; ++i) instance->ivars[i] = src_instance->ivars[i];
@@ -1653,7 +1653,7 @@ gravity_closure_t *gravity_instance_lookup_event (gravity_instance_t *i, const c
uint32_t gravity_instance_size (gravity_vm *vm, gravity_instance_t *i) {
SET_OBJECT_VISITED_FLAG(i, true);
-
+
uint32_t instance_size = sizeof(gravity_instance_t) + (i->objclass->nivars * sizeof(gravity_value_t));
gravity_delegate_t *delegate = gravity_vm_delegate(vm);
@@ -1674,7 +1674,7 @@ void gravity_instance_blacken (gravity_vm *vm, gravity_instance_t *i) {
for (uint32_t j=0; jobjclass->nivars; ++j) {
gravity_gray_value(vm, i->ivars[j]);
}
-
+
// xdata
if (i->xdata) {
gravity_delegate_t *delegate = gravity_vm_delegate(vm);
@@ -1828,7 +1828,7 @@ void gravity_value_serialize (gravity_value_t v, json_t *json) {
json_add_null(json, NULL);
return;
}
-
+
// UNDEFINED (convention used to represent an UNDEFINED value)
if (VALUE_ISA_UNDEFINED(v)) {
json_begin_object(json, NULL);
@@ -1914,13 +1914,13 @@ bool gravity_value_isobject (gravity_value_t v) {
if ((v.isa == NULL) || (v.isa == gravity_class_int) || (v.isa == gravity_class_float) ||
(v.isa == gravity_class_bool) || (v.isa == gravity_class_null) || (v.p == NULL)) return false;
-
+
// extra check to allow ONLY known objects
if ((v.isa == gravity_class_string) || (v.isa == gravity_class_object) || (v.isa == gravity_class_function) ||
(v.isa == gravity_class_closure) || (v.isa == gravity_class_fiber) || (v.isa == gravity_class_class) ||
(v.isa == gravity_class_instance) || (v.isa == gravity_class_module) || (v.isa == gravity_class_list) ||
(v.isa == gravity_class_map) || (v.isa == gravity_class_range) || (v.isa == gravity_class_upvalue)) return true;
-
+
return false;
}
@@ -2057,6 +2057,18 @@ gravity_list_t *gravity_list_new (gravity_vm *vm, uint32_t n) {
return list;
}
+gravity_value_t gravity_list_to_value(gravity_vm *vm, gravity_list_t *list) {
+ gravity_list_t *copy = mem_alloc(NULL, sizeof(gravity_list_t));
+ copy->array = list->array;
+
+ gravity_value_t value;
+ value.isa = gravity_class_list;
+ value.p = (gravity_object_t *)copy;
+
+ if (vm) gravity_vm_transfer(vm, (gravity_object_t*) copy);
+ return value;
+}
+
gravity_list_t *gravity_list_from_array (gravity_vm *vm, uint32_t n, gravity_value_t *p) {
gravity_list_t *list = (gravity_list_t *)mem_alloc(NULL, sizeof(gravity_list_t));
@@ -2086,16 +2098,22 @@ void gravity_list_append_list (gravity_vm *vm, gravity_list_t *list1, gravity_li
}
}
+void gravity_list_append_value (gravity_vm *vm, gravity_list_t *list, gravity_value_t *value) {
+ #pragma unused(vm)
+ // append value to list
+ marray_push(gravity_value_t, list->array, *value);
+}
+
uint32_t gravity_list_size (gravity_vm *vm, gravity_list_t *list) {
SET_OBJECT_VISITED_FLAG(list, true);
-
+
uint32_t internal_size = 0;
size_t count = marray_size(list->array);
for (size_t i=0; iarray, i));
}
internal_size += sizeof(gravity_list_t);
-
+
SET_OBJECT_VISITED_FLAG(list, false);
return internal_size;
}
@@ -2171,12 +2189,12 @@ static gravity_map_t *gravity_map_deserialize (gravity_vm *vm, json_value *json)
uint32_t gravity_map_size (gravity_vm *vm, gravity_map_t *map) {
SET_OBJECT_VISITED_FLAG(map, true);
-
+
uint32_t hash_size = 0;
gravity_hash_iterate2(map->hash, gravity_hash_internalsize, (void *)&hash_size, (void *)vm);
hash_size += gravity_hash_memsize(map->hash);
hash_size += sizeof(gravity_map_t);
-
+
SET_OBJECT_VISITED_FLAG(map, false);
return hash_size;
}
@@ -2186,6 +2204,112 @@ void gravity_map_blacken (gravity_vm *vm, gravity_map_t *map) {
gravity_hash_iterate(map->hash, gravity_hash_gray, (void *)vm);
}
+// void gravity_map_iterate(gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t value, void *data) {
+// char *key_str = VALUE_AS_STRING(key);
+// if (VALUE_ISA_STRING(value))
+// }
+
+static void gravity_map_stringify_iterator(gravity_hash_t *hashtable, gravity_value_t key, gravity_value_t v, void *data) {
+ #pragma unused(hashtable)
+ // TODO: make extra json characters length a constant, right now it's 10
+
+ // keys are always strings
+ assert(key.isa == gravity_class_string);
+ gravity_string_t *map_string = (gravity_string_t *)data;
+ char *substr;
+ char *key_string = VALUE_AS_CSTRING(key);
+ char *delimiter = ", ";
+
+ // if we just started, open with a curly brace
+
+ if (!strlen(map_string->s)) {
+ gravity_string_concat_cstring(NULL, map_string, "{");
+ delimiter = "";
+ }
+
+ // char *temp_map_string = mem_alloc(NULL, strlen(*map_string) * sizeof(char));
+ // BOOL
+ if (VALUE_ISA_BOOL(v)) {
+ substr = mem_alloc(NULL, strlen(key_string) + 11 * sizeof(char));
+ sprintf(substr, "%s\"%s\": %s", delimiter, key_string, (v.n == 0) ? "false" : "true");
+ gravity_string_concat_cstring(NULL, map_string, substr);
+ return;
+ }
+
+ // INT
+ if (VALUE_ISA_INT(v)) {
+ substr = mem_alloc(NULL, strlen(key_string) + sizeof(int64_t) + 10 * sizeof(char));
+ #ifdef __APPLE__
+ sprintf(substr, "%s\"%s\": %lld", delimiter, key_string, (int64_t)v.n);
+ #else
+ sprintf(substr, "%s\"%s\": %ld", delimiter, key_string, (int64_t)v.n);
+ #endif
+ gravity_string_concat_cstring(NULL, map_string, substr);
+ return;
+ }
+
+ // // FLOAT
+ if (VALUE_ISA_FLOAT(v)) {
+ substr = mem_alloc(NULL, strlen(key_string) + sizeof(float) + 10 * sizeof(char));
+ sprintf(substr, "%s\"%s\": %f", delimiter, key_string, v.f);
+ gravity_string_concat_cstring(NULL, map_string, substr);
+ return;
+ }
+
+ // STRING
+ if (VALUE_ISA_STRING(v)) {
+ char *value_string = VALUE_AS_CSTRING(v);
+ substr = mem_alloc(NULL, strlen(key_string) + strlen(value_string) + 10 * sizeof(char));
+ sprintf(substr, "%s\"%s\": \"%s\"", delimiter, key_string, value_string);
+ gravity_string_concat_cstring(NULL, map_string, substr);
+ return;
+ }
+
+ // MAP
+ if (VALUE_ISA_MAP(v)) {
+ gravity_map_t *value_map = VALUE_AS_MAP(v);
+ char *value_map_string = gravity_map_to_string(NULL, value_map);
+ substr = mem_alloc(NULL, strlen(key_string) + strlen(value_map_string) + 10 * sizeof(char));
+ sprintf(substr, "%s\"%s\": %s", delimiter, key_string, value_map_string);
+ gravity_string_concat_cstring(NULL, map_string, substr);
+ return;
+ }
+
+ if (VALUE_ISA_LIST(v)) {
+ gravity_list_t *value_list = VALUE_AS_LIST(v);
+ char *value_list_string = VALUE_AS_CSTRING(convert_value2string(NULL, v));
+ substr = mem_alloc(NULL, strlen(key_string) + strlen(value_list_string) + 10 * sizeof(char));
+ sprintf(substr, "%s%s: %s", delimiter, key_string, value_list_string);
+ gravity_string_concat_cstring(NULL, map_string, substr);
+ return;
+ }
+
+ // should never reach this point
+ assert(0);
+}
+
+char *gravity_map_to_string(gravity_vm *vm, gravity_map_t *map) {
+ gravity_string_t *map_string = gravity_string_new(NULL, "", 0, 0);
+ gravity_hash_iterate(map->hash, gravity_map_stringify_iterator, map_string);
+ gravity_string_concat_cstring(NULL, map_string, "}");
+ return map_string->s;
+}
+
+gravity_value_t gravity_map_to_value (gravity_vm *vm, gravity_map_t *map) {
+ // allocate the size of a string
+ gravity_hash_t *h = gravity_hash_dup(map->hash);
+ gravity_map_t *m = mem_alloc(vm, gravity_map_size(vm, map));
+ m->hash = h;
+
+
+ gravity_value_t value;
+ value.isa = gravity_class_map;
+ value.p = (gravity_object_t *)m;
+
+ if (vm) gravity_vm_transfer(vm, (gravity_object_t*)m);
+ return value;
+}
+
// MARK: -
gravity_range_t *gravity_range_new (gravity_vm *vm, gravity_int_t from_range, gravity_int_t to_range, bool inclusive) {
@@ -2208,11 +2332,11 @@ void gravity_range_free (gravity_vm *vm, gravity_range_t *range) {
uint32_t gravity_range_size (gravity_vm *vm, gravity_range_t *range) {
#pragma unused(vm, range)
-
+
SET_OBJECT_VISITED_FLAG(range, true);
uint32_t range_size = sizeof(gravity_range_t);
SET_OBJECT_VISITED_FLAG(range, false);
-
+
return range_size;
}
@@ -2252,12 +2376,23 @@ inline gravity_string_t *gravity_string_new (gravity_vm *vm, char *s, uint32_t l
obj->s = (char *)s;
obj->len = len;
obj->alloc = (alloc) ? alloc : len;
+ obj->s = mem_alloc(NULL, obj->alloc);
+ strcpy(obj->s, s);
if (s && len) obj->hash = gravity_hash_compute_buffer((const char *)s, len);
if (vm) gravity_vm_transfer(vm, (gravity_object_t*) obj);
return obj;
}
+inline void gravity_string_concat_cstring (gravity_vm *vm, gravity_string_t *obj, char *cstring) {
+ uint32_t len = (uint32_t)(obj->len + strlen(cstring));
+ uint32_t alloc = MAXNUM(len+1, DEFAULT_MINSTRING_SIZE);
+ obj->s = mem_realloc(NULL, obj->s, alloc);
+ memcpy(obj->s + obj->len, cstring, 1 + strlen(cstring));
+ obj->len = len;
+ obj->alloc = alloc;
+}
+
inline void gravity_string_set (gravity_string_t *obj, char *s, uint32_t len) {
obj->s = (char *)s;
obj->len = len;
@@ -2276,7 +2411,7 @@ uint32_t gravity_string_size (gravity_vm *vm, gravity_string_t *string) {
SET_OBJECT_VISITED_FLAG(string, true);
uint32_t string_size = (sizeof(gravity_string_t)) + string->alloc;
SET_OBJECT_VISITED_FLAG(string, false);
-
+
return string_size;
}
diff --git a/src/shared/gravity_value.h b/src/shared/gravity_value.h
index 10a80492..701760f2 100644
--- a/src/shared/gravity_value.h
+++ b/src/shared/gravity_value.h
@@ -499,6 +499,7 @@ GRAVITY_API uint32_t gravity_instance_size (gravity_vm *vm, gravity_i
// MARK: - VALUE -
GRAVITY_API bool gravity_value_equals (gravity_value_t v1, gravity_value_t v2);
GRAVITY_API bool gravity_value_vm_equals (gravity_vm *vm, gravity_value_t v1, gravity_value_t v2);
+GRAVITY_API uint16_t gravity_value_ptr_append (gravity_vm *vm, gravity_value_t *values, gravity_value_t value, uint16_t nvalues);
GRAVITY_API uint32_t gravity_value_hash (gravity_value_t value);
GRAVITY_API gravity_class_t *gravity_value_getclass (gravity_value_t v);
GRAVITY_API gravity_class_t *gravity_value_getsuper (gravity_value_t v);
@@ -521,9 +522,11 @@ GRAVITY_API const char *gravity_object_debug (gravity_object_t *obj, bo
// MARK: - LIST -
GRAVITY_API gravity_list_t *gravity_list_new (gravity_vm *vm, uint32_t n);
+GRAVITY_API gravity_value_t gravity_list_to_value (gravity_vm *vm, gravity_list_t *list);
GRAVITY_API gravity_list_t *gravity_list_from_array (gravity_vm *vm, uint32_t n, gravity_value_t *p);
GRAVITY_API void gravity_list_free (gravity_vm *vm, gravity_list_t *list);
GRAVITY_API void gravity_list_append_list (gravity_vm *vm, gravity_list_t *list1, gravity_list_t *list2);
+GRAVITY_API void gravity_list_append_value (gravity_vm *vm, gravity_list_t *list, gravity_value_t *value);
GRAVITY_API void gravity_list_blacken (gravity_vm *vm, gravity_list_t *list);
GRAVITY_API uint32_t gravity_list_size (gravity_vm *vm, gravity_list_t *list);
@@ -534,6 +537,8 @@ GRAVITY_API void gravity_map_append_map (gravity_vm *vm, gravity_
GRAVITY_API void gravity_map_insert (gravity_vm *vm, gravity_map_t *map, gravity_value_t key, gravity_value_t value);
GRAVITY_API void gravity_map_blacken (gravity_vm *vm, gravity_map_t *map);
GRAVITY_API uint32_t gravity_map_size (gravity_vm *vm, gravity_map_t *map);
+GRAVITY_API char *gravity_map_to_string (gravity_vm *vm, gravity_map_t *map);
+GRAVITY_API gravity_value_t gravity_map_to_value (gravity_vm *vm, gravity_map_t *map);
// MARK: - RANGE -
GRAVITY_API gravity_range_t *gravity_range_new (gravity_vm *vm, gravity_int_t from, gravity_int_t to, bool inclusive);
@@ -544,6 +549,7 @@ GRAVITY_API uint32_t gravity_range_size (gravity_vm *vm, gravity_rang
/// MARK: - STRING -
GRAVITY_API gravity_value_t gravity_string_to_value (gravity_vm *vm, const char *s, uint32_t len);
GRAVITY_API gravity_string_t *gravity_string_new (gravity_vm *vm, char *s, uint32_t len, uint32_t alloc);
+GRAVITY_API void gravity_string_concat_cstring (gravity_vm *vm, gravity_string_t *s, char *cstring);
GRAVITY_API void gravity_string_set(gravity_string_t *obj, char *s, uint32_t len);
GRAVITY_API void gravity_string_free (gravity_vm *vm, gravity_string_t *value);
GRAVITY_API void gravity_string_blacken (gravity_vm *vm, gravity_string_t *string);
diff --git a/src/utils/gravity_utils.c b/src/utils/gravity_utils.c
index 2bb2c870..2181c781 100644
--- a/src/utils/gravity_utils.c
+++ b/src/utils/gravity_utils.c
@@ -323,6 +323,10 @@ int string_cmp (const char *s1, const char *s2) {
return strcmp(s1, s2);
}
+bool string_starts_with(const char *s1, const char *s2) {
+ return strncmp(s1, s2, strlen(s2)) == 0;
+}
+
const char *string_dup (const char *s1) {
size_t len = (size_t)strlen(s1);
char *s = (char *)mem_alloc(NULL, len + 1);
diff --git a/src/utils/gravity_utils.h b/src/utils/gravity_utils.h
index 98045383..2b90d486 100644
--- a/src/utils/gravity_utils.h
+++ b/src/utils/gravity_utils.h
@@ -47,6 +47,7 @@ const char *directory_read (DIRREF ref, char *out);
int string_nocasencmp (const char *s1, const char *s2, size_t n);
int string_casencmp (const char *s1, const char *s2, size_t n);
int string_cmp (const char *s1, const char *s2);
+bool string_starts_with(const char *s1, const char *s2);
const char *string_dup (const char *s1);
const char *string_ndup (const char *s1, size_t n);
void string_reverse (char *p);