mirror of
https://github.com/janet-lang/janet
synced 2025-11-10 20:43:05 +00:00
Compare commits
31 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b2169e0d1 | ||
|
|
2c927ea768 | ||
|
|
f4bbcdcbc8 | ||
|
|
79c375b1af | ||
|
|
f443a3b3a1 | ||
|
|
684d2d63f4 | ||
|
|
1900d8f843 | ||
|
|
3c2af95d21 | ||
|
|
b35414ea0f | ||
|
|
fb5b056f7b | ||
|
|
7248c1dfdb | ||
|
|
4c7ea9e893 | ||
|
|
c7801ce277 | ||
|
|
f741a8e3ff | ||
|
|
6a92e8b609 | ||
|
|
9da91a8217 | ||
|
|
69853c8e5c | ||
|
|
1f41b6c138 | ||
|
|
e001efa9fd | ||
|
|
435e64d4cf | ||
|
|
f296c8f5fb | ||
|
|
8d0e6ed32f | ||
|
|
b6a36afffe | ||
|
|
e422abc269 | ||
|
|
221d71d07b | ||
|
|
9f35f0837e | ||
|
|
515891b035 | ||
|
|
94a506876f | ||
|
|
9bde57854a | ||
|
|
f456369941 | ||
|
|
e0b7533c39 |
@@ -13,7 +13,7 @@ tasks:
|
||||
gmake test-install
|
||||
- meson_min: |
|
||||
cd janet
|
||||
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dtyped_array=false -Dreduced_os=true -Dffi=false
|
||||
meson setup build_meson_min --buildtype=release -Dsingle_threaded=true -Dnanbox=false -Ddynamic_modules=false -Ddocstrings=false -Dnet=false -Dsourcemaps=false -Dpeg=false -Dassembler=false -Dint_types=false -Dreduced_os=true -Dffi=false
|
||||
cd build_meson_min
|
||||
ninja
|
||||
- meson_prf: |
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
# Changelog
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
## 1.23.1 - ???
|
||||
- Don't process shared object names passed to dlopen.
|
||||
- Add better support for windows console in the default shell.c for autocompletion and
|
||||
other shell-like input features.
|
||||
- Improve default error message from `assert`.
|
||||
- Add the `tabseq` macro for simpler table comprehensions.
|
||||
- Allow setting `(dyn :task-id)` in fibers to improve context in supervisor messages. Prior to
|
||||
this change, supverisor messages over threaded channels would be from ambiguous threads/fibers.
|
||||
|
||||
## 1.23.0 - 2022-06-20
|
||||
- Add experimental `ffi/` module for interfacing with dynamic libraries and raw function pointers. Only available
|
||||
on 64 bit linux, mac, and bsd systems.
|
||||
|
||||
6
Makefile
6
Makefile
@@ -1,4 +1,4 @@
|
||||
# Copyright (c) 2021 Calvin Rose
|
||||
# Copyright (c) 2022 Calvin Rose
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
@@ -283,7 +283,7 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc
|
||||
cp $(JANET_TARGET) '$(DESTDIR)$(BINDIR)/janet'
|
||||
mkdir -p '$(DESTDIR)$(INCLUDEDIR)/janet'
|
||||
cp -r build/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet'
|
||||
ln -sf '$(DESTDIR)$(INCLUDEDIR)/janet/janet.h' '$(DESTDIR)$(INCLUDEDIR)/janet.h'
|
||||
ln -sf -T ./janet/janet.h '$(DESTDIR)$(INCLUDEDIR)/janet.h' || true #fixme bsd
|
||||
mkdir -p '$(DESTDIR)$(JANET_PATH)'
|
||||
mkdir -p '$(DESTDIR)$(LIBDIR)'
|
||||
if test $(UNAME) = Darwin ; then \
|
||||
@@ -300,7 +300,7 @@ install: $(JANET_TARGET) $(JANET_LIBRARY) $(JANET_STATIC_LIBRARY) build/janet.pc
|
||||
cp janet.1 '$(DESTDIR)$(JANET_MANPATH)'
|
||||
mkdir -p '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)'
|
||||
cp build/janet.pc '$(DESTDIR)$(JANET_PKG_CONFIG_PATH)/janet.pc'
|
||||
[ -z '$(DESTDIR)' ] && $(LDCONFIG) || true
|
||||
[ -z '$(DESTDIR)' ] && $(LDCONFIG) || echo "You can ignore this error for non-Linux systems or local installs"
|
||||
|
||||
install-jpm-git: $(JANET_TARGET)
|
||||
mkdir -p build
|
||||
|
||||
@@ -20,7 +20,7 @@
|
||||
|
||||
project('janet', 'c',
|
||||
default_options : ['c_std=c99', 'build.c_std=c99', 'b_lundef=false', 'default_library=both'],
|
||||
version : '1.23.0')
|
||||
version : '1.23.1')
|
||||
|
||||
# Global settings
|
||||
janet_path = join_paths(get_option('prefix'), get_option('libdir'), 'janet')
|
||||
|
||||
@@ -76,6 +76,11 @@
|
||||
[name & more]
|
||||
~(var ,name :private ,;more))
|
||||
|
||||
(defmacro toggle
|
||||
"Set a value to its boolean inverse. Same as `(set value (not value))`."
|
||||
[value]
|
||||
~(set ,value (,not ,value)))
|
||||
|
||||
(defn defglobal
|
||||
"Dynamically create a global def."
|
||||
[name value]
|
||||
@@ -158,7 +163,7 @@
|
||||
(def ,v ,x)
|
||||
(if ,v
|
||||
,v
|
||||
(,error ,(if err err "assert failure")))))
|
||||
(,error ,(if err err (string/format "assert failure in %j" x))))))
|
||||
|
||||
(defn errorf
|
||||
"A combination of `error` and `string/format`. Equivalent to `(error (string/format fmt ;args))`."
|
||||
@@ -606,13 +611,20 @@
|
||||
See `loop` for details.``
|
||||
[head & body]
|
||||
(def $accum (gensym))
|
||||
~(do (def ,$accum @[]) (loop ,head (array/push ,$accum (do ,;body))) ,$accum))
|
||||
~(do (def ,$accum @[]) (loop ,head (,array/push ,$accum (do ,;body))) ,$accum))
|
||||
|
||||
(defmacro tabseq
|
||||
``Similar to `loop`, but accumulates key value pairs into a table.
|
||||
See `loop` for details.``
|
||||
[head key-body & value-body]
|
||||
(def $accum (gensym))
|
||||
~(do (def ,$accum @{}) (loop ,head (,put ,$accum ,key-body (do ,;value-body))) ,$accum))
|
||||
|
||||
(defmacro generate
|
||||
``Create a generator expression using the `loop` syntax. Returns a fiber
|
||||
that yields all values inside the loop in order. See `loop` for details.``
|
||||
[head & body]
|
||||
~(fiber/new (fn [] (loop ,head (yield (do ,;body)))) :yi))
|
||||
~(,fiber/new (fn [] (loop ,head (yield (do ,;body)))) :yi))
|
||||
|
||||
(defmacro coro
|
||||
"A wrapper for making fibers that may yield multiple values (coroutine). Same as `(fiber/new (fn [] ;body) :yi)`."
|
||||
@@ -2768,13 +2780,13 @@
|
||||
(def c ((:where p) 0))
|
||||
(def prpt (string "debug[" level "]:" c ":" status "> "))
|
||||
(getline prpt buf nextenv))
|
||||
(print "entering debug[" level "] - (quit) to exit")
|
||||
(eprint "entering debug[" level "] - (quit) to exit")
|
||||
(flush)
|
||||
(run-context
|
||||
{:chunks debugger-chunks
|
||||
:on-status (debugger-on-status-var nextenv (+ 1 level) true)
|
||||
:env nextenv})
|
||||
(print "exiting debug[" level "]")
|
||||
(eprint "exiting debug[" level "]")
|
||||
(flush)
|
||||
(nextenv :resume-value))
|
||||
|
||||
@@ -3013,7 +3025,7 @@
|
||||
:italics ["*" "*"]
|
||||
:bold ["**" "**"]}))
|
||||
(def modes @{})
|
||||
(defn toggle [mode]
|
||||
(defn toggle-mode [mode]
|
||||
(def active (get modes mode))
|
||||
(def delims (get delimiters mode))
|
||||
(put modes mode (not active))
|
||||
@@ -3123,7 +3135,7 @@
|
||||
(def token @"")
|
||||
(var token-length 0)
|
||||
(defn delim [mode]
|
||||
(def d (toggle mode))
|
||||
(def d (toggle-mode mode))
|
||||
(if-not has-color (+= token-length (length d)))
|
||||
(buffer/push token d))
|
||||
(defn endtoken []
|
||||
@@ -3134,16 +3146,18 @@
|
||||
(def b (get line i))
|
||||
(cond
|
||||
(or (= b (chr "\n")) (= b (chr " "))) (endtoken)
|
||||
(= b (chr `\`)) (do
|
||||
(++ token-length)
|
||||
(buffer/push token (get line (++ i))))
|
||||
(= b (chr "_")) (delim :underline)
|
||||
(= b (chr "`")) (delim :code)
|
||||
(= b (chr "*"))
|
||||
(if (= (chr "*") (get line (+ i 1)))
|
||||
(do (++ i)
|
||||
(delim :bold))
|
||||
(delim :italics))
|
||||
(not (modes :code)) (cond
|
||||
(= b (chr `\`)) (do
|
||||
(++ token-length)
|
||||
(buffer/push token (get line (++ i))))
|
||||
(= b (chr "_")) (delim :underline)
|
||||
(= b (chr "*"))
|
||||
(if (= (chr "*") (get line (+ i 1)))
|
||||
(do (++ i)
|
||||
(delim :bold))
|
||||
(delim :italics))
|
||||
(do (++ token-length) (buffer/push token b)))
|
||||
(do (++ token-length) (buffer/push token b))))
|
||||
(endtoken)
|
||||
(tuple/slice tokens))
|
||||
|
||||
@@ -5,9 +5,9 @@
|
||||
|
||||
#define JANET_VERSION_MAJOR 1
|
||||
#define JANET_VERSION_MINOR 23
|
||||
#define JANET_VERSION_PATCH 0
|
||||
#define JANET_VERSION_EXTRA ""
|
||||
#define JANET_VERSION "1.23.0"
|
||||
#define JANET_VERSION_PATCH 1
|
||||
#define JANET_VERSION_EXTRA "-dev"
|
||||
#define JANET_VERSION "1.23.1-dev"
|
||||
|
||||
/* #define JANET_BUILD "local" */
|
||||
|
||||
|
||||
@@ -535,10 +535,15 @@ static int janet_channel_push(JanetChannel *channel, Janet x, int mode);
|
||||
static int janet_channel_pop(JanetChannel *channel, Janet *item, int is_choice);
|
||||
|
||||
static Janet make_supervisor_event(const char *name, JanetFiber *fiber, int threaded) {
|
||||
Janet tup[2];
|
||||
Janet tup[3];
|
||||
tup[0] = janet_ckeywordv(name);
|
||||
tup[1] = threaded ? fiber->last_value : janet_wrap_fiber(fiber) ;
|
||||
return janet_wrap_tuple(janet_tuple_n(tup, 2));
|
||||
if (fiber->env != NULL) {
|
||||
tup[2] = janet_table_get(fiber->env, janet_ckeywordv("task-id"));
|
||||
} else {
|
||||
tup[2] = janet_wrap_nil();
|
||||
}
|
||||
return janet_wrap_tuple(janet_tuple_n(tup, 3));
|
||||
}
|
||||
|
||||
/* Common init code */
|
||||
@@ -2719,6 +2724,8 @@ JANET_CORE_FN(cfun_ev_go,
|
||||
return janet_wrap_fiber(fiber);
|
||||
}
|
||||
|
||||
#define JANET_THREAD_SUPERVISOR_FLAG 0x100
|
||||
|
||||
/* For ev/thread - Run an interpreter in the new thread. */
|
||||
static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
|
||||
JanetBuffer *buffer = (JanetBuffer *) args.argp;
|
||||
@@ -2741,7 +2748,7 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
|
||||
}
|
||||
|
||||
/* Get supervsior */
|
||||
if (flags & 0x8) {
|
||||
if (flags & JANET_THREAD_SUPERVISOR_FLAG) {
|
||||
Janet sup =
|
||||
janet_unmarshal(nextbytes, endbytes - nextbytes,
|
||||
JANET_MARSHAL_UNSAFE, NULL, &nextbytes);
|
||||
@@ -2793,6 +2800,10 @@ static JanetEVGenericMessage janet_go_thread_subr(JanetEVGenericMessage args) {
|
||||
} else {
|
||||
fiber = janet_unwrap_fiber(fiberv);
|
||||
}
|
||||
if (flags & 0x8) {
|
||||
if (NULL == fiber->env) fiber->env = janet_table(0);
|
||||
janet_table_put(fiber->env, janet_ckeywordv("task-id"), value);
|
||||
}
|
||||
fiber->supervisor_channel = janet_vm.user;
|
||||
janet_schedule(fiber, value);
|
||||
janet_loop();
|
||||
@@ -2837,6 +2848,7 @@ JANET_CORE_FN(cfun_ev_thread,
|
||||
"If you want to run the thread without waiting for a result, pass the `:n` flag to return nil immediately. "
|
||||
"Otherwise, returns nil. Available flags:\n\n"
|
||||
"* `:n` - return immediately\n"
|
||||
"* `:t` - set the task-id of the new thread to value. The task-id is passed in messages to the supervisor channel.\n"
|
||||
"* `:a` - don't copy abstract registry to new thread (performance optimization)\n"
|
||||
"* `:c` - don't copy cfunction registry to new thread (performance optimization)") {
|
||||
janet_arity(argc, 1, 4);
|
||||
@@ -2844,10 +2856,10 @@ JANET_CORE_FN(cfun_ev_thread,
|
||||
if (!janet_checktype(argv[0], JANET_FUNCTION)) janet_getfiber(argv, 0);
|
||||
uint64_t flags = 0;
|
||||
if (argc >= 3) {
|
||||
flags = janet_getflags(argv, 2, "nac");
|
||||
flags = janet_getflags(argv, 2, "nact");
|
||||
}
|
||||
void *supervisor = janet_optabstract(argv, argc, 3, &janet_channel_type, janet_vm.root_fiber->supervisor_channel);
|
||||
if (NULL != supervisor) flags |= 0x8;
|
||||
if (NULL != supervisor) flags |= JANET_THREAD_SUPERVISOR_FLAG;
|
||||
|
||||
/* Marshal arguments for the new thread. */
|
||||
JanetBuffer *buffer = janet_malloc(sizeof(JanetBuffer));
|
||||
@@ -2858,7 +2870,7 @@ JANET_CORE_FN(cfun_ev_thread,
|
||||
if (!(flags & 0x2)) {
|
||||
janet_marshal(buffer, janet_wrap_table(janet_vm.abstract_registry), NULL, JANET_MARSHAL_UNSAFE);
|
||||
}
|
||||
if (flags & 0x8) {
|
||||
if (flags & JANET_THREAD_SUPERVISOR_FLAG) {
|
||||
janet_marshal(buffer, janet_wrap_abstract(supervisor), NULL, JANET_MARSHAL_UNSAFE);
|
||||
}
|
||||
if (!(flags & 0x4)) {
|
||||
|
||||
@@ -1183,9 +1183,7 @@ JANET_CORE_FN(janet_core_raw_native,
|
||||
"Returns a `core/native`.") {
|
||||
janet_arity(argc, 0, 1);
|
||||
const char *path = janet_optcstring(argv, argc, 0, NULL);
|
||||
char *processed_name = (NULL == path) ? NULL : get_processed_name(path);
|
||||
Clib lib = load_clib(processed_name);
|
||||
if (NULL != path && path != processed_name) janet_free(processed_name);
|
||||
Clib lib = load_clib(path);
|
||||
if (!lib) janet_panic(error_clib());
|
||||
JanetAbstractNative *anative = janet_abstract(&janet_native_type, sizeof(JanetAbstractNative));
|
||||
anative->clib = lib;
|
||||
|
||||
@@ -545,6 +545,16 @@ static Janet cfun_io_printf_impl_x(int32_t argc, Janet *argv, int newline,
|
||||
if (newline) janet_buffer_push_u8(buf, '\n');
|
||||
return janet_wrap_nil();
|
||||
}
|
||||
case JANET_FUNCTION: {
|
||||
/* Special case function */
|
||||
JanetFunction *fun = janet_unwrap_function(x);
|
||||
JanetBuffer *buf = janet_buffer(0);
|
||||
janet_buffer_format(buf, fmt, offset, argc, argv);
|
||||
if (newline) janet_buffer_push_u8(buf, '\n');
|
||||
Janet args[1] = { janet_wrap_buffer(buf) };
|
||||
janet_call(fun, 1, args);
|
||||
return janet_wrap_nil();
|
||||
}
|
||||
case JANET_NIL:
|
||||
f = dflt_file;
|
||||
if (f == NULL) janet_panic("cannot print to nil");
|
||||
@@ -684,6 +694,16 @@ void janet_dynprintf(const char *name, FILE *dflt_file, const char *format, ...)
|
||||
janet_buffer_deinit(&buffer);
|
||||
break;
|
||||
}
|
||||
case JANET_FUNCTION: {
|
||||
JanetFunction *fun = janet_unwrap_function(x);
|
||||
int32_t len = 0;
|
||||
while (format[len]) len++;
|
||||
JanetBuffer *buf = janet_buffer(len);
|
||||
janet_formatbv(buf, format, args);
|
||||
Janet args[1] = { janet_wrap_buffer(buf) };
|
||||
janet_call(fun, 1, args);
|
||||
break;
|
||||
}
|
||||
case JANET_BUFFER:
|
||||
janet_formatbv(janet_unwrap_buffer(x), format, args);
|
||||
break;
|
||||
|
||||
@@ -1121,8 +1121,8 @@ JANET_CORE_FN(os_spawn,
|
||||
"Execute a program on the system and return a handle to the process. Otherwise, takes the "
|
||||
"same arguments as `os/execute`. Does not wait for the process. "
|
||||
"For each of the :in, :out, and :err keys to the `env` argument, one "
|
||||
"can also pass in the keyword `:pipe`"
|
||||
"to get streams for standard IO of the subprocess that can be read from and written to."
|
||||
"can also pass in the keyword `:pipe` "
|
||||
"to get streams for standard IO of the subprocess that can be read from and written to. "
|
||||
"The returned value `proc` has the fields :in, :out, :err, :return-code, and "
|
||||
"the additional field :pid on unix-like platforms. Use `(os/proc-wait proc)` to rejoin the "
|
||||
"subprocess or `(os/proc-kill proc)`.") {
|
||||
@@ -2036,23 +2036,23 @@ JANET_CORE_FN(os_open,
|
||||
"Allowed flags are as follows:\n\n"
|
||||
" * :r - open this file for reading\n"
|
||||
" * :w - open this file for writing\n"
|
||||
" * :c - create a new file (O_CREATE)\n"
|
||||
" * :e - fail if the file exists (O_EXCL)\n"
|
||||
" * :t - shorten an existing file to length 0 (O_TRUNC)\n\n"
|
||||
" * :c - create a new file (O\\_CREATE)\n"
|
||||
" * :e - fail if the file exists (O\\_EXCL)\n"
|
||||
" * :t - shorten an existing file to length 0 (O\\_TRUNC)\n\n"
|
||||
"Posix-only flags:\n\n"
|
||||
" * :a - append to a file (O_APPEND)\n"
|
||||
" * :x - O_SYNC\n"
|
||||
" * :C - O_NOCTTY\n\n"
|
||||
" * :a - append to a file (O\\_APPEND)\n"
|
||||
" * :x - O\\_SYNC\n"
|
||||
" * :C - O\\_NOCTTY\n\n"
|
||||
"Windows-only flags:\n\n"
|
||||
" * :R - share reads (FILE_SHARE_READ)\n"
|
||||
" * :W - share writes (FILE_SHARE_WRITE)\n"
|
||||
" * :D - share deletes (FILE_SHARE_DELETE)\n"
|
||||
" * :H - FILE_ATTRIBUTE_HIDDEN\n"
|
||||
" * :O - FILE_ATTRIBUTE_READONLY\n"
|
||||
" * :F - FILE_ATTRIBUTE_OFFLINE\n"
|
||||
" * :T - FILE_ATTRIBUTE_TEMPORARY\n"
|
||||
" * :d - FILE_FLAG_DELETE_ON_CLOSE\n"
|
||||
" * :b - FILE_FLAG_NO_BUFFERING\n") {
|
||||
" * :R - share reads (FILE\\_SHARE\\_READ)\n"
|
||||
" * :W - share writes (FILE\\_SHARE\\_WRITE)\n"
|
||||
" * :D - share deletes (FILE\\_SHARE\\_DELETE)\n"
|
||||
" * :H - FILE\\_ATTRIBUTE\\_HIDDEN\n"
|
||||
" * :O - FILE\\_ATTRIBUTE\\_READONLY\n"
|
||||
" * :F - FILE\\_ATTRIBUTE\\_OFFLINE\n"
|
||||
" * :T - FILE\\_ATTRIBUTE\\_TEMPORARY\n"
|
||||
" * :d - FILE\\_FLAG\\_DELETE\\_ON\\_CLOSE\n"
|
||||
" * :b - FILE\\_FLAG\\_NO\\_BUFFERING\n") {
|
||||
janet_arity(argc, 1, 3);
|
||||
const char *path = janet_getcstring(argv, 0);
|
||||
const uint8_t *opt_flags = janet_optkeyword(argv, argc, 1, (const uint8_t *) "r");
|
||||
|
||||
@@ -762,8 +762,7 @@ static const char *scanformat(
|
||||
memset(precision, '\0', 3);
|
||||
while (*p != '\0' && strchr(FMT_FLAGS, *p) != NULL)
|
||||
p++; /* skip flags */
|
||||
if ((size_t)(p - strfrmt) >= sizeof(FMT_FLAGS) / sizeof(char))
|
||||
janet_panic("invalid format (repeated flags)");
|
||||
if ((size_t)(p - strfrmt) >= sizeof(FMT_FLAGS)) janet_panic("invalid format (repeated flags)");
|
||||
if (isdigit((int)(*p)))
|
||||
width[0] = *p++; /* skip width */
|
||||
if (isdigit((int)(*p)))
|
||||
|
||||
@@ -530,7 +530,7 @@ JANET_CORE_FN(cfun_string_join,
|
||||
|
||||
JANET_CORE_FN(cfun_string_format,
|
||||
"(string/format format & values)",
|
||||
"Similar to `snprintf`, but specialized for operating with Janet values. Returns "
|
||||
"Similar to C's `snprintf`, but specialized for operating with Janet values. Returns "
|
||||
"a new string.") {
|
||||
janet_arity(argc, 1, -1);
|
||||
JanetBuffer *buffer = janet_buffer(0);
|
||||
|
||||
@@ -348,7 +348,7 @@ int32_t janet_hash(Janet x) {
|
||||
hash = (int32_t)((hilo << 16) | (hilo >> 16));
|
||||
} else {
|
||||
/* Assuming 4 byte pointer (or smaller) */
|
||||
ptrdiff_t diff = ((char *)janet_unwrap_pointer(x) - (char *)0);
|
||||
uintptr_t diff = (uintptr_t) janet_unwrap_pointer(x);
|
||||
uint32_t hilo = (uint32_t) diff * 2654435769u;
|
||||
hash = (int32_t)((hilo << 16) | (hilo >> 16));
|
||||
}
|
||||
|
||||
@@ -220,14 +220,14 @@
|
||||
/* Trace a function call */
|
||||
static void vm_do_trace(JanetFunction *func, int32_t argc, const Janet *argv) {
|
||||
if (func->def->name) {
|
||||
janet_printf("trace (%S", func->def->name);
|
||||
janet_eprintf("trace (%S", func->def->name);
|
||||
} else {
|
||||
janet_printf("trace (%p", janet_wrap_function(func));
|
||||
janet_eprintf("trace (%p", janet_wrap_function(func));
|
||||
}
|
||||
for (int32_t i = 0; i < argc; i++) {
|
||||
janet_printf(" %p", argv[i]);
|
||||
janet_eprintf(" %p", argv[i]);
|
||||
}
|
||||
janet_printf(")\n");
|
||||
janet_eprintf(")\n");
|
||||
}
|
||||
|
||||
/* Invoke a method once we have looked it up */
|
||||
@@ -1285,6 +1285,12 @@ JanetSignal janet_step(JanetFiber *fiber, Janet in, Janet *out) {
|
||||
return signal;
|
||||
}
|
||||
|
||||
static Janet void_cfunction(int32_t argc, Janet *argv) {
|
||||
(void) argc;
|
||||
(void) argv;
|
||||
janet_panic("placeholder");
|
||||
}
|
||||
|
||||
Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
|
||||
/* Check entry conditions */
|
||||
if (!janet_vm.fiber)
|
||||
@@ -1292,9 +1298,17 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
|
||||
if (janet_vm.stackn >= JANET_RECURSION_GUARD)
|
||||
janet_panic("C stack recursed too deeply");
|
||||
|
||||
/* Dirty stack */
|
||||
int32_t dirty_stack = janet_vm.fiber->stacktop - janet_vm.fiber->stackstart;
|
||||
if (dirty_stack) {
|
||||
janet_fiber_cframe(janet_vm.fiber, void_cfunction);
|
||||
}
|
||||
|
||||
/* Tracing */
|
||||
if (fun->gc.flags & JANET_FUNCFLAG_TRACE) {
|
||||
janet_vm.stackn++;
|
||||
vm_do_trace(fun, argc, argv);
|
||||
janet_vm.stackn--;
|
||||
}
|
||||
|
||||
/* Push frame */
|
||||
@@ -1322,6 +1336,10 @@ Janet janet_call(JanetFunction *fun, int32_t argc, const Janet *argv) {
|
||||
/* Teardown */
|
||||
janet_vm.stackn = oldn;
|
||||
janet_gcunlock(handle);
|
||||
if (dirty_stack) {
|
||||
janet_fiber_popframe(janet_vm.fiber);
|
||||
janet_vm.fiber->stacktop += dirty_stack;
|
||||
}
|
||||
|
||||
if (signal != JANET_SIGNAL_OK) {
|
||||
janet_panicv(*janet_vm.return_reg);
|
||||
|
||||
@@ -87,8 +87,30 @@ static void simpleline(JanetBuffer *buffer) {
|
||||
}
|
||||
}
|
||||
|
||||
/* Windows */
|
||||
#if defined(JANET_WINDOWS) || defined(JANET_SIMPLE_GETLINE)
|
||||
/* State */
|
||||
|
||||
#ifndef JANET_SIMPLE_GETLINE
|
||||
/* static state */
|
||||
#define JANET_LINE_MAX 1024
|
||||
#define JANET_MATCH_MAX 256
|
||||
#define JANET_HISTORY_MAX 100
|
||||
static JANET_THREAD_LOCAL int gbl_israwmode = 0;
|
||||
static JANET_THREAD_LOCAL const char *gbl_prompt = "> ";
|
||||
static JANET_THREAD_LOCAL int gbl_plen = 2;
|
||||
static JANET_THREAD_LOCAL char gbl_buf[JANET_LINE_MAX];
|
||||
static JANET_THREAD_LOCAL int gbl_len = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_pos = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_cols = 80;
|
||||
static JANET_THREAD_LOCAL char *gbl_history[JANET_HISTORY_MAX];
|
||||
static JANET_THREAD_LOCAL int gbl_history_count = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_historyi = 0;
|
||||
static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX];
|
||||
static JANET_THREAD_LOCAL int gbl_match_count = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_lines_below = 0;
|
||||
#endif
|
||||
|
||||
/* Fallback */
|
||||
#if defined(JANET_SIMPLE_GETLINE)
|
||||
|
||||
void janet_line_init() {
|
||||
;
|
||||
@@ -105,6 +127,80 @@ void janet_line_get(const char *p, JanetBuffer *buffer) {
|
||||
simpleline(buffer);
|
||||
}
|
||||
|
||||
/* Rich implementation */
|
||||
#else
|
||||
|
||||
/* Windows */
|
||||
#ifdef _WIN32
|
||||
|
||||
#include <stdbool.h>
|
||||
#include <stdio.h>
|
||||
#include <stdlib.h>
|
||||
#include <io.h>
|
||||
|
||||
static void setup_console_output(void) {
|
||||
/* Enable color console on windows 10 console and utf8 output and other processing */
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hOut, &dwMode);
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
SetConsoleMode(hOut, dwMode);
|
||||
SetConsoleOutputCP(65001);
|
||||
}
|
||||
|
||||
/* Ansi terminal raw mode */
|
||||
static int rawmode(void) {
|
||||
if (gbl_israwmode) return 0;
|
||||
HANDLE hOut = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hOut, &dwMode);
|
||||
dwMode &= ~ENABLE_LINE_INPUT;
|
||||
dwMode &= ~ENABLE_INSERT_MODE;
|
||||
dwMode &= ~ENABLE_ECHO_INPUT;
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_INPUT;
|
||||
dwMode &= ~ENABLE_PROCESSED_INPUT;
|
||||
if (!SetConsoleMode(hOut, dwMode)) return 1;
|
||||
gbl_israwmode = 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Disable raw mode */
|
||||
static void norawmode(void) {
|
||||
if (!gbl_israwmode) return;
|
||||
HANDLE hOut = GetStdHandle(STD_INPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hOut, &dwMode);
|
||||
dwMode |= ENABLE_LINE_INPUT;
|
||||
dwMode |= ENABLE_INSERT_MODE;
|
||||
dwMode |= ENABLE_ECHO_INPUT;
|
||||
dwMode &= ~ENABLE_VIRTUAL_TERMINAL_INPUT;
|
||||
dwMode |= ENABLE_PROCESSED_INPUT;
|
||||
SetConsoleMode(hOut, dwMode);
|
||||
gbl_israwmode = 0;
|
||||
}
|
||||
|
||||
static long write_console(const char *bytes, size_t n) {
|
||||
DWORD nwritten = 0;
|
||||
BOOL result = WriteConsole(GetStdHandle(STD_OUTPUT_HANDLE), bytes, (DWORD) n, &nwritten, NULL);
|
||||
if (!result) return -1; /* error */
|
||||
return (long)nwritten;
|
||||
}
|
||||
|
||||
static long read_console(char *into, size_t n) {
|
||||
DWORD numread;
|
||||
BOOL result = ReadConsole(GetStdHandle(STD_INPUT_HANDLE), into, (DWORD) n, &numread, NULL);
|
||||
if (!result) return -1; /* error */
|
||||
return (long)numread;
|
||||
}
|
||||
|
||||
static int check_simpleline(JanetBuffer *buffer) {
|
||||
if (!_isatty(_fileno(stdin)) || rawmode()) {
|
||||
simpleline(buffer);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Posix */
|
||||
#else
|
||||
|
||||
@@ -125,24 +221,7 @@ https://github.com/antirez/linenoise/blob/master/linenoise.c
|
||||
#include <string.h>
|
||||
#include <signal.h>
|
||||
|
||||
/* static state */
|
||||
#define JANET_LINE_MAX 1024
|
||||
#define JANET_MATCH_MAX 256
|
||||
#define JANET_HISTORY_MAX 100
|
||||
static JANET_THREAD_LOCAL int gbl_israwmode = 0;
|
||||
static JANET_THREAD_LOCAL const char *gbl_prompt = "> ";
|
||||
static JANET_THREAD_LOCAL int gbl_plen = 2;
|
||||
static JANET_THREAD_LOCAL char gbl_buf[JANET_LINE_MAX];
|
||||
static JANET_THREAD_LOCAL int gbl_len = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_pos = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_cols = 80;
|
||||
static JANET_THREAD_LOCAL char *gbl_history[JANET_HISTORY_MAX];
|
||||
static JANET_THREAD_LOCAL int gbl_history_count = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_historyi = 0;
|
||||
static JANET_THREAD_LOCAL struct termios gbl_termios_start;
|
||||
static JANET_THREAD_LOCAL JanetByteView gbl_matches[JANET_MATCH_MAX];
|
||||
static JANET_THREAD_LOCAL int gbl_match_count = 0;
|
||||
static JANET_THREAD_LOCAL int gbl_lines_below = 0;
|
||||
|
||||
/* Unsupported terminal list from linenoise */
|
||||
static const char *badterms[] = {
|
||||
@@ -152,15 +231,6 @@ static const char *badterms[] = {
|
||||
NULL
|
||||
};
|
||||
|
||||
static char *sdup(const char *s) {
|
||||
size_t len = strlen(s) + 1;
|
||||
char *mem = janet_malloc(len);
|
||||
if (!mem) {
|
||||
return NULL;
|
||||
}
|
||||
return memcpy(mem, s, len);
|
||||
}
|
||||
|
||||
/* Ansi terminal raw mode */
|
||||
static int rawmode(void) {
|
||||
struct termios t;
|
||||
@@ -186,13 +256,53 @@ static void norawmode(void) {
|
||||
gbl_israwmode = 0;
|
||||
}
|
||||
|
||||
static int checktermsupport() {
|
||||
const char *t = getenv("TERM");
|
||||
int i;
|
||||
if (!t) return 1;
|
||||
for (i = 0; badterms[i]; i++)
|
||||
if (!strcmp(t, badterms[i])) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
static long write_console(char *bytes, size_t n) {
|
||||
return write(STDOUT_FILENO, bytes, n);
|
||||
}
|
||||
|
||||
static long read_console(char *into, size_t n) {
|
||||
return read(STDIN_FILENO, into, n);
|
||||
}
|
||||
|
||||
static int check_simpleline(JanetBuffer *buffer) {
|
||||
if (!isatty(STDIN_FILENO) || !checktermsupport()) {
|
||||
simpleline(buffer);
|
||||
return 1;
|
||||
}
|
||||
if (rawmode()) {
|
||||
simpleline(buffer);
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static char *sdup(const char *s) {
|
||||
size_t len = strlen(s) + 1;
|
||||
char *mem = janet_malloc(len);
|
||||
if (!mem) {
|
||||
return NULL;
|
||||
}
|
||||
return memcpy(mem, s, len);
|
||||
}
|
||||
|
||||
static int curpos(void) {
|
||||
char buf[32];
|
||||
int cols, rows;
|
||||
unsigned int i = 0;
|
||||
if (write(STDOUT_FILENO, "\x1b[6n", 4) != 4) return -1;
|
||||
if (write_console("\x1b[6n", 4) != 4) return -1;
|
||||
while (i < sizeof(buf) - 1) {
|
||||
if (read(STDIN_FILENO, buf + i, 1) != 1) break;
|
||||
if (read_console(buf + i, 1) != 1) break;
|
||||
if (buf[i] == 'R') break;
|
||||
i++;
|
||||
}
|
||||
@@ -203,18 +313,23 @@ static int curpos(void) {
|
||||
}
|
||||
|
||||
static int getcols(void) {
|
||||
#ifdef _WIN32
|
||||
CONSOLE_SCREEN_BUFFER_INFO csbi;
|
||||
GetConsoleScreenBufferInfo(GetStdHandle(STD_OUTPUT_HANDLE), &csbi);
|
||||
return (int)(csbi.srWindow.Right - csbi.srWindow.Left + 1);
|
||||
#else
|
||||
struct winsize ws;
|
||||
if (ioctl(1, TIOCGWINSZ, &ws) == -1 || ws.ws_col == 0) {
|
||||
int start, cols;
|
||||
start = curpos();
|
||||
if (start == -1) goto failed;
|
||||
if (write(STDOUT_FILENO, "\x1b[999C", 6) != 6) goto failed;
|
||||
if (write_console("\x1b[999C", 6) != 6) goto failed;
|
||||
cols = curpos();
|
||||
if (cols == -1) goto failed;
|
||||
if (cols > start) {
|
||||
char seq[32];
|
||||
snprintf(seq, 32, "\x1b[%dD", cols - start);
|
||||
if (write(STDOUT_FILENO, seq, strlen(seq)) == -1) {
|
||||
if (write_console(seq, strlen(seq)) == -1) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -224,10 +339,11 @@ static int getcols(void) {
|
||||
}
|
||||
failed:
|
||||
return 80;
|
||||
#endif
|
||||
}
|
||||
|
||||
static void clear(void) {
|
||||
if (write(STDOUT_FILENO, "\x1b[H\x1b[2J", 7) <= 0) {
|
||||
if (write_console("\x1b[H\x1b[2J", 7) <= 0) {
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
@@ -259,7 +375,7 @@ static void refresh(void) {
|
||||
/* Move cursor to original position. */
|
||||
snprintf(seq, 64, "\r\x1b[%dC", (int)(_pos + gbl_plen));
|
||||
janet_buffer_push_cstring(&b, seq);
|
||||
if (write(STDOUT_FILENO, b.data, b.count) == -1) {
|
||||
if (write_console((char *) b.data, b.count) == -1) {
|
||||
exit(1);
|
||||
}
|
||||
janet_buffer_deinit(&b);
|
||||
@@ -285,7 +401,7 @@ static int insert(char c, int draw) {
|
||||
if (gbl_plen + gbl_len < gbl_cols) {
|
||||
/* Avoid a full update of the line in the
|
||||
* trivial case. */
|
||||
if (write(STDOUT_FILENO, &c, 1) == -1) return -1;
|
||||
if (write_console(&c, 1) == -1) return -1;
|
||||
} else {
|
||||
refresh();
|
||||
}
|
||||
@@ -312,7 +428,7 @@ static void historymove(int delta) {
|
||||
gbl_historyi = gbl_history_count - 1;
|
||||
}
|
||||
strncpy(gbl_buf, gbl_history[gbl_historyi], JANET_LINE_MAX - 1);
|
||||
gbl_pos = gbl_len = strlen(gbl_buf);
|
||||
gbl_pos = gbl_len = (int) strlen(gbl_buf);
|
||||
gbl_buf[gbl_len] = '\0';
|
||||
|
||||
refresh();
|
||||
@@ -527,6 +643,7 @@ static void check_specials(JanetByteView src) {
|
||||
check_cmatch(src, "unquote");
|
||||
check_cmatch(src, "var");
|
||||
check_cmatch(src, "while");
|
||||
check_cmatch(src, "upscope");
|
||||
}
|
||||
|
||||
static void resolve_format(JanetTable *entry) {
|
||||
@@ -740,14 +857,14 @@ static int line() {
|
||||
|
||||
addhistory();
|
||||
|
||||
if (write(STDOUT_FILENO, gbl_prompt, gbl_plen) == -1) return -1;
|
||||
if (write_console((char *) gbl_prompt, gbl_plen) == -1) return -1;
|
||||
for (;;) {
|
||||
char c;
|
||||
char seq[3];
|
||||
|
||||
int rc;
|
||||
do {
|
||||
rc = read(STDIN_FILENO, &c, 1);
|
||||
rc = read_console(&c, 1);
|
||||
} while (rc < 0 && errno == EINTR);
|
||||
if (rc <= 0) return -1;
|
||||
|
||||
@@ -764,8 +881,13 @@ static int line() {
|
||||
kleft();
|
||||
break;
|
||||
case 3: /* ctrl-c */
|
||||
clearlines();
|
||||
norawmode();
|
||||
#ifdef _WIN32
|
||||
ExitProcess(1);
|
||||
#else
|
||||
kill(getpid(), SIGINT);
|
||||
#endif
|
||||
/* fallthrough */
|
||||
case 17: /* ctrl-q */
|
||||
gbl_cancel_current_repl_form = 1;
|
||||
@@ -826,23 +948,25 @@ static int line() {
|
||||
case 23: /* ctrl-w */
|
||||
kbackspacew();
|
||||
break;
|
||||
#ifndef _WIN32
|
||||
case 26: /* ctrl-z */
|
||||
norawmode();
|
||||
kill(getpid(), SIGSTOP);
|
||||
rawmode();
|
||||
refresh();
|
||||
break;
|
||||
#endif
|
||||
case 27: /* escape sequence */
|
||||
/* Read the next two bytes representing the escape sequence.
|
||||
* Use two calls to handle slow terminals returning the two
|
||||
* chars at different times. */
|
||||
if (read(STDIN_FILENO, seq, 1) == -1) break;
|
||||
if (read_console(seq, 1) == -1) break;
|
||||
/* Esc[ = Control Sequence Introducer (CSI) */
|
||||
if (seq[0] == '[') {
|
||||
if (read(STDIN_FILENO, seq + 1, 1) == -1) break;
|
||||
if (read_console(seq + 1, 1) == -1) break;
|
||||
if (seq[1] >= '0' && seq[1] <= '9') {
|
||||
/* Extended escape, read additional byte. */
|
||||
if (read(STDIN_FILENO, seq + 2, 1) == -1) break;
|
||||
if (read_console(seq + 2, 1) == -1) break;
|
||||
if (seq[2] == '~') {
|
||||
switch (seq[1]) {
|
||||
case '1': /* Home */
|
||||
@@ -861,7 +985,7 @@ static int line() {
|
||||
}
|
||||
}
|
||||
} else if (seq[0] == 'O') {
|
||||
if (read(STDIN_FILENO, seq + 1, 1) == -1) break;
|
||||
if (read_console(seq + 1, 1) == -1) break;
|
||||
switch (seq[1]) {
|
||||
default:
|
||||
break;
|
||||
@@ -944,28 +1068,12 @@ void janet_line_deinit() {
|
||||
gbl_historyi = 0;
|
||||
}
|
||||
|
||||
static int checktermsupport() {
|
||||
const char *t = getenv("TERM");
|
||||
int i;
|
||||
if (!t) return 1;
|
||||
for (i = 0; badterms[i]; i++)
|
||||
if (!strcmp(t, badterms[i])) return 0;
|
||||
return 1;
|
||||
}
|
||||
|
||||
void janet_line_get(const char *p, JanetBuffer *buffer) {
|
||||
gbl_prompt = p;
|
||||
buffer->count = 0;
|
||||
gbl_historyi = 0;
|
||||
if (check_simpleline(buffer)) return;
|
||||
FILE *out = janet_dynfile("err", stderr);
|
||||
if (!isatty(STDIN_FILENO) || !checktermsupport()) {
|
||||
simpleline(buffer);
|
||||
return;
|
||||
}
|
||||
if (rawmode()) {
|
||||
simpleline(buffer);
|
||||
return;
|
||||
}
|
||||
if (line()) {
|
||||
norawmode();
|
||||
fputc('\n', out);
|
||||
@@ -981,6 +1089,13 @@ void janet_line_get(const char *p, JanetBuffer *buffer) {
|
||||
replacehistory();
|
||||
}
|
||||
|
||||
static void clear_at_exit(void) {
|
||||
if (!gbl_israwmode) {
|
||||
clearlines();
|
||||
norawmode();
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
/*
|
||||
@@ -993,18 +1108,11 @@ int main(int argc, char **argv) {
|
||||
JanetTable *env;
|
||||
|
||||
#ifdef _WIN32
|
||||
/* Enable color console on windows 10 console and utf8 output. */
|
||||
HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);
|
||||
DWORD dwMode = 0;
|
||||
GetConsoleMode(hOut, &dwMode);
|
||||
dwMode |= ENABLE_VIRTUAL_TERMINAL_PROCESSING;
|
||||
SetConsoleMode(hOut, dwMode);
|
||||
SetConsoleOutputCP(65001);
|
||||
setup_console_output();
|
||||
#endif
|
||||
|
||||
#if !defined(JANET_WINDOWS) && !defined(JANET_SIMPLE_GETLINE)
|
||||
/* Try and not leave the terminal in a bad state */
|
||||
atexit(norawmode);
|
||||
#if !defined(JANET_SIMPLE_GETLINE)
|
||||
atexit(clear_at_exit);
|
||||
#endif
|
||||
|
||||
#if defined(JANET_PRF)
|
||||
|
||||
@@ -93,5 +93,14 @@
|
||||
|
||||
(assert (= 10 (named-opt-arguments 1 :a 2 :b 3 :c 4)) "named arguments 2")
|
||||
|
||||
(let [b @""]
|
||||
(defn dummy [a b c]
|
||||
(+ a b c))
|
||||
(trace dummy)
|
||||
(defn errout [arg]
|
||||
(buffer/push b arg))
|
||||
(assert (= 6 (with-dyns [*err* errout] (dummy 1 2 3))) "trace to custom err function")
|
||||
(assert (deep= @"trace (dummy 1 2 3)\n" b) "trace buffer correct"))
|
||||
|
||||
(end-suite)
|
||||
|
||||
|
||||
30
test/suite0013.janet
Normal file
30
test/suite0013.janet
Normal file
@@ -0,0 +1,30 @@
|
||||
# Copyright (c) 2022 Calvin Rose & contributors
|
||||
#
|
||||
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
# of this software and associated documentation files (the "Software"), to
|
||||
# deal in the Software without restriction, including without limitation the
|
||||
# rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
|
||||
# sell copies of the Software, and to permit persons to whom the Software is
|
||||
# furnished to do so, subject to the following conditions:
|
||||
#
|
||||
# The above copyright notice and this permission notice shall be included in
|
||||
# all copies or substantial portions of the Software.
|
||||
#
|
||||
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
|
||||
# FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
|
||||
# IN THE SOFTWARE.
|
||||
|
||||
(import ./helper :prefix "" :exit true)
|
||||
(start-suite 13)
|
||||
|
||||
(assert (deep= (tabseq [i :in (range 3)] i (* 3 i))
|
||||
@{0 0 1 3 2 6}))
|
||||
|
||||
(assert (deep= (tabseq [i :in (range 3)] i)
|
||||
@{}))
|
||||
|
||||
(end-suite)
|
||||
0
tools/format.sh
Executable file → Normal file
0
tools/format.sh
Executable file → Normal file
Reference in New Issue
Block a user