Initial Commit
This commit is contained in:
371
tests/lsp_test.c
Normal file
371
tests/lsp_test.c
Normal file
@@ -0,0 +1,371 @@
|
||||
#include "../libs/cjson/cJSON.h"
|
||||
#include <fcntl.h>
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
#include <sys/ioctl.h>
|
||||
#include <sys/wait.h>
|
||||
#include <termios.h>
|
||||
#include <unistd.h>
|
||||
|
||||
int lsp_stdin_fd = -1;
|
||||
int lsp_stdout_fd = -1;
|
||||
int cols;
|
||||
|
||||
void start_lsp(const char *cmd, char *const argv[]) {
|
||||
int in_pipe[2];
|
||||
int out_pipe[2];
|
||||
if (pipe(in_pipe) == -1 || pipe(out_pipe) == -1) {
|
||||
perror("pipe");
|
||||
exit(1);
|
||||
}
|
||||
pid_t pid = fork();
|
||||
if (pid == -1) {
|
||||
perror("fork");
|
||||
exit(1);
|
||||
}
|
||||
if (pid == 0) {
|
||||
dup2(in_pipe[0], STDIN_FILENO);
|
||||
dup2(out_pipe[1], STDOUT_FILENO);
|
||||
int devnull = open("/dev/null", O_WRONLY);
|
||||
if (devnull != -1) {
|
||||
dup2(devnull, STDERR_FILENO);
|
||||
close(devnull);
|
||||
}
|
||||
close(in_pipe[1]);
|
||||
close(out_pipe[0]);
|
||||
execvp(cmd, argv);
|
||||
perror("execvp");
|
||||
_exit(1);
|
||||
}
|
||||
close(in_pipe[0]);
|
||||
close(out_pipe[1]);
|
||||
lsp_stdin_fd = in_pipe[1];
|
||||
lsp_stdout_fd = out_pipe[0];
|
||||
}
|
||||
|
||||
void lsp_send(cJSON *msg) {
|
||||
char *dump = cJSON_PrintUnformatted(msg);
|
||||
int len = strlen(dump);
|
||||
dprintf(lsp_stdin_fd, "Content-Length: %d\r\n\r\n%s", len, dump);
|
||||
free(dump);
|
||||
}
|
||||
|
||||
cJSON *lsp_recv() {
|
||||
char header[256];
|
||||
int content_length = 0;
|
||||
char c;
|
||||
int pos = 0;
|
||||
int newlines = 0;
|
||||
while (read(lsp_stdout_fd, &c, 1) == 1) {
|
||||
header[pos++] = c;
|
||||
if (pos >= sizeof(header) - 1)
|
||||
break;
|
||||
if (c == '\n') {
|
||||
header[pos] = '\0';
|
||||
if (strncmp(header, "Content-Length:", 15) == 0) {
|
||||
content_length = atoi(header + 15);
|
||||
}
|
||||
if (strcmp(header, "\r\n") == 0)
|
||||
break;
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
if (content_length <= 0)
|
||||
return NULL;
|
||||
char *buf = malloc(content_length + 1);
|
||||
if (!buf)
|
||||
return NULL;
|
||||
int got = 0;
|
||||
while (got < content_length) {
|
||||
int n = read(lsp_stdout_fd, buf + got, content_length - got);
|
||||
if (n <= 0)
|
||||
break;
|
||||
got += n;
|
||||
}
|
||||
buf[got] = '\0';
|
||||
cJSON *msg = cJSON_Parse(buf);
|
||||
free(buf);
|
||||
return msg;
|
||||
}
|
||||
|
||||
cJSON *make_fold_request(int id, const char *uri) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(root, "id", id);
|
||||
cJSON_AddStringToObject(root, "method", "textDocument/foldingRange");
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON *doc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(doc, "uri", uri);
|
||||
cJSON_AddItemToObject(params, "textDocument", doc);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
return root;
|
||||
}
|
||||
|
||||
cJSON *make_hover_request(int id, const char *uri) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(root, "id", id);
|
||||
cJSON_AddStringToObject(root, "method", "textDocument/completion");
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON *doc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(doc, "uri", uri);
|
||||
cJSON_AddItemToObject(params, "textDocument", doc);
|
||||
cJSON *pos = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(pos, "line", 15);
|
||||
cJSON_AddNumberToObject(pos, "character", 6);
|
||||
cJSON_AddItemToObject(params, "position", pos);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
return root;
|
||||
}
|
||||
|
||||
cJSON *make_document_symbol_request(int id, const char *uri) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(root, "id", id);
|
||||
cJSON_AddStringToObject(root, "method", "textDocument/documentSymbol");
|
||||
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON *textDocument = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(textDocument, "uri", uri);
|
||||
cJSON_AddItemToObject(params, "textDocument", textDocument);
|
||||
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
|
||||
return root;
|
||||
}
|
||||
|
||||
cJSON *make_features_request(int id, const char *root_uri) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(root, "id", id);
|
||||
cJSON_AddStringToObject(root, "method", "initialize");
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(params, "processId", "0");
|
||||
cJSON_AddStringToObject(params, "rootUri", root_uri);
|
||||
cJSON *capabilities = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(params, "capabilities", capabilities);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
return root;
|
||||
}
|
||||
|
||||
cJSON *make_folder_init_request(int id, const char *root_uri) {
|
||||
cJSON *init = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(init, "jsonrpc", "2.0");
|
||||
cJSON_AddNumberToObject(init, "id", id);
|
||||
cJSON_AddStringToObject(init, "method", "initialize");
|
||||
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(params, "rootUri", root_uri);
|
||||
|
||||
// capabilities
|
||||
cJSON *capabilities = cJSON_CreateObject();
|
||||
cJSON *textDocument = cJSON_CreateObject();
|
||||
cJSON *diagnosticProvider = cJSON_CreateObject();
|
||||
// Enable diagnostics
|
||||
cJSON_AddBoolToObject(diagnosticProvider, "interFileDependencies", false);
|
||||
cJSON_AddBoolToObject(diagnosticProvider, "workspaceDiagnostics", false);
|
||||
cJSON_AddItemToObject(textDocument, "diagnosticProvider", diagnosticProvider);
|
||||
cJSON_AddItemToObject(capabilities, "textDocument", textDocument);
|
||||
|
||||
cJSON_AddItemToObject(params, "capabilities", capabilities);
|
||||
cJSON_AddItemToObject(init, "params", params);
|
||||
|
||||
return init;
|
||||
}
|
||||
|
||||
cJSON *make_initialized_request(void) {
|
||||
cJSON *msg = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(msg, "jsonrpc", "2.0");
|
||||
cJSON_AddStringToObject(msg, "method", "initialized");
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON_AddItemToObject(msg, "params", params);
|
||||
return msg;
|
||||
}
|
||||
|
||||
cJSON *make_change_file_request(int id, const char *uri) {
|
||||
cJSON *root = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(root, "jsonrpc", "2.0");
|
||||
cJSON_AddStringToObject(root, "method", "textDocument/didChange");
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON *doc = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(doc, "uri", uri);
|
||||
cJSON_AddNumberToObject(doc, "version", 2);
|
||||
cJSON_AddItemToObject(params, "textDocument", doc);
|
||||
cJSON *changes = cJSON_CreateArray();
|
||||
cJSON *change = cJSON_CreateObject();
|
||||
cJSON *range = cJSON_CreateObject();
|
||||
cJSON *start = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(start, "line", 0);
|
||||
cJSON_AddNumberToObject(start, "character", 0);
|
||||
cJSON *end = cJSON_CreateObject();
|
||||
cJSON_AddNumberToObject(end, "line", 0);
|
||||
cJSON_AddNumberToObject(end, "character", 0);
|
||||
cJSON_AddItemToObject(range, "start", start);
|
||||
cJSON_AddItemToObject(range, "end", end);
|
||||
cJSON_AddItemToObject(change, "range", range);
|
||||
cJSON_AddStringToObject(change, "text", "");
|
||||
cJSON_AddItemToArray(changes, change);
|
||||
cJSON_AddItemToObject(params, "contentChanges", changes);
|
||||
cJSON_AddItemToObject(root, "params", params);
|
||||
return root;
|
||||
}
|
||||
|
||||
cJSON *make_open_file_request(const char *uri, const char *languageId,
|
||||
const char *text, int version) {
|
||||
cJSON *msg = cJSON_CreateObject();
|
||||
cJSON_AddStringToObject(msg, "jsonrpc", "2.0");
|
||||
cJSON_AddStringToObject(msg, "method", "textDocument/didOpen");
|
||||
|
||||
cJSON *params = cJSON_CreateObject();
|
||||
cJSON *doc = cJSON_CreateObject();
|
||||
|
||||
cJSON_AddStringToObject(doc, "uri", uri);
|
||||
cJSON_AddStringToObject(doc, "languageId", languageId);
|
||||
cJSON_AddNumberToObject(doc, "version", version);
|
||||
cJSON_AddStringToObject(doc, "text", text);
|
||||
|
||||
cJSON_AddItemToObject(params, "textDocument", doc);
|
||||
cJSON_AddItemToObject(msg, "params", params);
|
||||
|
||||
return msg;
|
||||
}
|
||||
|
||||
int get_key(void) {
|
||||
struct termios oldt, newt;
|
||||
int ch;
|
||||
tcgetattr(STDIN_FILENO, &oldt);
|
||||
newt = oldt;
|
||||
newt.c_lflag &= ~(ICANON | ECHO);
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &newt);
|
||||
ch = getchar();
|
||||
tcsetattr(STDIN_FILENO, TCSANOW, &oldt);
|
||||
struct winsize ws;
|
||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
|
||||
cols = ws.ws_col;
|
||||
return ch;
|
||||
}
|
||||
|
||||
char *read_file(const char *path) {
|
||||
FILE *f = fopen(path, "rb");
|
||||
if (!f)
|
||||
return NULL;
|
||||
|
||||
// seek to end to get size
|
||||
if (fseek(f, 0, SEEK_END) != 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
long size = ftell(f);
|
||||
if (size < 0) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
rewind(f);
|
||||
|
||||
char *buf = malloc(size + 1);
|
||||
if (!buf) {
|
||||
fclose(f);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
size_t read_size = fread(buf, 1, size, f);
|
||||
buf[read_size] = '\0'; // null terminate
|
||||
fclose(f);
|
||||
|
||||
return buf;
|
||||
}
|
||||
|
||||
void print_wrapped(const char *text) {
|
||||
struct winsize ws;
|
||||
ioctl(STDOUT_FILENO, TIOCGWINSZ, &ws);
|
||||
int cols = ws.ws_col > 0 ? ws.ws_col : 80; // fallback to 80 if unknown
|
||||
|
||||
int col = 0;
|
||||
for (size_t i = 0; text[i]; i++) {
|
||||
putchar(text[i]);
|
||||
col++;
|
||||
|
||||
if (text[i] == '\n') {
|
||||
col = 0; // reset column on explicit newline
|
||||
} else if (col >= cols) {
|
||||
putchar('\n');
|
||||
col = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int main() {
|
||||
char *argv[] = {"clangd", NULL};
|
||||
start_lsp("clangd", argv);
|
||||
cJSON *init =
|
||||
make_folder_init_request(1, "file:///home/syed/main/cubit/tests/");
|
||||
lsp_send(init);
|
||||
cJSON_Delete(init);
|
||||
cJSON *initialized = make_initialized_request();
|
||||
lsp_send(initialized);
|
||||
cJSON_Delete(initialized);
|
||||
char *text = read_file("/home/syed/main/cubit/tests/test.c");
|
||||
printf("%s\n", text);
|
||||
cJSON *req = make_open_file_request(
|
||||
"file:///home/syed/main/cubit/tests/test.c", "c", text, 1);
|
||||
lsp_send(req);
|
||||
cJSON_Delete(req);
|
||||
free(text);
|
||||
// cJSON *doc_sym_req = make_document_symbol_request(
|
||||
// 2, "file:///home/syed/main/cubit/tests/test.c");
|
||||
// lsp_send(doc_sym_req);
|
||||
// cJSON_Delete(doc_sym_req);
|
||||
usleep(1000000);
|
||||
int next_id = 3;
|
||||
while (1) {
|
||||
printf("Press 'f' to request fold ranges, 'q' to quit: ");
|
||||
fflush(stdout);
|
||||
int key = get_key();
|
||||
printf("\n");
|
||||
if (key == 'q')
|
||||
break;
|
||||
if (key == 'f') {
|
||||
cJSON *req = make_fold_request(
|
||||
next_id++, "file:///home/syed/main/cubit/tests/test.c");
|
||||
lsp_send(req);
|
||||
cJSON_Delete(req);
|
||||
}
|
||||
if (key == 'i') {
|
||||
cJSON *req = make_features_request(next_id++,
|
||||
"file:///home/syed/main/cubit/tests/");
|
||||
lsp_send(req);
|
||||
cJSON_Delete(req);
|
||||
}
|
||||
if (key == 'h') {
|
||||
cJSON *req = make_hover_request(
|
||||
next_id++, "file:///home/syed/main/cubit/tests/test.c");
|
||||
lsp_send(req);
|
||||
cJSON_Delete(req);
|
||||
}
|
||||
if (key == 'c') {
|
||||
cJSON *req = make_change_file_request(
|
||||
next_id++, "file:///home/syed/main/cubit/tests/test.c");
|
||||
lsp_send(req);
|
||||
cJSON_Delete(req);
|
||||
}
|
||||
cJSON *resp;
|
||||
if ((resp = lsp_recv()) == NULL)
|
||||
continue;
|
||||
char *dump = cJSON_PrintUnformatted(resp);
|
||||
cJSON *id = cJSON_GetObjectItem(resp, "id");
|
||||
// if (id != NULL && id->valueint == 40) {
|
||||
// cJSON *result = cJSON_GetObjectItem(resp, "result");
|
||||
// cJSON *contents = cJSON_GetObjectItem(result, "contents");
|
||||
// cJSON *value = cJSON_GetObjectItem(contents, "value");
|
||||
// if (value && value->type == cJSON_String)
|
||||
// printf("%s\n", value->valuestring);
|
||||
// }
|
||||
print_wrapped(dump);
|
||||
free(dump);
|
||||
cJSON_Delete(resp);
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
BIN
tests/regex
Executable file
BIN
tests/regex
Executable file
Binary file not shown.
87
tests/regex.c
Normal file
87
tests/regex.c
Normal file
@@ -0,0 +1,87 @@
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
|
||||
#include <pcre2.h>
|
||||
#include <stdio.h>
|
||||
#include <string.h>
|
||||
|
||||
typedef struct Pattern {
|
||||
const char *name;
|
||||
pcre2_code *re;
|
||||
} Pattern;
|
||||
|
||||
char *read_file(const char *filename) {
|
||||
FILE *fp = fopen(filename, "rb");
|
||||
if (!fp) {
|
||||
perror("fopen");
|
||||
return NULL;
|
||||
}
|
||||
fseek(fp, 0, SEEK_END);
|
||||
long size = ftell(fp);
|
||||
rewind(fp);
|
||||
char *buffer = malloc(size + 1);
|
||||
if (!buffer) {
|
||||
perror("malloc");
|
||||
fclose(fp);
|
||||
return NULL;
|
||||
}
|
||||
size_t read = fread(buffer, 1, size, fp);
|
||||
buffer[read] = '\0';
|
||||
fclose(fp);
|
||||
return buffer;
|
||||
}
|
||||
|
||||
int main() {
|
||||
const char *pattern = "\\b(class)\\s+(([a-zA-Z0-9_]+)((::[a-zA-Z0-9_]+)*))"
|
||||
"\\s*((<)\\s*(([a-zA-Z0-9_]+)((::[a-zA-Z0-9_]+)*))?)?";
|
||||
const char *input = read_file("/home/syed/main/cubit/tests/test.rb");
|
||||
|
||||
int errorcode;
|
||||
PCRE2_SIZE erroroffset;
|
||||
|
||||
// Compile the regex
|
||||
pcre2_code *re = pcre2_compile((PCRE2_SPTR)pattern, // pattern
|
||||
PCRE2_ZERO_TERMINATED, // pattern length
|
||||
0, // options
|
||||
&errorcode, // for error code
|
||||
&erroroffset, // for error offset
|
||||
NULL // compile context
|
||||
);
|
||||
|
||||
if (!re) {
|
||||
PCRE2_UCHAR buffer[256];
|
||||
pcre2_get_error_message(errorcode, buffer, sizeof(buffer));
|
||||
fprintf(stderr, "PCRE2 compilation failed at offset %d: %s\n",
|
||||
(int)erroroffset, buffer);
|
||||
return 1;
|
||||
}
|
||||
|
||||
// Prepare match data
|
||||
pcre2_match_data *match_data = pcre2_match_data_create_from_pattern(re, NULL);
|
||||
|
||||
int rc = pcre2_match(re, // the compiled pattern
|
||||
(PCRE2_SPTR)input, // subject string
|
||||
strlen(input), // length of subject
|
||||
0, // start offset
|
||||
0, // options
|
||||
match_data, // match data
|
||||
NULL // match context
|
||||
);
|
||||
|
||||
if (rc < 0) {
|
||||
printf("No match found.\n");
|
||||
} else {
|
||||
PCRE2_SIZE *ovector = pcre2_get_ovector_pointer(match_data);
|
||||
for (int i = 0; i < rc; i++) {
|
||||
PCRE2_SIZE start = ovector[2 * i];
|
||||
PCRE2_SIZE end = ovector[2 * i + 1];
|
||||
printf("Group %d: start=%lu end=%lu text=\"%.*s\"\n", i, start, end,
|
||||
(int)(end - start), input + start);
|
||||
}
|
||||
}
|
||||
|
||||
// Cleanup
|
||||
pcre2_match_data_free(match_data);
|
||||
pcre2_code_free(re);
|
||||
|
||||
return 0;
|
||||
}
|
4
tests/scratch.c
Normal file
4
tests/scratch.c
Normal file
@@ -0,0 +1,4 @@
|
||||
#include <tree_sitter/api.h>
|
||||
|
||||
TSRange *ts_tree_get_changed_ranges(const TSTree *old_tree,
|
||||
const TSTree *new_tree, uint32_t *length);
|
96
tests/test.c
Normal file
96
tests/test.c
Normal file
@@ -0,0 +1,96 @@
|
||||
#include "../libs/tree-sitter-ruby/bindings/c/tree-sitter-ruby.h"
|
||||
#include "../libs/tree-sitter/lib/include/tree_sitter/api.h"
|
||||
#include <stdint.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <string.h>
|
||||
|
||||
static void die(const char *msg) {
|
||||
fprintf(stderr, "error: %s\n", msg);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
static char *read_stdin(size_t *out_len) {
|
||||
size_t cap = 4096, len = 0;
|
||||
char *buf = (char *)malloc(cap);
|
||||
if (!buf)
|
||||
die("malloc failed");
|
||||
size_t n;
|
||||
while (!feof(stdin)) {
|
||||
if (cap - len < 2048) {
|
||||
cap *= 2;
|
||||
char *tmp = (char *)realloc(buf, cap);
|
||||
if (!tmp) {
|
||||
free(buf);
|
||||
die("realloc failed");
|
||||
}
|
||||
buf = tmp;
|
||||
}
|
||||
n = fread(buf + len, 1, cap - len, stdin);
|
||||
len += n;
|
||||
}
|
||||
// ensure NUL-terminated for convenience
|
||||
if (len == cap) {
|
||||
char *tmp = (char *)realloc(buf, cap + 1);
|
||||
if (!tmp) {
|
||||
free(buf);
|
||||
die("realloc term failed");
|
||||
}
|
||||
buf = tmp;
|
||||
}
|
||||
buf[len] = '\0';
|
||||
if (out_len)
|
||||
*out_len = len;
|
||||
return buf;
|
||||
}
|
||||
|
||||
static void walk(TSNode node, int depth) {
|
||||
uint16_t sym = ts_node_symbol(node);
|
||||
const char *type = ts_node_type(node);
|
||||
for (int i = 0; i < depth; i++)
|
||||
putchar(' ');
|
||||
uint32_t n = ts_node_child_count(node);
|
||||
printf("#define SYM_%s %u\n", type, sym);
|
||||
for (uint32_t i = 0; i < n; i++) {
|
||||
TSNode child = ts_node_child(node, i);
|
||||
walk(child, depth + 2);
|
||||
}
|
||||
}
|
||||
|
||||
int main(void) {
|
||||
size_t len = 0;
|
||||
char *src = read_stdin(&len);
|
||||
if (len == 0) {
|
||||
fprintf(stderr,
|
||||
"No input on stdin. Paste some Ruby code and press Ctrl-D.\n");
|
||||
free(src);
|
||||
return 1;
|
||||
}
|
||||
|
||||
TSParser *parser = ts_parser_new();
|
||||
if (!parser) {
|
||||
free(src);
|
||||
die("ts_parser_new failed");
|
||||
}
|
||||
|
||||
if (!ts_parser_set_language(parser, tree_sitter_ruby())) {
|
||||
ts_parser_delete(parser);
|
||||
free(src);
|
||||
die("ts_parser_set_language failed (is tree-sitter-ruby linked?)");
|
||||
}
|
||||
|
||||
TSTree *tree = ts_parser_parse_string(parser, NULL, src, (uint32_t)len);
|
||||
if (!tree) {
|
||||
ts_parser_delete(parser);
|
||||
free(src);
|
||||
die("parse failed");
|
||||
}
|
||||
|
||||
TSNode root = ts_tree_root_node(tree);
|
||||
walk(root, 0);
|
||||
|
||||
ts_tree_delete(tree);
|
||||
ts_parser_delete(parser);
|
||||
free(src);
|
||||
return 0;
|
||||
}
|
574
tests/test.rb
Normal file
574
tests/test.rb
Normal file
@@ -0,0 +1,574 @@
|
||||
# kitchen_sink.rb — a big Ruby syntax exercise file
|
||||
|
||||
# --- Constants, globals, class vars, instance vars
|
||||
$global_var = :glob
|
||||
GLOBAL_CONST = 123
|
||||
AnotherConst = { a: 1, b: 2 }
|
||||
|
||||
# --- Basic literals
|
||||
int_lit = 42
|
||||
float_lit = 3.1415
|
||||
big_int = 123_456_789_012_345_678
|
||||
ratio = Rational(2, 3)
|
||||
cplx = Complex(2, -5)
|
||||
bool_t = true
|
||||
bool_f = false
|
||||
nil_val = nil
|
||||
|
||||
|
||||
# --- Strings and symbols
|
||||
s1 = "double-quoted string with escape\nand interpolation: #{int_lit}"
|
||||
s2 = 'single-quoted string with \\n literal'
|
||||
s3 = %Q{interpolated %Q string #{1 + 1}}
|
||||
s4 = %q{non-interpolated %q string}
|
||||
sym1 = :symbol
|
||||
sym2 = :"spaced symbol"
|
||||
sym3 = %s{another_symbol}
|
||||
|
||||
# --- Heredocs
|
||||
heredoc1 = <<~RUBY
|
||||
def sample(x)
|
||||
x * 2
|
||||
end
|
||||
RUBY
|
||||
|
||||
heredoc2 = <<-'TXT'
|
||||
literal heredoc with \n not interpreted
|
||||
TXT
|
||||
|
||||
# --- Regex, ranges
|
||||
re = /foo\d+/i
|
||||
re2 = %r{^/api/v1/.*$}
|
||||
range_inc = 1..5
|
||||
range_exc = 1...5
|
||||
char_range = 'a'..'f'
|
||||
|
||||
# --- Arrays, hashes, keywords splats
|
||||
arr = [1, 2, 3, *[4, 5], 6]
|
||||
hsh = { a: 1, 'b' => 2, **{ c: 3 } }
|
||||
nested = [{ x: [1, 2, { y: :z }] }, 99]
|
||||
|
||||
# --- Multiple assignment / destructuring
|
||||
x, y, z = 1, 2, 3
|
||||
(a1, (a2, a3)), a4 = [10, [20, 30]], 40
|
||||
|
||||
# --- Procs, lambdas, blocks
|
||||
pr = Proc.new { |u| u * 2 }
|
||||
lm = ->(u) { u + 3 }
|
||||
|
||||
def takes_block(a, b)
|
||||
yield a + b if block_given?
|
||||
end
|
||||
|
||||
res1 = takes_block(2, 3) { |v| v * 10 }
|
||||
|
||||
# --- Enumerators
|
||||
enum = [1, 2, 3].map
|
||||
enum.each { |e| e * 2 }
|
||||
(1..5).lazy.map { |i| i * i }.take(3).force
|
||||
|
||||
# --- Control flow
|
||||
if int_lit > 40
|
||||
msg = "greater"
|
||||
elsif int_lit == 40
|
||||
msg = "equal"
|
||||
else
|
||||
msg = "less"
|
||||
end
|
||||
|
||||
msg2 = "ok" unless bool_f
|
||||
msg3 = "not ok" if !bool_t
|
||||
|
||||
case int_lit
|
||||
when 1..10
|
||||
range_case = :small
|
||||
when 11, 42
|
||||
range_case = :special
|
||||
else
|
||||
range_case = :other
|
||||
end
|
||||
|
||||
i = 0
|
||||
while i < 3
|
||||
i += 1
|
||||
end
|
||||
|
||||
j = 3
|
||||
until j == 0
|
||||
j -= 1
|
||||
end
|
||||
|
||||
for k in [1, 2, 3]
|
||||
k
|
||||
end
|
||||
|
||||
loop do
|
||||
break
|
||||
end
|
||||
|
||||
# redo example (rare!)
|
||||
redo_counter = 0
|
||||
[1, 2, 3].each do |n|
|
||||
redo_counter += 1
|
||||
redo if redo_counter < 1 && n == 1
|
||||
end
|
||||
|
||||
# --- Methods: positional, defaults, keyword, splats
|
||||
def args_demo(a, b = 2, *rest, c:, d: 4, **kw, &blk)
|
||||
blk.call(a + b + (c || 0) + d) if blk
|
||||
[a, b, rest, c, d, kw]
|
||||
end
|
||||
|
||||
args_demo(1, 2, 3, 4, c: 10, d: 20, e: 99) { |sum| sum }
|
||||
|
||||
# --- Pattern matching (Ruby 2.7+)
|
||||
person = { name: "Ada", age: 31, meta: { tags: %w[ruby math] } }
|
||||
|
||||
case person
|
||||
in { name:, age: 31, meta: { tags: [first_tag, *rest_tags] } }
|
||||
matched = [name, first_tag, rest_tags]
|
||||
else
|
||||
matched = :no
|
||||
end
|
||||
|
||||
# pin operator
|
||||
expected = 31
|
||||
case person
|
||||
in { age: ^expected }
|
||||
pinned = :ok
|
||||
else
|
||||
pinned = :nope
|
||||
end
|
||||
|
||||
# array patterns
|
||||
arr_pat = [1, 2, 3, 4]
|
||||
case arr_pat
|
||||
in [first, 2, *tail]
|
||||
pattern_arr = [first, tail]
|
||||
else
|
||||
pattern_arr = []
|
||||
end
|
||||
|
||||
# --- Modules, mixins, refinements
|
||||
module Mixin
|
||||
def mixed_in
|
||||
:mixed
|
||||
end
|
||||
end
|
||||
|
||||
module OtherMixin
|
||||
def other_mixed
|
||||
:other
|
||||
end
|
||||
end
|
||||
|
||||
module RefinementExample
|
||||
refine String do
|
||||
def shout
|
||||
upcase + "!"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
using RefinementExample
|
||||
refined_val = "hey".shout
|
||||
|
||||
# --- Classes, attrs, visibility, singleton methods, eigenclass
|
||||
class Base
|
||||
include Mixin
|
||||
extend OtherMixin
|
||||
|
||||
CONST_IN_CLASS = :klass
|
||||
|
||||
@@class_var = 0
|
||||
@class_inst = :ci
|
||||
|
||||
attr_reader :x
|
||||
attr_writer :y
|
||||
attr_accessor :z
|
||||
|
||||
def initialize(x, y: 0, **kw)
|
||||
@x = x
|
||||
@y = y
|
||||
@z = kw[:z]
|
||||
end
|
||||
|
||||
def self.build(*a, **k)
|
||||
new(*a, **k)
|
||||
end
|
||||
|
||||
class << self
|
||||
def in_eigen
|
||||
:eigen
|
||||
end
|
||||
end
|
||||
|
||||
def public_m
|
||||
:public_method
|
||||
end
|
||||
|
||||
protected
|
||||
|
||||
def prot_m; :protected_method end
|
||||
|
||||
private
|
||||
|
||||
def priv_m; :private_method end
|
||||
end
|
||||
|
||||
obj = Base.build(10, y: 20, z: 30)
|
||||
obj.z = 99
|
||||
_ = obj.mixed_in
|
||||
_ = Base.other_mixed
|
||||
_ = Base.in_eigen
|
||||
|
||||
def obj.singleton_thing
|
||||
:single
|
||||
end
|
||||
|
||||
# --- Inheritance, super, prepend
|
||||
module PrependDemo
|
||||
def public_m
|
||||
[:prepended, super]
|
||||
end
|
||||
end
|
||||
|
||||
class Child < Base
|
||||
prepend PrependDemo
|
||||
|
||||
def initialize(x, y: 0, **kw)
|
||||
super
|
||||
@child_only = true
|
||||
end
|
||||
end
|
||||
|
||||
child = Child.new(1, y: 2, z: 3)
|
||||
_ = child.public_m
|
||||
|
||||
# --- Structs and OpenStruct-like
|
||||
Point = Struct.new(:x, :y) do
|
||||
def length
|
||||
Math.sqrt(x * x + y * y)
|
||||
end
|
||||
end
|
||||
|
||||
p0 = Point.new(3, 4)
|
||||
_ = p0.length
|
||||
|
||||
# --- Operators as methods
|
||||
class Vec
|
||||
attr_reader :x, :y
|
||||
def initialize(x, y) @x, @y = x, y end
|
||||
def +(o) Vec.new(x + o.x, y + o.y) end
|
||||
def -@( ) Vec.new(-x, -y) end
|
||||
def [](i) i == 0 ? x : y end
|
||||
def []=(i, v) i == 0 ? @x = v : @y = v end
|
||||
def to_s() "(#{x},#{y})" end
|
||||
end
|
||||
|
||||
v1 = Vec.new(1, 2)
|
||||
v2 = Vec.new(3, 4)
|
||||
v3 = v1 + v2
|
||||
v4 = -v3
|
||||
_ = v4[0]
|
||||
v4[1] = 99
|
||||
|
||||
# --- method_missing / respond_to_missing?
|
||||
class Ghost
|
||||
def method_missing(name, *a, **k, &b)
|
||||
return :dynamic if name.to_s.start_with?("dyn_")
|
||||
super
|
||||
end
|
||||
def respond_to_missing?(name, inc = false)
|
||||
name.to_s.start_with?("dyn_") || super
|
||||
end
|
||||
end
|
||||
|
||||
g = Ghost.new
|
||||
_ = g.dyn_hello
|
||||
|
||||
# --- Exception handling
|
||||
def risky(x)
|
||||
raise ArgumentError, "bad!" if x < 0
|
||||
x * 2
|
||||
rescue ArgumentError => e
|
||||
:rescued
|
||||
ensure
|
||||
:ensured
|
||||
end
|
||||
|
||||
begin
|
||||
risky(-1)
|
||||
rescue => e
|
||||
# retry demo
|
||||
@tries ||= 0
|
||||
@tries += 1
|
||||
retry if @tries < 1
|
||||
ensure
|
||||
noop = :done
|
||||
end
|
||||
|
||||
# --- Fibers and Threads
|
||||
fib = Fiber.new do
|
||||
a = Fiber.yield 1
|
||||
b = Fiber.yield a + 1
|
||||
a + b
|
||||
end
|
||||
f1 = fib.resume
|
||||
f2 = fib.resume(10)
|
||||
f3 = fib.resume(5)
|
||||
|
||||
t = Thread.new { (1..3).map { |n| n * n } }
|
||||
thr_val = t.value
|
||||
|
||||
# --- Backticks, %x, system, __FILE__/__LINE__/__ENCODING__
|
||||
file_meta = [__FILE__, __LINE__, __ENCODING__]
|
||||
# cmd_out = `echo hi` # (avoid side-effects in a test file)
|
||||
# cmd2 = %x(printf "%s" hello)
|
||||
# system("true")
|
||||
|
||||
# --- Keyword lists, %w/%i
|
||||
words = %w[alpha beta gamma]
|
||||
syms = %i[al be ga]
|
||||
|
||||
# --- Hash with default proc
|
||||
count = Hash.new { |h, k| h[k] = 0 }
|
||||
%w[a b a c b a].each { |ch| count[ch] += 1 }
|
||||
|
||||
# --- Freeze, dup, clone
|
||||
frozen = "abc".freeze
|
||||
duped = frozen.dup
|
||||
cloned = frozen.clone rescue :cloned
|
||||
|
||||
# --- Marshaling (basic)
|
||||
dumped = Marshal.dump([1, 2, 3])
|
||||
loaded = Marshal.load(dumped)
|
||||
|
||||
# --- Encoding / force_encoding
|
||||
utf = "héllo"
|
||||
forced = utf.dup.force_encoding("ASCII-8BIT")
|
||||
|
||||
# --- Numeric methods, time
|
||||
num = 12.5.round
|
||||
now = Time.now
|
||||
later = now + 60
|
||||
|
||||
# --- Random, ranges, case equality ===
|
||||
rng = Random.new(1234)
|
||||
rand_val = rng.rand(1..10)
|
||||
in_range = (1..10) === rand_val
|
||||
|
||||
# --- Refinement scope check
|
||||
using RefinementExample
|
||||
refined_again = "yo".shout
|
||||
|
||||
# --- %r advanced, named captures
|
||||
md = /(?<word>\w+)-(?<num>\d+)/.match("abc-123")
|
||||
cap_word = md[:word] if md
|
||||
|
||||
# --- BEGIN/END blocks
|
||||
BEGIN {
|
||||
# Runs before everything (when file is loaded)
|
||||
_begin_marker = :begin_block
|
||||
}
|
||||
|
||||
END {
|
||||
# Runs at process exit
|
||||
_end_marker = :end_block
|
||||
}
|
||||
|
||||
# --- Numeric literals: hex, oct, bin
|
||||
hex = 0xff
|
||||
oct = 0o755
|
||||
bin = 0b101010
|
||||
|
||||
# --- Case with guards
|
||||
val = 10
|
||||
case val
|
||||
when Integer if val.even?
|
||||
case_guard = :even_int
|
||||
else
|
||||
case_guard = :other
|
||||
end
|
||||
|
||||
# --- Ternary, safe nav, assignment forms
|
||||
tern = val > 5 ? :big : :small
|
||||
safe_len = nil&.to_s&.length
|
||||
x_plus_eq = 5
|
||||
x_plus_eq += 3
|
||||
|
||||
# --- Ranges of strings, flip-flop (obscure)
|
||||
str_rng = "a".."z"
|
||||
ff_out = []
|
||||
i = 0
|
||||
(1..20).each do |n|
|
||||
i += 1 if (n == 3)..(n == 6) # flip-flop
|
||||
ff_out << i
|
||||
end
|
||||
|
||||
# --- Here-doc with interpolation & indentation
|
||||
name = "World"
|
||||
msg_hd = <<~MSG
|
||||
Hello, #{name}!
|
||||
The time is #{Time.now}
|
||||
MSG
|
||||
|
||||
# --- Files (read-only sample)
|
||||
# File.read(__FILE__) rescue nil
|
||||
|
||||
# --- Refinements: nested using
|
||||
module Outer
|
||||
module Ref
|
||||
refine Integer do
|
||||
def add2; self + 2 end
|
||||
end
|
||||
end
|
||||
def self.demo
|
||||
using Ref
|
||||
5.add2
|
||||
end
|
||||
end
|
||||
_ = Outer.demo
|
||||
|
||||
# --- Keyword argument forwarding (Ruby 3)
|
||||
def kf(**kw) kw end
|
||||
def wrapper(**kw) kf(**kw) end
|
||||
_ = wrapper(a: 1, b: 2)
|
||||
|
||||
# --- Pattern matching nested hashes and arrays again
|
||||
data = {
|
||||
user: { id: 1, name: "Ada", roles: %w[admin editor] },
|
||||
meta: { active: true }
|
||||
}
|
||||
|
||||
case data
|
||||
in { user: { id:, name:, roles: ["admin", *rest] }, meta: { active: true } }
|
||||
matched_user = [id, name, rest]
|
||||
else
|
||||
matched_user = nil
|
||||
end
|
||||
|
||||
# --- Anonymous class & module
|
||||
Anon = Class.new do
|
||||
def call; :anon end
|
||||
end
|
||||
ModAnon = Module.new do
|
||||
def mod_call; :mod_anon end
|
||||
end
|
||||
class Anon
|
||||
include ModAnon
|
||||
end
|
||||
_ = Anon.new.call
|
||||
_ = Anon.new.mod_call
|
||||
|
||||
# --- Numeric refinements w/ prepend class method hook
|
||||
module P
|
||||
def self.prepended(klass); @hooked = klass end
|
||||
def to_s; "P(#{super})" end
|
||||
end
|
||||
|
||||
class NWrap
|
||||
prepend P
|
||||
def to_s; "NWrap" end
|
||||
end
|
||||
|
||||
_ = NWrap.new.to_s
|
||||
|
||||
# --- Case equality custom
|
||||
class Matcher
|
||||
def ===(other) other.is_a?(String) && other.start_with?("ok") end
|
||||
end
|
||||
|
||||
case "ok-go"
|
||||
when Matcher.new
|
||||
_ = :matched
|
||||
end
|
||||
|
||||
# --- Frozen string literal magic comment (simulated usage)
|
||||
fsl = +"mutable" # unary + dup
|
||||
|
||||
# --- Rescue modifier
|
||||
risky_value = (1 / 0) rescue :div_by_zero
|
||||
|
||||
# --- tap/yield_self/then
|
||||
tapped = { a: 1 }.tap { |h| h[:b] = 2 }
|
||||
ys = 5.yield_self { |n| n * 10 }
|
||||
thn = 10.then { |n| n + 1 }
|
||||
|
||||
# --- Method aliasing
|
||||
class AliasDemo
|
||||
def orig; :orig end
|
||||
alias :alt :orig
|
||||
end
|
||||
_ = AliasDemo.new.alt
|
||||
|
||||
# --- Method visibility change
|
||||
class VisDemo
|
||||
def a; :a end
|
||||
def b; :b end
|
||||
private :b
|
||||
end
|
||||
|
||||
# --- Keyword args + default nil + double splat
|
||||
def kcombo(a, b: nil, **opt) [a, b, opt] end
|
||||
_ = kcombo(5, b: 7, c: 9)
|
||||
|
||||
# --- Hash pattern with defaults via fetch
|
||||
hdef = { foo: 1 }
|
||||
hdef_v = hdef.fetch(:bar, :default)
|
||||
|
||||
# --- Refinement + method lookup sanity
|
||||
module NumRef
|
||||
refine Numeric do
|
||||
def sq; self * self end
|
||||
end
|
||||
end
|
||||
using NumRef
|
||||
_ = 6.sq
|
||||
|
||||
# --- rescue with multiple types
|
||||
begin
|
||||
raise IOError, "io"
|
||||
rescue SystemCallError, IOError => e
|
||||
@got = e.class
|
||||
end
|
||||
|
||||
# --- ensure altering outer variable
|
||||
outer = 0
|
||||
begin
|
||||
outer = 1
|
||||
ensure
|
||||
outer += 1
|
||||
end
|
||||
|
||||
# --- Symbol to proc, &: syntax
|
||||
doubled = [1, 2, 3].map(&:to_s)
|
||||
|
||||
# --- Safe pattern matching nil
|
||||
case nil
|
||||
in Integer
|
||||
:nope
|
||||
else
|
||||
:ok_nil
|
||||
end
|
||||
|
||||
# --- Hash literal with trailing comma, labeled args-like
|
||||
cfg = {
|
||||
host: "localhost",
|
||||
port: 5432,
|
||||
}
|
||||
|
||||
# --- Numeric separators and exponents
|
||||
exp = 1.23e-4
|
||||
big = 1_000_000
|
||||
|
||||
# --- %i/%w with interpolation (only in %W)
|
||||
inter = "X"
|
||||
words2 = %W[a b #{inter}]
|
||||
|
||||
# --- Dir, ENV (read only)
|
||||
_ = Dir.pwd
|
||||
_ = ENV["SHELL"]
|
||||
|
||||
# --- END of the kitchen sink
|
||||
puts "Loaded kitchen_sink.rb with many Ruby constructs." if __FILE__ == $0
|
BIN
tests/ts_ruby_dump
Executable file
BIN
tests/ts_ruby_dump
Executable file
Binary file not shown.
Reference in New Issue
Block a user