-
Notifications
You must be signed in to change notification settings - Fork 6
/
Copy patheval
1690 lines (1630 loc) · 62.7 KB
/
eval
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 ctr
#:language XFrozen
Broom autoAlloc: True.
Reflect ignoreInTrace: (Program argument: 1). #eval or whatever it is
#Pen write: 'Extensions: ' + (File extensionsPath), brk.
Broom memoryLimit: 1024 * 1024 * 1024 * 4.
var PIDFILE is '~/.ctrpid.pid'.
var logo is ?> _____ _ _
/ ____(_) |
| | _| |_ _ __ ___ _ __
| | | | __| '__/ _ \| '_ \ %s
| |____| | |_| | | (_) | | | |
\_____|_|\__|_| \___/|_| |_|
%s %s<?.
var display_logo is True.
var GREEN_COLOR is 'green' toSymbol.
var ITALIC_EFFECT is 'italic' toSymbol.
var inp is ''.
var _ is Nil.
var __ is Nil.
var it is Nil.
var stderr is File special: 'stderr'.
var inBlock is 0.
var inBlockNew is 0.
var enterRepeat is False.
var TerminalWidth is 85.
import
Library/Data/Map
Library/Data/Set
Library/Utils/Highlight
Library/Control/MethodResolve
Library/Data/String
Library/Data/IO/UTF-8/input
Library/System/File/Temp
Library/System/system: { system => Csystem. }
Library/Functional/LazyBoolean: ['and', 'or']
Library/Prelude: \*
Library/Data/Array/LRArray: 'LRArray'
Library/Utils/CLIContextMenu: ['generate_context_menu', 'clear_next_line', 'write_next_line', 'move_cursor']
Library/Data/IO/Pen: 'pPen'.
#:declare lazyev or
#:declare lazyev and
var OneGateFlipper is Object new on: 'new' do: { ^me cnew: {my value is False.}. }.
OneGateFlipper on: 'set' do: { my value is True. }.
OneGateFlipper on: 'get' do: { var v is my value. my value is False. ^v. }.
Pen is pPen for: (File special: 'stdout').
var errPen is Pen for: stderr.
var linesp is 1.
Eval is Object new.
Eval on: 'init' do: {
my end is False.
my color is 'resetColor'.
my prompt is '⠕ '.
my args is Array new.
my allowedColors is ('red blue magenta cyan yellow green resetColor' split: ' ') join: '|'.
my globalvars is False.
my history is LRArray new init: 100.
my histdir is '~/.citron'.
my hs_file is Nil.
my commands is Nil.
my command_comps is Nil.
my userDefs is Nil.
my continuation is '⠒⠂'.
genAccessors: ['evaluatorObject', 'completerObject'].
my evaluatorObject is me. # self-ref for ::'evaluate:'
my completerObject is me. # self-ref for ::'complete:withToc:'
my flags is Map new
put: False at: 'ptype',
put: False at: 'stats',
put: True at: 'printvs',
put: True at: 'highlight',
put: False at: 'paste',
put: False at: 'equit',
put: False at: 'jsonout',
put: True at: 'intty',
put: True at: 'processig',
put: False at: 'clear_errors',
put: True at: 'history',
put: False at: 'html_colors',
put: False at: 'searching',
put: False at: 'searchfailed',
put: False at: 'startServer'.
my port is Nil.
me genReader: 'eServer'.
my errd is False.
my last_error is Nil.
my inNewBlock is False.
my breakpoints is Map new.
my debug is False.
my stepDebug is False.
my returnValue is Array new.
my evaluationContext is Map new
on: 'toString' do: {^'[GlobalEvalContext]'.}.
my displayObject is Pen. #override from commandline, maybe?
my printObject is Pen.
^me.
}.
Eval on: 'initHistory' do: {
(var hddir is File new: my histdir) exists ifFalse: { hddir mkdir. }.
my hs_file is File new: my histdir + '/ctrhist'.
my hs_file exists ifFalse: { my hs_file write: ''. }.
var history is my history.
(my hs_file read split: '\n\n\n') each_v: \:x history push: x.
}.
Eval on: 'print' do: {
^my printObject.
}.
Eval on: 'print:' do: {:x
my printObject is x.
}.
Eval on: 'display' do: {
^my displayObject.
}.
Eval on: 'display:' do: {:x
my displayObject is x.
}.
['clear', 'green', 'cyan', 'magenta',
'purple', 'blue', 'yellow', 'resetColor',
'red', 'brk', 'clearLine', 'write:',
'writeln:']
each_v: {:v
Eval on: 'display/$$v' do: {:*args
^my displayObject message: const v arguments: args.
}.
}.
['clear', 'green', 'cyan', 'magenta',
'purple', 'blue', 'yellow', 'resetColor',
'red', 'brk', 'clearLine', 'write:',
'writeln:']
each_v: {:v
Eval on: 'print/$$v' do: {:*args
^my printObject message: const v arguments: args.
}.
}.
Eval on: 'disambiguateError:' do: {:e
getFlag: 'clear_errors', ifFalse: { ^[e, '']. }.
e isA: String, ifFalse: { ^e. }.
e is e + ''.
e startsWith: 'Key not found: "', ifTrue: {
^[e, 'Try \'%s\' to create a string' % [(e skip: 16, ~ 's/"$//')]].
}.
e startsWith: 'Key not found: ', ifTrue: {
var key is e skip: 15, trim lower.
var names is Eval allTheNames.
names is names filter: \:_:v v lower = key.
^names count > 0 either: {
^[e, (names count = 1 either: 'Did you mean %L?' or: 'Did you mean one of (%L)?') % [names]].
} or: [e, ''].
}.
e = 'Invalid bind' ifTrue: {
^[e, 'Check your assignment target (typevar/pattern)'].
}.
^[e, ''].
}.
Eval on: 'handleException:' do: {:self:e
my errd is True.
my last_error is e.
^me getFlag: 'equit', either: {thisBlock error: e.} or: {
var dis is self disambiguateError: e.
self display/red write: (dis last either: ('%s\n\t' + Colors reset + 'Tip: ' + Colors magenta + '%s') or: '%s%s', % dis), resetColor brk.
^e.
}.
}.
Eval on: 'handleInternalException:' do: {:e
(var bugtrack is File new: '~/.citron/citron_eval_bug_track') exists ifFalse: {
File mkdir: bugtrack path.
}.
var oldp is Program changeDirectory: bugtrack realPath.
File new: 'citron_eval_bug_track_%s' % [Clock new time], write: '%s\nTime %s\n===========================\n%s' % [e, Clock new, (Reflect lastTrace tail tail tail chunks: 2, fmap: (\:x x join: ' at '), join: '\n')].
errPen writeln: 'Eval bug, $$e'.
Program changeDirectory: oldp.
}.
Eval on: 'errored?' do: {^my errd.}.
Eval on: 'lastError' do: {^my last_error.}.
Eval on: 'trace' do: {^Reflect lastTrace.}.
Eval on: 'end' do: {
my end is True.
my debug ifFalse: {
me stopEvalServer.
getFlag: 'history', & my hs_file isNil not ifTrue: {
my hs_file write: (
my history join: '\n\n\n'
).
}.
}.
}.
Eval on: 'break' do: { my end ifTrue: {Broom sweep.}. ^my end. }.
Eval on: 'allowedColors' do: { ^my allowedColors. }.
Eval on: 'isAllowedColor:' do: { :c
^(c containsPattern: my allowedColors).
}.
Eval on: 'setColor:' do: { :c
(me isAllowedColor: c) ifFalse: { thisBlock error: 'value $$c' + ' is not an allowed color.'. }.
my color is c.
}.
Eval on: 'setPrompt:' do: { :p my prompt is p. }.
Eval on: 'setContinuation:' do: { :p my continuation is p. }.
Eval on: 'setArgs:' do: { :a my args is a. }.
Eval on: 'args' do: { ^my args. }.
Eval on: 'color' do: { ^my color. }.
Eval on: 'prompt' do: { ^(me preprompt) + my prompt + (me proprompt). }.
Eval on: 'preprompt' do: {
var pp is Array new.
me getFlag: 'highlight', ifFalse: { pp push: 'p'. }.
me getFlag: 'searching', ifTrue: {
pp push: 's' + (getFlag: 'searchfailed', either: '-failing' or: '').
}.
pp is pp join: ':'.
^pp length > 0 either: pp + ' ' or: pp.
}.
Eval on: 'proprompt' do: {
^me inNewBlock either: ' ' * 2 * inBlock or: ''.
}.
Eval on: 'inNewBlock' do: {^my inNewBlock.}.
Eval on: 'newBlock' do: { my inNewBlock is True. }.
Eval on: 'endBlock' do: { my inNewBlock is False. }.
Eval on: 'continuation' do: { ^my continuation. }.
Eval on: 'evalContext' do: { ^my evaluationContext. }.
Eval on: 'shiftArgs' do: { ^my args shift. }.
Eval on: 'enableFlag:' do: {:flag my flags put: True at: flag. }.
Eval on: 'disableFlag:' do: {:flag my flags put: False at: flag. }.
Eval on: 'getFlag:' do: {:flag ^my flags at: flag. }.
Eval on: 'toggleFlag:' do: {:flag my flags put: (my flags @ flag) not at: flag. }.
Eval on: 'flags' do: {^my flags. }.
Eval on: 'modifyFlags:' do: {:flag
flag startsWith: 'no-', ifTrue: {
me disableFlag: (flag skip: 3).
}, else: {
me enableFlag: flag.
}.
}.
Eval on: 'histdir:' do: {:q my histdir is q. }.
Eval on: 'argCount' do: { ^(my args count). }.
Eval on: 'onlyRelevantArgs' do: {
my args do shift shift done.
}. #get rid of ./ctr eval
Eval on: 'startEvalServer' do: {
my port is 5000 + (Dice rollWithSides: 1000).
enableFlag: 'startServer'.
^my port.
}.
Eval on: 'startEvalServerOnPort:' do: {:port
port => port.
enableFlag: 'startServer'.
^port.
}.
Eval on: 'stopEvalServer' do: {
^my eServer isNil not either: {my eServer signal: 1. ^True.} or: {^False.}.
}.
Eval on: 'addUserDef:definition:' do: {:name:def
me userDefs put: def at: name.
}.
Eval on: 'userDefs' do: {
^my userDefs is my userDefs or: {
^Map new.
}.
}.
Eval genAccessors: ['nextInput', 'port'].
Eval on: 'returnValue:' do: {:x my returnValue do pop push: x, done. }.
Eval on: 'stepDebug:' do: {:x my stepDebug is x. }.
Eval on: 'debug' do: { ^my debug. }.
Eval on: 'Commands' do: {
my commands is my commands or: {
var c is Map new
put: {:args Eval end. ^''.} at: 'q',
put: {:args Eval modifyFlags: args. ^''.} at: '!f',
put: {:args Eval modifyFlags: args ~ ?>s/^-/no-/g<? ~ ?>s/^\+//g<?. ^''.} at: 'set',
put: {:args ^Reflect strTypeOf: (Eval evaluatorObject evaluate: args).} at: 't',
put: {:args ^(args trim = '') either: {^Eval startEvalServer.} or: {^Eval startEvalServerOnPort: (Eval evaluatorObject evaluate: args).}.} at: 'server',
put: {:args ('#:declare %s %d %s\n\n0' % (args reSplit: '\\s+')) eval. ^''.} at: '!fix',
put: {:args ^'Interpreter metacommands help:
+ q -- Quit
+ t <expression> -- type of expression
+ server [<port>] -- make an eval server on port <port>, or a random port.
+ !server -- kill the eval server
+ !m <expression> -- evaluates and shows the methods of expression.
+ !p <expression> -- evaluates and shows the properties of expression.
+ !f <[no-]flag> -- modifies flag
+ last [(<nth>)] <query> -- sets the next input to the last (nth if provided) match in history
+ !last [n] -- sets the next input to the last nth expression in history
+ hist [(<nth>)] <query> -- searches for entry in history, and returns it as a block
+ !hist [(<nth>)] <query> -- searches and executes (like hist but executes immediately)
+ list <{hist}> [n] -- lists the last n items in history
+ !repeat [-<stride>] <count> -- execute the last count expressions starting at stride?0 items up
+ set <(+|-)flag> -- alternative for !f with easier(?) syntax
+ !def <name> <expression> -- create a metacommand
+ p <expression> -- prints the value of expression
+ c -- clear screen and history
+ break <obj> <msg> -- break on object\'s message
+ !enable <b[reak]> -- enable option
+ % <...> -- line comment in REPL (ignores args)
+ h -- show this help
- ! <cmd> -- execute shell command
- :{\\n stmt* \\n:}\\n -- block expression'
+ '\nDebug Mode commands (Currently Experimental):
+ !return <value> -- return value for this message
+ !step -- single step through messages
+ !cont -- continue
+ !frame [show, all, <id>] -- show frames'
+ '\nUser-defined commands:
' + (Eval userDefs fmap: \:k:v '+ %s -- %s' % [k, v], values join: '\n', or: '-None-')
+ '\nKeybindings:
+ tab
attempt code completion in edit mode
+ enter
run code if complete, otherwise newline (in edit mode)
+ <C-c>
break out of all blocks if in edit mode
interrupt running code if running
+ <C-d>
Immediately exit REPL anywhere in edit mode
+ <C-l>
clear screen in edit mode
+ F2
enter or exit paste mode'.} at: 'h',
put: {^Eval stopEvalServer.} at: '!server',
put: {:args ^Reflect getPropertiesOf: (Eval evaluatorObject evaluate: args).} at: '!p',
put: {:args Eval display/writeln: (Eval evaluatorObject evaluate: args). ^''.} at: 'p',
put: {:args Eval display/clear. (var h is Eval getHistory) count times: { h pop. }. ^''.} at: 'c',
put: {:args ^Reflect getMethodsOf: (Eval evaluatorObject evaluate: args).} at: '!m',
put: {:args AST pragma: args trim. ^''.} at: '!pragmaOnce',
put: {^''.} at: '%',
put: {:args
var msgs is args reSplit: '\\s+'.
var obj is Eval evaluatorObject evaluate: msgs head.
msgs tail each_v: \:msg Eval breakFor: obj message: msg.
^'will break on messages %L' % [msgs].
} at: 'break',
put: {:args
args startsWith: 'b', either: {
Eval instrument.
} or: {
thisBlock error: 'Invalid option $$args'.
}.
^''.
} at: '!enable',
put: {:args
Eval debug ifTrue: {
Eval returnValue: (Eval evaluatorObject evaluate: args).
Eval end.
}.
^''.
} at: '!return',
put: {
Eval debug ifTrue: {
Eval stepDebug: False.
Eval end.
}.
^''.
} at: '!continue',
put: {
Eval debug ifTrue: {
Eval stepDebug: True.
Eval end.
}.
^''.
} at: '!step',
put: {:argstr
import Library/AST/ast2ctrcode: 'ast2ctrcode'.
var f is File tempFile: '/tmp/XXXXXX.ctr'.
f open: 'w+'.
f write: '# Write your code in here\n# and close the editor when done\n'.
f close.
Csystem['${{Program env: 'EDITOR', or: 'vim'}}$ "${{f path escapeDoubleQuotes}}$"'].
var code is AST parse: '{\n' + f read + '\n} run.'.
var source is ast2ctrcode[code].
Eval pushHist: source.
^Eval evaluatorObject evaluate: (source from: 0 to: source length - 1).
} at: 'edit',
put: {:argstr
var nth is 0.
var cmd is 'Nil.'.
argstr findPattern: ?>(?:\((\d+|\*)\))?\s*(.+)$<? do: {:groups
nth is groups at: 1.
nth = '*' ifTrue: { nth is -1. }, ifFalse: { nth is nth toNumber. }.
cmd is groups at: 2.
}.
cmd is Eval getHistory filter: {\:_:x x startsWith: cmd.}.
cmd count = 0 ifTrue: {
thisBlock error: 'No match found $$argstr'.
}.
nth is nth min: cmd count - 1.
^nth = -1 either: {
Pen red writeln: 'Will execute: ', resetColor.
Pen cyan write: '+ ', writeln: (cmd join: '\n+ '), resetColor.
^{\ cmd fmap: \:x x eval. }.
} or: {
cmd is cmd at: nth.
Pen red writeln: 'Will execute: ', resetColor.
Pen cyan writeln: cmd, resetColor.
^{\ cmd eval. }.
}.
} at: 'hist',
put: {:argstr
var nth is -1.
var cmd is 'Nil.'.
argstr findPattern: ?>(?:\((\d+|\*)\))?\s*(.+)$<? do: {:groups
nth is groups at: 1.
nth = '*' ifTrue: { nth is -1. }, ifFalse: { nth is nth toNumber. }.
cmd is groups at: 2.
}.
cmd is Eval getHistory filter: {\:_:x x startsWith: cmd.}.
cmd count = 0 ifTrue: {
thisBlock error: 'No match found $$argstr'.
}.
nth is nth min: cmd count - 1.
^nth = -1 either: {
Eval nextInput: cmd last.
^''.
} or: {
Eval nextInput: cmd @ nth.
^''.
}.
} at: 'last',
put: {:argstr
argstr is argstr trim.
argstr = '' ifTrue: {
Eval nextInput: Eval getHistory last.
^''.
} ifFalse: {
Eval nextInput: (Eval getHistory at: Eval getHistory count - 1 - argstr toNumber).
^''.
}.
} at: '!last',
put: {:argstr
var fs is argstr trim reSplit: '\\s+'.
var first is fs shift.
^(frozen _ is String
case: '' do: {
^'List what? {history}'.
},
case: 'h'
case: 'hist'
case: 'history' do: {
var hist is Eval getHistory copy.
var len is hist count min: (fs count = 0 either: (10) or: {^fs head toNumber.}).
^hist from: hist count - len length: len, reverse imap!: {\:i:x ((i + 1) toString paddingRight: 3) + '| ' + (x from: 0 to: [TerminalWidth - 6, x length] min).}, join: '\n'.
},
default: {
^'Unknown listing: $$first'.
}) switch: first.
} at: 'list',
put: {:argstr
var nth is 0.
var cmd is 'Nil.'.
argstr findPattern: ?>(?:\((\d+|\*)\))?\s*(.+)$<? do: {:groups
nth is groups at: 1.
nth = '*' ifTrue: { nth is -1. }, ifFalse: { nth is nth toNumber. }.
cmd is groups at: 2.
}.
cmd is Eval getHistory filter: {\:_:x x startsWith: cmd.}.
cmd count = 0 ifTrue: {
thisBlock error: 'No match found $$argstr'.
}.
nth is nth min: cmd count - 1.
^nth = -1 either: {
^Reflect run: {:cmd:nth ^cmd fmap: \:x x eval. } inContextAsMain: Eval evalContext arguments: [cmd, nth].
} or: {
^Reflect run: {:cmd:nth ^cmd at: nth, eval. } inContextAsMain: Eval evalContext arguments: [cmd, nth].
}.
} at: '!hist',
put: {:argstr
var stride is 0.
var count is 0.
var args is argstr reSplit: '[^-\\d]+'.
args head startsWith: '-', ifTrue: {
stride is args shift toNumber.
}.
args count = 0 ifTrue: {
thisBlock error: 'Missing argument 0 (count) for !repeat'.
}.
count is args head toNumber.
var hist is Eval getHistory.
var chunk is hist from: hist count + stride - count length: count.
chunk count > 0 ifTrue: {
Pen writeln: 'Will now execute $$count expressions:\n\t%:L' % ['\n\t', (chunk fmap: \:x Highlight highlight: x)].
var res is chunk fmap: {:x ^Eval evaluatorObject evaluate: x.}.
^res.
}.
^''.
} at: '!repeat',
put: {:args
var name is args reSplit: '\\s+'.
^Reflect run: {:name
name count = 0 ifTrue: {
^Reflect frame: (Reflect frameId - 2).
}.
name is name head.
^(name case: 'show' do: {
var s is Reflect frame: (Reflect frameId - 2), kvlist: \:kv [kv head, (Reflect strTypeOf: kv last), kv last], fmap: \:x '%s (%s) = %s' % x, join: ', '.
^'frame %d { %s }' % [Reflect frameId - 2, s].
}, case: 'all' do: {
var x is Array new.
Reflect frameId - 1 times: ({:i
x push: (Reflect frame: i + 1).
} catch: {}).
^x imap: (\:i:f 'frame %d { %s }' % [i + 1, (f kvlist: \:kv [kv head, (Reflect strTypeOf: kv last), kv last], fmap: \:x '%s (%s) = %s' % x, join: ', ')]), join: '\n'.
}, default: {
^Reflect frame: (name toNumber), kvlist: \:kv [kv head, (Reflect strTypeOf: kv last), kv last], fmap: \:x '%s (%s) = %s' % x, join: ', '.
}, switch).
} inContextAsMain: Eval evalContext arguments: [name].
} at: '!frame',
put: {:args
[name, fn] is args split: ' ' max: 1.
Eval addUserDef: name definition: fn.
Eval addMetaCommand: name function: (Eval evaluatorObject evaluate: fn).
^'+def %s %s' % [name, fn].
} at: '!def'.
c on: 'get:' do: {:self:cmd
^me at: cmd, or: {
^(cmd containsPattern: '^\\W$')
either: Nil
or: {
var candidates is self keys filter: \:_:c c startsWith: cmd.
^candidates count
case: 0 do: { ^Nil. },
case: 1 do: { ^self at: candidates head. },
default: {
var candidates2 is candidates filter: \:_:x (x = cmd).
^candidates2 count
case: 1 do: { ^self at: candidates2 head. },
default: { ^\:args 'Ambiguous command %s, which one of (%L) did you mean?' % [cmd, candidates]. },
switch.
},
switch.
}.
}.
}.
^c.
}.
^my commands.
}.
Eval on: 'CommandCompletions' do: {
^my command_comps is my command_comps or: {
^Map new
put: {:argstr var flags is Eval flags keys.
^argstr count either: {
var flag is argstr head trim.
flag startsWith: 'no-', either: { flag is flag skip: 3. } or: { }.
^flags filter: \:i:v v startsWith: flag, fmap: \:x x skip: flag length.
} or: flags.
} at: ':!f',
put: {:argstr
var vars is ['history'].
^argstr count either: {
var f is argstr head trim.
^vars filter: \:_:x (x startsWith: f), fmap: \:x x skip: f length.
} or: vars.
} at: ':list',
put: {:argstr
^argstr count either: {
var path is argstr head split: ' ', last ~ ?>s|.*?((/*\w+)(/\w+)+/?).*|$1|<?.
var path2 is path contains: '*', either: path or: path + '*'.
^(File expand: path2, fmap: (\:x x skip: path length)).
} or: [].
} at: ':!',
put: {:filter
filter is filter or: [''], last.
^(['all', 'show'] + (Array new fill: Reflect frameId with: \:i i toString)) filter: (\:_:x x startsWith: filter), fmap: \:x x skip: filter length.
} at: ':!frame',
put: {:filter ^Eval Commands keys filter: (\:_:x x startsWith: filter), fmap: \:x x skip: filter length.} at: 'generic'.
}.
}.
Eval on: 'addMetaCommand:function:' do: {:name:fn
Eval Commands put: fn at: name.
}.
Eval on: 'doMeta:' do: {:str:ptr
var cmd is 'h'.
var args is ''.
str trim findPattern: '(\\W?\\w+|\\W)(\\s+(.*))?' do: {:parts cmd is parts @ 1. args is parts count > 2 either: {^parts @ 3.} or: ''.}.
var cmds is Eval Commands.
var unknownMetaCommandHandler is my unknownMetaCommandHandler.
^{
^cmds get: cmd, or: {
^cmd = '!'
either: { ^(\:x Shell call: x). }
or: {
^unknownMetaCommandHandler[cmd, args].
}.
}, applyTo: args.
} catch: {:e
^'Exception was thrown while evaluating `${{str}}$\': $$e'.
}, run.
}.
Eval on: 'unknownMetaCommandHandler:' do: {:hnd my unknownMetaCommandHandler is hnd. }.
Eval unknownMetaCommandHandler: {:cmd
^'Invalid meta-command \':%s\'.\n' % [cmd] + (Eval doMeta: 'h').
}.
Eval on: 'metaCompletion:' do: {:m
var meta is m head.
var argstrs is m tail.
var cmds is Eval CommandCompletions.
^cmds at: meta, or: {
argstrs is meta skip: 1.
^cmds at: 'generic'.
}, applyTo: argstrs.
}.
Eval on: 'evaluate:' do: { :str
str empty? ifTrue: { ^''. }.
my errd is False.
var eval_result_x is Nil.
var evalContext is my evaluationContext.
var f is (str at: 0, = ':')
either:
({ ^eval_result_x is Eval doMeta: (str skip: 1). } catch: {:e Eval handleException: e.})
or:
({
^(str startsWith: 'import') not
either: ({
^Reflect run: {:str ^str eval.} inContextAsMain: evalContext arguments: [str].
} catch: {:e Eval handleException: e. ^Nil.}
) or: ({
^str eval.
} catch: {:e Eval handleException: e. ^Nil.}).
}).
^(Eval getFlag: 'jsonout') either: { ^Json serialize: [(f toString replace: '\\' with: '\\\\'), (Json serialize: [(Eval errored?), (Eval lastError), (Eval trace)])]. } or: {^f.}.
}.
Eval on: 'addFile:named:' do: {:str:fname
var varname is fname split: '.' max: 1, head.
Reflect run: {:fname:ctx:str ('var %s is {
Eval statsb: 0, statsa: 0, exectime: 0.
Reflect run: {:str str eval.} inContext: const ctx arguments: [const str].
}' % [fname]) eval. } inContextAsMain: my evaluationContext arguments: [varname, my evaluationContext, '{\n $$str \n}'].
}.
Eval on: 'pushHist:' do: {:h
my history push: h.
}.
Eval on: 'executeLast:' do: {:n
^me evaluate: (my history @ n).
}.
Eval on: 'getHistory' do: {^my history.}.
Eval on: 'queryState:' do: {:query
^(((query countOf: '\'') - (query countOf: '\\\'')) even) & ((query countOf: '{') = (query countOf: '}')) & ((query countOf: '[') = (query countOf: ']')).
}.
Eval init.
Eval setArgs: (Array new fill: Program argCount with: {:i ^Program argument: i.}).
Eval onlyRelevantArgs.
#arguments that were not consumed
var args is Array new.
#global vars for evaluator
var it is Nil.
var _ is Nil.
var __ is Nil.
Eval on: 'tryNumrify:' do: {:s
var r is s.
{r is s toNumber.} catch: {r is s.}, run.
^r.
}.
Eval on: 'flatten:' do: {:list
var lst is Array new.
list each_v: {:l
(Reflect isObject: l linkedTo: Array) either: {
lst is lst + (Eval flatten: l).
} or: {
lst push: l.
}.
}.
^lst.
}.
Eval on: 'instrument' do: {
Reflect globalInstrument: me tryDebug.
Reflect enableInstrumentation.
}.
Eval on: 'breakFor:message:' do: {:o:m
my breakpoints put: True at: (o &responder: m).
}.
Eval on: 'hasBreakPoint:message:' do: {:object:message
^my breakpoints at: (object &responder: message), or: my stepDebug.
}.
Eval on: 'tryDebug' do: {:self
var ctx is my evaluationContext.
^{:object:message:args
var this is me.
var eval is const self.
(Reflect isInFrame: const ctx) ifFalse: {^this.}.
var bp is eval hasBreakPoint: object message: message.
^bp either: {
^eval debugRepl: [this, object, message, args].
} or: {
^this.
}.
}.
}.
Eval on: 'allTheNames' do: {
^(Eval Commands keys fmap: {:i ^':' + i.}) + ['var', 'my', 'is'] + (Eval flatten: my evaluationContext keys) + (HashSet new addAll: (Eval flatten: Reflect getContext), toArray).
}.
Eval on: 'allTheNames:' do: {:obj
obj id = import id ifTrue: {
# SPeCIAl CaSe
^[
\import,
{:path-or-m
var meths is Reflect getMethodsOf: import.
var pml is path-or-m lower.
# Early exit in case it's not a path...probably
meths is meths filter_v: {:meth
^meth lower startsWith: pml.
}.
meths count > 0 ifTrue: { ^meths. }.
# It's a path...maybe
var paths is path-or-m split: '/'.
path-or-m endsWith: '/', ifTrue: { paths push: ''. }.
var path is paths last lower.
var pathv is paths init join: '/'.
^import searchPaths fmap: {:spath
var dir is File new: spath + '/' + pathv.
dir exists ifFalse: { ^[]. }.
^File list: dir path,
filter_v: {:v
^ (['.', '..'] contains: v @ 'file', not)
`and`
(v @ 'type' = 'folder')
`or`
( (v @ 'type' = 'file')
`and`
(v @ 'file' endsWith: '.ctr')
). },
fmap: {:value ^value at: 'file'. },
filter_v: {:x ^x lower startsWith: path. },
fmap: {:x
x endsWith: '.ctr',
ifTrue: { ^ (pathv length > 0 either: pathv + '/' or: '') + (x from: 0 to: x length - 4). }
ifFalse: { ^(pathv length > 0 either: pathv + '/' or: '') + (x).}.
}.
}, sum.
}
].
}.
^obj isNil not either: {^[\method, (Reflect getMethodsOf: obj)].} or: {^[\object, (Eval allTheNames)].}.
}.
Eval on: 'togglePasteMode' do: {
me toggleFlag: 'highlight'.
me toggleFlag: 'paste'.
}.
# parse arguments
var tidyIndentOptions is Map new.
{ ^(Eval argCount > 0). } whileTrue: {
# get an option
var opt is Eval shiftArgs.
args push: opt.
# when passed -h, show help, then exit.
(frozen select_arg_q is case: '-h' do: {
args pop. #consume option.
Eval display/green write: 'Citron Evaluator.\n', resetColor.
var P is Eval display.
P on: 'indent:write:' do: {:self:i:w self write: (' ' * i), write: w. }.
P indent: 1 write: 'Eval mode help:', brk.
P cyan indent: 1 write: 'Eval namespace', resetColor brk.
P magenta indent: 2 write: '[Eval] setPrompt: [String] ', resetColor write: '-- Sets the prompt.', brk.
P magenta indent: 2 write: '[Eval] setColor: [String] ', resetColor write: '-- sets the color of the output.', brk.
P magenta indent: 2 write: '[Eval] getHistory ', resetColor write: '-- returns the 100 last commands.', brk.
P magenta indent: 2 write: '[Eval] executeLast: [Number] ', resetColor write: '-- evaluates the Nth element of the history list.', brk.
P magenta indent: 2 write: '[Eval] startEvalServer ', resetColor write: '-- starts an eval server in the background. returns the port.', brk.
P magenta indent: 2 write: '[Eval] startEvalServerOnPort: [Number] ', resetColor write: '-- same as startEvalServer, with the port specified.', brk.
P magenta indent: 2 write: '[Eval] end ', resetColor write: '-- Ends the eval.', brk.
P indent: 1 write: 'Evaluates expressions in a single context.', brk.
P indent: 1 write: 'Since all expressions are in single lines, pragma oneLineExpressions is active,', brk.
P indent: 1 write: 'Hence ending expressions with a dot (.) is not required.', brk.
P indent: 1 write: 'All the args that were not consumed are in the variable `args`.', brk.
P cyan indent: 1 write: 'Program arguments\n',
resetColor indent: 2 write: '-p : set the prompt\n',
indent: 2 write: '--pc : set the prompt continuation\n',
indent: 2 write: '-c : set the result color\n',
indent: 2 write: '-h : show this help\n',
indent: 2 write: '-f : evaluate file. many can be passed. They will be executed in order\n',
indent: 2 write: '-u : evaluate content at url. many can be passed. They will be executed in order\n',
indent: 2 write: '-F<[no-]flag> : enables the passed flag\n',
indent: 2 write: '--Fs : display all the available flags\n',
indent: 2 write: '--Fsel : allow the user to pick from available flags via a terminal selection menu\n',
indent: 2 write: '--meta <command> : execute a metacommand (sans the initial :)\n',
indent: 2 write: '-e : evaluate expression and exit, all args after the expr will be passed to the code as var `args`\n',
indent: 2 write: '--qe : evaluate expression without printing the value, then continue, all args after the expr will be passed to the code as var `args`\n',
indent: 2 write: '-v : print version information and exit\n',
indent: 2 write: '--tidy : [Experimental] try to tidy up the given file and write it back out to stdout\n',
indent: 3 write: '-ml <int> : set max line length for --tidy\n',
indent: 3 write: '-il <int> : set indent length in spaces for --tidy\n',
indent: 3 write: '-pk : enable parentesize_kwmsgs for --tidy\n',
indent: 2 write: '-s <port>: start a evaluation server on the specified port (Forked and the current process will be killed. pidfile at ~/.ctrpid.pid)\n',
indent: 2 write: '-m <executable module> <modu_arg>* : Execute an executable module (at Library/Executable)\n',
indent: 2 write: '--import <module_names_colon_sep> : preload modules separated by colons\n',
indent: 2 write: '--pid <pidfile>: set the pid file. value is ignored unless [-s <port>] comes after\n',
indent: 2 write: '--no-logo : don\'t display the ascii logo, use normal text instead\n',
indent: 2 write: '--interactive-print : Use this object instead of `Pen\' to print values\n',
indent: 2 write: '--interactive-output : Use this object instead of `Pen\' to print output\n',
indent: 2 write: '--evaluator-object <symbol> : use this object\'s ::\'evaluate:\' responder to eval things\n',
indent: 2 write: '--completer-object <symbol> : use this object\'s ::\'complete\' responder to complete things\n',
indent: 2 write: '--highlight-file <file|-> : highlight the file (or stdin) and quit\n',
indent: 2 write: '--assume-non-tty : Assume the terminal is not a tty\n',
indent: 2 write: '--terminal-width <int> : Set the perceived terminal width (this will be automatically determined unless --assume-non-tty is passed)\n',
indent: 2 write: '--without-signals : Disable signal processing\n',
indent: 2 write: '--without-history : Disable recording of history\n',
indent: 2 write: '--history-dir <dir> : Save the history in dir/ctrhist\n',
indent: 2 write: '--update-stdlib -- Update the stdlib from alimpfard/citron_standard_library\n',
indent: 2 write: '--update-stdlib-from <path> -- Update the stdlib from the given path\n',
indent: 2 write: '--clear-errors : show tips for some common errors\n',
indent: 2 write: '--html-colors : output html for syntax highlighting\n',
magenta indent: 1 write: 'Any other option will be consumed and dumped into the `args` array.', brk.
P green write: 'default imported modules in eval are:',
cyan indent: 1 write: (import __loaded_modules keys reverse join: ' ') ~ 's/\\s|^/\\n+ /g'.
P resetColor brk.
Program exit.
# when passed -p, get the next arg, and set the prompt to that.
}, case: '-p' do: {
args pop. #consume option..
var value is Eval shiftArgs.
(value isNil) ifTrue: { thisBlock error: '-p requires an arg.'. }.
Eval setPrompt: value.
# when passed -pc, get the next arg, and set the continuation prompt to that.
}, case: '--pc' do: {
args pop. #consume option..
var value is Eval shiftArgs.
(value isNil) ifTrue: { thisBlock error: '--pc requires an arg.'. }.
Eval setContinuation: value.
# when passed -c, get the next arg, and set the result color to that.
}, case: '-c' do: {
args pop. #consume option.
var value is Eval shiftArgs.
(value isNil) ifTrue: { thisBlock error: '-c requires an arg, valid arguments are: \n\t' + (((Eval allowedColors) split: '|') join: '\n\t'). }.
Eval setColor: value.
}, case: '-f' do: {
args pop. #consume option.
var file is Eval shiftArgs.
(file isNil) ifTrue: { thisBlock error: '-f requires a file name.'. }.
AST parse: (File new: file, read), evaluate.
}, case: '-u' do: {
args pop. #consume option.
var url is Eval shiftArgs.
(url isNil) ifTrue: { thisBlock error: '-u requires a url.'. }.
AST parse: (Curl new url: url, perform), evaluate.
}, case: '--Fs' do: {
args pop. #consume option.
Eval display/writeln: (Eval flags keys join: '\n').
Program exit.
}, case: '--Fsel' do: {
import Library/Utils/CLISelectMenu: \*.
var keys is Eval flags keys.
var res is MultiSelectMenu
new: keys
pagesize: 5
preSelected: (Eval flags keys filter: \:_:k Eval flags at: k),
request.
keys each_v: {:key
res contains: key, ifTrue: {
Eval enableFlag: key.
} ifFalse: {
Eval disableFlag: key.
}.
}.
}, case: '--meta' do: {
args pop.
Eval doMeta: Eval shiftArgs.
}, case: '-ml' do: {
args pop.
tidyIndentOptions put: (Eval shiftArgs toNumber) at: 'max_line_length'.
}, case: '-il' do: {
args pop.
tidyIndentOptions put: (Eval shiftArgs toNumber) at: 'indent_length'.
}, case: 'pk' do: {
args pop.
tidyIndentOptions put: True at: 'parenthesize_kwmsgs'.
}, case: '--tidy' do: {
args pop.
var fname is Eval shiftArgs.
fname isNil ifTrue: { thisBlock error: '--tidy requires an argument'. }.
var code is Nil.
fname = '-' ifTrue: {
code is Program input.
} ifFalse: {
code is File new: fname, read.
Pen writeln: '# File $$fname'.
}.
import Library/AST/ast2ctrcode: 'ast2ctrcode'.
var options is tidyIndentOptions.
Pen writeln: ast2ctrcode[(AST parse: code), 0, options].
Program exit.
}, case: '-e' do: {
args pop. #consume option..
var value is Eval shiftArgs rightTrim.
(value isNil) ifTrue: { thisBlock error: '-e requires an argument.'. }.
import
Library/Utils/Shell: \*
Library/Control/MethodResolve: \*
.
Reflect addGlobalVariable: '%0'.
%0 is value.
{ ^(Eval argCount > 0). } whileTrue: { args push: Eval shiftArgs. }. #consume all other arguments.
args imap: {:i:n Reflect addGlobalVariable: '%' + (i + 1). Reflect set: '%' + (i + 1) to: (Eval tryNumrify: n).}.
Reflect addGlobalVariable: '%_'.
%_ is args.
value endsWith: '.', ifFalse: { value +=: '.'. }.
value is '{ ^$$value }'.
var val is (Eval evaluatorObject evaluate: value) run.
val isA: Array, ifTrue: {
Eval display/write: val join. # convert to lines
} ifFalse: {
val isA: Generator, ifTrue: {
val each_v: {:v
Eval display/write: v.
}.
} ifFalse: {
Eval display/write: val.
}.
}.
Program exit.
}, case: '--qe' do: {
args pop. #consume option..
var value is Eval shiftArgs rightTrim.
(value isNil) ifTrue: { thisBlock error: '--qe requires an argument.'. }.
Reflect addGlobalVariable: '%0'.
%0 is value.
value endsWith: '.', ifFalse: { value +=: '.'. }.
value is '{ $$value }'.
(Eval evaluatorObject evaluate: value) run.
}, case: '-v' do: {
Eval display/writeln: 'Citron Version ' + Reflect version.
Eval display/writeln: 'Built with the extension prefix of ' + File extensionsPath.
Eval display/writeln: 'Compiled with ' + Reflect compilerInfo.
Program exit.
}, case: '-V' do: {
Eval display/writeln: 'Citron'.
Eval display/writeln: '[%L]' % [(Reflect version split: '.')].
Program exit.
}, case: '--pid' do: {
args pop.
var f is Eval shiftArgs.
(f isNil) ifTrue: { thisBlock error: '--pid requires an argument <pidfile>'. }.
PIDFILE is f.
}, case: '-s' do: {
args pop.
var f is Eval shiftArgs toNumber.
(f <=: 0) ifTrue: { thisBlock error: 'Invalid port specified: $$f'. }.
Eval enableFlag: 'jsonout'.
Eval startEvalServerOnPort: f.
}, case: '-m' do: {
args pop.
var mod is Eval shiftArgs.
(mod isNil) ifTrue: { thisBlock error: 'no module to run?'. }.
var args is Eval args.
import respondTo: 'Library/Executable/%s:' % [mod] and: ['Main'].
{ Main applyTo: args. } catch: {:e Eval display/red writeln: e, resetColor.}, run.
Program exit.
}, case: '--import' do: {
args pop.
var mod is Eval shiftArgs.
(mod isNil) ifTrue: { thisBlock error: 'Expected to get a list of fully qualified module names for --import'. }.
mod split: ':', each_v: {:mod
import respondTo: mod.
}.
}, case: '--no-logo' do: {
args pop.
display_logo is False.
}, case: '--interactive-print' do: {
args pop.
var obj is Eval shiftArgs.
obj is Eval evaluatorObject evaluate: obj, or: \:_ thisBlock error: '--interactive-print requires an argument'.
Eval display: obj.
}, case: '--interactive-output' do: {
args pop.
var obj is Eval shiftArgs.
obj is Eval evaluatorObject evaluate: obj, or: \:_ thisBlock error: '--interactive-output requires an argument'.
Eval print: obj.
}, case: '--evaluator-object' do: {
args pop.
var obj is Eval shiftArgs.