-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathm5.m4
1686 lines (1431 loc) · 80.7 KB
/
m5.m4
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
/// The M5 M4 Macro library.
/// Documentation is generated from M5_spec.adoc.
/// Internal Coding Conventions:
///
/// o Private macros use "__". A helper macro for m5_foo would be called m5_foo__xxx.
/// The existance of m5_foo reserves the namespace m5_foo__* for private use.
/// o Data structures use ".". E.g. "m4_foo.bar" holds field "bar" of object "m5_foo".
/// (These names cannot be called directly, but can be accessed/called via m4_defn, m4_indir, etc.)
m5_pragma_disable_sugar
m5_pragma_disable_literal_comma /// This library was written before the existence of literal commas.
/// #############################
/// Debugging
/// See docs.
m4_define(['m5_recursion_limit'], ['['300']'])
m4_define(['m5__stack_depth'], ['['0']'])
/// These are set in TL-Verilog use model, but may not be otherwise.
m4_ifdef(['m5__stmt_line'],,['m4_define(['m5__stmt_line'], ?)'])
m4_ifdef(['m5__debug_level'],,['m4_define(['m5__debug_level'], default)'])
/// Abbreviate a string, replacing long strings with ['...'].
/// m5_abbreviate(string, max-length)
m4_define(['m5_abbreviate'],
['...'])
/// TODO: Pull code from abbreviate_args into abbreviate. Also, make a macro to print a string, truncating a printable string and appending ....
/// See docs.
m4_define(['m5_abbreviate_args'],
['m5_abbreviate_args__guts([''], $@)'])
/// Guts of m5_abbreviate_args
/// Args: (comma, ...), where ... are the args of m5_abbreviate_args with the count decrementing.
m4_define(['m5_abbreviate_args__guts'],
['m4_ifelse(m4_eval($# > 3), 1,
['['$1']m4_ifelse(m5_calc(['$2 <= 0']), 1,
['[' ...']'],
['m4_ifelse(m5_calc(m4_len(['$4'])[' > $3 || (']m4_index(['$4'], m5_nl)[' >= 0)']), 1, /// use "..." if arg is too long or contains new line
['['['...']']'],
['['['$4']']'])m5_recurse(20, abbreviate_args__guts, [','], m4_eval(['$2-1']), ['$3']m5_comma_shift(m4_shift(m4_shift(m4_shift($@)))))'])'])'])
/// Abbreviate m5_func_stack_top (just the top value) by truncating args. This is done lazily only when needed.
m4_define(['m5__stack_element'],
[' ['$1']m5_nl() -> m5_printable(m4_patsubst(m5_translit(m5_fn__to_call(m4_shift($@)), m5_nl, ['']), /// Transliterated to remove new lines, which affect regex.
['^\(\w+\)(\(.*\))$'],
['['\1(']m5_abbreviate_args(6, 15, \2)[')']']))'])
/// Dump the function call stack by recursively peeling into m5_func_stack_top.
/// We abbreviate lines as they are processed (even if they have already been abbreviated).
/// TODO: Do this instead in m5_fn__to_call.
m4_define(['m5_func_stack'],
['m4_ifdef(['m5_func_stack_top'],
['m5_eval(m5__concat(
['m4_pushdef(['m5_func_stack_tmp'], m4_defn(['m5_func_stack_top']))'],
['m4_popdef(['m5_func_stack_top'])'],
['m5_func_stack(['$1'])'],
['m4_pushdef(['m5_func_stack_top'], m4_defn(['m5_func_stack_tmp']))'],
['m4_popdef(['m5_func_stack_tmp'])'],
['m5_nl()m5_func_stack_top']))'])'])
/// Given function and args-list, add the call to the stack.
m4_define(['m5_fn__push_stack'],
['m4_pushdef(['m5_func_stack_top'], ['m5__stack_element($@)'])m4_define(['m5__stack_depth'], m4_eval(m5__stack_depth + 1))m4_ifelse(m4_eval(m5__stack_depth > m5_recursion_limit), 1, ['m5_fatal_error(['Call stack reached maximum depth. Exiting.'])'])'])
/// Given function and args-list, represent them as a call.
m4_define(['m5_fn__to_call'],
['['$1']m4_ifelse(['$2'], [''], [''], ['['($2)']'])'])
m4_define(['m5_fn__pop_stack'],
['m4_popdef(['m5_func_stack_top'])m4_popdef(['m5_func_stack_top'])m4_define(['m5__stack_depth'], m4_eval(m5__stack_depth - 1))'])
/// Report error/warning.
/// $1: error/warning
/// $2: message
/// $3: (opt) tag
/// $4: (opt) max times to report this tag (required if tag given); should be the same for all uses of this tag.
m4_define(['m5_report'],
m5__concat(
['m5_comment(['If given, record max for this tag.'])'],
['m4_ifelse(m4_eval($# >= 4), 1, ['m4_define(['m5_report__max_$3'], ['$4'])'])'],
['m5_comment(['Provide a default max for this tag.'])'],
['m4_ifdef(['m5_report__max_$3'], [''], ['m4_define(['m5_report__max_$3'], ['m4_ifelse(m4_eval($# >= 3), 1, ['10'], ['60'])'])'])'],
['m5_comment(['Initialize or increment count (including no-tag case).'])'],
['m4_define(['m5_report__cnt_$3'], m4_ifdef(['m5_report__cnt_$3'], ['m4_incr(m5_report__cnt_$3)'], ['1']))'],
['m5_comment(['Report error if we should.'])'],
['m4_ifelse(m4_eval(m5_report__cnt_$3 <= m5_report__max_$3), 1, ['m5_errprint_nl(m5_nl()m5_printable(['$2'])m4_ifelse(m5_report__cnt_$3, m5_report__max_$3, ['m5_nl['(Subsequent $1s of this type ($3) will be ignored.)']'])m5_func_stack()m5_nl[' ']m5__stmt_file:m5__stmt_line)'])']))
/// See docs.
m4_define(['m5_warning'],
['m5_report(warning, ['Warning: $1']m5_comma_shift($@))'])
m4_define(['m5_error'],
['m5_report(error, ['Error: $1']m5_comma_shift($@))'])
m4_define(['m5_fatal_error'],
['m5_error($@)m4_m4exit(1)'])
m4_define(['m5_DEBUG'],
['m5_errprint_nl(['DEBUG: ']$@)'])
m4_define(['m5_DEBUG_stack'],
['m5_error(['DEBUG: ']$@)'])
/// See docs.
m4_define(['m5_fatal_error_if'],
['m5_if(['$1'], ['m5_fatal_error(['$2'])'])'])
m4_define(['m5_error_if'],
['m5_if(['$1'], ['m5_error(['$2'])'])'])
m4_define(['m5_warning_if'],
['m5_if(['$1'], ['m5_warning(['$2'])'])'])
m4_define(['m5_DEBUG_if'],
['m5_if(['$1'], ['m5_DEBUG(['$2'])'])'])
/// See docs.
m4_define(['m5_assert'],
['m5_if(['$1'], ['m5_error(['Failed assertion: $1.'])'])'])
m4_define(['m5_fatal_assert'],
['m5_if(['$1'], ['m5_fatal_error(['Failed assertion: $1.'])'])'])
/// Set the maximum number of errors/warnings to report with a given tag (including none).
/// $1: tag
/// $2: max
m4_define(['m5_set_report_max'],
['m4_define(['m5_report__max_$1'], ['$2'])'])
m4_define(['m5_errprint'], ['m4_errprint(m5_printable(m4_quote($@)))'])
/// m5_errprint with a new line appended.
m4_define(['m5_errprint_nl'],
['m5_errprint($@m5_nl)'])
/// #############################
/// Code Blocks
m4_define(['~status'], [''])
m4_define(['~sticky_status'], [''])
/// #############################
/// Fundamental
/// =======================
/// Private
/// Uniquifier -- A globally unique name.
/// There may be a reason to expose these publicly, but I couldn't come up with a reasonable one.
/// These are used by lazy functions.
/// Use ['m5_']m5__new_uniquifier and subsequently ['m5_']m5__same_uniquifier (prior to other m5__new_uniquifier) as a unique macro name.
m4_define(['m5__uniquifier'], ['['0']'])
m4_define(['m5__same_uniquifier'], ['['_U']m5__uniquifier'])
m4_define(['m5__new_uniquifier'],
['m4_define(['m5__uniquifier'], m4_dquote(m4_eval(m5__uniquifier + 1)))m5__same_uniquifier'])
/// =======================
/// Renamed from M4
m4_define(['m5_WRAP'], m4_defn(['m4_dnl']))
m4_define(['m5_shift'], m4_defn(['m4_shift']))
m4_define(['m5_length'], m4_defn(['m4_len']))
m4_define(['m5_index_of'], m4_defn(['m4_index']))
m4_define(['m5_format_eval'], m4_defn(['m4_format']))
/// =======================
/// Misc
m4_define(['m5_inline'], ['m5_deprecated(['$0'])$1'])
m4_define(['m5_comment'], [''])
m4_define(['m5_nullify'], [''])
/// =======================
/// Stacks
/// See docs.
m4_define(['m5_defn_ago'],
['m5_deprecated(['$0'])m4_ifdef(['m5_$1'], ['m4_ifelse(m4_eval(['$2 <= 0']), 1, ['m4_defn(['m5_$1'])'], ['m4_pushdef(['m5_$1__reverse'], m4_defn(['m5_$1']))m4_popdef(['m5_$1'])$0(['$1'], m4_eval(['$2 - 1']))m4_pushdef(['m5_$1'], m4_defn(['m5_$1__reverse']))m4_popdef(['m5_$1__reverse'])'])'])'])
m4_define(['m5_value_ago'],
['m5_eval(m5_defn_ago($@))'])
m4_define(['m5_var_depth'],
['m5_deprecated(['$0'])m4_ifdef(['m5_$1'],
['m4_ifelse(m4_ifelse(['$2'], [''], ['0'], ['m4_eval(['$2'] <= 0)']), 1,
['0'],
['m4_pushdef(['m5_$1__reverse'], m4_defn(['m5_$1']))m4_popdef(['m5_$1'])m4_eval(1 + $0(['$1'], m4_ifelse(['$2'], [''], [''], ['m4_eval(['$2 - 1'])'])))m4_pushdef(['m5_$1'], m4_defn(['m5_$1__reverse']))m4_popdef(['m5_$1__reverse'])'])'],
['0'])'])
/// Replacing the above.
/// TODO: update docs
m4_define(['m5_get_ago'],
['m4_ifdef(['~$1'], ['m4_ifelse(m4_eval(['$2 <= 0']), 1, ['m4_defn(['~$1'])'], ['m4_pushdef(['m5_$1__reverse'], m4_defn(['~$1']))m4_popdef(['~$1'])$0(['$1'], m4_eval(['$2 - 1']))m4_pushdef(['~$1'], m4_defn(['m5_$1__reverse']))m4_popdef(['m5_$1__reverse'])'])'])'])
m4_define(['m5_var_ago'], ['m5_deprecated(['$0'])']m5_defn(get_ago))
m4_define(['m5_depth_of'],
['m4_ifdef(['~$1'],
['m4_ifelse(m4_ifelse(['$2'], [''], ['0'], ['m4_eval(['$2'] <= 0)']), 1,
['0'],
['m4_pushdef(['m5_$1__reverse'], m4_defn(['~$1']))m4_popdef(['~$1'])m4_eval(1 + $0(['$1'], m4_ifelse(['$2'], [''], [''], ['m4_eval(['$2 - 1'])'])))m4_pushdef(['~$1'], m4_defn(['m5_$1__reverse']))m4_popdef(['m5_$1__reverse'])'])'],
['0'])'])
m4_define(['m5_var_depth'], ['m5_deprecated(['$0'])']m5_defn(depth_of))
/// =======================
/// Recursion
/// See docs.
/// m5_recurse(20, myself, args) /// Recursive call to myself to a limited recursion depth of 20.
m4_define(['m5_recurse'],
['m4_ifdef(['m5_$2__rec_depth'], [''], ['m4_define(['m5_$2__rec_depth'], 0)'])m4_define(['m5_$2__rec_depth'], m4_eval(m5_$2__rec_depth + 1))m4_ifelse(m4_eval(m5_$2__rec_depth > ['$1']), 1, ['m5_error(['Recursion limit for $2 exceeded $1.'])'], ['m4_indir(['m5_$2']m4_ifelse(m4_eval($# > 2), 1, [', m4_shift(m4_shift($@))']))'])m4_define(['m5_$2__rec_depth'], m4_eval(m5_$2__rec_depth - 1))'])
/// #############################
/// Text/String Processing/Parsing
/// =======================
/// General
/// See docs.
m4_define(['m5_is_null'],
['m5_var_must_exist(['$1'])m4_ifelse(m5_get(['$1']), [''], ['['1']'], ['['0']'])'])
m4_define(['m5_isnt_null'],
['m5_var_must_exist(['$1'])m4_ifelse(m5_get(['$1']), [''], ['['0']'], ['['1']'])'])
/// See docs.
m4_define(['m5_append_var'], ['m5_set(['$1'], m5_get(['$1'])['$2'])'])
m4_define(['m5_prepend_var'], ['m5_set(['$1'], ['$2']m5_get(['$1']))'])
m4_define(['m5_append_macro'], ['m4_define(['m5_$1'], m4_defn(['m5_$1'])['$2'])'])
m4_define(['m5_prepend_macro'], ['m4_define(['m5_$1'], ['$2']m4_defn(['m5_$1']))'])
/// Concatenate strings.
/// Output the quoted concatenation of the arguments.
/// Mostly this is useful for spanning multiple lines.
m4_define(m5__concat,
['m4_ifelse(['$#$1'], ['1'], [''], ['['$1']$0(m4_shift($@))'])'])
/// Merge all arguments into a delimited string.
/// m5_join([','], ['one'], ['two']) => ['one'][',']['two']
m4_define(['m5_join'],
['m4_ifelse(m4_eval($# <= 2), 1, ['['$2']'], ['$0(['$1'], ['$2$1$3']m4_ifelse(m4_eval($# > 3), 1, [', m4_shift(m4_shift(m4_shift($@)))']))'])'])
m4_define(['m5_translit_eval'], m4_defn(['m4_translit'])['['']'])
/// Transliterate. Note that quotes characters cannot be transliterated.
m4_define(['m5_translit'], ['m4_translit(['['$1']'], ['$2'], ['$3'])'])
m4_define(['m5_uppercase'], ['m4_translit(['['$1']'], ['a-z'], ['A-Z'])'])
m4_define(['m5_lowercase'], ['m4_translit(['['$1']'], ['A-Z'], ['a-z'])'])
/// See docs.
m4_define(['m5_replicate'],
['m5_if($1 > 0, ['['$2']m5_recurse(1000, replicate, m5_calc($1 - 1), ['$2'])'])'])
/// E.g: m5_extract_prefix(['!!'], var) strips m5_var of a ['!!'] prefix if it has one and evaluates to ['!!'] or [''].
/// Any text beginning from a new line is also stripped.
/// Note that, 'var' is assumed to be a variable (so quoted).
///m4_define(['m5_extract_prefix'],
/// ['m4_ifelse(m5_substr(m5_$2, 0, m4_len(['$1'])), ['$1'],
/// ['['$1']m5_set($2, m5_substr(m5_$2, m4_len(['$1'])))'])'])
/// m5_extract_prefix_eval(<prefix>, var)
/// A faster alternative to m5_extract_prefix that evaluates both the prefix and the assigned values (so a safe
/// alternative if these are known not to be affected by evaluation).
///m4_define(['m5_extract_prefix_eval'],
/// ['m4_ifelse(m4_substr(m5_$2, 0, m4_len(['$1'])), ['$1'],
/// ['['$1']m5_set($2, m4_substr(m5_$2, m4_len(['$1'])))'])'])
/// Evaluate to the number of lines in $1.
m4_define(['m5_num_lines'], ['m4_len(m4_patsubst(['$1'], ['[^']m5_nl[']+'], ['']))'])
/// m5_prefix_lines(['prefix'], ['body'])
/// Add a prefix after all newlines in body, returning the quoted updated body.
/// Note that the first line is not prefixed.
m4_define(['m5_prefix_lines'], ['m4_patsubst(['['$2']'], m5_nl, m5_nl['$1'])'])
/// See docs.
m4_pragma_disable_paren_checks
m4_define(['m5_open_quote'],
['m4_changequote()['m5_nullify('])m4_changequote([','])'])
m4_define(['m5_close_quote'],
['m4_changequote()m5_nullify([')']m4_changequote([','])'])
m4_pragma_enable_paren_checks
/// See docs.
m4_define(['m5_orig_open_quote'],
['m4_changequote(<,>)<[><'>m4_changequote([','])'])
m4_define(['m5_orig_close_quote'],
['['''][']']'])
/// Establish scope, like m5__scope, but without stack trace tracking (so also without the first arg).
m4_define(['m5__raw_scope'],
['['m4_pushdef(['m5_block_output'], [''])m5__begin_scope['']m5_exec(['$1'])m5__end_scope['']m5_eval(m5_defn(block_output))m4_popdef(['m5_block_output'])']'])
/// Use of control characters (surrogates) by M5.
/// Char Hex Description Replaces Scenario
/// ---- --- ----------- -------- --------
/// 01 Start of Heading SoT([') An alternate surrogate quote for substr.
/// 02 Start of Text [' Substituted prior to M4 by pre_m4 and requote_stream scripts. Reversed by post_m4.
/// 03 End of Text '] "
/// 04 End of Transmission / For safe strings.
/// 05 Enquiry , A literal comma that cannot separate macro arguments.
/// 06 Acknowledge SoT([') Used to provide quoted output in a m4_translit that substitutes quotes.
/// 0E Shift Out ( For safe strings.
/// 0F Shift In ) "
/// 10 Data Link Esc. \n Used for regex to avoid implications of \n.
/// 15 Negative Acknowledge EoT(']) Used to provide quoted output in a m4_translit that substitutes quotes.
/// 17 End of Trans. Block , For safe strings.
/// 19 End of Medium EoT(']) An alternate surrogate quote for substr.
/// 1A Substitute _ For safe strings (to prevent macro expansion).
/// 1B Escape N/A The value m5_UNDEFINED.
/// 1C File Separator SoT([') An alternate surrogate quote for safe strings.
/// 1D Group Separator EoT(']) "
/// 1E Record Separator SoT([') An alternate surrogate quote for dequoted strings.
/// 1F Unit Separator EoT(']) "
/// A unique untypeable value indicating that no assignment has been made.
/// This is not used by any standard macro, but is available for explicit use.
m4_define(['m5_UNDEFINED'], ['['']'])
/// Quoting and dequoting.
/// Dequoted strings use alternate surrogate quotes and can therefore be safely broken into (quoted) substrings.
/// A dequoted string or a partially dequoted string can be dequoted again. Requoting (once) will restore all quotes.
/// See docs.
/// Produce a quoted string by giving the string alternate-surrogate-quote bookends that translate to quotes,
/// and translit quotes to their surrogates.
m4_define(['m5_dequote'],
['m4_translit(['$1'], ['['']'], ['['']'])'])
/// Inverse of dequoting, using the inverse approach.
m4_define(['m5_requote'],
['m4_translit(['$1'], [''], ['['']['']'])'])
/// Same as m5_dequote/m5_requote using different alternate quotes.
/// These are used by m5_substr to test dequoting.
/// Inverse of dequoting, using the inverse approach.
m4_define(['m5__alt_dequote'],
['m4_translit(['$1'], ['['']'], ['['']'])'])
m4_define(['m5__alt_requote'],
['m4_translit(['$1'], [''], ['['']['']'])'])
m4_define(['m5_printable_open_quote'], ['['⌈']']) /// other options: ‹⦗⧼᚜⦃⎨༼⦓〈‹⟬≺
m4_define(['m5_printable_close_quote'], ['['⌉']'])
/// Substitute control-character quotes for printable UTF-8 quotes.
m4_define(['m5_printable'],
['m4_patsubst(m4_patsubst(m5_nquote(2, m5_dequote(['$1'])), [''], m5_printable_open_quote), [''], m5_printable_close_quote)'])
/// See docs.
m4_define(['m5_output_with_restored_quotes'],
['m4_pushdef(['m4_tmp'], m5_printable(['$1']))m4_patsubst(m4_patsubst(m5_nquote(2, m4_defn(['m4_tmp'])), m5_printable_open_quote, m5_orig_open_quote), m5_printable_close_quote, m5_orig_close_quote)m4_popdef(['m4_tmp'])'])
/// Make sure string has no quotes.
m4_define(['m5_no_quotes'],
['m4_ifelse(['$1'], m5_dequote(['$1']), [''], ['m5_error(['String "$1" should not contain quotes.'])'])'])
/// See docs.
m4_define(['m5_substr'],
m5__raw_scope(['m5_var(ret, m5__unsafe_string(m4_substr(m5__safe_string_with_check(m5__alt_dequote(['$1'])), m5_shift($@))))m4_ifelse(m4_ifelse(m4_index(m5_get(ret), ['']), ['-1'], ['m4_index(m5_get(ret), [''])'], ['['0']']), ['-1'], [''], ['m5_error(['$0 extracted a substring containing quotes. Use m5_dequote/m5_requote, perhaps. String: ']"m5_get(ret)")'])m5_out(m5_get(ret))']))
m4_define(['m5_substr_eval'], m4_defn(['m4_substr'])['['']'])
m4_define(['m5_substr_inline'], ['m5_deprecated(['$0'])']m4_defn(['m4_substr']))
/// Private macros to deal with the fact that m4_substr produces an unquoted result.
/// Replace characters that can be problematic for elaboration with control characters.
/// These include parentheses, underscore (to alter macro names), and quotes, detailed under
/// "Use of control characters (surrogates) by M5".
/// This function may be used with any string, including dequoted ones.
/// The resulting string, and substrings of it, can be used without quotes with the assurance
/// that it will be unaltered as long as all defined macros have a "m5" or "m5_" prefix and
/// there are no control characters other than those used in this library in the original string.
/// The safe string aligns character-for-character with the original, so string lengths, etc. are
/// preserved.
/// This mechanism is reversible (character-for-character) using m5_unsafe_string.
/// Users should not require this functionality since M5 provides macros with quoted output.
/// TODO: comma and comment??
m4_define(['m5__safe_string'],
['m4_translit(['$1'], ['()_/,['']'], ['['']'])'])
/// Same as m5__safe_string, but verifies its safety (in current context).
m4_define(['m5__safe_string_with_check'],
m5__raw_scope(['m5_var(orig, ['$1'])m5_var(ret, m5__safe_string(['$1']))m5_var(restored_str, m5__unsafe_string(m5_eval(m5_get(ret))))m4_ifelse(m5_get(orig), m5_get(restored_str), [''], ['m5_error(['$0 produced a string that reverses to "']m5_get(restored_str)['" which does not match the input string.'])'])m5_out(m5_get(ret))']))
m4_define(['m5__unsafe_string'],
['m4_translit(['$1'], [''], ['()_/,['']['']'])'])
/// #############################
/// Bodies
/// =======================
/// Scopes
/// m5_out(<string>, ...) /// For use within the body of a function declared with m5_fn(..).
/// /// Appends quoted argument to function output string. Multiple arguments
/// /// will be concatinated (to enable splitting output over multiple lines).
/// m5_out_nl(<string>, ...) /// m5_out with a new line appended (to each argument).
/// m5_out_eval(<string>) /// m5_out, where the output is to be evaluated, not literal. By convention,
/// /// this should be used as ~out_eval to highlight its impact on output, even
/// /// though the "~" has no effect.
/// /// TODO: Should we have a different convention for side-effects. Maybe upper-case, like DEBUG(..).
m4_define(['m5_out_nl'], ['m5_deprecated(['$0'])m4_define(['m5_block_output'], m4_defn(['m5_block_output'])['['$1']']m4_quote(m5_nl))m4_ifelse(m4_eval(['$# > 1']), 1, ['$0(m4_shift($@))'])'])
m4_define(['m5_out_inline'], ['m5_deprecated(['$0'])m4_define(['m5_block_output'], m4_defn(['m5_block_output'])['$1'])m4_ifelse(m4_eval(['$# > 1']), 1, ['$0(m4_shift($@))'])'])
m4_define(['m5_out_eval'], ['m4_define(['m5_block_output'], m4_defn(['m5_block_output'])['$1['']'])m4_ifelse(m4_eval(['$# > 1']), 1, ['$0(m4_shift($@))'])'])
m4_define(['m5_eval_out'],
['m5_deprecated(['$0'])m5_out_eval($@)'])
m4_define(['m5_inline_out'],
['m5_deprecated(['$0'])m5_out_inline($@)'])
/// #############################
/// Declarations
/// =======================
/// Declaration Helpers
// m4_arg(3) evaluates to ['['$3']'].
// m4_dollar(3) evaluates to ['$3'].
// These defer argument evaluation and are useful for generating strings to use as definitions.
m4_define(['m4_arg'], ['['['$$1']']'])
m4_define(['m4_dollar'], ['['$$1']'])
/// Provides the body of a declaration macro.
/// $1: value expression
/// $2: [''] or the recursive macro name (without prefix) if the declaration macro should support a list.
/// TODO: add checking of odd $# (when recursing).
m4_define(['m5__declare_body'],
['['m4_pushdef(['m5_']']m4_arg(1)[', ['$1'])m5_prepend_macro(_end_scope_expr, ['m4_popdef(['m5_']']']m4_dquote(m4_arg(1))['[')'])']m4_ifelse(['$2'], [''], [''], ['['m4_ifelse(m4_eval(']']m4_arg(#)['[' == 3), 1, ['m5_error(['$2 called with an odd number of arguments.'])'])m4_ifelse(m4_eval(']']m4_arg(#)['[' > 2), 1, ['m5_$2(m4_shift(m4_shift(']']']m4_dquote(m4_arg(@))['['[')))'])']'])'])
/// Same as m5__declare_body, but for 'push' variants.
m4_define(['m5__declare_push_body'],
['['m4_pushdef(['m5_']']m4_arg(1)[', ['$1'])']m4_ifelse(['$2'], [''], [''], ['['m4_ifelse(m4_eval(']']m4_arg(#)['[' == 3), 1, ['m5_error(['$2 called with an odd number of arguments.'])'])m4_ifelse(m4_eval(']']m4_arg(#)['[' > 2), 1, ['m5_$2(m4_shift(m4_shift(']']']m4_dquote(m4_arg(@))['['[')))'])']'])'])
/**
/// A pre-filter for declaration macros to quickly recognize whether a declaration might have a colon comment.
/// $1: {variable/macro/function} declaration type
/// $2: declared name with optional comment
/// $3: (opt) Default value
m4_define(['m5__doc_declare'],
['m4_ifelse(m4_index(['$2'], [':']), -1, ['['$2']'], ['m5__declare_full($@)'])'])
/// Helper to process a declared name and optional comment. Result in the name.
/// Same interface as m5__declare.
m4_define(['m5__declare_full'],
['m4_regexp(['$2'], ['\([a-zA-Z_\.]*\)\(: ?\)?\(.*\)'], ['m5__declare_guts(['$1'], ['\1'], ['\3'], ['$3'])'])'])
/// Guts of m5__declare.
/// $1: {variable/macro/function} declaration type
/// $2: declared name
/// $3: description separator
/// $4: remaining (description) string
/// $5: (opt) default value
m4_define(['m5__declare_guts'],
['['$2']m4_ifelse(['$3']['$4'], [''],
[''],
['$3'], [''],
['m5_error(['Malformed $1 name/description argument: "$2$3$4".'])'],
['m5__doc_declare(['$1'], ['$2'], ['$4'], ['$5'])'])'])
/// A scoped declaration's pop expression.
m4_define(['m5__declare_end'],
['m5_prepend_macro(_end_scope_expr, ['m4_popdef(['$1'])'])'])
**/
/// Push a macro definition.
/// TODO: There's now a non-private version.
m4_define(['m5__push_macro'],
['m4_pushdef(['m5_$1'], ['$2'])m4_ifelse(m4_eval($# > 2), 1, ['$0(m4_shift(m4_shift($@)))'])'])
/// Push a var definition.
m4_define(['m5__push_var'],
['m5_error(['m5__push_var is broken.'])m4_pushdef(['m5_$1'], ['['$2']'])m4_ifelse(m4_eval($# > 2), 1, ['$0(m4_shift(m4_shift($@)))'])'])
/// Pop a list of macro names (with implied "m5_" prefix), to go with m5__push_macro/var(..)
m4_define(['m5__pop'],
['m5_error(['m5__pop is broken.'])m4_popdef(['m5_$1'])m4_ifelse(m4_eval($# > 1), 1, ['$0(m4_shift($@))'])'])
/// m5__pop_push(pop_var, push_var)
/// Pops one var/macro and pushes the popped definition into another.
m4_define(['m5__pop_push'],
['m5_error(['m5__pop_push may be broken.'])m5__push_macro(['$2'], m4_defn(['m5_$1']))m5__pop(['$1'])'])
/// =======================
/// Variables
/// See docs.
/// TODO: Var macros do some temporary stuff with macro defs.
m4_define(['m5_var'],
['m4_pushdef(['~$1'], ['$2'])m5_prepend_macro(_end_scope_expr, ['m4_popdef(['~$1'])'])'])
/// TODO:
m4_define(['m5_vars'],
['m4_ifelse($#, 1, ['m5_error(['Odd number of arguments to $0.'])'], ['m5_var(['$1'], ['$2'])m5_if($# > 2, ['$0(m5_shift(m5_shift($@)))'])'])'])
/// Declare a universal variable. TODO: Should we use m5_var for this and autodetect whether we're in scope (or upper vs. lower case) or would this be too much overhead?
m4_define(['m5_universal_var'],
['m4_ifdef(['~$1'], ['m5_error(['Redefining m5_$1.'])'])m4_define(['~$1'], ['$2'])'])
m4_define(['m5_universal_vars'],
['m4_ifelse($#, 1, ['m5_error(['Odd number of arguments to $0.'])'], ['m5_universal_var(['$1'], ['$2'])m5_if($# > 2, ['$0(m5_shift(m5_shift($@)))'])'])'])
/// Set a variable that has already been declared. E.g. m5_set(var, value).
m4_define(['m5_set'],
['m4_ifelse(m4_eval(['$# < 2']), 1,
['m5_error(['$0 given an odd number of arguments (or none).'])'])m4_ifdef(['~$1'], [''], ['m5_error(['Setting an undefined variable "$1".'])'])m4_define(['~$1'], ['$2'])'])
/// Explicitly pushed/popped vars (use should be rare).
/// TODO
m4_define(['m5_push_var'],
['m4_pushdef(['~$1'], ['$2'])'])
/// Pop a pushed macro (var/traditional-macro/function)
/// When m5_var only defines the var.
///m4_define(['m5_pop'],
/// ['m5_if(m4_ifdef(['~$1'], 1, 0) && m4_ifdef(['m5_$1'], 1, 0), ['m5_error(['m5_pop($1) not sure whether to pop a variable or a macro.'])'])m4_ifdef(['~$1'], ['m4_popdef(['~$1'])'], ['m4_popdef(['m5_$1'])'])m4_ifelse(['$2'], [''], [''], ['m5_pop(m5_shift($@))'])'])
m4_define(['m5_pop'],
['m4_ifdef(['~$1'], ['m4_popdef(['~$1'])'], ['m5_error(['pop($1): $1 not defined as a var.'])'])m4_ifelse(['$2'], [''], [''], ['m5_pop(m5_shift($@))'])'])
/// Deprecated.
/// TODO: stringify doesn't work if string contains quotes. Use m5_defn(['$1']__VALUE) approach instead.
m4_define(['m5_stringify'],
['m5_deprecated(['$0'])m5_set_macro(['$1'], m4_patsubst(m4_dquote(m4_defn(['m5_$1'])), ['\$'], ['$']m5_close_quote()m5_open_quote()))'])
m4_define(['m5_var_str'],
['m5_deprecated(['$0'])m5_var(['$1'], ['$2'])m5_stringify(['$1'])'])
m4_define(['m5_set_str'],
['m5_deprecated(['$0'])m5_set(['$1'], ['$2'])m5_stringify(['$1'])'])
/// Set a variable if it is empty.
m4_define(['m5_default'],
['m4_ifelse(m5_get(['$1']), [''], ['m5_set(['$1'], ['$2'])'])'])
/// Set a variable if it is empty.
m4_define(['m5_default_v'],
['m4_ifdef(['~$1'], [''], ['m4_ifdef(['m5_$1'], ['m5_warning(['$0 seems to be setting a macro "$1".'])'])m5_var(['$1'], ['$2'])'])'])
/// Declare variables with [''] values.
/// Args: list of variable names to declare.
m4_define(['m5_null_vars'],
['m4_ifelse($# > 0, 1, ['m5_var(['$1'], [''])m5_recurse(100, null_vars['']m5_comma_shift($@))'])'])
/// An unsanctioned macro to provide a default definition for a variable.
/// It is unsanctioned because implicitly passing variables is discouraged.
/// If there is no external variable definition, define to a default value.
m4_define(['m5_default_var'],
['m5_def_body2(['default_var'], ['default_v'], [''], m4_process_description($@))'])
/// Get the value of a variable.
/// m5_value_of(foo) has the same effect as m4_foo except when the value contains argument strings such as "$1".
/// This evaluates the variable definition, rather than expanding the variable. It also reports an error if the
/// variable doesn't exist.
m4_define(['m5_value_of'],
['m5_deprecated(['$0'])m4_ifdef(['~$1'], ['m4_defn(['~$1'])'], ['m5_if_def(['$1'], ['m5_warning(['Using $0 for macro "$1".'], value_of, 10)'], ['m5_error(['Can't get value_of non-existent variable "$1".'])'])m5_eval(m4_defn(['m5_$1']))'])'])
/// =======================
/// Traditional Macros
/// m5_macro(name, ['<body>'])
/// Declare a macro. (See M5 spec.)
m4_define(['m5_macro'],
m5__declare_body($['']2['['']'], ['']))
m4_define(['m5_set_macro'],
['m4_ifelse(m4_eval(['$# < 2']), 1,
['m5_error(['$0 given an odd number of arguments (or none).'])'])m4_ifdef(['m5_$1'], [''], ['m5_error(['Setting an undefined variable "$1".'])'])m4_define(['m5_$1'], ['$2'])'])
/// See docs.
m4_define(['m5_macro_inline'],
['m5_deprecated(['$0'])']m5__declare_body($['']2, ['']))
m4_define(['m5_inline_macro'],
['m5_deprecated(['$0'])m5_macro_inline($@)'])
/// Output nothing. This is used by m5_null_macro to evaluate without producing output.
m4_define(['m5__null'], [''])
/// m5_null_macro(body).
m4_define(['m5_null_macro'],
m5__declare_body(['m5__null($']['2)'], ['']))
/// Explicitly pushed/popped macro (use should be rare). Inline/null versions not defined.
m4_define(['m5_push_macro'],
m5__declare_push_body(m4_dollar(2), push_macro))
m4_define(['m5_pop_macro'], m4_defn(['m5_popdef'])) /// TODO: Transition this out. Used in WARP-V \TLV macros, which should become scope.
/// =======================
/// Functions
/// Binding bodies.
/// Deprecated: Use
/// on_return(m5_Body)
/// return_status(...)
/// instead (requiring execution as a aftermath).
/// TODO: This will also pop/push m5_my.
/// TODO: This could also be accomplished by popping aftermath into a temp, then pushing it after the call.
/// This would be consistent with the above todo.
m4_define(['m5_eval_body_arg'],
['m4_pushdef(~fn__aftermath, [''])$1['']m5_on_return(m4_defn(~fn__aftermath))m4_popdef(~fn__aftermath)'])
/// Return values (in addition to the output text of the block):
/// These are preferred over directly setting variables whose names are given due to possible masking (name collisions with local declarations).
/// See docs.
m4_define(['m5_return_status'], ['m5_append_var(fn__aftermath, m4_ifelse($#, 0, ['['m5_set(status, m5_get(status))']'], ['['m5_set(status, ['$1'])']']))'])
m4_define(['m5_on_return'], ['m5_append_var(fn__aftermath, ['m5_call($@)['']'])'])
m4_define(['m5_on_return_inline'], ['m5_deprecated(['$0'])m5_append_var(fn__aftermath, ['m5_call($@)'])'])
/// Evaluate $1 that should not produce any output other than whitespace and comments. No output is produced, and an error is reported if the evaluation was non-empty.
/// TODO: Permitting //-comments can be deprecated since they are no longer given special treatment by M5. ///-comments should be used in \m5 regions.
/// TODO: Deprecated by m5__exec.
m4_define(['m5_exec'],
['m4_pushdef(['m4__out'], m4_patsubst(m4_dquote(m4_joinall([','], $1)), ['\s*'], ['']))m4_ifelse(m4_defn(['m4__out']), [''], [''], ['m5_warning(['Block contained the following unprocessed text: "']m4_defn(['m4__out'])['"'])'])m4_popdef(['m4__out'])'])
/// m5_fn uses this for the definitions it creates (function and its body).
/// It is used by m5_doc_as_fn to configure m5_fn for docs with low overhead.
m4_define(['m5__fn_def'], m4_defn(['m4_pushdef']))
/// Helpers for m5_fn.
/// m4_fn__too_many_args_check(fn_name, given_args, arg_cnt, arg1). Accept a single empty arg as zero args.
m4_define(['m4_fn__too_many_args_check'],
['m4_ifelse(m4_eval(['$2 > $3']), 1, ['m4_ifelse(['$2$3$4'], ['10'], [''], ['m5_error(['Too many arguments ($2) given to function "$1" which accepts $3.'])'])'])'])
/// m4_fn__check_args(fn_name, given_args, arg_cnt, required_arg_cnt, var_args(0/1), arg1)
m4_define(['m4_fn__check_args'],
['m4_ifelse(m4_eval(['$2 < $4']), 1, ['m5_error(['Function "$1" requires $4 arguments; $2 given.'])'])m4_ifelse(['$5'], 0, ['m4_fn__too_many_args_check(['$1'], ['$2'], ['$3'], ['$6'])'])'])
/// m5_fn(...)
/// See docs.
///
/// Implementation comments:
///
/// m5_fn(['foo'], ['param1'], ['param2: opt param2 desc'], ['Body using m5_param1, m5_param2, and $@.'])
/// Is processed recursively, one argument at a time to effectively create these definitions which provide parts
/// of a subsequent definition. Note that these cannot contain $ substitutions while being constructed, but must
/// evaluate to contain them:
/// m4_pushdef(['m4_push_named_args'], ['['['m4_pushdef(['~param1'], ']m4_arg(1)[')']']['['m5_pushdef(['~param2'], ']m4_arg(2)[')']']'])
/// m4_pushdef(['m4_dollar_args'], ['[',['$1'],['$3'],['<inheritted>']']']) /// for numbered arg
/// m4_pushdef(['m4_extra_args'], ['['['m4_shift(m4_shift(']']']['['m4_dollar(@)']']['['['))']']'] /// if ... (via m4_varargs)
/// -OR- ['['['...m5_error(...)']']']) /// if no ..., print error except for case of 0 parameters and 1 empty arg
/// m4_pushdef(['m4_pop_named_args'], ['['m4_popdef(['~param1'])']['m4_popdef(['~param2'])']'])
/// And permanently defines:
/// m4_define(['m4_foo__body'], ['Body using m4_param1, m4_param2, and $@.'])
/// Then combines them as:
/// m4_define(['m4_foo'], m5_eval(m4_push_named_args)...)
/// And pops them.
///
m5_null_macro(fn, ['
/// Initialize for recursive arg processing.
m4_ifelse(m4_index(['$1'], :), -1, [''], ['m5_error(['Function with a comment is deprecated: $1'])'])
m4_pushdef(['m5_func_name'], m4_regexp(['$1'], ['^\(\w*\)'], ['['\1']'])) /// TODO: Remove support for function comments (not param comments) (for better lazy performance).
m4_pushdef(['m4_push_named_args'], ['['']'])
m4_pushdef(['m4_dollar_args'], ['['']'])
m4_pushdef(['m4_varargs'], ['['m4_dollar(@)']'])
m4_pushdef(['m4_extra_args'], [''])
m4_pushdef(['m4_assign_outputs'], ['['']'])
m4_pushdef(['m4_pop_named_args'], [''])
m4_pushdef(['m4_arg_cnt'], 0)
m4_pushdef(['m4_numbered_cnt'], 1)
m4_pushdef(['m4_optional_found'], 0)
m4_pushdef(['m4_required_arg_cnt'], 0)
m4_define(['m4_fn__has_inherited'], 0) /// A global, checked by lazy_fn after it constructs the function.
m5_doc_fn__begin()
/// Recurse, processing params, and capturing body.
m4_ifelse(m4_eval($# > 1), 1, ['m5_fn__recurse(m4_shift($@))'])
/// Process body.
/// Declare the function.
/// TODO: Change m4_pushdef to m5_macro
m5__fn_def(['m5_']m5_func_name,
m5__concat(
['m4_pushdef(['~status'], [''])'], /// Give function its own status, so we can restore status on return.
['m4_pushdef(['~fn__aftermath'],['m4_popdef(['~fn__aftermath'])['']'])'],
/// Check parameter count.
['m4_fn__check_args(']m5_func_name[', $']['#, ']m4_arg_cnt[', ']m4_required_arg_cnt[', ']m4_ifelse(m4_defn(['m4_extra_args']), [''], 0, 1)[', ']m4_arg(1)[')'],
m5_eval(m4_push_named_args),
['m5_fn__push_stack(m5__stmt_file:m5__stmt_line:, ']m5_func_name[', ']m4_arg(@)[')'],
m5_pragma_disable_paren_checks(),
['m5_eval(['m4_indir(['m4_']']']m5_func_name['['['__body']']'],
m4_defn(['m4_dollar_args']),
['m4_ifelse(m4_eval(']m4_dollar(#)[' > ']m4_arg_cnt['), 1, ']m4_dquote(m4_dquote(m5_eval(m4_extra_args)))[')'],
['[')'])'],
m5_pragma_enable_paren_checks(),
['m5_fn__pop_stack()'],
m4_pop_named_args,
m5_eval(m4_assign_outputs), /// TODO: This will be problematic if output variables are pushed with new values. These will be popped by body before we see them. So use m5_after and deprecate *output.
['m4_popdef(['~status'])'],
['m5_eval(m4_defn(['~fn__aftermath']))'], /// Eval aftermath (without numbered parameter substitution). Note that aftermath pops itself first, so aftermath happens in the parent context.
['']))
m5_doc_fn__end()
/// Pop.
m4_popdef(['m5_func_name'])
m4_popdef(['m4_push_named_args'])
m4_popdef(['m4_dollar_args'])
m4_popdef(['m4_varargs'])
m4_popdef(['m4_extra_args'])
m4_popdef(['m4_assign_outputs'])
m4_popdef(['m4_pop_named_args'])
m4_popdef(['m4_arg_cnt'])
m4_popdef(['m4_numbered_cnt'])
m4_popdef(['m4_optional_found'])
m4_popdef(['m4_required_arg_cnt'])
'])
/// Helper for m5_fn that verifies that any text after parameter name is a comment.
m4_define(['m5_fn__param_comment'], ['
m4_ifelse(m4_index(m5__param, [':']), 0, [''], ['
/// No colon to strip. Should be no comment.
m4_ifelse(m5__param, [''], [''], ['
m5_error(['In declaration of function "']m5_func_name['", $1 contains unrecognized text "']m5__param['".'])
'])
'])
'])
/// A helper for fn__recurse.
/// m5_extract_regex(regex, var) like m5_extract_prefix, except the prefix is given as a regex subexpression.
/// Note that for optimal performance m5_get is not used on the string, which is not declared as a variable.
m4_define(['m5_extract_prefix_regex'],
['m4_regexp(m5_$2, ['^\($1\)?\(.*\)'], ['m4_define(['m5_$2'], ['['\2']'])['\1']'])'])
/// Recursive guts of m5_fn for arg processing.
/// ...: arg list
/// Return quoted body (final arg quoted)
m4_define(
['m5_fn__recurse'],
['m4_ifelse(
m4_eval($# <= 1), 1,
['m5__fn_def(['m4_']m5_func_name['__body'], ['m4_pushdef(['~fn_args'], ']m4_arg(@)[')$1['']m4_popdef(['~fn_args'])'])'],
['m4_ifelse(['$1'], [''],
['m5_error(['In declaration of function ']m5_func_name[', empty function argument no longer permitted.'])'],
['// First, look for ['...'].
m4_pushdef(['m5__param'], ['['$1']'])
m4_ifelse(m5_extract_prefix_regex(['\.\.\.'], _param), ['...'], ['
///m4_ifelse(['$#'], 2, [''], ['m5_error(['In declaration of function ']m5_func_name[', arguments after "...".'])'])
m4_define(['m4_extra_args'], ['['[', ']']']m4_defn(['m4_varargs']))
m4_define(['m5__param_name'], ...)
m4_define(['m5_out_var_prefix'], [''])
m4_def(optional_prefix, [''], cnt_prefix, [''])
m5_fn__param_comment(['parameter "..."'])
m5_doc_fn__param()
'], ['
/// Extract possible '*', '?', '[<number>]', and/or '^' prefixes from argument value.
/// TODO: These should be pushdef/popdef:
m4_define(['m5_out_var_prefix'], m5_extract_prefix_regex(\*, _param)) /// Deprecated.
m4_def(optional_prefix, m5_extract_prefix_regex(\?, _param))
m4_def(cnt_prefix, [''])
m4_regexp(m5__param, ['^\[\([0-9]+\)\]\(.*\)'], ['m4_def(cnt_prefix, ['\1'])m4_define(['m5__param'], ['\2'])'])
m4_regexp(m5__param, ['^\([0-9]+\)\(\.\)?\(.*\)'], ['m5_fn__num_dot_param(['\1'], ['\2'], ['\3'])'])
m4_def(inherit_prefix, m5_extract_prefix_regex(\^, _param))
m4_define(['m5__param_name'], m5_extract_prefix_regex(['\w*'], _param))
m5_fn__param_comment(['parameter "']m5__param_name['"']) /// TODO: This should only be done in debug mode.
/// Done processing m5__param. It's value is now its comment.
/// * can't be combined with other prefixes. TODO: * is deprecated.
m4_ifelse(m5_out_var_prefix, ['*'], ['m4_ifelse(m4_optional_prefix['']m4_cnt_prefix['']m4_inherit_prefix, [''], [''], ['m5_error(['Output parameter "']m5__param_name['" of function ']m5_func_name[' shouldn't have other prefix symbols.'])'])'])
/// The parameter name or number for error messages.
m4_define(['m5__param_tag'], m4_ifelse(m5__param_name, [''], m4_cnt_prefix, m5__param_name))
/// Verify that <name> or <number> is \w+.
m4_ifelse(m4_regexp(m5__param_tag, ['^\w+$']), -1, ['m5_error(['Bug: In declaration of function ']m5_func_name[', parameter has illegal name ']m5__param_tag['.']m5_nl[''])'])
m4_ifelse(m4_inherit_prefix, ^,
['// Inherited.
m4_define(['m4_fn__has_inherited'], 1)
/// Must be defined unless optional.
m4_ifelse(m4_optional_prefix, [''], ['m4_ifdef(~m5__param_name, [''], ['m5_error(['In declaration of function ']m5_func_name[', non-optional, inherited parameter ']m5__param_name[' is undefined.']m5_nl[''])'])'])'],
['// This parameter corresponds to an argument.
/// Document it.
m5_doc_fn__param()
m4_ifelse(m4_optional_prefix, ?,
['m4_define(['m4_optional_found'], 1)'],
['// Required.
m4_define(['m4_required_arg_cnt'], m4_incr(m4_required_arg_cnt))
m4_ifelse(m4_optional_found, 1,
['m5_error(['In declaration of function ']m5_func_name[', parameter ']m5__param_tag[' follows an optional parameter, but is not itself optional.']m5_nl[''])'])
']
)
m4_define(['m4_arg_cnt'], m4_incr(m4_arg_cnt))
/// Exclude arg from varargs
m4_define(['m4_varargs'], ['['['m4_shift(']']']m4_dquote(m4_varargs)['['[')']']'])'])
/// Expression for the argument corresponding to this parameter (for named and/or numbered arg). E.g. ['$-3'] for non-inherited; m4_defn(['m5_<name>']) for inherited.
/// Deltas for proc vs. func are applied later:
/// o non-inherited named args are quoted: e.g. ['['$-3']']
/// o inherited numbered args are evaluated: m4_<name>
m4_pushdef(['m4_func_arg'], m4_dquote(m4_ifelse(m4_inherit_prefix, ^,
['m4_dquote(m4_dquote(m4_defn(['m5_']m5__param_name)))'],
['['m4_arg(']m4_arg_cnt[')']'])))
/// If this param is named, calls must push its value, quoted if (not ^) and proc.
m4_ifelse(m5__param_name, [''], [''], ['
m4_ifelse(m5_out_var_prefix, *, [' /// TODO: * is deprecated.
m5_deprecated(['fn with * parameter'])
/// Outputs are initialized by pushing their current value (or [''] if undefined), and finalized by copying into a new var named according to the argument.
m4_append(push_named_args, ['['m4_pushdef(~']']m5__param_name['[', m5_eval(m5_defn(']']m4_func_arg['[')))']'])
m4_append(assign_outputs, ['['m5_var(']']m4_func_arg['[', m5_get(']']m5__param_name['['))m5_pop(']']m5__param_name['[')']'])
'], ['
m4_append(push_named_args, ['['m4_pushdef(~']']m5__param_name['[', ']']m4_ifelse(m4_inherit_prefix, ^, ['m4_dquote(m4_dquote(m5_get(m5__param_name)))'], ['['m4_arg(']m4_arg_cnt[')']'])['[')']'])
m4_append(pop_named_args, ['m4_popdef(~']m5__param_name[')'])
'])
'])
/// If this is a numbered param, confirm count and append arg to the call.
m4_ifelse(m4_cnt_prefix, [''], [''],
['m4_ifelse(m4_cnt_prefix, m4_numbered_cnt, [''],
['m5_error(['In declaration of function ']m5_func_name[', numbered parameters are out of sequence. Parameter given as '][m4_cnt_prefix][' should be '][m4_numbered_cnt]['.'])'])
m4_define(['m4_numbered_cnt'], m4_incr(m4_numbered_cnt))
m4_str_append(dollar_args, m4_dquote([',']m4_ifelse(m4_inherit_prefix, ^, ['m4_defn(['m5_']']m5__param_name[')'], m4_func_arg)))'])
m4_popdef(['m4_func_arg'])
'])
m4_popdef(['m5__param_name'])
'])
m4_dquote($0(m4_shift($@)))
'])
'])
/// A helper to process parameter specifiers like "3.foo".
m4_define(['m5_fn__num_dot_param'],
['m4_def(cnt_prefix, ['$1'])m4_define(['m5__param'], ['$3'])m4_ifelse(m4_eval((m4_len(['$2']) == 0) != (m4_len(['$3']) == 0)), ['1'],
['m5_error(['In declaration of function ']m5_func_name[', bad formatting of numbered parameter "$1$3".'])'])'])
/// Define m5_lazy_fn.
///
/// Identical to m5_fn, except in performance. A lazy function waits to define the function
/// until it is first called. Specifically, it records the function definition arguments and
/// defines the function to use them to redefine and call itself. "^" params are not supported.
///
/// Implementation: This defines a "maker" (whose definition can be evaluated to make the fn),
/// then defines the fn to use the maker then call the made function.
/// The maker body cannot be embedded in the lazy function itself because it may contain numbered parameters.
/// The maker is given a globally unique name to simplify maintaining its association with this function given
/// that this function can be on a stack, and the stack can contain a mix of lazy and non-lazy functions.
m4_define(['m5_lazy_fn'],
m4_ifelse(m5_need_docs, ['yes'], /// Defined prior to including library if docs are to be enabled.
['['m5_fn($@)']'], /// Don't be lazy if we're creating docs.
['['m5_lazy_fn__guts(m4_regexp(['$1'], ['^\(\w*\)'], ['['\1']']), m4_shift($@))']'])) /// Strip docs from function name.
/// Guts of m5_lazy_fn for non-documenation case.
/// Same args as m5_lazy_fn, but without docs on function name.
m4_define(['m5_lazy_fn__guts'],
['m4_define(['m5_']m5__new_uniquifier, ['m5_fn($@)'])m4_pushdef(['m5_$1'], ['m4_popdef(['m5_$1'])m5_eval(m5_defn(']m5__same_uniquifier['))m4_ifelse(m4_fn__has_inherited, 1, ['m5_error(['Lazy function $1 has an inherited argument. This is not supported.'])'])m4_ifelse(']m4_dollar(#)[', 0, ['m5_$1'], ['m5_$1(']']m4_arg(@)['[')'])'])'])
/// m4_define(['m5_lazy_fn'], m5_defn(fn)) /// Disable m5_lazy_fn for testing.
/// See docs.
m4_define(['m5_fn_args'], ['m4_indir(~fn_args)'])
m4_define(['m5_fn_arg'], ['m5_argn($1, m4_indir(~fn_args))'])
/// Number of arguments in fn_args.
m4_define(['m5_fn_arg_cnt'],
['m4_ifelse(m4_len(m4_defn(['~fn_args'])), 0, ['0'], ['m5_nargs(m5_fn_args())'])'])
m4_define(['m5_comma_fn_args'],
['m4_ifelse(m4_defn(~fn_args), [''], [''], [', m5_fn_args()'])'])
/// #############################
/// For manipulating arguments
/// See docs.
m4_define(['m5_nargs'], ['['$#']'])
m4_define(['m5_argn'],
['m4_ifelse(['$1'], 1, ['['$2']'],
['m5_argn(m4_decr(['$1']), m4_shift(m4_shift($@)))'])'])
/// For providing numbered arg references in nested macro bodies.
/// m5_nquote_dollar(#, 1) is equivalent to ['['$#']'], which all evaluate to ['thing'].
m4_define(['m5_nquote_dollar'],
['m5_nquote(['$2'], ['$$1'])'])
/// A variant of m5_shift that includes the preceding comma and thus differentiates no args from a single empty arg.
m4_define(['m5_comma_shift'],
['m4_ifelse($#, 0, ['m5_error(['m5_comma_shift() called with no arguments.'])'], $#, 1, [''], [', m5_shift($@)'])'])
/// Return the arguments. (Useful for processing parameters that are parenthesized argument lists.)
m4_define(['m5_args'], ['$@'])
/// See docs.
m4_define(['m5_verify_min_args'],
['m4_ifelse(m5_calc(['$3 < $2']), 1, ['m5_error(['$1 requires at least $2 arguments; given $3.'])'])'])
m4_define(['m5_verify_num_args'],
['m4_ifelse(m5_calc(['$3 != $2']), 1, ['m5_error(['$1 requires $2 arguments; given $3.'])'])'])
m4_define(['m5_verify_min_max_args'],
['m4_ifelse(m5_calc(['($4 < $2) || ($4 > $3)']), 1, ['m5_error(['$1 requires at least $2 and no more than $3 arguments; given $4.'])'])'])
/// See docs.
m4_define(['m5_comma_args'],
['m4_ifelse(['$1'], [''], [''], [', $1'])'])
/// Deprecating this in favor of the above, which is a little messier but more general and consistent for
/// more cases.
/// m5_call_varargs(my_fn, arg1, ['$@'])
/// M4 syntax for argument lists is inconvenient for passing zero arguments.
/// This variant of m5_call has a final argument that is a list of 0 or more arguments.
m4_define(['m5_call_varargs'],
['m5_deprecated(['$0'])m5_verify_min_args(['$0'], 2, $#)m5_call(m5_eval(m5_call_varargs__args($@)))'])
/// Process args of m5_call_varargs into a quoted argument list for m5_call.
m4_define(['m5_call_varargs__args'],
['m4_ifelse($#, 2, ['['['$1']']m4_ifelse(['$2'], [''], [''], ['[',$2']'])'], ['['['$1'],']$0(m5_shift($@))'])'])
/// Given a quoted arg list, produces a quoted arg list with the first arg removed.
/// m5_shift_quoted(['['one'],['two']']) => ['['two']']
/// Commonly: m5_call_varargs(my_fn, m5_shift_quoted(['$@']))
m4_define(['m5_shift_quoted'],
['m4_ifelse($#, 1, [''], ['m5_error(['$0 requires 1 argument; given $#.'])'])m4_ifelse(['$1'], [''], ['m5_error(['$0: Nothing to shift.'])'], ['m5_call(quote['']m5_comma_shift(m5_eval($@)))'])'])
/// #############################
/// Math
m4_ifelse(m5__debug_level, ['max'], ['
m4_define(['m5_calc'], ['m4_pushdef(['m4__tmp'], m4_eval($@))m4_ifelse(m4__tmp, , ['m5_error(['Bad calc expression: "$1".'])'])m4_ifelse(['$1'], , ['m5_error(['Empty calc expression.'])'])m4__tmp['']m4_popdef(['m4__tmp'])'])
'], ['
m4_define(['m5_calc'], m4_defn(['m4_eval']))
'])
/// #############################
/// Regex
/// See docs.
/// Same as m4_regexp, but with quoted output.
m4_define(['m5_regex'],
['m4_regexp(['$1'], ['$2']m4_ifelse(m5_calc($# > 2), 1, [', ['['$3']']']))'])
m4_define(['m5_regex_eval'], m4_defn(['m4_regexp']))
m5_pragma_enable_sugar
m5_do([
/Declare variables initialized with [''] values.
lazy_fn(null_vars,
...: list of variable names to declare,
[
if($# > 0, [
var("$1", "")
recurse(100, null_vars""comma_shift($@))
])
])
/See docs.
/TODO: Surround with \(\), and increment \#s in var_regex__match_str. This should be functionally equivalent,
/ but avoid M4 error if no match expressions. Also need to prevent () arg list from looking like a single null arg name.
/ Also, improve on this. Since there is not lazy match, it's hard to get preceding text. Do this by
/ adding \(.*\)$ only if no $ and surround this updated expression with \(\) assign the .* pattern to
/ $post; then compute $pre based on the length of the surrounding match. This is compute-heavy.
lazy_fn(var_regex, string, re, var_list, {
/Make sure var_list is as expected.
if_eq(m4_regexp($var_list, "^(.*)$"), "-1", {fatal_error("Malformed argument var_list for function var_regex should be a list in parentheses. Value is "~"$var_list""~.")})
var(exp_cnt, 0) /// Count of regexp expressions.
/The function result for evaluation.
var(rslt_expr, m4_regexp($string, $re, ['['['']']']quote(eval(['m5_\var_regex__match_str']$var_list)))) /// ['['['']']'] ensures non-empty if matched.
if_null(rslt_expr, [
set(rslt_expr, ['m5_\null_vars']$var_list)
return_status(no-match)
], [
return_status()
])
/TODO: Use $on_return now.
~out_eval($rslt_expr)
/on_return(eval, $rslt_expr)
})
/Process arguments to produce replacement string. E.g.
/ var_regex__match_str("foo", "bar")
/becomes:
/ ['m5_var(foo", "\1")m5_var("bar", "\2")']
lazy_fn(var_regex__match_str, ..., [
increment(exp_cnt)
~if($# > 0, [
~(['m5_var(['$1'], ']quote(['\']$exp_cnt)[')'])
~recurse(100, var_regex__match_str""comma_shift($@))
])
])
/For chaining var_regex to parse text that could match a number of formats.
/Each pattern match is in its own scope. Return status is non-null if no expression matched.
/TODO: It's not okay to call a body from a function. on_return/return_status will return status from
/ this function, not from the funtion that calls this. Maybe we need a version of fn that doesn't
/ do aftermath (though this one needs it)?? Others are similar.
lazy_fn(if_regex, string, re, var_list, body, ..., {
return_status("") /// default
var_regex($string, $re, $var_list)
~if_so({m5_eval($body)}) /// TODO: Use m5_eval_body_arg.
~else_if($# == 1, "$1") /// else body
~else_if($# > 1, [
/recurse
~if_regex($string, $@)
/propagate status
return_status($status)
])
else([
/Nothing left to try.
return_status(else)
])
})
macro(else_if_regex,
{m4_ifelse($status, "", {}, {if_regex($@)})})
macro(if_status,
{deprecated("$0")"$1"if_so(shift($@))})
macro(else_if_status,
{deprecated("$0")m4_ifelse($status, "", {}, {if_status($@)})})
/See docs.
macro(echo_args, "$""@")