forked from ground/ground
Compare commits
4 Commits
7443722dd5
...
8f34705965
| Author | SHA1 | Date | |
|---|---|---|---|
| 8f34705965 | |||
| e58eec6f08 | |||
| e73fdddb4c | |||
| dc2781559d |
@@ -1,35 +1,23 @@
|
|||||||
#include <groundext.h>
|
#include <groundext.h>
|
||||||
#include <curl/curl.h>
|
|
||||||
#include <groundvm.h>
|
#include <groundvm.h>
|
||||||
|
#include <curl/curl.h>
|
||||||
#include <stdio.h>
|
#include <stdio.h>
|
||||||
#include <string.h>
|
#include <stdbool.h>
|
||||||
#include <stdlib.h>
|
|
||||||
|
|
||||||
bool curl_init = false;
|
CURL* curlHandle = NULL;
|
||||||
|
|
||||||
|
GroundStruct template;
|
||||||
|
|
||||||
|
const char* VALID_REQUEST_TYPES[] = {
|
||||||
|
"GET",
|
||||||
|
"POST"
|
||||||
|
};
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
char* data;
|
char* data;
|
||||||
size_t size;
|
size_t size;
|
||||||
} ResponseBuffer;
|
} ResponseBuffer;
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
CURL* handle;
|
|
||||||
bool connected;
|
|
||||||
char* url;
|
|
||||||
} WebSocketConnection;
|
|
||||||
|
|
||||||
// Global storage for WebSocket connections (simple implementation)
|
|
||||||
#define MAX_WS_CONNECTIONS 10
|
|
||||||
WebSocketConnection ws_connections[MAX_WS_CONNECTIONS] = {0};
|
|
||||||
|
|
||||||
void curl_setup() {
|
|
||||||
if (!curl_init) {
|
|
||||||
curl_global_init(CURL_GLOBAL_ALL);
|
|
||||||
curl_init = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Proper write callback that accumulates data
|
|
||||||
size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) {
|
size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) {
|
||||||
size_t total_size = size * nmemb;
|
size_t total_size = size * nmemb;
|
||||||
ResponseBuffer* buffer = (ResponseBuffer*)userdata;
|
ResponseBuffer* buffer = (ResponseBuffer*)userdata;
|
||||||
@@ -48,295 +36,117 @@ size_t write_callback(void* ptr, size_t size, size_t nmemb, void* userdata) {
|
|||||||
return total_size;
|
return total_size;
|
||||||
}
|
}
|
||||||
|
|
||||||
// GET request
|
GroundValue ground_request(GroundScope* scope, List args) {
|
||||||
GroundValue get_request(GroundScope* scope, List args) {
|
|
||||||
curl_setup();
|
|
||||||
CURL* handle = curl_easy_init();
|
|
||||||
|
|
||||||
if (!handle) {
|
|
||||||
ERROR("CURL failed to init", "GenericRequestError");
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponseBuffer buffer = {0};
|
|
||||||
buffer.data = malloc(1);
|
|
||||||
buffer.data[0] = '\0';
|
|
||||||
buffer.size = 0;
|
|
||||||
|
|
||||||
curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_HTTPGET, 1L);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
|
|
||||||
|
|
||||||
CURLcode result = curl_easy_perform(handle);
|
|
||||||
|
|
||||||
if (result != CURLE_OK) {
|
|
||||||
char buf[256];
|
|
||||||
snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result));
|
|
||||||
ERROR(buf, "ActionRequestError");
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_easy_cleanup(handle);
|
|
||||||
|
|
||||||
GroundValue ret = groundCreateValue(STRING, buffer.data);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// POST request
|
GroundObject obj = *args.values[0].data.customVal;
|
||||||
GroundValue post_request(GroundScope* scope, List args) {
|
|
||||||
curl_setup();
|
|
||||||
CURL* handle = curl_easy_init();
|
|
||||||
|
|
||||||
if (!handle) {
|
|
||||||
ERROR("CURL failed to init", "GenericRequestError");
|
|
||||||
}
|
|
||||||
|
|
||||||
ResponseBuffer buffer = {0};
|
|
||||||
buffer.data = malloc(1);
|
|
||||||
buffer.data[0] = '\0';
|
|
||||||
buffer.size = 0;
|
|
||||||
|
|
||||||
curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_POST, 1L);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_POSTFIELDS, args.values[1].data.stringVal);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_WRITEFUNCTION, write_callback);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_WRITEDATA, &buffer);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_FOLLOWLOCATION, 1L);
|
|
||||||
|
|
||||||
CURLcode result = curl_easy_perform(handle);
|
|
||||||
|
|
||||||
if (result != CURLE_OK) {
|
|
||||||
free(buffer.data);
|
|
||||||
char buf[256];
|
|
||||||
snprintf(buf, sizeof(buf) - 1, "Curl request failed: %s\n", curl_easy_strerror(result));
|
|
||||||
curl_easy_cleanup(handle);
|
|
||||||
ERROR(buf, "ActionRequestError");
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_easy_cleanup(handle);
|
|
||||||
|
|
||||||
GroundValue ret = groundCreateValue(STRING, buffer.data);
|
|
||||||
return ret;
|
|
||||||
}
|
|
||||||
|
|
||||||
// ============== WebSocket Functions ==============
|
// check arg types
|
||||||
|
GroundObjectField* address = groundFindField(obj, "address");
|
||||||
|
if (address == NULL || address->value.type != STRING) {
|
||||||
|
ERROR("Object does not have address field as string", "ValueError");
|
||||||
|
}
|
||||||
|
GroundObjectField* type = groundFindField(obj, "type");
|
||||||
|
if (type == NULL || type->value.type != STRING) {
|
||||||
|
ERROR("Object does not have type field as string", "ValueError");
|
||||||
|
}
|
||||||
|
GroundObjectField* userAgent = groundFindField(obj, "userAgent");
|
||||||
|
if (userAgent == NULL || userAgent->value.type != STRING) {
|
||||||
|
ERROR("Object does not have userAgent field as string", "ValueError");
|
||||||
|
}
|
||||||
|
GroundObjectField* httpHeaders = groundFindField(obj, "httpHeaders");
|
||||||
|
if (httpHeaders == NULL || httpHeaders->value.type != LIST) {
|
||||||
|
ERROR("Object does not have httpHeaders field as list<string>", "ValueError");
|
||||||
|
}
|
||||||
|
GroundObjectField* verbose = groundFindField(obj, "verbose");
|
||||||
|
if (verbose == NULL || verbose->value.type != BOOL) {
|
||||||
|
ERROR("Object does not have verbose field as bool", "ValueError");
|
||||||
|
}
|
||||||
|
|
||||||
// Find an empty slot for a new WebSocket connection
|
// check request type string
|
||||||
int find_ws_slot() {
|
bool requestTypeOk = false;
|
||||||
for (int i = 0; i < MAX_WS_CONNECTIONS; i++) {
|
for (int i = 0; i < sizeof(VALID_REQUEST_TYPES)/sizeof(VALID_REQUEST_TYPES[0]); i++) {
|
||||||
if (!ws_connections[i].connected) {
|
if (strcmp(type->value.data.stringVal, VALID_REQUEST_TYPES[i]) == 0) {
|
||||||
return i;
|
requestTypeOk = true;
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return -1;
|
if (!requestTypeOk)
|
||||||
}
|
ERROR("Invalid request type! Choices: GET, POST", "ValueError");
|
||||||
|
|
||||||
|
|
||||||
// WebSocket Connect - returns connection ID as INT
|
ResponseBuffer buffer = {0};
|
||||||
GroundValue ws_connect(GroundScope* scope, List args) {
|
|
||||||
curl_setup();
|
|
||||||
|
|
||||||
int slot = find_ws_slot();
|
|
||||||
if (slot == -1) {
|
|
||||||
ERROR("Maximum WebSocket connections reached", "OverflowError");
|
|
||||||
}
|
|
||||||
|
|
||||||
CURL* handle = curl_easy_init();
|
|
||||||
if (!handle) {
|
|
||||||
ERROR("CURL failed to init", "GenericWSError");
|
|
||||||
}
|
|
||||||
|
|
||||||
curl_easy_setopt(handle, CURLOPT_URL, args.values[0].data.stringVal);
|
|
||||||
curl_easy_setopt(handle, CURLOPT_CONNECT_ONLY, 2L); // WebSocket mode
|
|
||||||
curl_easy_setopt(handle, CURLOPT_TIMEOUT, 10L);
|
|
||||||
|
|
||||||
CURLcode result = curl_easy_perform(handle);
|
|
||||||
|
|
||||||
if (result != CURLE_OK) {
|
|
||||||
char buf[256];
|
|
||||||
snprintf(buf, sizeof(buf) - 1, "Curl WebSocket failed: %s\n", curl_easy_strerror(result));
|
|
||||||
curl_easy_cleanup(handle);
|
|
||||||
ERROR(buf, "ActionWSError");
|
|
||||||
}
|
|
||||||
|
|
||||||
ws_connections[slot].handle = handle;
|
|
||||||
ws_connections[slot].connected = true;
|
|
||||||
ws_connections[slot].url = strdup(args.values[0].data.stringVal);
|
|
||||||
|
|
||||||
return groundCreateValue(INT, (int64_t)slot);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket Send - sends a text message, returns bytes sent as INT
|
// set curl params
|
||||||
GroundValue ws_send(GroundScope* scope, List args) {
|
curl_easy_setopt(curlHandle, CURLOPT_URL, address->value.data.stringVal);
|
||||||
int conn_id = (int)args.values[0].data.intVal;
|
curl_easy_setopt(curlHandle, CURLOPT_WRITEFUNCTION, write_callback);
|
||||||
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
|
curl_easy_setopt(curlHandle, CURLOPT_WRITEDATA, (void*)&buffer);
|
||||||
ERROR("Invalid WebSocket connection ID", "GenericWSError");
|
curl_easy_setopt(curlHandle, CURLOPT_USERAGENT, userAgent->value.data.stringVal);
|
||||||
}
|
curl_easy_setopt(curlHandle, CURLOPT_VERBOSE, verbose->value.data.boolVal);
|
||||||
|
|
||||||
CURL* handle = ws_connections[conn_id].handle;
|
|
||||||
const char* message = args.values[1].data.stringVal;
|
|
||||||
size_t sent;
|
|
||||||
|
|
||||||
CURLcode result = curl_ws_send(handle, message, strlen(message), &sent, 0, CURLWS_TEXT);
|
|
||||||
|
|
||||||
if (result != CURLE_OK) {
|
|
||||||
char buf[256];
|
|
||||||
snprintf(buf, sizeof(buf) - 1, "WebSocket send failed: %s\n", curl_easy_strerror(result));
|
|
||||||
ERROR(buf, "ActionWSError");
|
|
||||||
}
|
|
||||||
|
|
||||||
return groundCreateValue(INT, (int64_t)sent);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket Receive - receives a message (blocking)
|
// append headers to http request
|
||||||
GroundValue ws_receive(GroundScope* scope, List args) {
|
struct curl_slist* httpHeaderList = NULL;
|
||||||
int conn_id = (int)args.values[0].data.intVal;
|
GroundValue* httpHeaderStrings = httpHeaders->value.data.listVal.values;
|
||||||
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
|
for (int i = 0; i < httpHeaders->value.data.listVal.size; i++) {
|
||||||
ERROR("Invalid WebSocket connection ID", "GenericWSError");
|
httpHeaderList = curl_slist_append(httpHeaderList, httpHeaderStrings[i].data.stringVal);
|
||||||
}
|
}
|
||||||
|
curl_easy_setopt(curlHandle, CURLOPT_HTTPHEADER, httpHeaderList);
|
||||||
CURL* handle = ws_connections[conn_id].handle;
|
|
||||||
char buffer[4096];
|
|
||||||
size_t received;
|
|
||||||
const struct curl_ws_frame* meta;
|
|
||||||
|
|
||||||
CURLcode result = curl_ws_recv(handle, buffer, sizeof(buffer) - 1, &received, &meta);
|
|
||||||
|
|
||||||
if (result != CURLE_OK) {
|
|
||||||
if (result == CURLE_AGAIN) {
|
|
||||||
// No data available right now
|
|
||||||
return groundCreateValue(STRING, "");
|
|
||||||
}
|
|
||||||
char buf[256];
|
|
||||||
snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result));
|
|
||||||
ERROR(buf, "ActionWSError");
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[received] = '\0';
|
|
||||||
|
|
||||||
// Handle different frame types
|
|
||||||
if (meta->flags & CURLWS_CLOSE) {
|
|
||||||
printf("WebSocket close frame received\n");
|
|
||||||
ws_connections[conn_id].connected = false;
|
|
||||||
return groundCreateValue(STRING, "[CLOSED]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_PING) {
|
|
||||||
// Automatically respond to ping with pong
|
|
||||||
curl_ws_send(handle, "", 0, &received, 0, CURLWS_PONG);
|
|
||||||
return groundCreateValue(STRING, "[PING]");
|
|
||||||
}
|
|
||||||
|
|
||||||
char* data = malloc(received + 1);
|
|
||||||
memcpy(data, buffer, received);
|
|
||||||
data[received] = '\0';
|
|
||||||
|
|
||||||
return groundCreateValue(STRING, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket Receive with timeout (non-blocking version)
|
|
||||||
GroundValue ws_receive_timeout(GroundScope* scope, List args) {
|
|
||||||
int conn_id = (int)args.values[0].data.intVal;
|
|
||||||
long timeout_ms = (long)args.values[1].data.intVal;
|
|
||||||
|
|
||||||
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
|
|
||||||
ERROR("Invalid WebSocket connection ID", "GenericWSError");
|
|
||||||
}
|
|
||||||
|
|
||||||
CURL* handle = ws_connections[conn_id].handle;
|
|
||||||
|
|
||||||
// Set socket to non-blocking mode
|
|
||||||
curl_easy_setopt(handle, CURLOPT_TIMEOUT_MS, timeout_ms);
|
|
||||||
|
|
||||||
char buffer[4096];
|
|
||||||
size_t received;
|
|
||||||
const struct curl_ws_frame* meta;
|
|
||||||
|
|
||||||
CURLcode result = curl_ws_recv(handle, buffer, sizeof(buffer) - 1, &received, &meta);
|
|
||||||
|
|
||||||
if (result == CURLE_AGAIN || result == CURLE_OPERATION_TIMEDOUT) {
|
|
||||||
return groundCreateValue(STRING, "");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (result != CURLE_OK) {
|
|
||||||
char buf[256];
|
|
||||||
snprintf(buf, sizeof(buf) - 1, "WebSocket receive failed: %s\n", curl_easy_strerror(result));
|
|
||||||
ERROR(buf, "ActionWSError");
|
|
||||||
}
|
|
||||||
|
|
||||||
buffer[received] = '\0';
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_CLOSE) {
|
|
||||||
ws_connections[conn_id].connected = false;
|
|
||||||
return groundCreateValue(STRING, "[CLOSED]");
|
|
||||||
}
|
|
||||||
|
|
||||||
if (meta->flags & CURLWS_PING) {
|
|
||||||
curl_ws_send(handle, "", 0, &received, 0, CURLWS_PONG);
|
|
||||||
return groundCreateValue(STRING, "[PING]");
|
|
||||||
}
|
|
||||||
|
|
||||||
char* data = malloc(received + 1);
|
|
||||||
memcpy(data, buffer, received);
|
|
||||||
data[received] = '\0';
|
|
||||||
|
|
||||||
return groundCreateValue(STRING, data);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket Close - properly close a connection, returns BOOL success
|
CURLcode curlStatus = curl_easy_perform(curlHandle);
|
||||||
GroundValue ws_close(GroundScope* scope, List args) {
|
curl_slist_free_all(httpHeaderList); // dont want em' memory leaks :P - spooopy
|
||||||
int conn_id = (int)args.values[0].data.intVal;
|
|
||||||
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
|
|
||||||
ERROR("Invalid or already connected websocket", "GenericWSError");
|
|
||||||
}
|
|
||||||
|
|
||||||
CURL* handle = ws_connections[conn_id].handle;
|
|
||||||
size_t sent;
|
|
||||||
|
|
||||||
// Send close frame
|
|
||||||
curl_ws_send(handle, "", 0, &sent, 0, CURLWS_CLOSE);
|
|
||||||
|
|
||||||
// Cleanup
|
|
||||||
curl_easy_cleanup(handle);
|
|
||||||
free(ws_connections[conn_id].url);
|
|
||||||
ws_connections[conn_id].handle = NULL;
|
|
||||||
ws_connections[conn_id].connected = false;
|
|
||||||
ws_connections[conn_id].url = NULL;
|
|
||||||
|
|
||||||
return groundCreateValue(BOOL, true);
|
|
||||||
}
|
|
||||||
|
|
||||||
// WebSocket Send Binary data, returns bytes sent as INT
|
// check for any curl errors
|
||||||
GroundValue ws_send_binary(GroundScope* scope, List args) {
|
if (curlStatus != CURLE_OK) {
|
||||||
int conn_id = (int)args.values[0].data.intVal;
|
char curlErrorMsg[255] = {0};
|
||||||
if (conn_id < 0 || conn_id >= MAX_WS_CONNECTIONS || !ws_connections[conn_id].connected) {
|
|
||||||
ERROR("Invalid WebSocket connection ID", "GenericWSError");
|
snprintf(curlErrorMsg, 255, "%s\n", curl_easy_strerror(curlStatus));
|
||||||
|
ERROR(curlErrorMsg, "CurlError");
|
||||||
}
|
}
|
||||||
|
|
||||||
CURL* handle = ws_connections[conn_id].handle;
|
// get the http response code
|
||||||
const char* data = args.values[1].data.stringVal;
|
long httpCode = 0;
|
||||||
size_t sent;
|
curl_easy_getinfo(curlHandle, CURLINFO_RESPONSE_CODE, &httpCode);
|
||||||
|
|
||||||
CURLcode result = curl_ws_send(handle, data, strlen(data), &sent, 0, CURLWS_BINARY);
|
// create return value and return it
|
||||||
|
GroundValue value = groundCreateValue(CUSTOM, &template);
|
||||||
if (result != CURLE_OK) {
|
|
||||||
char buf[256];
|
GroundObjectField* status = groundFindField(*value.data.customVal, "statusCode");
|
||||||
snprintf(buf, sizeof(buf) - 1, "WebSocket binary send failed: %s\n", curl_easy_strerror(result));
|
status->value.data.intVal = httpCode;
|
||||||
ERROR(buf, "ActionWSError");
|
|
||||||
}
|
GroundObjectField* data = groundFindField(*value.data.customVal, "data");
|
||||||
|
data->value.data.stringVal = buffer.data;
|
||||||
return groundCreateValue(INT, (int64_t)sent);
|
|
||||||
|
value.type = CUSTOM;
|
||||||
|
|
||||||
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ground_init(GroundScope* scope) {
|
void ground_init(GroundScope* scope) {
|
||||||
// HTTP Functions
|
// init libcurl
|
||||||
groundAddNativeFunction(scope, "request_Get", get_request, STRING, 1, STRING, "url");
|
curl_global_init(CURL_GLOBAL_ALL);
|
||||||
groundAddNativeFunction(scope, "request_Post", post_request, STRING, 2, STRING, "url", STRING, "data");
|
curlHandle = curl_easy_init();
|
||||||
|
|
||||||
// WebSocket Functions
|
groundAddValueToScope(scope, "requestInitSuccess", groundCreateValue(BOOL, curlHandle != NULL));
|
||||||
groundAddNativeFunction(scope, "ws_Connect", ws_connect, INT, 1, STRING, "url");
|
if (curlHandle == NULL) {
|
||||||
groundAddNativeFunction(scope, "ws_Send", ws_send, INT, 2, INT, "conn_id", STRING, "message");
|
return;
|
||||||
groundAddNativeFunction(scope, "ws_Receive", ws_receive, STRING, 1, INT, "conn_id");
|
}
|
||||||
groundAddNativeFunction(scope, "ws_ReceiveTimeout", ws_receive_timeout, STRING, 2, INT, "conn_id", INT, "timeout_ms");
|
|
||||||
groundAddNativeFunction(scope, "ws_Close", ws_close, BOOL, 1, INT, "conn_id");
|
// create struct
|
||||||
groundAddNativeFunction(scope, "ws_SendBinary", ws_send_binary, INT, 2, INT, "conn_id", STRING, "data");
|
template = groundCreateStruct();
|
||||||
}
|
groundAddFieldToStruct(&template, "statusCode", groundCreateValue(INT, 200));
|
||||||
|
groundAddFieldToStruct(&template, "data", groundCreateValue(STRING, ""));
|
||||||
|
groundAddFieldToStruct(&template, "ok", groundCreateValue(BOOL, 1));
|
||||||
|
|
||||||
|
// create struct that users can pass to this library
|
||||||
|
GroundStruct requestStruct = groundCreateStruct();
|
||||||
|
groundAddFieldToStruct(&requestStruct, "address", groundCreateValue(STRING, ""));
|
||||||
|
groundAddFieldToStruct(&requestStruct, "type", groundCreateValue(STRING, "GET"));
|
||||||
|
groundAddFieldToStruct(&requestStruct, "userAgent", groundCreateValue(STRING, "Ground/1.0"));
|
||||||
|
groundAddFieldToStruct(&requestStruct, "httpHeaders", groundCreateValue(LIST, 0));
|
||||||
|
groundAddFieldToStruct(&requestStruct, "verbose", groundCreateValue(BOOL, 0));
|
||||||
|
|
||||||
|
groundAddValueToScope(scope, "request", groundCreateValue(STRUCTVAL, requestStruct));
|
||||||
|
|
||||||
|
// create functions
|
||||||
|
groundAddNativeFunction(scope, "request_Request", ground_request, CUSTOM, 1, CUSTOM, "requestInfo");
|
||||||
|
}
|
||||||
@@ -541,6 +541,7 @@ void printGroundInstruction(GroundInstruction* gi) {
|
|||||||
}
|
}
|
||||||
if (gi->type != CREATELABEL) printf(" ");
|
if (gi->type != CREATELABEL) printf(" ");
|
||||||
for (size_t i = 0; i < gi->args.length; i++) {
|
for (size_t i = 0; i < gi->args.length; i++) {
|
||||||
|
if (i != 0) printf(" ");
|
||||||
if (gi->args.args[i].type == VALUE && gi->args.args[i].value.value.type == STRING) {
|
if (gi->args.args[i].type == VALUE && gi->args.args[i].value.value.type == STRING) {
|
||||||
printf("\"");
|
printf("\"");
|
||||||
printGroundArg(&gi->args.args[i]);
|
printGroundArg(&gi->args.args[i]);
|
||||||
@@ -548,7 +549,6 @@ void printGroundInstruction(GroundInstruction* gi) {
|
|||||||
} else {
|
} else {
|
||||||
printGroundArg(&gi->args.args[i]);
|
printGroundArg(&gi->args.args[i]);
|
||||||
}
|
}
|
||||||
printf(" ");
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user