-
Notifications
You must be signed in to change notification settings - Fork 0
/
websocket-stdio
executable file
·7062 lines (6884 loc) · 749 KB
/
websocket-stdio
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
#!/usr/bin/env luajit
local help_string = "./websocket-stdio [-i *.html | -b builtin] [opts]\nMap websocket read write events to stdin and stdout events.\n\n -i html file to map to index.html\n -b use a builtin html file for index.html\n -d root directory of files to serve - optional\n -t host - optional - defaults to localhost\n -p websocket port - optional - defaults to 9000\n -l log file - optional - won't log unless provided\n -a list all available builtin html files\n -h show this text\n\nexamples:\n## Host index.html at localhost:8080/\n./websocket-stdio -i index.html -p 8080\n\n## Host repl.html at localhost:8080/ and All files in project folder will\n## be available under localhost:8080/. For example localhost:8080/src/main.lua\n./websocket-stdio -i repl.html -d project-folder/ -p 8080\n\n## Use the builtin html file lovejs-repl on port 3000\n./websocket-stdio -b lovejs-repl -d game-folder/ -p 3000"
local _loader package.preload["luarocks.loader"] = package.preload["luarocks.loader"] or function(...) --- A module which installs a Lua package loader that is LuaRocks-aware.
-- This loader uses dependency information from the LuaRocks tree to load
-- correct versions of modules. It does this by constructing a "context"
-- table in the environment, which records which versions of packages were
-- used to load previous modules, so that the loader chooses versions
-- that are declared to be compatible with the ones loaded earlier.
-- luacheck: globals luarocks
local loaders = package.loaders or package.searchers
local require, ipairs, table, type, next, tostring, error =
require, ipairs, table, type, next, tostring, error
local unpack = unpack or table.unpack
local loader = {}
local is_clean = not package.loaded["luarocks.core.cfg"]
-- This loader module depends only on core modules.
local cfg = require("luarocks.core.cfg")
local cfg_ok, err = cfg.init()
if cfg_ok then
cfg.init_package_paths()
end
local path = require("luarocks.core.path")
local manif = require("luarocks.core.manif")
local vers = require("luarocks.core.vers")
local require = nil -- luacheck: ignore 411
--------------------------------------------------------------------------------
-- Workaround for wrappers produced by older versions of LuaRocks
local temporary_global = false
local status, luarocks_value = pcall(function() return luarocks end)
if status and luarocks_value then
-- The site_config.lua file generated by old versions uses module(),
-- so it produces a global `luarocks` table. Since we have the table,
-- add the `loader` field to make the old wrappers happy.
luarocks.loader = loader
else
-- When a new version is installed on top of an old version,
-- site_config.lua may be replaced, and then it no longer creates
-- a global.
-- Detect when being called via -lluarocks.loader; this is
-- most likely a wrapper.
local info = debug and debug.getinfo(2, "nS")
if info and info.what == "C" and not info.name then
luarocks = { loader = loader }
temporary_global = true
-- For the other half of this hack,
-- see the next use of `temporary_global` below.
end
end
loader.context = {}
--- Process the dependencies of a package to determine its dependency
-- chain for loading modules.
-- @param name string: The name of an installed rock.
-- @param version string: The version of the rock, in string format
function loader.add_context(name, version)
-- assert(type(name) == "string")
-- assert(type(version) == "string")
if temporary_global then
-- The first thing a wrapper does is to call add_context.
-- From here on, it's safe to clean the global environment.
luarocks = nil
temporary_global = false
end
local tree_manifests = manif.load_rocks_tree_manifests()
if not tree_manifests then
return nil
end
return manif.scan_dependencies(name, version, tree_manifests, loader.context)
end
--- Internal sorting function.
-- @param a table: A provider table.
-- @param b table: Another provider table.
-- @return boolean: True if the version of a is greater than that of b.
local function sort_versions(a,b)
return a.version > b.version
end
--- Request module to be loaded through other loaders,
-- once the proper name of the module has been determined.
-- For example, in case the module "socket.core" has been requested
-- to the LuaRocks loader and it determined based on context that
-- the version 2.0.2 needs to be loaded and it is not the current
-- version, the module requested for the other loaders will be
-- "socket.core_2_0_2".
-- @param module The module name requested by the user, such as "socket.core"
-- @param name The rock name, such as "luasocket"
-- @param version The rock version, such as "2.0.2-1"
-- @param module_name The actual module name, such as "socket.core" or "socket.core_2_0_2".
-- @return table or (nil, string): The module table as returned by some other loader,
-- or nil followed by an error message if no other loader managed to load the module.
local function call_other_loaders(module, name, version, module_name)
for _, a_loader in ipairs(loaders) do
if a_loader ~= loader.luarocks_loader then
local results = { a_loader(module_name) }
if type(results[1]) == "function" then
return unpack(results)
end
end
end
return "Failed loading module "..module.." in LuaRocks rock "..name.." "..version
end
local function add_providers(providers, entries, tree, module, filter_file_name)
for i, entry in ipairs(entries) do
local name, version = entry:match("^([^/]*)/(.*)$")
local file_name = tree.manifest.repository[name][version][1].modules[module]
if type(file_name) ~= "string" then
error("Invalid data in manifest file for module "..tostring(module).." (invalid data for "..tostring(name).." "..tostring(version)..")")
end
file_name = filter_file_name(file_name, name, version, tree.tree, i)
if loader.context[name] == version then
return name, version, file_name
end
version = vers.parse_version(version)
table.insert(providers, {name = name, version = version, module_name = file_name, tree = tree})
end
end
--- Search for a module in the rocks trees
-- @param module string: module name (eg. "socket.core")
-- @param filter_file_name function(string, string, string, string, number):
-- a function that takes the module file name (eg "socket/core.so"), the rock name
-- (eg "luasocket"), the version (eg "2.0.2-1"), the path of the rocks tree
-- (eg "/usr/local"), and the numeric index of the matching entry, so the
-- filter function can know if the matching module was the first entry or not.
-- @return string, string, string, (string or table):
-- * name of the rock containing the module (eg. "luasocket")
-- * version of the rock (eg. "2.0.2-1")
-- * return value of filter_file_name
-- * tree of the module (string or table in `tree_manifests` format)
local function select_module(module, filter_file_name)
--assert(type(module) == "string")
--assert(type(filter_module_name) == "function")
local tree_manifests = manif.load_rocks_tree_manifests()
if not tree_manifests then
return nil
end
local providers = {}
local initmodule
for _, tree in ipairs(tree_manifests) do
local entries = tree.manifest.modules[module]
if entries then
local n, v, f = add_providers(providers, entries, tree, module, filter_file_name)
if n then
return n, v, f
end
else
initmodule = initmodule or module .. ".init"
entries = tree.manifest.modules[initmodule]
if entries then
local n, v, f = add_providers(providers, entries, tree, initmodule, filter_file_name)
if n then
return n, v, f
end
end
end
end
if next(providers) then
table.sort(providers, sort_versions)
local first = providers[1]
return first.name, first.version.string, first.module_name, first.tree
end
end
--- Search for a module
-- @param module string: module name (eg. "socket.core")
-- @return string, string, string, (string or table):
-- * name of the rock containing the module (eg. "luasocket")
-- * version of the rock (eg. "2.0.2-1")
-- * name of the module (eg. "socket.core", or "socket.core_2_0_2" if file is stored versioned).
-- * tree of the module (string or table in `tree_manifests` format)
local function pick_module(module)
return
select_module(module, function(file_name, name, version, tree, i)
if i > 1 then
file_name = path.versioned_name(file_name, "", name, version)
end
return path.path_to_module(file_name)
end)
end
--- Return the pathname of the file that would be loaded for a module.
-- @param module string: module name (eg. "socket.core")
-- @return filename of the module (eg. "/usr/local/lib/lua/5.1/socket/core.so"),
-- the rock name and the rock version.
function loader.which(module)
local rock_name, rock_version, file_name = select_module(module, path.which_i)
return file_name, rock_name, rock_version
end
--- Package loader for LuaRocks support.
-- A module is searched in installed rocks that match the
-- current LuaRocks context. If module is not part of the
-- context, or if a context has not yet been set, the module
-- in the package with the highest version is used.
-- @param module string: The module name, like in plain require().
-- @return table: The module table (typically), like in plain
-- require(). See <a href="http://www.lua.org/manual/5.1/manual.html#pdf-require">require()</a>
-- in the Lua reference manual for details.
function loader.luarocks_loader(module)
local name, version, module_name = pick_module(module)
if not name then
return "No LuaRocks module found for "..module
else
loader.add_context(name, version)
return call_other_loaders(module, name, version, module_name)
end
end
table.insert(loaders, 1, loader.luarocks_loader)
if is_clean then
for modname, _ in pairs(package.loaded) do
if modname:match("^luarocks%.") then
package.loaded[modname] = nil
end
end
end
return loader end _loader = require("luarocks.loader")
local getopt = require("getopt")
local lustache = require("lustache")
local websocket_stdio_lib package.preload["lib.websocket-stdio-lib"] = package.preload["lib.websocket-stdio-lib"] or function(...) local fennel = require("lib.fennel") local _loader = require("luarocks.loader") local server = require("http.server") local headers = require("http.headers") local websocket = require("http.websocket") local tls = require("http.tls") local log local function _1_(self, file_name_3f) if file_name_3f then self["file-name"] = file_name_3f else end local ok, handle = pcall(io.open, self["file-name"], "w") if ok then self["file-handle"] = handle return nil else return nil end end local function _4_(self) if self["file-handle"] then local ok = pcall(io.close, self["file-handle"]) if ok then self["file-handle"] = nil return nil else return nil end else return nil end end local function _7_(self, message_7chandle_3f, message_3f) local message = (message_3f or message_7chandle_3f) local handle if message_3f then handle = message_7chandle_3f else handle = nil end if not self["file-handle"] then self:open() else end if self["file-handle"] then if handle then self["file-handle"]:write(handle) self["file-handle"]:write(" ") else end self["file-handle"]:write(message) self["file-handle"]:write("\n") return self["file-handle"]:flush() else return nil end end local function _12_(self) self.enabled = false return nil end local function _13_(self) self.enabled = true return nil end local function _14_(self, ...) if self.enabled then return self:log(...) else return nil end end log = setmetatable({["file-handle"] = nil, ["file-name"] = "log~", enabled = true}, {__index = {open = _1_, close = _4_, log = _7_, disable = _12_, enable = _13_}, __call = _14_}) local function load_html(file_name, internal_server_error_3f) local internal_server_error = (internal_server_error_3f or "<html>\n<head><title>Page Load Error</title</head>\n<body>\n<p>The server failed to load the page requested</p>\n</body\n</html") local ok, html = nil, nil local function _16_() local fin = io.open(file_name) local function close_handlers_12_(ok_13_, ...) fin:close() if ok_13_ then
return ... else return error(..., 0) end end local function _18_() return fin:read("*all") end local _20_ do local t_19_ = _G if (nil ~= t_19_) then t_19_ = t_19_.package else end if (nil ~= t_19_) then t_19_ = t_19_.loaded else end if (nil ~= t_19_) then t_19_ = t_19_.fennel else end _20_ = t_19_ end local or_24_ = _20_ or _G.debug if not or_24_ then local function _25_() return "" end or_24_ = {traceback = _25_} end return close_handlers_12_(_G.xpcall(_18_, or_24_.traceback)) end ok, html = pcall(_16_) if ok then return html, 200 else return internal_server_error, 500 end end local function make_static_response(msg, status, content_type_3f) local function _27_(_s, stream, req_headers) local req_method = req_headers:get(":method") local res_headers = headers.new() for key, value in pairs({[":status"] = tostring(status), ["content-type"] = (content_type_3f or "text/html; charset=utf-8")}) do res_headers:append(key, value) end assert(stream:write_headers(res_headers, (req_method == "HEAD"))) if (req_method ~= "HEAD") then return assert(stream:write_chunk(msg, true)) else return nil end end return _27_ end local function make_static_page(page) local function _29_(_s, stream, req_headers) local msg, status = load_html(page) local req_method = req_headers:get(":method") local res_headers = headers.new() for key, value in pairs({[":status"] = tostring(status), ["content-type"] = "text/html; charset=utf-8"}) do res_headers:append(key, value) end assert(stream:write_headers(res_headers, (req_method == "HEAD"))) if (req_method ~= "HEAD") then return assert(stream:write_chunk(msg, true)) else return nil end end return _29_ end local watcher local function _31_(self, dir) if ("/" == dir:sub(#dir, #dir)) then self.dir = dir else self.dir = (dir .. "/") end return nil end local function _33_(self, callback, dir_3f) local function append__2f(dir) if ("/" == dir:sub(#dir, #dir)) then return dir else return (dir .. "/") end end local lfs = require("lfs") local dir = append__2f((dir_3f or self.dir)) for file in lfs.dir(dir) do if ((file ~= ".") and (file ~= "..")) then local dfile = (dir .. file) local atts = lfs.attributes(dfile) if atts then local _35_ = atts.mode if (_35_ == "file") then if (atts.modification > self["last-modified"]) then self["last-modified"] = atts.modification callback(self, dfile, atts) else end elseif (_35_ == "directory") then self["check-modified"](self, callback, dfile) else end else end else end end return nil end local function _40_(self) local function _42_(self0, _file, _41_) local modification = _41_["modification"] self0["last-modified"] = modification return nil end self["check-modified"](self, _42_) return self["last-modified"] end local function _43_(self, callback) local function _45_(self0, file, _44_) local modification = _44_["modification"] self0["last-modified"] = modification return callback(file) end return self["check-modified"](self, _45_) end local function _46_(self, callback) local cqueues = require("cqueues") self["get-last-modified"](self) while true do self:step(callback) cqueues.sleep(self["check-period"]) end return nil end watcher = setmetatable({dir = "./", ["check-period"] = 0.1, ["last-modified"] = 0}, {__index = {["set-directory"] = _31_, ["check-modified"] = _33_, ["get-last-modified"] = _40_, step = _43_, loop = _46_}}) local r404 = make_static_response("404 - Page not found\n", 404) local function ws(ws_server, stream, req_headers) log("*opening-new-websocket") local function loop(ws0) local _47_ = ws0.got_close_code if (_47_ == 1000) then return ws0:close(1000, "Connection Closed") else local _ = _47_ local _48_ = ws0:receive() if (nil ~= _48_) then local msg = _48_ io.stdout:write(msg) log("*msg", msg) io.stdout:flush() return loop(ws0) else return nil end end end local function stdin(handle, ws0) local function loop0() local input = handle:read() log("*stdin", input) log("*ws", fennel.view(ws0, {["one-line?"] = true})) ws0:send(input) return loop0() end return loop0 end local res_headers = headers.new() local _51_ = req_headers:get("connection") if (_51_ == "Upgrade") then local new_ws = websocket.new_from_stream(stream, req_headers) local success_3f, err, err_no = new_ws:accept({headers = res_headers}) if err then log("*con-failed", fennel.view({err, err_no, new_ws}, {["one-line?"] = true})) else end if success_3f then log("*con-success", fennel.view(new_ws, {["one-line?"] = true})) local cs = require("cqueues.socket") ws_server.cq:wrap(stdin(cs.fdopen(0), new_ws)) return loop(new_ws) else return nil end else return nil end end local function file_change_events(_server, stream, _req_headers, serve_directories) local res_headers = headers.new() if serve_directories then local serve_directory = serve_directories[1] local match_string = serve_directories[1]:gsub("%.", "%%%.") log("*file-change-events", "Success!") res_headers:append(":status", "200") res_headers:append("content-type", "text/event-stream") assert(stream:write_headers(res_headers, false)) watcher.dir = serve_directory watcher["check-period"] = 0.1 assert(stream:write_chunk((": comment" .. "\n\n"), false)) assert(stream:write_chunk(("data: #connected" .. "\n\n"), false)) local function _55_(file) log("*watcher", file) assert(stream:write_chunk((": comment" .. "\n\n"), false)) return assert(stream:write_chunk(("data: " .. file:gsub(match_string, "") .. "\n\n"), false)) end return watcher:loop(_55_, serve_directory) else log("*file-change-events", "Failed!") res_headers:append(":status", "500") res_headers:append("content-type", "text/text") assert(stream:write_headers(res_headers, false)) return assert(stream:write_chunk("Error 500: The server is not configured to host static pages.", false)) end end local function make_paths(opts, builtin) local path if (opts.b and builtin) then path = builtin[opts.b].path({port = opts.p, host = opts.t}) elseif opts.i then path = {["/"] = make_static_page(opts.i)} else path = nil end path["/ws"] = ws path["/file-change-events"] = file_change_events return path end local function err(ws_server, ctx, op, err0, errno) local function _58_() if err0 then return (": " .. tostring(err0)) else return "" end end return log("*err", string.format("%s on %s failed%s", op, tostring(ctx), _58_())) end local function get_static_file(file__2f, serve_directories) local lfs = require("lfs") local file = file__2f:sub(2, #file__2f) if serve_directories then for _, serve_directory in ipairs(serve_directories) do local dfile = ((serve_directory or "./") .. file) local attrs = lfs.attributes(dfile) log("*serve-directory", (serve_directory or "Not Defined!")) log("*file", dfile) log("*attrs", fennel.view(attrs, {["one-line?"] = true})) if (serve_directory and attrs and (attrs.mode == "file")) then local fin = io.open(dfile, "r") local str = (fin and fin:read("*all")) if fin then fin:close() else end if str then local content_type if dfile:find("%.wasm$") then content_type = "application/wasm" elseif dfile:find("%.js$") then content_type = "text/javascript" elseif dfile:find("%.html$") then content_type = "text/html" else content_type = nil end local fun = make_static_response(str, 200, content_type) return fun else end else end end return nil else return nil end end local function new_server(opts, builtin) local port = (opts.p or 9000) local paths = make_paths(opts, builtin) log:disable() if opts.l then log:enable() if ("string" == type(opts.l)) then log["file-name"] = opts.l else end else end local serve_directories if opts.d then local function process_directory(dir) local function _66_(dir0) if ("/" ~= dir0:sub(#dir0, #dir0)) then return (dir0 .. "/") else return dir0 end end local function _68_(dir0) if ("./" ~= dir0:sub(1, 2)) then return ("./" .. dir0) else return dir0 end end return _66_(_68_(dir)) end local tbl_21_ = {} local i_22_ = 0 for dir in opts.d:gmatch("([^,]*)") do local val_23_ if (dir ~= "") then val_23_ = process_directory(dir) else val_23_ = nil end if (nil ~= val_23_) then i_22_ = (i_22_ + 1) tbl_21_[i_22_] = val_23_ else end end serve_directories = tbl_21_ else serve_directories = nil end local function reply(s, stream) local req_headers = assert(stream:get_headers()) local path_fun = (paths[req_headers:get(":path")] or get_static_file(req_headers:get(":path"), serve_directories) or r404) log("*path", req_headers:get(":path")) log("*headers", fennel.view(req_headers, {["one-line?"] = true})) return path_fun(s, stream, req_headers, serve_directories) end local ws_server local _73_ if ("localhost" == opts.t) then _73_ = opts.t else _73_ = "0.0.0.0" end ws_server = assert(server.listen({host = _73_, port = port, onstream = reply, onerror = err})) return ws_server end return {["new-server"] = new_server, watcher = watcher, ["make-static-response"] = make_static_response, ["make-static-page"] = make_static_page} end package.preload["lib.fennel"] = package.preload["lib.fennel"] or function(...) -- SPDX-License-Identifier: MIT
-- SPDX-FileCopyrightText: Calvin Rose and contributors
package.preload["fennel.repl"] = package.preload["fennel.repl"] or function(...)
local utils = require("fennel.utils")
local parser = require("fennel.parser")
local compiler = require("fennel.compiler")
local specials = require("fennel.specials")
local view = require("fennel.view")
local depth = 0
local function prompt_for(top_3f)
if top_3f then
return (string.rep(">", (depth + 1)) .. " ")
else
return (string.rep(".", (depth + 1)) .. " ")
end
end
local function default_read_chunk(parser_state)
io.write(prompt_for((0 == parser_state["stack-size"])))
io.flush()
local input = io.read()
return (input and (input .. "\n"))
end
local function default_on_values(xs)
io.write(table.concat(xs, "\9"))
return io.write("\n")
end
local function default_on_error(errtype, err)
local function _702_()
local _701_0 = errtype
if (_701_0 == "Runtime") then
return (compiler.traceback(tostring(err), 4) .. "\n")
else
local _ = _701_0
return ("%s error: %s\n"):format(errtype, tostring(err))
end
end
return io.write(_702_())
end
local function splice_save_locals(env, lua_source, scope)
local saves = nil
do
local tbl_17_ = {}
local i_18_ = #tbl_17_
for name in pairs(env.___replLocals___) do
local val_19_ = ("local %s = ___replLocals___[%q]"):format((scope.manglings[name] or name), name)
if (nil ~= val_19_) then
i_18_ = (i_18_ + 1)
tbl_17_[i_18_] = val_19_
end
end
saves = tbl_17_
end
local binds = nil
do
local tbl_17_ = {}
local i_18_ = #tbl_17_
for raw, name in pairs(scope.manglings) do
local val_19_ = nil
if not scope.gensyms[name] then
val_19_ = ("___replLocals___[%q] = %s"):format(raw, name)
else
val_19_ = nil
end
if (nil ~= val_19_) then
i_18_ = (i_18_ + 1)
tbl_17_[i_18_] = val_19_
end
end
binds = tbl_17_
end
local gap = nil
if lua_source:find("\n") then
gap = "\n"
else
gap = " "
end
local function _708_()
if next(saves) then
return (table.concat(saves, " ") .. gap)
else
return ""
end
end
local function _711_()
local _709_0, _710_0 = lua_source:match("^(.*)[\n ](return .*)$")
if ((nil ~= _709_0) and (nil ~= _710_0)) then
local body = _709_0
local _return = _710_0
return (body .. gap .. table.concat(binds, " ") .. gap .. _return)
else
local _ = _709_0
return lua_source
end
end
return (_708_() .. _711_())
end
local commands = {}
local function completer(env, scope, text, _3ffulltext, _from, _to)
local max_items = 2000
local seen = {}
local matches = {}
local input_fragment = text:gsub(".*[%s)(]+", "")
local stop_looking_3f = false
local function add_partials(input, tbl, prefix)
local scope_first_3f = ((tbl == env) or (tbl == env.___replLocals___))
local tbl_17_ = matches
local i_18_ = #tbl_17_
local function _713_()
if scope_first_3f then
return scope.manglings
else
return tbl
end
end
for k, is_mangled in utils.allpairs(_713_()) do
if (max_items <= #matches) then break end
local val_19_ = nil
do
local lookup_k = nil
if scope_first_3f then
lookup_k = is_mangled
else
lookup_k = k
end
if ((type(k) == "string") and (input == k:sub(0, #input)) and not seen[k] and ((":" ~= prefix:sub(-1)) or ("function" == type(tbl[lookup_k])))) then
seen[k] = true
val_19_ = (prefix .. k)
else
val_19_ = nil
end
end
if (nil ~= val_19_) then
i_18_ = (i_18_ + 1)
tbl_17_[i_18_] = val_19_
end
end
return tbl_17_
end
local function descend(input, tbl, prefix, add_matches, method_3f)
local splitter = nil
if method_3f then
splitter = "^([^:]+):(.*)"
else
splitter = "^([^.]+)%.(.*)"
end
local head, tail = input:match(splitter)
local raw_head = (scope.manglings[head] or head)
if (type(tbl[raw_head]) == "table") then
stop_looking_3f = true
if method_3f then
return add_partials(tail, tbl[raw_head], (prefix .. head .. ":"))
else
return add_matches(tail, tbl[raw_head], (prefix .. head))
end
end
end
local function add_matches(input, tbl, prefix)
local prefix0 = nil
if prefix then
prefix0 = (prefix .. ".")
else
prefix0 = ""
end
if (not input:find("%.") and input:find(":")) then
return descend(input, tbl, prefix0, add_matches, true)
elseif not input:find("%.") then
return add_partials(input, tbl, prefix0)
else
return descend(input, tbl, prefix0, add_matches, false)
end
end
do
local _722_0 = tostring((_3ffulltext or text)):match("^%s*,([^%s()[%]]*)$")
if (nil ~= _722_0) then
local cmd_fragment = _722_0
add_partials(cmd_fragment, commands, ",")
else
local _ = _722_0
for _0, source in ipairs({scope.specials, scope.macros, (env.___replLocals___ or {}), env, env._G}) do
if stop_looking_3f then break end
add_matches(input_fragment, source)
end
end
end
return matches
end
local function command_3f(input)
return input:match("^%s*,")
end
local function command_docs()
local _724_
do
local tbl_17_ = {}
local i_18_ = #tbl_17_
for name, f in utils.stablepairs(commands) do
local val_19_ = (" ,%s - %s"):format(name, ((compiler.metadata):get(f, "fnl/docstring") or "undocumented"))
if (nil ~= val_19_) then
i_18_ = (i_18_ + 1)
tbl_17_[i_18_] = val_19_
end
end
_724_ = tbl_17_
end
return table.concat(_724_, "\n")
end
commands.help = function(_, _0, on_values)
return on_values({("Welcome to Fennel.\nThis is the REPL where you can enter code to be evaluated.\nYou can also run these repl commands:\n\n" .. command_docs() .. "\n ,return FORM - Evaluate FORM and return its value to the REPL's caller.\n ,exit - Leave the repl.\n\nUse ,doc something to see descriptions for individual macros and special forms.\nValues from previous inputs are kept in *1, *2, and *3.\n\nFor more information about the language, see https://fennel-lang.org/reference")})
end
do end (compiler.metadata):set(commands.help, "fnl/docstring", "Show this message.")
local function reload(module_name, env, on_values, on_error)
local _726_0, _727_0 = pcall(specials["load-code"]("return require(...)", env), module_name)
if ((_726_0 == true) and (nil ~= _727_0)) then
local old = _727_0
local _ = nil
package.loaded[module_name] = nil
_ = nil
local new = nil
do
local _728_0, _729_0 = pcall(require, module_name)
if ((_728_0 == true) and (nil ~= _729_0)) then
local new0 = _729_0
new = new0
elseif (true and (nil ~= _729_0)) then
local _0 = _728_0
local msg = _729_0
on_error("Repl", msg)
new = old
else
new = nil
end
end
specials["macro-loaded"][module_name] = nil
if ((type(old) == "table") and (type(new) == "table")) then
for k, v in pairs(new) do
old[k] = v
end
for k in pairs(old) do
if (nil == new[k]) then
old[k] = nil
end
end
package.loaded[module_name] = old
end
return on_values({"ok"})
elseif ((_726_0 == false) and (nil ~= _727_0)) then
local msg = _727_0
if msg:match("loop or previous error loading module") then
package.loaded[module_name] = nil
return reload(module_name, env, on_values, on_error)
elseif specials["macro-loaded"][module_name] then
specials["macro-loaded"][module_name] = nil
return nil
else
local function _734_()
local _733_0 = msg:gsub("\n.*", "")
return _733_0
end
return on_error("Runtime", _734_())
end
end
end
local function run_command(read, on_error, f)
local _737_0, _738_0, _739_0 = pcall(read)
if ((_737_0 == true) and (_738_0 == true) and (nil ~= _739_0)) then
local val = _739_0
local _740_0, _741_0 = pcall(f, val)
if ((_740_0 == false) and (nil ~= _741_0)) then
local msg = _741_0
return on_error("Runtime", msg)
end
elseif (_737_0 == false) then
return on_error("Parse", "Couldn't parse input.")
end
end
commands.reload = function(env, read, on_values, on_error)
local function _744_(_241)
return reload(tostring(_241), env, on_values, on_error)
end
return run_command(read, on_error, _744_)
end
do end (compiler.metadata):set(commands.reload, "fnl/docstring", "Reload the specified module.")
commands.reset = function(env, _, on_values)
env.___replLocals___ = {}
return on_values({"ok"})
end
do end (compiler.metadata):set(commands.reset, "fnl/docstring", "Erase all repl-local scope.")
commands.complete = function(env, read, on_values, on_error, scope, chars)
local function _745_()
return on_values(completer(env, scope, table.concat(chars):gsub("^%s*,complete%s+", ""):sub(1, -2)))
end
return run_command(read, on_error, _745_)
end
do end (compiler.metadata):set(commands.complete, "fnl/docstring", "Print all possible completions for a given input symbol.")
local function apropos_2a(pattern, tbl, prefix, seen, names)
for name, subtbl in pairs(tbl) do
if (("string" == type(name)) and (package ~= subtbl)) then
local _746_0 = type(subtbl)
if (_746_0 == "function") then
if ((prefix .. name)):match(pattern) then
table.insert(names, (prefix .. name))
end
elseif (_746_0 == "table") then
if not seen[subtbl] then
local _748_
do
seen[subtbl] = true
_748_ = seen
end
apropos_2a(pattern, subtbl, (prefix .. name:gsub("%.", "/") .. "."), _748_, names)
end
end
end
end
return names
end
local function apropos(pattern)
return apropos_2a(pattern:gsub("^_G%.", ""), package.loaded, "", {}, {})
end
commands.apropos = function(_env, read, on_values, on_error, _scope)
local function _752_(_241)
return on_values(apropos(tostring(_241)))
end
return run_command(read, on_error, _752_)
end
do end (compiler.metadata):set(commands.apropos, "fnl/docstring", "Print all functions matching a pattern in all loaded modules.")
local function apropos_follow_path(path)
local paths = nil
do
local tbl_17_ = {}
local i_18_ = #tbl_17_
for p in path:gmatch("[^%.]+") do
local val_19_ = p
if (nil ~= val_19_) then
i_18_ = (i_18_ + 1)
tbl_17_[i_18_] = val_19_
end
end
paths = tbl_17_
end
local tgt = package.loaded
for _, path0 in ipairs(paths) do
if (nil == tgt) then break end
local _755_
do
local _754_0 = path0:gsub("%/", ".")
_755_ = _754_0
end
tgt = tgt[_755_]
end
return tgt
end
local function apropos_doc(pattern)
local tbl_17_ = {}
local i_18_ = #tbl_17_
for _, path in ipairs(apropos(".*")) do
local val_19_ = nil
do
local tgt = apropos_follow_path(path)
if ("function" == type(tgt)) then
local _756_0 = (compiler.metadata):get(tgt, "fnl/docstring")
if (nil ~= _756_0) then
local docstr = _756_0
val_19_ = (docstr:match(pattern) and path)
else
val_19_ = nil
end
else
val_19_ = nil
end
end
if (nil ~= val_19_) then
i_18_ = (i_18_ + 1)
tbl_17_[i_18_] = val_19_
end
end
return tbl_17_
end
commands["apropos-doc"] = function(_env, read, on_values, on_error, _scope)
local function _760_(_241)
return on_values(apropos_doc(tostring(_241)))
end
return run_command(read, on_error, _760_)
end
do end (compiler.metadata):set(commands["apropos-doc"], "fnl/docstring", "Print all functions that match the pattern in their docs")
local function apropos_show_docs(on_values, pattern)
for _, path in ipairs(apropos(pattern)) do
local tgt = apropos_follow_path(path)
if (("function" == type(tgt)) and (compiler.metadata):get(tgt, "fnl/docstring")) then
on_values({specials.doc(tgt, path)})
on_values({})
end
end
return nil
end
commands["apropos-show-docs"] = function(_env, read, on_values, on_error)
local function _762_(_241)
return apropos_show_docs(on_values, tostring(_241))
end
return run_command(read, on_error, _762_)
end
do end (compiler.metadata):set(commands["apropos-show-docs"], "fnl/docstring", "Print all documentations matching a pattern in function name")
local function resolve(identifier, _763_0, scope)
local _764_ = _763_0
local env = _764_
local ___replLocals___ = _764_["___replLocals___"]
local e = nil
local function _765_(_241, _242)
return (___replLocals___[scope.unmanglings[_242]] or env[_242])
end
e = setmetatable({}, {__index = _765_})
local function _766_(...)
local _767_0, _768_0 = ...
if ((_767_0 == true) and (nil ~= _768_0)) then
local code = _768_0
local function _769_(...)
local _770_0, _771_0 = ...
if ((_770_0 == true) and (nil ~= _771_0)) then
local val = _771_0
return val
else
local _ = _770_0
return nil
end
end
return _769_(pcall(specials["load-code"](code, e)))
else
local _ = _767_0
return nil
end
end
return _766_(pcall(compiler["compile-string"], tostring(identifier), {scope = scope}))
end
commands.find = function(env, read, on_values, on_error, scope)
local function _774_(_241)
local _775_0 = nil
do
local _776_0 = utils["sym?"](_241)
if (nil ~= _776_0) then
local _777_0 = resolve(_776_0, env, scope)
if (nil ~= _777_0) then
_775_0 = debug.getinfo(_777_0)
else
_775_0 = _777_0
end
else
_775_0 = _776_0
end
end
if ((_G.type(_775_0) == "table") and (nil ~= _775_0.linedefined) and (nil ~= _775_0.short_src) and (nil ~= _775_0.source) and (_775_0.what == "Lua")) then
local line = _775_0.linedefined
local src = _775_0.short_src
local source = _775_0.source
local fnlsrc = nil
do
local _780_0 = compiler.sourcemap
if (nil ~= _780_0) then
_780_0 = _780_0[source]
end
if (nil ~= _780_0) then
_780_0 = _780_0[line]
end
if (nil ~= _780_0) then
_780_0 = _780_0[2]
end
fnlsrc = _780_0
end
return on_values({string.format("%s:%s", src, (fnlsrc or line))})
elseif (_775_0 == nil) then
return on_error("Repl", "Unknown value")
else
local _ = _775_0
return on_error("Repl", "No source info")
end
end
return run_command(read, on_error, _774_)
end
do end (compiler.metadata):set(commands.find, "fnl/docstring", "Print the filename and line number for a given function")
commands.doc = function(env, read, on_values, on_error, scope)
local function _785_(_241)
local name = tostring(_241)
local path = (utils["multi-sym?"](name) or {name})
local ok_3f, target = nil, nil
local function _786_()
return (scope.specials[name] or utils["get-in"](scope.macros, path) or resolve(name, env, scope))
end
ok_3f, target = pcall(_786_)
if ok_3f then
return on_values({specials.doc(target, name)})
else
return on_error("Repl", ("Could not find " .. name .. " for docs."))
end
end
return run_command(read, on_error, _785_)
end
do end (compiler.metadata):set(commands.doc, "fnl/docstring", "Print the docstring and arglist for a function, macro, or special form.")
commands.compile = function(_, read, on_values, on_error, _0, _1, opts)
local function _788_(_241)
local _789_0, _790_0 = pcall(compiler.compile, _241, opts)
if ((_789_0 == true) and (nil ~= _790_0)) then
local result = _790_0
return on_values({result})
elseif (true and (nil ~= _790_0)) then
local _2 = _789_0
local msg = _790_0
return on_error("Repl", ("Error compiling expression: " .. msg))
end
end
return run_command(read, on_error, _788_)
end
do end (compiler.metadata):set(commands.compile, "fnl/docstring", "compiles the expression into lua and prints the result.")
local function load_plugin_commands(plugins)
for i = #(plugins or {}), 1, -1 do
for name, f in pairs(plugins[i]) do
local _792_0 = name:match("^repl%-command%-(.*)")
if (nil ~= _792_0) then
local cmd_name = _792_0
commands[cmd_name] = f
end
end
end
return nil
end
local function run_command_loop(input, read, loop, env, on_values, on_error, scope, chars, opts)
local command_name = input:match(",([^%s/]+)")
do
local _794_0 = commands[command_name]
if (nil ~= _794_0) then
local command = _794_0
command(env, read, on_values, on_error, scope, chars, opts)
else
local _ = _794_0
if ((command_name ~= "exit") and (command_name ~= "return")) then
on_values({"Unknown command", command_name})
end
end
end
if ("exit" ~= command_name) then
return loop((command_name == "return"))
end
end
local function try_readline_21(opts, ok, readline)
if ok then
if readline.set_readline_name then
readline.set_readline_name("fennel")
end
readline.set_options({histfile = "", keeplines = 1000})
opts.readChunk = function(parser_state)
local prompt = nil
if (0 < parser_state["stack-size"]) then
prompt = ".. "
else
prompt = ">> "
end
local str = readline.readline(prompt)
if str then
return (str .. "\n")
end
end
local completer0 = nil
opts.registerCompleter = function(repl_completer)
completer0 = repl_completer
return nil
end
local function repl_completer(text, from, to)
if completer0 then
readline.set_completion_append_character("")
return completer0(text:sub(from, to), text, from, to)
else
return {}
end
end
readline.set_complete_function(repl_completer)
return readline
end
end
local function should_use_readline_3f(opts)
return (("dumb" ~= os.getenv("TERM")) and not opts.readChunk and not opts.registerCompleter)
end
local function repl(_3foptions)
local old_root_options = utils.root.options
local _803_ = utils.copy(_3foptions)
local opts = _803_
local _3ffennelrc = _803_["fennelrc"]
local _ = nil
opts.fennelrc = nil
_ = nil
local readline = (should_use_readline_3f(opts) and try_readline_21(opts, pcall(require, "readline")))
local _0 = nil
if _3ffennelrc then
_0 = _3ffennelrc()
else
_0 = nil
end
local env = specials["wrap-env"]((opts.env or rawget(_G, "_ENV") or _G))
local callbacks = {["view-opts"] = (opts["view-opts"] or {depth = 4}), env = env, onError = (opts.onError or default_on_error), onValues = (opts.onValues or default_on_values), pp = (opts.pp or view), readChunk = (opts.readChunk or default_read_chunk)}
local save_locals_3f = (opts.saveLocals ~= false)
local byte_stream, clear_stream = nil, nil
local function _805_(_241)
return callbacks.readChunk(_241)
end
byte_stream, clear_stream = parser.granulate(_805_)
local chars = {}
local read, reset = nil, nil
local function _806_(parser_state)
local b = byte_stream(parser_state)
if b then
table.insert(chars, string.char(b))
end
return b
end
read, reset = parser.parser(_806_)
depth = (depth + 1)
if opts.message then
callbacks.onValues({opts.message})
end
env.___repl___ = callbacks
opts.env, opts.scope = env, compiler["make-scope"]()
opts.useMetadata = (opts.useMetadata ~= false)
if (opts.allowedGlobals == nil) then
opts.allowedGlobals = specials["current-global-names"](env)
end
if opts.init then
opts.init(opts, depth)
end
if opts.registerCompleter then
local function _812_()
local _811_0 = opts.scope
local function _813_(...)
return completer(env, _811_0, ...)
end
return _813_
end
opts.registerCompleter(_812_())
end
load_plugin_commands(opts.plugins)
if save_locals_3f then
local function newindex(t, k, v)
if opts.scope.manglings[k] then
return rawset(t, k, v)
end
end
env.___replLocals___ = setmetatable({}, {__newindex = newindex})
end
local function print_values(...)
local vals = {...}
local out = {}
local pp = callbacks.pp
env._, env.__ = vals[1], vals
for i = 1, select("#", ...) do
table.insert(out, pp(vals[i], callbacks["view-opts"]))
end
return callbacks.onValues(out)
end
local function save_value(...)
env.___replLocals___["*3"] = env.___replLocals___["*2"]
env.___replLocals___["*2"] = env.___replLocals___["*1"]
env.___replLocals___["*1"] = ...
return ...
end
opts.scope.manglings["*1"], opts.scope.unmanglings._1 = "_1", "*1"
opts.scope.manglings["*2"], opts.scope.unmanglings._2 = "_2", "*2"
opts.scope.manglings["*3"], opts.scope.unmanglings._3 = "_3", "*3"
local function loop(exit_next_3f)
for k in pairs(chars) do
chars[k] = nil
end
reset()
local ok, parser_not_eof_3f, form = pcall(read)
local src_string = table.concat(chars)
local readline_not_eof_3f = (not readline or (src_string ~= "(null)"))
local not_eof_3f = (readline_not_eof_3f and parser_not_eof_3f)
if not ok then
callbacks.onError("Parse", not_eof_3f)
clear_stream()
return loop()
elseif command_3f(src_string) then
return run_command_loop(src_string, read, loop, env, callbacks.onValues, callbacks.onError, opts.scope, chars, opts)
else
if not_eof_3f then
local function _817_(...)
local _818_0, _819_0 = ...
if ((_818_0 == true) and (nil ~= _819_0)) then
local src = _819_0
local function _820_(...)
local _821_0, _822_0 = ...
if ((_821_0 == true) and (nil ~= _822_0)) then
local chunk = _822_0
local function _823_()
return print_values(save_value(chunk()))
end
local function _824_(...)
return callbacks.onError("Runtime", ...)
end
return xpcall(_823_, _824_)
elseif ((_821_0 == false) and (nil ~= _822_0)) then
local msg = _822_0
clear_stream()
return callbacks.onError("Compile", msg)
end
end
local function _827_(...)
local src0 = nil
if save_locals_3f then
src0 = splice_save_locals(env, src, opts.scope)
else
src0 = src
end
return pcall(specials["load-code"], src0, env)
end
return _820_(_827_(...))
elseif ((_818_0 == false) and (nil ~= _819_0)) then
local msg = _819_0
clear_stream()
return callbacks.onError("Compile", msg)
end
end
local function _829_()
opts["source"] = src_string
return opts
end
_817_(pcall(compiler.compile, form, _829_()))
utils.root.options = old_root_options
if exit_next_3f then
return env.___replLocals___["*1"]
else
return loop()
end
end
end
end
local value = loop()