-
Notifications
You must be signed in to change notification settings - Fork 36
Expand file tree
/
Copy pathgoldfish.hpp
More file actions
2705 lines (2373 loc) · 86 KB
/
goldfish.hpp
File metadata and controls
2705 lines (2373 loc) · 86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
//
// Copyright (C) 2024 The Goldfish Scheme Authors
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
#include <algorithm>
#include <argh.h>
#include <chrono>
#include <cctype>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <fstream>
#include <iostream>
#include <limits>
#include <memory>
#include <mutex>
#include <sstream>
#include <stdexcept>
#include "s7.h"
#include <string>
#include <unordered_map>
#include <vector>
#include <thread>
#include <tbox/platform/file.h>
#include <tbox/platform/path.h>
#include <tbox/tbox.h>
#ifdef TB_CONFIG_OS_WINDOWS
#include <io.h>
#include <windows.h>
#elif TB_CONFIG_OS_MACOSX
#include <limits.h>
#include <mach-o/dyld.h>
#elif defined(__EMSCRIPTEN__)
#include <limits.h>
#else
#include <linux/limits.h>
#endif
#if !defined(TB_CONFIG_OS_WINDOWS)
#include <errno.h>
#include <pwd.h>
#include <unistd.h>
#if !defined(__EMSCRIPTEN__)
#include <wordexp.h>
#endif
#endif
#include <nlohmann/json.hpp>
#ifdef GOLDFISH_WITH_REPL
#include <functional>
#include <isocline.h>
#endif
#define GOLDFISH_VERSION "18.11.8"
#define GOLDFISH_PATH_MAXN TB_PATH_MAXN
static std::vector<std::string> command_args= std::vector<std::string> ();
// Declare environ for non-Windows platforms (needed for f_getenvs)
#if !defined(TB_CONFIG_OS_WINDOWS)
extern char **environ;
#endif
namespace goldfish {
using std::cerr;
using std::cout;
using std::endl;
using std::string;
using std::vector;
namespace fs = std::filesystem;
using nlohmann::json;
inline void
glue_define (s7_scheme* sc, const char* name, const char* desc, s7_function f, s7_int required, s7_int optional);
static s7_pointer
f_function_libraries (s7_scheme* sc, s7_pointer args);
static s7_pointer
f_gfproject_load_config (s7_scheme* sc, s7_pointer args);
static bool
split_library_query (const string& query, string& group, string& library);
static string
find_goldfish_library ();
static vector<string>
find_function_libraries_in_load_path (s7_scheme* sc, const string& function_name);
void glue_njson (s7_scheme* sc);
void glue_http (s7_scheme* sc);
void glue_http_async (s7_scheme* sc);
void glue_liii_hashlib (s7_scheme* sc);
void glue_liii_os (s7_scheme* sc);
void glue_liii_path (s7_scheme* sc);
void glue_subprocess_run_values (s7_scheme* sc);
inline s7_pointer
string_vector_to_s7_vector (s7_scheme* sc, vector<string> v) {
int N = v.size ();
s7_pointer ret= s7_make_vector (sc, N);
for (int i= 0; i < N; i++) {
s7_vector_set (sc, ret, i, s7_make_string (sc, v[i].c_str ()));
}
return ret;
}
inline void
glue_define (s7_scheme* sc, const char* name, const char* desc, s7_function f, s7_int required, s7_int optional) {
s7_pointer cur_env= s7_curlet (sc);
s7_pointer func = s7_make_typed_function (sc, name, f, required, optional, false, desc, NULL);
s7_define (sc, cur_env, s7_make_symbol (sc, name), func);
}
static s7_pointer
f_version (s7_scheme* sc, s7_pointer args) {
return s7_make_string (sc, GOLDFISH_VERSION);
}
static s7_pointer
f_delete_file (s7_scheme* sc, s7_pointer args) {
const char* path_c= s7_string (s7_car (args));
return s7_make_boolean (sc, tb_file_remove (path_c));
}
inline void
glue_goldfish (s7_scheme* sc) {
s7_pointer cur_env= s7_curlet (sc);
const char* s_version = "version";
const char* d_version = "(version) => string";
const char* s_delete_file = "g_delete-file";
const char* d_delete_file = "(g_delete-file string) => boolean";
const char* s_function_libraries= "g_function-libraries";
const char* d_function_libraries=
"(g_function-libraries function-name) => list, returns visible library names such as '((liii string)) that "
"export function-name in the current *load-path*";
const char* s_gfproject_load_config = "g_gfproject-load-config";
const char* d_gfproject_load_config = "(g_gfproject-load-config) => string, returns merged gfproject.json";
s7_define (sc, cur_env, s7_make_symbol (sc, s_version),
s7_make_typed_function (sc, s_version, f_version, 0, 0, false, d_version, NULL));
s7_define (sc, cur_env, s7_make_symbol (sc, s_delete_file),
s7_make_typed_function (sc, s_delete_file, f_delete_file, 1, 0, false, d_delete_file, NULL));
s7_define (sc, cur_env, s7_make_symbol (sc, s_function_libraries),
s7_make_typed_function (sc, s_function_libraries, f_function_libraries, 1, 0, false, d_function_libraries, NULL));
s7_define (sc, cur_env, s7_make_symbol (sc, s_gfproject_load_config),
s7_make_typed_function (sc, s_gfproject_load_config, f_gfproject_load_config, 0, 0, false,
d_gfproject_load_config, NULL));
}
// old `f_current_second` TODO: use std::chrono::tai_clock::now() when using C++ 20
// NOTE(jinser): use a new name for tai
// `current-second` impl by g_get-time-of-day now
static s7_pointer
f_get_time_of_day (s7_scheme* sc, s7_pointer args) {
using namespace std::chrono;
auto now = time_point_cast<microseconds>(system_clock::now());
auto since_epoch = now.time_since_epoch();
auto sec = duration_cast<seconds>(since_epoch);
s7_pointer vs = s7_list(sc, 2,
s7_make_integer(sc, sec.count()),
s7_make_integer(sc, (since_epoch - sec).count()));
return s7_values(sc, vs);
}
static s7_pointer
f_monotonic_nanosecond (s7_scheme* sc, s7_pointer args) {
using namespace std::chrono;
auto now = steady_clock::now();
auto duration = now.time_since_epoch();
auto count = duration_cast<std::chrono::nanoseconds>(duration).count();
return s7_make_integer(sc, count);
}
template<typename Clock>
constexpr int64_t clock_resolution_ns() {
typedef std::chrono::duration<double, std::nano> NS;
NS ns = typename Clock::duration(1);
return ns.count();
}
inline void
glue_scheme_time (s7_scheme* sc) {
s7_pointer cur_env= s7_curlet (sc);
const char* s_get_time_of_day= "g_get-time-of-day";
const char* d_get_time_of_day= "(g_get-time-of-day): () => (integer, integer), return the "
"current second and microsecond in integer";
s7_define (sc, cur_env, s7_make_symbol (sc, s_get_time_of_day),
s7_make_typed_function (sc, s_get_time_of_day, f_get_time_of_day, 0, 0, false, d_get_time_of_day, NULL));
const char* s_monotonic_nanosecond= "g_monotonic-nanosecond";
const char* d_monotonic_nanosecond= "(g_monotonic-nanosecond): () => integer, returns the steady clock's monotonic nanoseconds since an unspecified epoch";
s7_define (sc, cur_env, s7_make_symbol (sc, s_monotonic_nanosecond),
s7_make_typed_function (sc, s_monotonic_nanosecond, f_monotonic_nanosecond, 0, 0, false, d_monotonic_nanosecond, NULL));
s7_define_constant_with_environment (sc, cur_env, "g_system-clock-resolution",
s7_make_integer(sc, clock_resolution_ns<std::chrono::system_clock>()));
s7_define_constant_with_environment (sc, cur_env, "g_steady-clock-resolution",
s7_make_integer(sc, clock_resolution_ns<std::chrono::steady_clock>()));
}
static s7_pointer
f_get_environment_variable (s7_scheme* sc, s7_pointer args) {
#ifdef _MSC_VER
std::string path_sep= ";";
#else
std::string path_sep= ":";
#endif
std::string ret;
tb_size_t size = 0;
const char* key = s7_string (s7_car (args));
tb_environment_ref_t environment= tb_environment_init ();
if (environment) {
size= tb_environment_load (environment, key);
if (size >= 1) {
tb_for_all_if (tb_char_t const*, value, environment, value) { ret.append (value).append (path_sep); }
}
}
tb_environment_exit (environment);
if (size == 0) { // env key not found
return s7_make_boolean (sc, false);
}
else {
return s7_make_string (sc, ret.substr (0, ret.size () - 1).c_str ());
}
}
static s7_pointer
f_command_line (s7_scheme* sc, s7_pointer args) {
s7_pointer ret = s7_nil (sc);
int size= command_args.size ();
for (int i= size - 1; i >= 0; i--) {
ret= s7_cons (sc, s7_make_string (sc, command_args[i].c_str ()), ret);
}
return ret;
}
static s7_pointer
f_getenvs (s7_scheme* sc, s7_pointer args) {
s7_pointer p = s7_nil(sc);
#ifdef TB_CONFIG_OS_WINDOWS
// Windows: use GetEnvironmentStrings
LPCH env_strings = GetEnvironmentStrings();
if (env_strings) {
LPCH env = env_strings;
while (*env) {
const char* eq = strchr(env, '=');
if (eq && eq != env) { // skip empty variable names
s7_pointer name = s7_make_string_with_length(sc, env, eq - env);
s7_pointer value = s7_make_string(sc, eq + 1);
p = s7_cons(sc, s7_cons(sc, name, value), p);
}
env += strlen(env) + 1;
}
FreeEnvironmentStrings(env_strings);
}
#else
// Unix/Linux/macOS: use environ (declared at global scope)
for (int32_t i = 0; environ[i]; i++) {
const char* eq = strchr(environ[i], '=');
if (eq) {
s7_pointer name = s7_make_string_with_length(sc, environ[i], eq - environ[i]);
s7_pointer value = s7_make_string(sc, eq + 1);
p = s7_cons(sc, s7_cons(sc, name, value), p);
}
}
#endif
return p;
}
inline void
glue_scheme_process_context (s7_scheme* sc) {
s7_pointer cur_env= s7_curlet (sc);
const char* s_get_environment_variable= "g_get-environment-variable";
const char* d_get_environment_variable= "(g_get-environemt-variable string) => string";
const char* s_command_line = "g_command-line";
const char* d_command_line = "(g_command-line) => string";
const char* s_getenvs = "g_getenvs";
const char* d_getenvs = "(g_getenvs) => alist, returns all environment variables as an alist";
s7_define (sc, cur_env, s7_make_symbol (sc, s_get_environment_variable),
s7_make_typed_function (sc, s_get_environment_variable, f_get_environment_variable, 1, 0, false,
d_get_environment_variable, NULL));
s7_define (sc, cur_env, s7_make_symbol (sc, s_command_line),
s7_make_typed_function (sc, s_command_line, f_command_line, 0, 0, false, d_command_line, NULL));
s7_define (sc, cur_env, s7_make_symbol (sc, s_getenvs),
s7_make_typed_function (sc, s_getenvs, f_getenvs, 0, 0, false, d_getenvs, NULL));
}
string
goldfish_exe () {
#ifdef TB_CONFIG_OS_WINDOWS
char buffer[GOLDFISH_PATH_MAXN];
GetModuleFileName (NULL, buffer, GOLDFISH_PATH_MAXN);
return string (buffer);
#elif TB_CONFIG_OS_MACOSX
char buffer[PATH_MAX];
uint32_t size= sizeof (buffer);
if (_NSGetExecutablePath (buffer, &size) == 0) {
char real_path[GOLDFISH_PATH_MAXN];
if (realpath (buffer, real_path) != NULL) {
return string (real_path);
}
}
#elif TB_CONFIG_OS_LINUX
char buffer[GOLDFISH_PATH_MAXN];
ssize_t len= readlink ("/proc/self/exe", buffer, sizeof (buffer) - 1);
if (len != -1) {
buffer[len]= '\0';
return std::string (buffer);
}
#endif
return "";
}
static s7_pointer
f_executable (s7_scheme* sc, s7_pointer args) {
string exe_path= goldfish_exe ();
return s7_make_string (sc, exe_path.c_str ());
}
inline void
glue_executable (s7_scheme* sc) {
const char* name= "g_executable";
const char* desc= "(g_executable) => string";
glue_define (sc, name, desc, f_executable, 0, 0);
}
inline void
glue_liii_sys (s7_scheme* sc) {
glue_executable (sc);
}
static s7_pointer
f_sleep(s7_scheme* sc, s7_pointer args) {
s7_double seconds = s7_real(s7_car(args));
// 使用 tbox 的 tb_sleep 函数,参数是毫秒
tb_msleep((tb_long_t)(seconds * 1000));
return s7_nil(sc);
}
inline void
glue_sleep(s7_scheme* sc) {
const char* name = "g_sleep";
const char* desc = "(g_sleep seconds) => nil, sleep for the specified number of seconds";
glue_define(sc, name, desc, f_sleep, 1, 0);
}
static s7_pointer
f_uuid4 (s7_scheme* sc, s7_pointer args) {
tb_char_t uuid[37];
const tb_char_t* ret= tb_uuid4_make_cstr (uuid, tb_null);
return s7_make_string (sc, ret);
}
inline void
glue_uuid4 (s7_scheme* sc) {
const char* name= "g_uuid4";
const char* desc= "(g_uuid4) => string";
glue_define (sc, name, desc, f_uuid4, 0, 0);
}
inline void
glue_liii_uuid (s7_scheme* sc) {
glue_uuid4 (sc);
}
static s7_pointer
f_datetime_now (s7_scheme* sc, s7_pointer args) {
// Get current time using tbox for year, month, day, etc.
tb_time_t now= tb_time ();
// Get local time
tb_tm_t lt= {0};
if (!tb_localtime (now, <)) {
return s7_f (sc);
}
// Use C++ chrono to get microseconds
std::uint64_t micros= 0;
#ifdef TB_CONFIG_OS_WINDOWS
// On Windows, ensure we properly handle chrono
FILETIME ft;
ULARGE_INTEGER uli;
GetSystemTimeAsFileTime (&ft);
uli.LowPart = ft.dwLowDateTime;
uli.HighPart= ft.dwHighDateTime;
// Convert to microseconds and get modulo
micros= (uli.QuadPart / 10) % 1000000; // Convert from 100-nanosecond intervals to microseconds
#else
// Standard approach for other platforms
auto now_chrono= std::chrono::system_clock::now ();
auto duration = now_chrono.time_since_epoch ();
micros = std::chrono::duration_cast<std::chrono::microseconds> (duration).count () % 1000000;
#endif
// Create a vector with the time components - vector is easier to index than list in Scheme
s7_pointer time_vec= s7_make_vector (sc, 7);
// Fill the vector with values
s7_vector_set (sc, time_vec, 0, s7_make_integer (sc, lt.year)); // year
s7_vector_set (sc, time_vec, 1, s7_make_integer (sc, lt.month)); // month
s7_vector_set (sc, time_vec, 2, s7_make_integer (sc, lt.mday)); // day
s7_vector_set (sc, time_vec, 3, s7_make_integer (sc, lt.hour)); // hour
s7_vector_set (sc, time_vec, 4, s7_make_integer (sc, lt.minute)); // minute
s7_vector_set (sc, time_vec, 5, s7_make_integer (sc, lt.second)); // second
s7_vector_set (sc, time_vec, 6, s7_make_integer (sc, micros)); // micro-second
return time_vec;
}
inline void
glue_datetime_now (s7_scheme* sc) {
const char* name= "g_datetime-now";
const char* desc= "(g_datetime-now) => datetime, create a datetime object with current time";
s7_define_function (sc, name, f_datetime_now, 0, 0, false, desc);
}
static s7_pointer
f_date_now (s7_scheme* sc, s7_pointer args) {
// Get current time using tbox for year, month, day, etc.
tb_time_t now= tb_time ();
// Get local time
tb_tm_t lt= {0};
if (!tb_localtime (now, <)) {
return s7_f (sc);
}
// Create a vector with the time components - vector is easier to index than list in Scheme
s7_pointer time_vec= s7_make_vector (sc, 3);
// Fill the vector with values
s7_vector_set (sc, time_vec, 0, s7_make_integer (sc, lt.year)); // year
s7_vector_set (sc, time_vec, 1, s7_make_integer (sc, lt.month)); // month
s7_vector_set (sc, time_vec, 2, s7_make_integer (sc, lt.mday)); // day
return time_vec;
}
inline void
glue_date_now (s7_scheme* sc) {
const char* name= "g_date-now";
const char* desc= "(g_date-now) => date, create a date object with current date";
s7_define_function (sc, name, f_date_now, 0, 0, false, desc);
}
inline void
glue_liii_time (s7_scheme* sc) {
glue_sleep (sc);
}
inline void
glue_liii_datetime (s7_scheme* sc) {
glue_datetime_now (sc);
glue_date_now (sc);
}
// -------------------------------- iota --------------------------------
static inline s7_pointer
iota_list (s7_scheme* sc, s7_int count, s7_pointer start, s7_int step) {
s7_pointer res= s7_nil (sc);
s7_int val;
for (val= s7_integer (start) + step * (count - 1); count > 0; count--) {
res= s7_cons (sc, s7_make_integer (sc, val), res);
val-= step;
}
return res;
}
static s7_pointer
iota_list_p_ppp (s7_scheme* sc, s7_pointer count, s7_pointer start, s7_pointer step) {
if (!s7_is_integer (count)) {
return s7_error (sc, s7_make_symbol (sc, "type-error"),
s7_list (sc, 2, s7_make_string (sc, "iota: count must be an integer"), count));
}
if (!s7_is_integer (start)) {
return s7_error (sc, s7_make_symbol (sc, "type-error"),
s7_list (sc, 2, s7_make_string (sc, "iota: start must be an integer"), start));
}
if (!s7_is_integer (step)) {
return s7_error (sc, s7_make_symbol (sc, "type-error"),
s7_list (sc, 2, s7_make_string (sc, "iota: step must be an integer"), step));
}
s7_int cnt= s7_integer (count);
if (cnt < 0) {
return s7_error (sc, s7_make_symbol (sc, "value-error"),
s7_list (sc, 2, s7_make_string (sc, "iota: count is negative"), count));
}
s7_int st = s7_integer (start);
s7_int stp= s7_integer (step);
return iota_list (sc, cnt, start, stp);
}
static s7_pointer
g_iota_list (s7_scheme* sc, s7_pointer args) {
s7_pointer arg1 = s7_car (args); // count
s7_pointer rest1= s7_cdr (args);
s7_pointer arg2 = (s7_is_pair (rest1)) ? s7_car (rest1) : s7_make_integer (sc, 0); // start value, default 0
s7_pointer rest2= s7_cdr (rest1);
s7_pointer arg3 = (s7_is_pair (rest2)) ? s7_car (rest2) : s7_make_integer (sc, 1); // step size, default 1
return iota_list_p_ppp (sc, arg1, arg2, arg3);
}
inline void
glue_iota_list (s7_scheme* sc) {
const char* name= "iota";
const char* desc= "(iota count [start [step]]) => list, returns a list of count elements starting from start "
"(default 0) with step (default 1)";
s7_define_function (sc, name, g_iota_list, 1, 2, false, desc);
}
inline void
glue_liii_list (s7_scheme* sc) {
glue_iota_list (sc);
}
void
glue_for_community_edition (s7_scheme* sc) {
glue_goldfish (sc);
glue_scheme_time (sc);
glue_scheme_process_context (sc);
glue_liii_sys (sc);
glue_liii_os (sc);
glue_subprocess_run_values (sc);
glue_liii_path (sc);
glue_liii_list (sc);
glue_liii_time (sc);
glue_liii_datetime (sc);
glue_liii_uuid (sc);
glue_liii_hashlib (sc);
glue_njson (sc);
glue_http (sc);
glue_http_async (sc);
}
static void
display_help () {
cout << "Goldfish Scheme " << GOLDFISH_VERSION << " by LiiiLabs" << endl;
cout << endl;
cout << "Commands:" << endl;
cout << " help Display this help message" << endl;
cout << " version Display version" << endl;
cout << " eval CODE Evaluate Scheme code" << endl;
cout << " Example: gf eval '(+ 1 2)'" << endl;
cout << " Prefer single quotes so double quotes inside Scheme strings usually do not need escaping" << endl;
cout << " load FILE Load Scheme code from FILE, then enter REPL" << endl;
cout << " fix [options] PATH Format PATH (PATH can be a .scm file or directory)" << endl;
cout << " Options:" << endl;
cout << " --dry-run Print formatted result to stdout" << endl;
cout << " source ORG/LIB Print the exact source of ORG/LIB from current *load-path*" << endl;
cout << " Reads the real library file, not tests/ or generated docs" << endl;
cout << " Example: gf source liii/path" << endl;
cout << " doc ORG/LIB Show the library overview for ORG/LIB from tests/" << endl;
cout << " Usually reads tests/ORG/LIB-test.scm" << endl;
cout << " Example: gf doc liii/path" << endl;
cout << " doc ORG/LIB FUNC Show the function doc/test file for FUNC under a specific library" << endl;
cout << " Best when you already know the library, or the name is ambiguous" << endl;
cout << " Example: gf doc liii/path \"path-read-text\"" << endl;
cout << " Quote FUNC for names like \"bag-delete!\", \"path?\", \"alist->fxmapping\", or \"bag<=?\"" << endl;
cout << " This preserves symbols such as ! ? > < and keeps FUNC as one shell argument" << endl;
cout << " doc FUNC Search visible libraries for exported FUNC, then show its doc/test file" << endl;
cout << " If multiple libraries export it, candidates are listed" << endl;
cout << " Example: gf doc \"string-split\"" << endl;
cout << " Quote FUNC for names like \"bag-delete!\", \"path?\", \"alist->fxmapping\", or \"bag<=?\"" << endl;
cout << " This keeps shell-sensitive symbols intact and makes it clear FUNC is one argument" << endl;
cout << " doc --build-json Rebuild tests/function-library-index.json for global gf doc FUNC lookup" << endl;
cout << " Needed by function-name search and fuzzy suggestions" << endl;
cout << " Run this after changing exports, or before packaging" << endl;
cout << " test [PATTERN] Run tests (all *-test.scm files under tests/)" << endl;
cout << " PATTERN can be:" << endl;
cout << " (none) Run all tests" << endl;
cout << " FILE.scm Run specific test file" << endl;
cout << " DIR/ Run tests in directory" << endl;
cout << " name-test.scm Match by file name" << endl;
cout << " substring Match by path substring" << endl;
cout << " run TARGET Run main function from TARGET" << endl;
cout << " TARGET can be:" << endl;
cout << " FILE.scm Load file and run main" << endl;
cout << " x/y/z.scm Load file and run main" << endl;
cout << " module.name Import (module name) and run main" << endl;
#ifdef GOLDFISH_WITH_REPL
cout << " repl Enter interactive REPL mode" << endl;
#endif
cout << " FILE Load and evaluate Scheme code from FILE" << endl;
cout << endl;
cout << "Options:" << endl;
cout << " --mode, -m MODE Set mode: default, liii, sicp, r7rs, s7" << endl;
cout << " -I DIR Prepend DIR to library search path" << endl;
cout << " -A DIR Append DIR to library search path" << endl;
cout << " -e CODE Alias for eval CODE" << endl;
cout << endl;
cout << "If no command is specified, help is displayed by default." << endl;
}
static void
display_version () {
cout << "Goldfish Scheme " << GOLDFISH_VERSION << " by LiiiLabs" << endl;
cout << "based on S7 Scheme " << S7_VERSION << " (" << S7_DATE << ")" << endl;
}
static void
display_for_invalid_options (const std::vector<std::string>& invalid_opts) {
for (const auto& opt : invalid_opts) {
std::cerr << "Invalid option: " << opt << "\n";
}
std::cerr << "\n";
display_help ();
}
static void
goldfish_eval_file (s7_scheme* sc, string path, bool quiet) {
s7_pointer result= s7_load (sc, path.c_str ());
if (!result) {
cerr << "Failed to load " << path << endl;
exit (-1);
}
if (!quiet) {
cout << path << " => " << s7_object_to_c_string (sc, result) << endl;
}
}
static string
goldfish_cli_program_name () {
if (!command_args.empty ()) {
string program= fs::path (command_args.front ()).filename ().string ();
if (!program.empty ()) {
return program;
}
}
return "gf";
}
static bool
goldfish_is_fix_hint_candidate_error (const string& errmsg) {
return errmsg.find ("unexpected close paren") != string::npos || errmsg.find ("missing close paren") != string::npos;
}
static string
goldfish_extract_scheme_path_from_error (const string& errmsg) {
size_t marker= errmsg.find (".scm[");
while (marker != string::npos) {
size_t start= marker;
while (start > 0) {
unsigned char ch= static_cast<unsigned char> (errmsg[start - 1]);
if (std::isspace (ch) || ch == '"' || ch == '\'' || ch == '`' || ch == '(' || ch == ')' || ch == ',' || ch == ';') {
break;
}
--start;
}
string candidate= errmsg.substr (start, marker + 4 - start);
if (!candidate.empty ()) {
return candidate;
}
marker= errmsg.find (".scm[", marker + 1);
}
return "";
}
static string
goldfish_extract_error_expression (const string& errmsg, size_t search_start) {
const string infix= " in ";
size_t start= errmsg.find (infix, search_start);
if (start == string::npos) {
return "";
}
start += infix.size ();
size_t end= errmsg.find ('\n', start);
if (end == string::npos) {
end= errmsg.size ();
}
return errmsg.substr (start, end - start);
}
static bool
goldfish_form_contains_called_symbol (s7_scheme* sc, s7_pointer form, const string& function_name) {
if (s7_is_pair (form)) {
s7_pointer operator_form= s7_car (form);
if (s7_is_symbol (operator_form) && (function_name == s7_symbol_name (operator_form))) {
return true;
}
for (s7_pointer iter= form; s7_is_pair (iter); iter= s7_cdr (iter)) {
if (goldfish_form_contains_called_symbol (sc, s7_car (iter), function_name)) {
return true;
}
}
s7_pointer tail= form;
while (s7_is_pair (tail)) {
tail= s7_cdr (tail);
}
if ((!s7_is_null (sc, tail)) && goldfish_form_contains_called_symbol (sc, tail, function_name)) {
return true;
}
}
return false;
}
static bool
goldfish_error_expression_contains_function_call (s7_scheme* sc, const string& expression, const string& function_name) {
if (expression.empty ()) {
return false;
}
s7_pointer port = s7_open_input_string (sc, expression.c_str ());
s7_pointer eof_object= s7_eof_object (sc);
s7_pointer form = s7_read (sc, port);
s7_close_input_port (sc, port);
if ((form == eof_object) || (!form)) {
return expression.find ("(" + function_name) != string::npos;
}
return goldfish_form_contains_called_symbol (sc, form, function_name);
}
static string
goldfish_extract_unbound_function_name_from_error (s7_scheme* sc, const string& errmsg) {
const string prefix= "unbound variable ";
size_t start = errmsg.find (prefix);
if (start == string::npos) {
return "";
}
start += prefix.size ();
size_t end= start;
while (end < errmsg.size ()) {
unsigned char ch= static_cast<unsigned char> (errmsg[end]);
if (std::isspace (ch) || ch == '(' || ch == ')' || ch == ';') {
break;
}
++end;
}
if (end == start) {
return "";
}
string function_name= errmsg.substr (start, end - start);
if (errmsg.find ("in (" + function_name, end) != string::npos) {
return function_name;
}
string error_expression= goldfish_extract_error_expression (errmsg, end);
if (!goldfish_error_expression_contains_function_call (sc, error_expression, function_name)) {
return "";
}
return function_name;
}
static string
goldfish_format_scheme_error_message (const char* errmsg) {
if ((!errmsg) || (!*errmsg)) {
return "";
}
string formatted= errmsg;
if (formatted.find ("Hint: try `") != string::npos) {
return formatted;
}
if (!goldfish_is_fix_hint_candidate_error (formatted)) {
return formatted;
}
string path= goldfish_extract_scheme_path_from_error (formatted);
if (path.empty ()) {
return formatted;
}
if ((!formatted.empty ()) && (formatted.back () != '\n')) {
formatted += '\n';
}
formatted += "Hint: try `" + goldfish_cli_program_name () + " fix " + path + "` to repair common parenthesis issues.\n";
return formatted;
}
static string
goldfish_shell_double_quote (const string& value) {
string quoted= "\"";
for (char ch : value) {
switch (ch) {
case '\\':
quoted += "\\\\";
break;
case '"':
quoted += "\\\"";
break;
case '$':
quoted += "\\$";
break;
case '`':
quoted += "\\`";
break;
default:
quoted += ch;
break;
}
}
quoted += "\"";
return quoted;
}
static string
goldfish_library_display_name (const string& library_query) {
string group;
string library;
if (!split_library_query (library_query, group, library)) {
return library_query;
}
return "(" + group + " " + library + ")";
}
static string
goldfish_library_import_form (const string& library_query) {
string group;
string library;
if (!split_library_query (library_query, group, library)) {
return "";
}
return "(import (" + group + " " + library + "))";
}
static string
goldfish_library_doc_command (const string& library_query, const string& function_name) {
return goldfish_cli_program_name () + " doc " + library_query + " " + goldfish_shell_double_quote (function_name);
}
static string
goldfish_append_doc_hint_if_needed (s7_scheme* sc, const string& errmsg) {
if (errmsg.find ("Hint: try `") != string::npos) {
return errmsg;
}
string function_name= goldfish_extract_unbound_function_name_from_error (sc, errmsg);
if (function_name.empty ()) {
return errmsg;
}
string formatted= errmsg;
if ((!formatted.empty ()) && (formatted.back () != '\n')) {
formatted += '\n';
}
vector<string> library_queries;
try {
library_queries= find_function_libraries_in_load_path (sc, function_name);
}
catch (const std::exception&) {
library_queries.clear ();
}
if (library_queries.empty ()) {
formatted += "Hint: try `" + goldfish_cli_program_name () + " doc " + goldfish_shell_double_quote (function_name) +
"`\n";
formatted += "`" + goldfish_cli_program_name () + " doc` may show similarly named functions when there is no exact match.\n";
formatted += "If it finds nothing similar, try searching the codebase with `git grep "
+ goldfish_shell_double_quote (function_name)
+ "`, implement that function yourself, or stop using it.\n";
return formatted;
}
if (library_queries.size () == 1) {
string import_form= goldfish_library_import_form (library_queries.front ());
formatted += "Hint: function `" + function_name + "` exists in library `" +
goldfish_library_display_name (library_queries.front ()) + "`.\n";
if (!import_form.empty ()) {
formatted += "Please import that library first: `" + import_form + "`.\n";
}
return formatted;
}
formatted += "Hint: function `" + function_name + "` exists in multiple visible libraries:\n";
for (const auto& library_query : library_queries) {
formatted += " " + goldfish_library_display_name (library_query) + "\n";
}
formatted += "Try one of these commands to decide which library to use:\n";
for (const auto& library_query : library_queries) {
formatted += " " + goldfish_library_doc_command (library_query, function_name) + "\n";
}
return formatted;
}
static void
goldfish_render_scheme_error_message (s7_scheme* sc, const char* errmsg, string& rendered) {
rendered= goldfish_append_doc_hint_if_needed (sc, goldfish_format_scheme_error_message (errmsg));
if ((!rendered.empty ()) && (rendered.back () != '\n')) {
rendered += '\n';
}
}
static void
goldfish_print_scheme_error_message (s7_scheme* sc, const char* errmsg) {
if ((errmsg) && (*errmsg)) {
string rendered;
goldfish_render_scheme_error_message (sc, errmsg, rendered);
cout << rendered;
}
}
static void
goldfish_print_prefixed_scheme_error_message (s7_scheme* sc, const string& prefix, const char* errmsg) {
if ((errmsg) && (*errmsg)) {
string rendered;
goldfish_render_scheme_error_message (sc, errmsg, rendered);
cerr << prefix;
if ((!prefix.empty ()) && (prefix.back () != '\n')) {
cerr << '\n';
}
cerr << rendered;
}
}
static void
goldfish_eval_code (s7_scheme* sc, string code) {
string wrapped_code = "(begin " + code + " )";
s7_pointer x= s7_eval_c_string (sc, wrapped_code.c_str ());
cout << s7_object_to_c_string (sc, x) << endl;
}
static string
find_golddoc_tool_root (const char* gf_lib) {
std::error_code ec;
vector<fs::path> candidates= {fs::path (gf_lib) / "tools" / "doc", fs::path (gf_lib).parent_path () / "tools" / "doc"};
for (const auto& candidate : candidates) {
if (fs::is_directory (candidate, ec)) {
return candidate.string ();
}
ec.clear ();
}
return "";
}
static string
find_goldsource_tool_root (const char* gf_lib) {
std::error_code ec;
vector<fs::path> candidates= {fs::path (gf_lib) / "tools" / "source", fs::path (gf_lib).parent_path () / "tools" / "source"};
for (const auto& candidate : candidates) {
if (fs::is_directory (candidate, ec)) {
return candidate.string ();
}
ec.clear ();
}
return "";
}
static string
find_goldhelp_tool_root (const char* gf_lib) {
std::error_code ec;
vector<fs::path> candidates= {fs::path (gf_lib) / "tools" / "help", fs::path (gf_lib).parent_path () / "tools" / "help"};
for (const auto& candidate : candidates) {
if (fs::is_directory (candidate, ec)) {