Menu

[r3166]: / trunk / Src / USourceGen.pas  Maximize  Restore  History

Download this file

1452 lines (1314 with data), 52.0 kB

   1
   2
   3
   4
   5
   6
   7
   8
   9
  10
  11
  12
  13
  14
  15
  16
  17
  18
  19
  20
  21
  22
  23
  24
  25
  26
  27
  28
  29
  30
  31
  32
  33
  34
  35
  36
  37
  38
  39
  40
  41
  42
  43
  44
  45
  46
  47
  48
  49
  50
  51
  52
  53
  54
  55
  56
  57
  58
  59
  60
  61
  62
  63
  64
  65
  66
  67
  68
  69
  70
  71
  72
  73
  74
  75
  76
  77
  78
  79
  80
  81
  82
  83
  84
  85
  86
  87
  88
  89
  90
  91
  92
  93
  94
  95
  96
  97
  98
  99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313
 314
 315
 316
 317
 318
 319
 320
 321
 322
 323
 324
 325
 326
 327
 328
 329
 330
 331
 332
 333
 334
 335
 336
 337
 338
 339
 340
 341
 342
 343
 344
 345
 346
 347
 348
 349
 350
 351
 352
 353
 354
 355
 356
 357
 358
 359
 360
 361
 362
 363
 364
 365
 366
 367
 368
 369
 370
 371
 372
 373
 374
 375
 376
 377
 378
 379
 380
 381
 382
 383
 384
 385
 386
 387
 388
 389
 390
 391
 392
 393
 394
 395
 396
 397
 398
 399
 400
 401
 402
 403
 404
 405
 406
 407
 408
 409
 410
 411
 412
 413
 414
 415
 416
 417
 418
 419
 420
 421
 422
 423
 424
 425
 426
 427
 428
 429
 430
 431
 432
 433
 434
 435
 436
 437
 438
 439
 440
 441
 442
 443
 444
 445
 446
 447
 448
 449
 450
 451
 452
 453
 454
 455
 456
 457
 458
 459
 460
 461
 462
 463
 464
 465
 466
 467
 468
 469
 470
 471
 472
 473
 474
 475
 476
 477
 478
 479
 480
 481
 482
 483
 484
 485
 486
 487
 488
 489
 490
 491
 492
 493
 494
 495
 496
 497
 498
 499
 500
 501
 502
 503
 504
 505
 506
 507
 508
 509
 510
 511
 512
 513
 514
 515
 516
 517
 518
 519
 520
 521
 522
 523
 524
 525
 526
 527
 528
 529
 530
 531
 532
 533
 534
 535
 536
 537
 538
 539
 540
 541
 542
 543
 544
 545
 546
 547
 548
 549
 550
 551
 552
 553
 554
 555
 556
 557
 558
 559
 560
 561
 562
 563
 564
 565
 566
 567
 568
 569
 570
 571
 572
 573
 574
 575
 576
 577
 578
 579
 580
 581
 582
 583
 584
 585
 586
 587
 588
 589
 590
 591
 592
 593
 594
 595
 596
 597
 598
 599
 600
 601
 602
 603
 604
 605
 606
 607
 608
 609
 610
 611
 612
 613
 614
 615
 616
 617
 618
 619
 620
 621
 622
 623
 624
 625
 626
 627
 628
 629
 630
 631
 632
 633
 634
 635
 636
 637
 638
 639
 640
 641
 642
 643
 644
 645
 646
 647
 648
 649
 650
 651
 652
 653
 654
 655
 656
 657
 658
 659
 660
 661
 662
 663
 664
 665
 666
 667
 668
 669
 670
 671
 672
 673
 674
 675
 676
 677
 678
 679
 680
 681
 682
 683
 684
 685
 686
 687
 688
 689
 690
 691
 692
 693
 694
 695
 696
 697
 698
 699
 700
 701
 702
 703
 704
 705
 706
 707
 708
 709
 710
 711
 712
 713
 714
 715
 716
 717
 718
 719
 720
 721
 722
 723
 724
 725
 726
 727
 728
 729
 730
 731
 732
 733
 734
 735
 736
 737
 738
 739
 740
 741
 742
 743
 744
 745
 746
 747
 748
 749
 750
 751
 752
 753
 754
 755
 756
 757
 758
 759
 760
 761
 762
 763
 764
 765
 766
 767
 768
 769
 770
 771
 772
 773
 774
 775
 776
 777
 778
 779
 780
 781
 782
 783
 784
 785
 786
 787
 788
 789
 790
 791
 792
 793
 794
 795
 796
 797
 798
 799
 800
 801
 802
 803
 804
 805
 806
 807
 808
 809
 810
 811
 812
 813
 814
 815
 816
 817
 818
 819
 820
 821
 822
 823
 824
 825
 826
 827
 828
 829
 830
 831
 832
 833
 834
 835
 836
 837
 838
 839
 840
 841
 842
 843
 844
 845
 846
 847
 848
 849
 850
 851
 852
 853
 854
 855
 856
 857
 858
 859
 860
 861
 862
 863
 864
 865
 866
 867
 868
 869
 870
 871
 872
 873
 874
 875
 876
 877
 878
 879
 880
 881
 882
 883
 884
 885
 886
 887
 888
 889
 890
 891
 892
 893
 894
 895
 896
 897
 898
 899
 900
 901
 902
 903
 904
 905
 906
 907
 908
 909
 910
 911
 912
 913
 914
 915
 916
 917
 918
 919
 920
 921
 922
 923
 924
 925
 926
 927
 928
 929
 930
 931
 932
 933
 934
 935
 936
 937
 938
 939
 940
 941
 942
 943
 944
 945
 946
 947
 948
 949
 950
 951
 952
 953
 954
 955
 956
 957
 958
 959
 960
 961
 962
 963
 964
 965
 966
 967
 968
 969
 970
 971
 972
 973
 974
 975
 976
 977
 978
 979
 980
 981
 982
 983
 984
 985
 986
 987
 988
 989
 990
 991
 992
 993
 994
 995
 996
 997
 998
 999
1000
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
{
* This Source Code Form is subject to the terms of the Mozilla Public License,
* v. 2.0. If a copy of the MPL was not distributed with this file, You can
* obtain one at http://mozilla.org/MPL/2.0/
*
* Copyright (C) 2005-2013, Peter Johnson (www.delphidabbler.com).
*
* $Rev$
* $Date$
*
* Implements a class that is used to generate Pascal source code containing
* specified database snippets.
}
unit USourceGen;
interface
uses
// Delphi
Classes, Generics.Collections,
// Project
ActiveText.UMain, DB.USnippet, UBaseObjects, UIStringList;
type
/// <summary>Enumeration of different styles of commenting used when
/// documenting snippets with their descriptions.</summary>
TCommentStyle = (
csNone, // no documentation of snippets
csAfter, // description of snippet between prototype and body
csBefore // description of snippet immediatly preceeds code
);
type
/// <summary>Static class that provides information about comment styles and
/// which formats comments in the appropriate style.</summary>
TSourceComments = class(TNoConstructObject)
strict private
/// <summary>Formats the given comment text into lines with a fixed
/// maximum width indented by the given number of spaces on the left.
/// </summary>
class function FormatCommentLines(const Text: string;
const Indent: Cardinal): string;
public
/// <summary>Returns a description of the given comment style.</summary>
/// <remarks>The description is in a form uitable for use in the UI.
/// </remarks>
class function CommentStyleDesc(const Style: TCommentStyle): string;
/// <summary>Formats a snippet's descriptive comment as a Pascal comment
/// with to a specified commenting style.</summary>
/// <param name="Style">TCommentStyle [in] Required commenting style.
/// </param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not comment is to be truncated at the end of the first paragraph of
/// multi-paragraph text.</param>
/// <param name="Text">IActiveText [in] Active text of comment.</param>
/// <returns>string.Formatted comment or empty string if Style = csNone.
/// </returns>
class function FormatSnippetComment(const Style: TCommentStyle;
const TruncateComments: Boolean; const Text: IActiveText): string;
/// <summary>Formats document's header text as a Pascal comment.</summary>
/// <param name="Comments">IStringList [in] List of paragraphs of header
/// text.</param>
/// <returns>string. Formatted comments.</returns>
class function FormatHeaderComments(const Comments: IStringList): string;
end;
type
/// <summary>Class that receives snippets for which source is to be
/// generated, determines dependencies and pulls in any required snippets.
/// Data structures are created that can be used to emit source code with all
/// dependencies resolved.</summary>
TSourceAnalyser = class(TObject)
strict private
var
/// <summary>Value of TypesAndConsts property.</summary>
fTypesAndConsts: TObjectList<TSnippet>;
/// <summary>Value of IntfRoutines property.</summary>
fIntfRoutines: TSnippetList;
/// <summary>Value of AllRoutines property.</summary>
fAllRoutines: TSnippetList;
/// <summary>Value of ForwardRoutines property.</summary>
fForwardRoutines: TSnippetList;
/// <summary>Value of RequiredRoutines property.</summary>
fRequiredRoutines: TSnippetList;
/// <summary>Value of Units property.</summary>
fUnits: TStringList;
/// <summary>Adds given user-specified routine to the analysis.</summary>
/// <remarks>Duplicates are ignored.</remarks>
procedure AddIntfRoutine(const Routine: TSnippet);
/// <summary>Adds the given type or constant to the analysis.</summary>
/// <remarks>Duplicates are ignored.</remarks>
procedure AddTypeOrConst(const TypeOrConst: TSnippet);
/// <summary>Adds all snippets in given list to a list of required
/// snippets, according to type.</summary>
procedure RequireSnippets(const Snips: TSnippetList);
/// <summary>Adds given snippet to appropriate list of required snippets,
/// according to type.</summary>
procedure RequireSnippet(const Snippet: TSnippet);
/// <summary>Adds each unit in given list to list of required units.
/// </summary>
/// <remarks>Duplicates are ignored.</remarks>
procedure RequireUnits(const Units: TStringList);
/// <summary>Adds given unit to list of required units.</summary>
/// <remarks>Duplicates are ignored.</remarks>
procedure RequireUnit(const UnitName: string);
/// <summary>Adds given routine, that has not been directly required by
/// user, to the analysis.</summary>
/// <remarks>Duplicates are ignored.</remarks>
procedure RequireRoutine(const Routine: TSnippet);
public
/// <summary>Constructs new object instance.</summary>
constructor Create;
/// <summary>Destroys object instance.</summary>
destructor Destroy; override;
/// <summary>Adds the given user-defined snippet to the analysis.</summary>
/// <remarks>Freeform snippets are ignored.</remarks>
procedure AddSnippet(const Snippet: TSnippet);
/// <summary>Performs analysis and generates data structures that are
/// exposed via the object's properties.</summary>
/// <remarks>Must be called after last snippet has been added to the
/// analysis.</remarks>
procedure Generate;
/// <summary>List of types and constants that have either been added by the
/// user or required by other snippets.</summary>
property TypesAndConsts: TObjectList<TSnippet> read fTypesAndConsts;
/// <summary>List of routines added by the user.</summary>
/// <remarks>These routines are those which would appear in a unit's
/// interface section.</remarks>
property IntfRoutines: TSnippetList read fIntfRoutines;
/// <summary>List of routines that have been required by other snippets.
/// </summary>
property RequiredRoutines: TSnippetList read fRequiredRoutines;
/// <summary>List of all routines, both added and required.</summary>
/// <remarks>Not valid until Generate has been called. Invalidated if
/// further snippets are added without calling Generate again.</remarks>
property AllRoutines: TSnippetList read fAllRoutines;
/// <summary>List of required routines that have not also been added by the
/// user.</summary>
/// <remarks>
/// <para>These routines are those that would appear as 'forward' routines
/// in a unit's implementation section.</para>
/// <para>Not valid until Generate has been called. Invalidated if further
/// snippets are added without calling Generate again.</para>
/// </remarks>
property ForwardRoutines: TSnippetList read fForwardRoutines;
/// <summary>List of required units.</summary>
property Units: TStringList read fUnits;
end;
type
/// <summary>Generates Pascal source code containing all specified snippets
/// along with any other snippets that are required by the specified
/// snippets.</summary>
TSourceGen = class(TObject)
strict private
var
/// <summary>Object that analyses specified snippets and their
/// dependencies.</summary>
fSourceAnalyser: TSourceAnalyser;
public
/// <summary>Constructs new object instance.</summary>
constructor Create;
/// <summary>Destroys object instance.</summary>
destructor Destroy; override;
/// <summary>Includes the given snippet in the source code.</summary>
procedure IncludeSnippet(const Snippet: TSnippet);
/// <summary>Includes all snippets from the given list in the source code.
/// </summary>
procedure IncludeSnippets(const Snips: TSnippetList);
/// <summary>Generates source code of a Pascal unit containing all the
/// specified snippets along with any other snippets that are required to
/// compile the code.</summary>
/// <param name="UnitName">string [in] Name of unit.</param>
/// <param name="CommentStyle">TCommentStyle [in] Style of commenting used
/// in documenting snippets.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not documentation comments are to be truncated at the end of the first
/// paragraph of multi-paragraph text.</param>
/// <param name="HeaderComments">IStringList [in] List of comments to be
/// included at top of unit.</param>
/// <returns>string. Unit source code.</returns>
function UnitAsString(const UnitName: string;
const CommentStyle: TCommentStyle = csNone;
const TruncateComments: Boolean = False;
const HeaderComments: IStringList = nil): string;
/// <summary>Generates source code of a Pascal include file containing all
/// the specified snippets. Also writes comments that note which units,
/// types, consts and other routines are required to compile the specified
/// snippets.</summary>
/// <param name="CommentStyle">TCommentStyle [in] Style of commenting used
/// in documenting snippets.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not documentation comments are to be truncated at the end of the first
/// paragraph of multi-paragraph text.</param>
/// <param name="HeaderComments">IStringList [in] List of comments to be
/// included at top of unit.</param>
/// <returns>string. Source code of include file.</returns>
function IncFileAsString(const CommentStyle: TCommentStyle = csNone;
const TruncateComments: Boolean = False;
const HeaderComments: IStringList = nil): string;
/// <summary>Creates and returns a unit name based on the given file name.
/// </summary>
/// <remarks>
/// <para>The unit name is the base file name with any extension removed.
/// </para>
/// <para>NOTE: not all file names are suitable for creating unit names:
/// use the IsFileNameValidUnitName method to check a file name for
/// validity.</para>
/// </remarks>
class function UnitNameFromFileName(const FileName: string): string;
/// <summary>Checks if the given file name is valid as the basis for a
/// unit name.</summary>
class function IsFileNameValidUnitName(const FileName: string): Boolean;
end;
implementation
uses
// Delphi
SysUtils,
// Project
ActiveText.UTextRenderer, DB.USnippetKind, UConsts, UExceptions, UPreferences,
USnippetValidator, UStrUtils, UWarnings, Hiliter.UPasLexer;
const
/// <summary>Maximum number of characters on a source code line.</summary>
cLineWidth = 80;
const
/// <summary>Size of indenting used for source code, in characters.</summary>
cIndent = 2;
type
/// <summary>Static class that can format a routine to include descriptive
/// comments.</summary>
TRoutineFormatter = class(TNoConstructObject)
strict private
/// <summary>Splits source code of a routine snippet into the head (routine
/// prototype) and body.</summary>
/// <param name="Routine">TSnippet [in] Routine whose source code is to be
/// split.</param>
/// <param name="Head">string [out] Set to routine prototype.</param>
/// <param name="Body">string [out] Body of routine that follows the
/// prototype.</param>
class procedure Split(const Routine: TSnippet; out Head, Body: string);
/// <summary>Creates and returns a comment containing a routine's
/// description.</summary>
/// <param name="CommentStyle">TCommentStyle [in] Required commenting
/// style.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not comment is to be truncated at the end of the first paragraph of
/// multi-paragraph text.</param>
/// <param name="Routine">TSnippet [in] Routine for which comments are to
/// be rendered. Snippet kind must be skRoutine.</param>
/// <returns>string. Formatted comments.</returns>
class function RenderDescComment(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const Routine: TSnippet): string;
public
/// <summary>Extracts and returns the given routine snippet's prototype
/// from its source code.</summary>
class function ExtractPrototype(const Routine: TSnippet): string;
/// <summary>Format's a routine snippet's prototype, including a comment
/// containing its description if required.</summary>
/// <param name="Routine">TSnippet [in] Routine whose prototype is to be
/// formatted. Snippet kind must be skRoutine.</param>
/// <param name="CommentStyle">TCommentStyle [in] Style of commenting to
/// be used for routine's description.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not description comment is to be truncated at the end of the first
/// paragraph of multi-paragraph text.</param>
/// <returns>string. Formatted prototype.</returns>
class function FormatRoutinePrototype(const Routine: TSnippet;
CommentStyle: TCommentStyle; const TruncateComments: Boolean): string;
/// <summary>Formats the whole source code of a routine snippet, including
/// a comment containing its description if required.</summary>
/// <param name="CommentStyle">TCommentStyle [in] Style of commenting to
/// be used for routine's description.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not description comment is to be truncated at the end of the first
/// paragraph of multi-paragraph text.</param>
/// <param name="Routine">TSnippet [in] Routine whose source code is to be
/// formatted.</param>
/// <returns>string. Formatted source code.</returns>
class function FormatRoutine(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const Routine: TSnippet): string;
end;
type
/// <summary>Static class that can format a constant or simple type
/// definition to include descriptive comments.</summary>
TConstAndTypeFormatter = class(TNoConstructObject)
strict private
/// <summary>Splits source code of a constant or simple type snippet into
/// the prefix (text up to 'const' or 'type' and the following definition.
/// </summary>
/// <param name="ConstOrType">TSnippet [in] Constant or simple type snippet
/// whose source code is to be split.</param>
/// <param name="Prefix">string [out] Text up to 'const' or 'type' keyword.
/// </param>
/// <param name="Body">string [out] Remainder of source code without
/// prefix.</param>
class procedure Split(const ConstOrType: TSnippet; out Prefix,
Body: string);
/// <summary>Creates and returns a comment containing a constant or simple
/// type's description.</summary>
/// <param name="CommentStyle">TCommentStyle [in] Required commenting
/// style.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not comment is to be truncated at the end of the first paragraph of
/// multi-paragraph text.</param>
/// <param name="ConstOrType">TSnippet [in] Constant or simple type for
/// which comments are to be rendered. Snippet kind must be skConstant or
/// skTypeDef.</param>
/// <returns>string. Formatted comments.</returns>
class function RenderDescComment(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const ConstOrType: TSnippet): string;
public
/// <summary>Formats the source code of a constant or simple type snippet,
/// including a comment containing its description if required.</summary>
/// <param name="CommentStyle">TCommentStyle [in] Style of commenting to
/// be used for snippet's description.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not description comment is to be truncated at the end of the first
/// paragraph of multi-paragraph text.</param>
/// <param name="ConstOrType">TSnippet [in] Constant or simple type whose
/// source code is to be formatted.</param>
/// <returns>string. Formatted source code.</returns>
class function FormatConstOrType(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const ConstOrType: TSnippet): string;
end;
type
/// <summary>Static class that can format a class or advanced record type
/// definition to include descriptive comments.</summary>
TClassFormatter = class(TNoConstructObject)
strict private
class var
/// <summary>List of directive names that are valid for use as method
/// names.</summary>
fValidDirsInMethodNames: IStringList;
/// <summary>Creates and returns a comment containing a class or advanced
/// record type's description.</summary>
/// <param name="CommentStyle">TCommentStyle [in] Required commenting
/// style.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not comment is to be truncated at the end of the first paragraph of
/// multi-paragraph text.</param>
/// <param name="Snippet">TSnippet [in] Class or advanced record type for
/// which comments are to be rendered.</param>
/// <returns>string. Formatted comments.</returns>
class function RenderDescComment(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const Snippet: TSnippet): string;
/// <summary>Removes any introductory 'type' keyword from a class or
/// advanced record type declaration, if possible.</summary>
/// <param name="Decl">string [in] Type declaration to be processed.
/// </param>
/// <param name="DeclBody">string [out] Source code that follows 'type'
/// keyword if found, otherwise set to Decl.</param>
/// <returns>Boolean. True if 'type' keyword was removed, False if not.
/// </returns>
class function RemoveKeywordFromDecl(const Decl: string;
out DeclBody: string): Boolean;
/// <summary>Parses complete class or advanced record source code and
/// splits declaration from definition.</summary>
/// <param name="Source">string [in] Source code to be parsed.</param>
/// <param name="Decl">string [out] Set to declaration section.</param>
/// <param name="Defn">string [out] Set to definition section.</param>
class procedure SplitDeclFromDefn(const Source: string; out Decl,
Defn: string);
public
/// <summary>Instantiates class objects.</summary>
class constructor Create;
/// <summary>Formats source code of a class or advanced record snippet's
/// declaration, including a comment containing its description if
/// required.</summary>
/// <param name="CommentStyle">TCommentStyle [in] Style of commenting to
/// be used for snippet's description.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not description comment is to be truncated at the end of the first
/// paragraph of multi-paragraph text.</param>
/// <param name="Snippet">TSnippet [in] Class or advanced record type whose
/// declaration is to be formatted.</param>
/// <returns>string. Formatted declaration source code.</returns>
class function FormatClassDeclaration(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const Snippet: TSnippet): string;
/// <summary>Formats source code of a class or advanced record snippet's
/// definition, including a comment containing its description if required.
/// </summary>
/// <param name="CommentStyle">TCommentStyle [in] Style of commenting to
/// be used for snippet's description.</param>
/// <param name="TruncateComments">Boolean [in] Flag indicating whether or
/// not description comment is to be truncated at the end of the first
/// paragraph of multi-paragraph text.</param>
/// <param name="Snippet">TSnippet [in] Class or advanced record type whose
/// definition is to be formatted.</param>
/// <returns>string. Formatted definition source code.</returns>
class function FormatClassDefinition(const Snippet: TSnippet): string;
end;
{ TSourceGen }
constructor TSourceGen.Create;
begin
inherited;
fSourceAnalyser := TSourceAnalyser.Create;
end;
destructor TSourceGen.Destroy;
begin
fSourceAnalyser.Free;
inherited;
end;
function TSourceGen.IncFileAsString(const CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const HeaderComments: IStringList): string;
resourcestring
// Comment text
sReqUnits = 'Required unit(s):';
sReqRoutines = 'Additional required routine(s):';
sReqConstsAndTypes = 'Required constant(s) and / or type(s):';
sXRefRoutines = 'Cross referenced routine(s):';
var
Idx: Integer; // loops thru snippets list
Writer: TStringBuilder; // used to build source code string
ForwardWritten: Boolean; // flag true if forward decls have been written
FirstForward: Boolean; // flag true when first forward decl to be written
Snippet: TSnippet; // accesses various snippet objects
UnitName: string; // accesses unit names from a list
begin
// Generate the unit data
fSourceAnalyser.Generate;
// Create writer used to build source code
Writer := TStringBuilder.Create;
try
// Write header comment
Writer.Append(TSourceComments.FormatHeaderComments(HeaderComments));
// Write required units, additional routines, types and consts
if (fSourceAnalyser.Units.Count > 0) or
(fSourceAnalyser.ForwardRoutines.Count > 0) or
(fSourceAnalyser.TypesAndConsts.Count > 0) then
begin
Writer.AppendLine('{');
if fSourceAnalyser.Units.Count > 0 then
begin
// list of required units
Writer.AppendLine(' ' + sReqUnits);
for UnitName in fSourceAnalyser.Units do
Writer.AppendLine(' ' + UnitName);
end;
if fSourceAnalyser.TypesAndConsts.Count > 0 then
begin
// list of types and consts
if (fSourceAnalyser.Units.Count > 0) then
Writer.AppendLine;
Writer.AppendLine(' ' + sReqConstsAndTypes);
for Snippet in fSourceAnalyser.TypesAndConsts do
Writer.AppendLine(' ' + Snippet.DisplayName);
end;
if fSourceAnalyser.ForwardRoutines.Count > 0 then
begin
// list of other routines required to compile
if (fSourceAnalyser.Units.Count > 0) or
(fSourceAnalyser.TypesAndConsts.Count > 0) then
Writer.AppendLine;
Writer.AppendLine(' ' + sReqRoutines);
for Snippet in fSourceAnalyser.ForwardRoutines do
Writer.AppendLine(' ' + Snippet.DisplayName);
end;
Writer.AppendLine('}');
Writer.AppendLine;
end;
// Write out forward declarations for included routines required by others
FirstForward := True;
ForwardWritten := False;
for Snippet in fSourceAnalyser.IntfRoutines do
begin
if fSourceAnalyser.RequiredRoutines.Contains(Snippet) then
begin
if FirstForward then
begin
Writer.AppendLine('// ' + sXRefRoutines);
FirstForward := False;
end;
Writer.AppendLine(
TRoutineFormatter.FormatRoutinePrototype(Snippet, csNone, False)
);
Writer.AppendLine(' forward;');
ForwardWritten := True;
end;
end;
if ForwardWritten then
Writer.AppendLine;
// Write routines
for Idx := 0 to Pred(fSourceAnalyser.IntfRoutines.Count) do
begin
Snippet := fSourceAnalyser.IntfRoutines[Idx];
Writer.AppendLine(
TRoutineFormatter.FormatRoutine(CommentStyle, TruncateComments, Snippet)
);
if Idx < Pred(fSourceAnalyser.IntfRoutines.Count) then
Writer.AppendLine;
end;
// Return string containing source code
Result := Writer.ToString;
finally
Writer.Free;
end;
end;
procedure TSourceGen.IncludeSnippet(const Snippet: TSnippet);
begin
fSourceAnalyser.AddSnippet(Snippet);
end;
procedure TSourceGen.IncludeSnippets(const Snips: TSnippetList);
var
Snippet: TSnippet; // iterates through snippets to be added
begin
for Snippet in Snips do
IncludeSnippet(Snippet);
end;
class function TSourceGen.IsFileNameValidUnitName(const FileName: string):
Boolean;
begin
Result := IsValidIdent(UnitNameFromFileName(FileName));
end;
function TSourceGen.UnitAsString(const UnitName: string;
const CommentStyle: TCommentStyle = csNone;
const TruncateComments: Boolean = False;
const HeaderComments: IStringList = nil): string;
var
Writer: TStringBuilder; // used to build source code string
Snippet: TSnippet; // reference to a snippet object
Warnings: IWarnings; // object giving info about any inhibited warnings
begin
// Generate the unit data
fSourceAnalyser.Generate;
// Create writer object onto string stream that receives output
Writer := TStringBuilder.Create;
try
// Write unit
// heading comment
Writer.Append(TSourceComments.FormatHeaderComments(HeaderComments));
// unit name
Writer.AppendFormat('unit %s;', [UnitName]).AppendLine;
Writer.AppendLine;
// any conditional compilation symbols
Warnings := Preferences.Warnings;
if Warnings.Enabled and not Warnings.IsEmpty then
begin
Writer.Append(Warnings.Render);
Writer.AppendLine;
end;
// open interface section
Writer.AppendLine('interface');
Writer.AppendLine;
// uses statement
if fSourceAnalyser.Units.Count > 0 then
begin
Writer.AppendLine('uses');
Writer.AppendLine(
StrWrap(
StrJoin(fSourceAnalyser.Units, ', ') + ';',
cLineWidth - cIndent,
cIndent
)
);
Writer.AppendLine;
end;
// consts and types
for Snippet in fSourceAnalyser.TypesAndConsts do
begin
case Snippet.Kind of
skTypeDef, skConstant:
Writer.AppendLine(
TConstAndTypeFormatter.FormatConstOrType(
CommentStyle, TruncateComments, Snippet
)
);
skClass:
Writer.AppendLine(
TClassFormatter.FormatClassDeclaration(
CommentStyle, TruncateComments, Snippet
)
);
end;
Writer.AppendLine;
end;
// routine prototypes
for Snippet in fSourceAnalyser.IntfRoutines do
begin
Writer.AppendLine(
TRoutineFormatter.FormatRoutinePrototype(
Snippet, CommentStyle, TruncateComments
)
);
Writer.AppendLine;
end;
// open implementation section
Writer.AppendLine('implementation');
Writer.AppendLine;
// forward declarations
if fSourceAnalyser.ForwardRoutines.Count > 0 then
begin
for Snippet in fSourceAnalyser.ForwardRoutines do
begin
Writer.AppendLine(TRoutineFormatter.ExtractPrototype(Snippet));
Writer.AppendLine(' forward;');
end;
Writer.AppendLine;
end;
// routine source code
for Snippet in fSourceAnalyser.AllRoutines do
begin
Writer.AppendLine(
TRoutineFormatter.FormatRoutine(CommentStyle, TruncateComments, Snippet)
);
Writer.AppendLine;
end;
for Snippet in fSourceAnalyser.TypesAndConsts do
begin
if Snippet.Kind = skClass then
begin
Writer.AppendLine(TClassFormatter.FormatClassDefinition(Snippet));
Writer.AppendLine;
end;
end;
// close unit
Writer.AppendLine('end.');
// Return string built in string stream
Result := Writer.ToString;
finally
Writer.Free;
end;
end;
class function TSourceGen.UnitNameFromFileName(const FileName: string): string;
var
BaseFileName: string; // base file name (i.e. file name without path)
Ext: string; // file's extension
begin
BaseFileName := ExtractFileName(FileName);
Ext := ExtractFileExt(FileName);
Result := StrSliceLeft(BaseFileName, Length(BaseFileName) - Length(Ext));
end;
{ TSourceAnalyser }
procedure TSourceAnalyser.AddIntfRoutine(const Routine: TSnippet);
begin
Assert(Routine.Kind = skRoutine,
ClassName + '.AddIntfRoutine: Routine must have kind skRoutine');
if not fIntfRoutines.Contains(Routine) then
begin
fIntfRoutines.Add(Routine); // add to user-specified list
RequireUnits(Routine.Units); // add all routine's required units
RequireSnippets(Routine.Depends); // add all routine's required snippets
end;
end;
procedure TSourceAnalyser.AddSnippet(const Snippet: TSnippet);
var
ErrorMsg: string; // any error message
begin
// NOTE: this method must not be called from any other method of this class
// Validate the snippet
if not TSnippetValidator.Validate(Snippet, ErrorMsg) then
raise ECodeSnip.Create(ErrorMsg);
// Process the snippet
case Snippet.Kind of
skRoutine:
AddIntfRoutine(Snippet);
skTypeDef, skConstant:
AddTypeOrConst(Snippet);
skFreeform:
{Ignore};
skUnit:
{Ignore};
skClass:
AddTypeOrConst(Snippet);
end;
end;
procedure TSourceAnalyser.AddTypeOrConst(const TypeOrConst: TSnippet);
var
ErrorMsg: string; // any error message
begin
Assert(Assigned(TypeOrConst), ClassName + '.Add: ConstOrType in nil');
Assert(TypeOrConst.Kind in [skTypeDef, skConstant, skClass],
ClassName + '.Add: ConstOrType.Kind is not valid');
// Ignore if already in list
if fTypesAndConsts.Contains(TypeOrConst) then
Exit;
// Validate dependency list
if not TSnippetValidator.ValidateDependsList(TypeOrConst, ErrorMsg) then
raise ECodeSnip.Create(ErrorMsg);
// Add all required snippets to list before adding this one: this ensures
// required snippets preceed those that depend on them
RequireSnippets(TypeOrConst.Depends);
RequireUnits(TypeOrConst.Units);
fTypesAndConsts.Add(TypeOrConst)
end;
constructor TSourceAnalyser.Create;
begin
inherited;
fTypesAndConsts := TObjectList<TSnippet>.Create(False);
fIntfRoutines := TSnippetList.Create;
fAllRoutines := TSnippetList.Create;
fForwardRoutines := TSnippetList.Create;
fRequiredRoutines := TSnippetList.Create;
fUnits := TStringList.Create;
end;
destructor TSourceAnalyser.Destroy;
begin
fTypesAndConsts.Free;
fIntfRoutines.Free;
fAllRoutines.Free;
fForwardRoutines.Free;
fRequiredRoutines.Free;
fUnits.Free;
inherited;
end;
procedure TSourceAnalyser.Generate;
var
Routine: TSnippet; // iterates through various routine lists
begin
fForwardRoutines.Clear;
fAllRoutines.Clear;
// Build forward routines list
for Routine in fRequiredRoutines do
if not fIntfRoutines.Contains(Routine)
and not fForwardRoutines.Contains(Routine) then
fForwardRoutines.Add(Routine);
// Build all routines list
for Routine in fIntfRoutines do
fAllRoutines.Add(Routine);
for Routine in fForwardRoutines do
fAllRoutines.Add(Routine);
end;
procedure TSourceAnalyser.RequireRoutine(const Routine: TSnippet);
begin
if not fRequiredRoutines.Contains(Routine) then
begin
fRequiredRoutines.Add(Routine); // add routine to required list
RequireUnits(Routine.Units); // add all routine's required unit
RequireSnippets(Routine.Depends); // require all snippets in depends list
end;
end;
procedure TSourceAnalyser.RequireSnippet(const Snippet: TSnippet);
resourcestring
// Error message
sCantDependOnFreeform = 'Can''t depend on "%s" - it is freeform code';
begin
case Snippet.Kind of
skRoutine: // require routine
RequireRoutine(Snippet);
skConstant, skTypeDef, skClass: // add type/const allowing for dependencies
AddTypeOrConst(Snippet);
skFreeform: // can't require a freeform snippet
raise ECodeSnip.CreateFmt(sCantDependOnFreeform, [Snippet.DisplayName]);
end;
end;
procedure TSourceAnalyser.RequireSnippets(const Snips: TSnippetList);
var
Snippet: TSnippet; // iterates through snippets list
begin
for Snippet in Snips do
RequireSnippet(Snippet);
end;
procedure TSourceAnalyser.RequireUnit(const UnitName: string);
begin
if fUnits.IndexOf(UnitName) = -1 then
fUnits.Add(UnitName);
end;
procedure TSourceAnalyser.RequireUnits(const Units: TStringList);
var
UnitName: string; // iterates through list of units.
begin
for UnitName in Units do
RequireUnit(UnitName);
end;
{ TRoutineFormatter }
class function TRoutineFormatter.ExtractPrototype(const
Routine: TSnippet): string;
var
DummyBody: string; // stores unused routine body retrieved from Split
begin
Split(Routine, Result, DummyBody);
Result := StrTrim(Result);
end;
class function TRoutineFormatter.FormatRoutine(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const Routine: TSnippet): string;
var
Prototype, Body: string; // prototype and body of routine
begin
Assert(Routine.Kind = skRoutine,
ClassName + '.FormatRoutine: Routine must have kind skRoutine');
case CommentStyle of
csAfter:
begin
// Format is: routine prototype - comment - routine body
Split(Routine, Prototype, Body);
Result := StrTrim(Prototype)
+ EOL
+ RenderDescComment(CommentStyle, TruncateComments, Routine)
+ EOL
+ StrTrim(Body);
end;
csBefore:
// Format is: comment - routine
Result := RenderDescComment(CommentStyle, TruncateComments, Routine)
+ EOL
+ StrTrim(Routine.SourceCode);
else
// No commenting: just return source code
Result := StrTrim(Routine.SourceCode);
end;
end;
class function TRoutineFormatter.FormatRoutinePrototype(const Routine: TSnippet;
CommentStyle: TCommentStyle; const TruncateComments: Boolean): string;
var
Prototype: string; // prototype of given routine
begin
Assert(Routine.Kind = skRoutine,
ClassName + '.FormatRoutinePrototype: Routine must have kind skRoutine');
// Get prototype
Prototype := ExtractPrototype(Routine);
// Write comment depending on style
case CommentStyle of
csAfter:
// comments follow prototype
Result := Prototype
+ EOL
+ RenderDescComment(CommentStyle, TruncateComments, Routine);
csBefore:
// comments preceed prototype
Result := RenderDescComment(CommentStyle, TruncateComments, Routine)
+ EOL
+ Prototype;
else
// no comments: just return prototype
Result := Prototype;
end;
end;
class function TRoutineFormatter.RenderDescComment(
CommentStyle: TCommentStyle; const TruncateComments: Boolean;
const Routine: TSnippet): string;
begin
Assert(Routine.Kind = skRoutine,
ClassName + '.RenderDescComment: Routine must have kind skRoutine');
// Format the output
Result := TSourceComments.FormatSnippetComment(
CommentStyle, TruncateComments, Routine.Description
);
end;
class procedure TRoutineFormatter.Split(const Routine: TSnippet; out Head,
Body: string);
// Checks if given symbol is a calling convention directive.
function IsDirective(const Symbol: string): Boolean;
const
// list of calling convention directives
cCallConventions: array[0..4] of string = (
'register', 'pascal', 'cdecl', 'stdcall', 'safecall'
);
var
ConventionList: IStringList; // list of calling conventions
begin
ConventionList := TIStringList.Create(cCallConventions);
ConventionList.CaseSensitive := False;
Result := ConventionList.Contains(Symbol);
end;
var
SourceCode: string; // routine's source code
StartParam: Integer; // possible position of start of any parameters
AfterParams: Integer; // possible position of end of any parameters
EndDeclaration: Integer; // position of end of routine declaration
StartCodeBody: Integer; // position of start of body of routine
SemiColonPos: Integer; // position of a semi colon
Fragment: string; // a fragment of code
const
cOverload = 'overload'; // overload directive
begin
// Record code without any surrounding white space
SourceCode := StrTrim(Routine.SourceCode);
// Find relative positions of first key characters
StartParam := StrPos('(', SourceCode);
AfterParams := StrPos(')', SourceCode) + 1;
SemiColonPos := StrPos(';', SourceCode);
// Determine end of head section
if SemiColonPos > StartParam then
begin
// semi colon after param => we have params: skip them before looking for
// ending ';'
EndDeclaration := StrPos(
';',
Copy(SourceCode, AfterParams, Length(SourceCode) - AfterParams + 1)
) + AfterParams - 1;
end
else
begin
// semi colon before "params" => no params and ';' ends header
EndDeclaration := SemiColonPos;
end;
// Look for directives that are part of prototype
// first look for calling conventions
SemiColonPos := StrPos(';', SourceCode, EndDeclaration + 1);
if SemiColonPos > 0 then
begin
Fragment := StrTrim(
Copy(SourceCode, EndDeclaration + 1, SemiColonPos - EndDeclaration - 1)
);
if IsDirective(Fragment) then
EndDeclaration := SemiColonPos + 1;
end;
// now look for 'overload' directive
SemiColonPos := StrPos(';', SourceCode, EndDeclaration + 1);
if SemiColonPos > 0 then
begin
Fragment := StrTrim(
Copy(SourceCode, EndDeclaration + 1, SemiColonPos - EndDeclaration - 1)
);
if StrToLower(Fragment) = cOverload then
EndDeclaration := SemiColonPos + 1;
end;
// Record declaration (i.e. prototype)
Head := Copy(SourceCode, 1, EndDeclaration);
// Get code body
StartCodeBody := EndDeclaration + 1;
Body := StrTrim(Copy(SourceCode, StartCodeBody, MaxInt));
end;
{ TConstAndTypeFormatter }
class function TConstAndTypeFormatter.FormatConstOrType(
CommentStyle: TCommentStyle; const TruncateComments: Boolean;
const ConstOrType: TSnippet): string;
var
Keyword: string; // keyword that preceeds source code body
Body: string; // source code that follows keyword
begin
Assert(ConstOrType.Kind in [skConstant, skTypeDef],
ClassName + '.FormatConstOrType: ConstOrType must have kind skTypeDef or '
+ 'skConstant');
Result := '';
case CommentStyle of
csNone:
Result := StrTrim(ConstOrType.SourceCode);
csBefore:
Result := RenderDescComment(CommentStyle, TruncateComments, ConstOrType)
+ EOL
+ StrTrim(ConstOrType.SourceCode);
csAfter:
begin
Split(ConstOrType, Keyword, Body);
if Keyword <> '' then
Result := Keyword
+ EOL
+ RenderDescComment(CommentStyle, TruncateComments, ConstOrType)
+ EOL
+ Body
else
Result := ConstOrType.SourceCode;
end;
end;
end;
class function TConstAndTypeFormatter.RenderDescComment(
CommentStyle: TCommentStyle; const TruncateComments: Boolean;
const ConstOrType: TSnippet): string;
begin
Assert(ConstOrType.Kind in [skConstant, skTypeDef],
ClassName + '.RenderDescComment: ConstOrType must have kind skTypeDef or '
+ 'skConstant');
Result := TSourceComments.FormatSnippetComment(
CommentStyle, TruncateComments, ConstOrType.Description
);
end;
class procedure TConstAndTypeFormatter.Split(const ConstOrType: TSnippet;
out Prefix, Body: string);
// Splits the given source code and the first occurence of keyword KW,
// returning the code before the keyword in Prefix and the code following the
// keyword in Body. If KW is not found, Prefix is set to the empty string and
// Body is set to SourceCode.
procedure SplitAtKeyword(const SourceCode, KW: string;
out Prefix, Body: string);
var
Lexer: THilitePasLexer; // parses Pascal code
PrefixCode: TStringBuilder; // records prefix code
const
SkipTokens = [tkComment, tkCompilerDir, tkWhitespace, tkEOL];
WhiteSpaceTokens = [tkWhitespace, tkEOL];
resourcestring
sTypeKwdError = '"%s" must be first keyword in source code';
begin
Lexer := THilitePasLexer.Create(SourceCode);
try
PrefixCode := TStringBuilder.Create;
try
while Lexer.NextToken in SkipTokens do
PrefixCode.Append(Lexer.TokenStr);
if (Lexer.Token = tkKeyword) and StrSameText(Lexer.TokenStr, KW) then
begin
PrefixCode.Append(Lexer.TokenStr);
Prefix := StrTrimRight(PrefixCode.ToString);
while Lexer.NextToken in WhiteSpaceTokens do
PrefixCode.Append(Lexer.TokenStr);
Body := ' ' +
StrTrim(
StrSliceRight(
SourceCode, Length(SourceCode) - Length(PrefixCode.ToString)
)
);
end
else
begin
Prefix := '';
Body := SourceCode;
end;
finally
PrefixCode.Free;
end;
finally
Lexer.Free;
end;
end;
begin
if ConstOrType.Kind = skConstant then
SplitAtKeyword(ConstOrType.SourceCode, 'const', Prefix, Body)
else // if ConstOrType.Kind = skTypeDef
SplitAtKeyword(ConstOrType.SourceCode, 'type', Prefix, Body)
end;
{ TSourceComments }
class function TSourceComments.CommentStyleDesc(
const Style: TCommentStyle): string;
resourcestring
// Comment style descriptions
sCSNone = 'No descriptive comments';
sCSAfter = 'Comments after snippet header';
sCSBefore = 'Comments before snippet';
const
// Map of comment styles to descriptions
sDescriptions: array[TCommentStyle] of string = (
sCSNone, sCSAfter, sCSBefore
);
begin
Result := sDescriptions[Style];
end;
class function TSourceComments.FormatCommentLines(const Text: string;
const Indent: Cardinal): string;
var
Lines: TStringList;
begin
Lines := TStringList.Create;
try
Lines.Text := Text;
Result := StrTrimRight(StrWrap(Lines, cLineWidth - Indent, Indent, False));
finally
Lines.Free;
end;
end;
class function TSourceComments.FormatHeaderComments(
const Comments: IStringList): string;
var
Line: string; // loops thru each line of comments & exploded comments
Lines: IStringList; // comments after exploding multiple wrapped lines
const
cLinePrefix = ' * '; // prefixes each comment line
begin
// Only create comment if some comment text is provided
if Assigned(Comments) and (Comments.Count > 0) then
begin
// text wrap each line of comments and exploded into separate lines
Lines := TIStringList.Create;
for Line in Comments do
if Length(Line) > 0 then
Lines.Add(
StrWrap(Line, cLineWidth - Length(cLinePrefix), 0), EOL, True
)
else
Lines.Add('');
Result := '{';
// write out each comment line
for Line in Lines do
Result := Result + EOL + cLinePrefix + Line;
Result := Result + EOL + '}' + EOL2;
end
else
Result := '';
end;
class function TSourceComments.FormatSnippetComment(const Style: TCommentStyle;
const TruncateComments: Boolean; const Text: IActiveText): string;
var
Renderer: TActiveTextTextRenderer;
PlainText: string;
Lines: IStringList;
begin
Renderer := TActiveTextTextRenderer.Create;
try
Renderer.DisplayURLs := False;
PlainText := Renderer.Render(Text);
if TruncateComments then
begin
// use first non-empty paragraph of Text as comment
Lines := TIStringList.Create(PlainText, string(sLineBreak), False);
if Lines.Count > 0 then
PlainText := Lines[0];
end;
case Style of
csNone:
Result := '';
csBefore:
Result := '{'
+ EOL
+ FormatCommentLines(PlainText, cIndent)
+ EOL
+ '}';
csAfter:
Result := FormatCommentLines(
'{' + PlainText + '}', cIndent
);
end;
finally
Renderer.Free;
end;
end;
{ TClassFormatter }
class constructor TClassFormatter.Create;
begin
// record names of directives that are valid for use as method names
fValidDirsInMethodNames := TIStringList.Create([
'absolute', 'abstract', 'assembler', 'cdecl', 'contains', 'default',
'delayed', 'deprecated', 'dispid', 'dynamic', 'experimental', 'export',
'external', 'far', 'final', 'forward', 'helper', 'implements', 'index',
'local', 'message', 'name', 'near', 'nodefault', 'overload', 'override',
'package', 'pascal', 'platform', 'read', 'readonly', 'reference',
'register', 'reintroduce', 'requires', 'resident', 'safecall', 'sealed',
'static', 'stdcall', 'stored', 'strict', 'unsafe', 'varargs', 'virtual',
'winapi', 'write', 'writeonly'
]);
fValidDirsInMethodNames.CaseSensitive := False;
fValidDirsInMethodNames.Sort;
end;
class function TClassFormatter.FormatClassDeclaration(
CommentStyle: TCommentStyle; const TruncateComments: Boolean;
const Snippet: TSnippet): string;
var
Dummy: string;
Decl: string;
DeclBody: string;
begin
SplitDeclFromDefn(Snippet.SourceCode, Decl, Dummy);
Decl := StrTrim(Decl);
case CommentStyle of
csNone:
Result := StrTrim(Decl);
csBefore:
Result := RenderDescComment(CommentStyle, TruncateComments, Snippet)
+ EOL
+ StrTrim(Decl);
csAfter:
begin
if RemoveKeywordFromDecl(Decl, DeclBody) then
Result := 'type'
+ EOL
+ RenderDescComment(CommentStyle, TruncateComments, Snippet)
+ EOL
+ DeclBody
else
Result := Decl;
end;
end;
end;
class function TClassFormatter.FormatClassDefinition(const Snippet: TSnippet):
string;
var
Dummy: string;
begin
SplitDeclFromDefn(Snippet.SourceCode, Dummy, Result);
end;
class function TClassFormatter.RemoveKeywordFromDecl(const Decl: string;
out DeclBody: string): Boolean;
const
Keyword = 'type';
begin
Result := StrStartsStr(Keyword, Decl);
if Result then
DeclBody := ' ' + StrTrim(Copy(Decl, Length(Keyword) + 1, MaxInt))
else
// "type" not found - can't remove
DeclBody := Decl;
end;
class function TClassFormatter.RenderDescComment(CommentStyle: TCommentStyle;
const TruncateComments: Boolean; const Snippet: TSnippet): string;
begin
Result := TSourceComments.FormatSnippetComment(
CommentStyle, TruncateComments, Snippet.Description
);
end;
class procedure TClassFormatter.SplitDeclFromDefn(const Source: string;
out Decl, Defn: string);
var
Lexer: THilitePasLexer;
SB: TStringBuilder;
ClassTypeName: string;
Temp: string;
const
WhiteSpaceTokens = [tkComment, tkCompilerDir, tkWhitespace, tkEOL];
/// <summary>Checks if given token is one of the keywords used to introduce
/// a method.</summary>
function IsMethodKwd(const Tok: string): Boolean;
begin
Result := StrSameText(Lexer.TokenStr, 'function')
or StrSameText(Lexer.TokenStr, 'procedure')
or StrSameText(Lexer.TokenStr, 'constructor')
or StrSameText(Lexer.TokenStr, 'destructor')
or StrSameText(Lexer.TokenStr, 'operator');
end;
/// <summary>Checks if current lexer token can represent a method name.
/// </summary>
function IsMethodName(const Lexer: THilitePasLexer): Boolean;
begin
// Either an identifier or one of a certain number of directives can be used
// as the name of a method.
if Lexer.Token = tkIdentifier then
Exit(True);
Result := (Lexer.Token = tkDirective)
and fValidDirsInMethodNames.Contains(Lexer.TokenStr);
end;
resourcestring
sTypeKwdError = '"type" must be first keyword in source code';
sClassTypeNameError = 'Class type name expected in source code';
sBadTypeError = 'Invalid class or advanced record type';
sImplementationKwdError = '"implementation" keyword not permitted in class '
+ 'or advanced record snippets.';
begin
Lexer := THilitePasLexer.Create(Source);
try
SB := TStringBuilder.Create;
try
// skip any leading white space and comments to first Pascal token
// this must be "type" keyword
while Lexer.NextToken in WhiteSpaceTokens do
SB.Append(Lexer.TokenStr);
if (Lexer.Token <> tkKeyword)
and not StrSameText(Lexer.TokenStr, 'type') then
raise ECodeSnip.Create(sTypeKwdError);
SB.Append(Lexer.TokenStr);
// get name of class from following identifier
while Lexer.NextToken in WhiteSpaceTokens do
SB.Append(Lexer.TokenStr);
if Lexer.Token <> tkIdentifier then
raise ECodeSnip.Create(sClassTypeNameError);
ClassTypeName := Lexer.TokenStr;
SB.Append(Lexer.TokenStr);
while True do
begin
// look for methods
while not (Lexer.NextToken in [tkKeyword, tkEOF])
and not (
IsMethodKwd(Lexer.TokenStr)
or StrSameText(Lexer.TokenStr, 'class')
or StrSameText(Lexer.TokenStr, 'implementation')
) do
SB.Append(Lexer.TokenStr);
if Lexer.Token = tkEOF then
raise ECodeSnip.Create(sBadTypeError);
if (Lexer.Token = tkKeyword)
and StrSameText(Lexer.TokenStr, 'implementation') then
raise ECodeSnip.Create(sImplementationKwdError);
// check if function is followed by ClassTypeName and a dot => start
// of declaration
Temp := '';
if not IsMethodKwd(Lexer.TokenStr) then
begin
// token is not method keyword - we're only interested if token is
// "class" so record token and go round again if not "class"
if not StrSameText(Lexer.TokenStr, 'class') then
begin
SB.Append(Lexer.TokenStr);
Continue;
end;
// must have "class" keyword: record it
Temp := Lexer.TokenStr;
// skip whitespace following "class"
while Lexer.NextToken in WhiteSpaceTokens do
Temp := Temp + Lexer.TokenStr;
// now look for one of method keywords that can follow "class"
if (Lexer.Token <> tkKeyword) and not IsMethodKwd(Lexer.TokenStr) then
begin
// didn't find method: record text read and go round again
SB.Append(Temp); // "class" and whitespace
SB.Append(Lexer.TokenStr); // token after whitespace
Continue;
end;
end;
// record method name
Temp := Temp + Lexer.TokenStr;
// record following white space
while Lexer.NextToken in WhiteSpaceTokens do
Temp := Temp + Lexer.TokenStr;
// record item after white space
Temp := Temp + Lexer.TokenStr;
if not IsMethodName(Lexer)
or not StrSameText(Lexer.TokenStr, ClassTypeName) then
begin
// not the required identifier: record text and go round again
SB.Append(Temp);
Continue;
end;
// check for following '.'
while Lexer.NextToken in WhiteSpaceTokens do
Temp := Temp + Lexer.TokenStr;
Temp := Temp + Lexer.TokenStr;
if (Lexer.Token <> tkSymbol) or (Lexer.TokenStr <> '.') then
begin
SB.Append(Temp);
Continue;
end;
// check for following identifier
while Lexer.NextToken in WhiteSpaceTokens do
Temp := Temp + Lexer.TokenStr;
Temp := Temp + Lexer.TokenStr;
if not IsMethodName(Lexer) then
begin
SB.Append(Temp);
Continue;
end;
Break;
end;
// Lexer replaces CRLF with LF, but we need CRLF to keep string length
// same as original so that string slice below works
Decl := StrReplace(SB.ToString, LF, CRLF);
finally
SB.Free;
end;
Defn := StrSliceRight(Source, Length(Source) - Length(Decl));
finally
Lexer.Free;
end;
end;
end.
Want the latest updates on software, tech news, and AI?
Get latest updates about software, tech news, and AI from SourceForge directly in your inbox once a month.