Menu

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

Download this file

1371 lines (1278 with data), 45.9 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
{
* USourceGen.pas
*
* Implements a class that is used to generate Pascal source code containing
* specified database snippets.
*
* Originally named UUnitGen.pas. Renamed as USourceGen.pas as at v0.4.
*
* $Rev$
* $Date$
*
* ***** BEGIN LICENSE BLOCK *****
*
* Version: MPL 1.1
*
* The contents of this file are subject to the Mozilla Public License Version
* 1.1 (the "License"); you may not use this file except in compliance with the
* License. You may obtain a copy of the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS IS" basis,
* WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
* the specific language governing rights and limitations under the License.
*
* The Original Code is USourceGen.pas
*
* The Initial Developer of the Original Code is Peter Johnson
* (http://www.delphidabbler.com/).
*
* Portions created by the Initial Developer are Copyright (C) 2005-2012 Peter
* Johnson. All Rights Reserved.
*
* Contributor(s)
* NONE
*
* ***** END LICENSE BLOCK *****
}
unit USourceGen;
interface
uses
// Delphi
Classes, Generics.Collections,
// Project
ActiveText.UMain, DB.USnippet, UBaseObjects, UIStringList;
type
{
TCommentStyle:
Different styles of commenting used when documenting snippets from database.
}
TCommentStyle = (
csNone, // no documentation of snippets
csAfter, // description of snippet between prototype and body
csBefore // description of snippet immediatly preceeds code
);
{
TSourceComments:
Static class that provides information about comment styles and formats
comments in appropriate style.
}
TSourceComments = class(TNoConstructObject)
strict private
class function FormatCommentLines(const Text: string;
const Indent: Cardinal): string;
public
class function CommentStyleDesc(const Style: TCommentStyle): string;
{Gets description of a comment style for use in UI elements.
@param Style [in] Comment style for which description wanted.
@return Required description.
}
class function FormatSnippetComment(const Style: TCommentStyle;
const Text: IActiveText): string;
{Formats a snippet's comment text as Pascal comment according to
commenting style.
@param Style [in] Desired commenting style.
@param Text [in] Active text of comment. Ignored if Style = csNone.
@return Formatted comment. Empty string if Style = csNone.
}
class function FormatHeaderComments(const Comments: IStringList): string;
{Formats header comment text as Pascal comments.
@param Comments [in] List of comments to format.
@return Formatted comments.
}
end;
{
TSourceAnalyser:
Class that receives snippets for which source is to be generated and
analyses relationships, pulling in any required snippets. Creates data
structures that can be used to emit source code with all dependencies
resolved.
}
TSourceAnalyser = class(TObject)
strict private
var
fTypesAndConsts: TObjectList<TSnippet>; // Value of TypesAndConsts prop
fIntfRoutines: TSnippetList; // Value of IntfRoutines property
fAllRoutines: TSnippetList; // Value of AllRoutines property
fForwardRoutines: TSnippetList; // Value of ForwardRoutines property
fRequiredRoutines: TSnippetList; // Value of RequiredRoutines property
fUnits: TStringList; // Value of Units property
procedure AddIntfRoutine(const Routine: TSnippet);
{Adds a user-specified routine to list of routines specified by user.
Duplicates ignored.
@param Routine [in] Routine to be added.
}
procedure AddTypeOrConst(const TypeOrConst: TSnippet);
{Adds a user specified or required type or constant to the analysis.
@param TypeOrConst [in] Type of constant snippet to be added.
}
procedure RequireSnippets(const Snips: TSnippetList);
{Process all snippets in a dependency list.
@param Snips [in] List of snippets to process.
}
procedure RequireSnippet(const Snippet: TSnippet);
{Process a snippet from a dependency list.
@param Snippet [in] Snippet to be processed.
@except Exception raised if attempt is made to require freeform snippet
}
procedure RequireUnits(const Units: TStringList);
{Adds a list of units to required units list. Duplicates ignored.
@param Units [in] List of units.
}
procedure RequireUnit(const UnitName: string);
{Add a unit to list of required units. Duplicates ignored.
@param UnitName [in] Name of required unit.
}
procedure RequireRoutine(const Routine: TSnippet);
{Adds a routine from a dependency list to the analysis. Duplicates
ignored.
@param Routine [in] Routine to be added.
}
public
constructor Create;
{Constructor. Sets up object.
}
destructor Destroy; override;
{Destructor. Tears down object.
}
procedure AddSnippet(const Snippet: TSnippet);
{Adds a user-specified snippet to the analysis. Freeform snippets are
ignored.
@param Snippet [in] Snippet to be added.
}
procedure Generate;
{Generates the analysis.
}
property TypesAndConsts: TObjectList<TSnippet> read fTypesAndConsts;
{List of both added and required Types and constants, in required order}
property IntfRoutines: TSnippetList read fIntfRoutines;
{List of routines added by user. These routines would appear in a unit's
interface section}
property RequiredRoutines: TSnippetList read fRequiredRoutines;
{List of routines required by other snippets, i.e. that appear in
dependency lists}
property AllRoutines: TSnippetList read fAllRoutines;
{List of all routines, both added and required. Not valid until Generate
method called. Invalidated if further snippets added}
property ForwardRoutines: TSnippetList read fForwardRoutines;
{List of required routines that are not also specified by user. These
routines would appear in forward section of a unit. Not valid until
Generate method called. Invalidated if further snippets added}
property Units: TStringList read fUnits;
{List of units required by any snippet}
end;
{
TSourceGen:
Generates Pascal source containing specified snippets and any other snippets
that are required.
}
TSourceGen = class(TObject)
strict private
var
fSourceAnalyser: TSourceAnalyser; // Analyses snippets and dependencies
public
constructor Create;
{Constructor. Sets up the object.
}
destructor Destroy; override;
{Destructor. Tears down object.
}
procedure IncludeSnippet(const Snippet: TSnippet);
{Includes a snippet in the source code.
@param Routine [in] Snippet to be included.
}
procedure IncludeSnippets(const Snips: TSnippetList);
{Includes a one or more snippets in the source code.
@param Routines [in] List of snippets to be included.
}
function UnitAsString(const UnitName: string;
const CommentStyle: TCommentStyle = csNone;
const HeaderComments: IStringList = nil): string;
{Generates source code of a unit containing all specified snippets and
any additional snippets depended upon by the included snippets.
@param UnitName [in] Name of unit.
@param CommentStyle [in] Style of commenting used in documenting
snippets.
@param HeaderComments [in] List of comments to be included at top of
unit.
@return Unit source code.
}
function IncFileAsString(const CommentStyle: TCommentStyle = csNone;
const HeaderComments: IStringList = nil): string;
{Generates source code of an include file containing all specified
routines and notes in comments which units, types, consts and other
routines are also required.
@param CommentStyle [in] Style of commenting used in documenting
snippets.
@param HeaderComments [in] List of comments to be included at top of
snippet.
@return Source code of include file.
}
class function UnitNameFromFileName(const FileName: string): string;
{Creates a unit name from a file name. The unit name is the base file name
with any extension removed.
@param FileName [in] Name of file.
@return Unit name.
}
class function IsFileNameValidUnitName(const FileName: string): Boolean;
{Checks if a file name is valid as basis for a unit name.
@param FileName [in] Name of file to be checked.
@return True if file name is valid for unit name, false if not.
}
end;
implementation
uses
// Delphi
SysUtils,
// Project
ActiveText.UTextRenderer, DB.USnippetKind, UConsts, UExceptions, UPreferences,
USnippetValidator, UStrUtils, UWarnings, Hiliter.UPasLexer;
const
cLineWidth = 80; // max characters on line
cIndent = 2; // indent size
type
{
TRoutineFormatter:
Static class that can format a routine to include descriptive comments at
required position.
}
TRoutineFormatter = class(TNoConstructObject)
strict private
class procedure Split(const Routine: TSnippet; out Head, Body: string);
{Splits source code of a routine into the head (routine prototype) and
body code.
@param Routine [in] Routine whose source code to be split.
@param Head [out] Routine prototype.
@param Body [out] Remainder of routine without prototype.
}
class function RenderDescComment(CommentStyle: TCommentStyle;
const Routine: TSnippet): string;
{Creates comment in required style that contains routine's description.
@param CommentStyle [in] Required commenting style.
@param Routine [in] Routine for which comments required.
@return Formatted comments.
}
public
class function ExtractPrototype(const Routine: TSnippet): string;
{Extracts a routine's prototype from source code.
@param Routine [in] Routine whose source code to be processed.
@return Routine prototype.
}
class function FormatRoutinePrototype(const Routine: TSnippet;
CommentStyle: TCommentStyle = csNone): string;
{Formats a routine's prototype, documented by the routine's description in
a comment.
@param Routine [in] Routine whose prototype is to be formatted.
@param CommentStyle [in] Style of commenting used in documenting
routine.
@return Formatted prototype.
}
class function FormatRoutine(CommentStyle: TCommentStyle;
const Routine: TSnippet): string;
{Formats a routine's whole source code, documented by the routine's
description in a comment.
@param Routine [in] Routine whose source code is to be formatted.
@param CommentStyle [in] Style of commenting used in documenting
routine.
@return Formatted prototype.
}
end;
{
TConstAndTypeFormatter:
Static class that can format a constant or type definition to include
descriptive comments at required position.
}
TConstAndTypeFormatter = class(TNoConstructObject)
strict private
class procedure Split(const ConstOrType: TSnippet; out Keyword,
Body: string);
{Splits source code of a type or constant into the keyword ("const" or
"type") and definition itself (body code).
@param ConstOrType [in] Constant or type whose source code to be split.
@param Keyword [out] "const" or "type" keyword.
@param Body [out] Remainder of constant or type without keyword.
}
class function RenderDescComment(CommentStyle: TCommentStyle;
const ConstOrType: TSnippet): string;
{Creates comment in required style that contains constant or type's
description.
@param CommentStyle [in] Required commenting style.
@param ConstOrType [in] Constant or type for which comments required.
@return Formatted comments.
}
public
class function FormatConstOrType(CommentStyle: TCommentStyle;
const ConstOrType: TSnippet): string;
{Formats a constant or type's source code, documented by the snippet's
description in a comment.
@param ConstOrType [in] Constant or type whose source code is to be
formatted.
@param CommentStyle [in] Style of commenting used in documenting
constant or type.
@return Formatted prototype.
}
end;
TClassFormatter = class(TNoConstructObject)
strict private
class function RenderDescComment(CommentStyle: TCommentStyle;
const Snippet: TSnippet): string;
{Creates comment in required style that contains class' description.
@param CommentStyle [in] Required commenting style.
@param Snippet [in] Class for which comments required.
@return Formatted comments.
}
class function RemoveKeywordFromDecl(const Decl: string;
out DeclBody: string): Boolean;
{Removes any introductory "type" keyword from a type declaration, if
possible.
@param Decl [in] Type declaration to be processed.
@param DeclBody [out] Source code that follows "type" keyword if
keyword is found, otherwise set to Decl.
@returns True if successful, False if not.
}
class procedure SplitDeclFromDefn(const Source: string; out Decl,
Defn: string);
public
class function FormatClassDeclaration(CommentStyle: TCommentStyle;
const Snippet: TSnippet): string;
class function FormatClassDefinition(const Snippet: TSnippet): string;
end;
{ TSourceGen }
constructor TSourceGen.Create;
{Constructor. Sets up the object.
}
begin
inherited;
fSourceAnalyser := TSourceAnalyser.Create;
end;
destructor TSourceGen.Destroy;
{Destructor. Tears down object.
}
begin
fSourceAnalyser.Free;
inherited;
end;
function TSourceGen.IncFileAsString(const CommentStyle: TCommentStyle;
const HeaderComments: IStringList): string;
{Generates source code of an include file containing all specified routines
and notes in comments which units, types, consts and other routines are also
required.
@param CommentStyle [in] Style of commenting used in documenting
snippets.
@param HeaderComments [in] List of comments to be included at top of
snippet.
@return Source code of include file.
}
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.Name);
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.Name);
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));
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, 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);
{Includes a snippet in the source code.
@param Snippet [in] Snippet to be included.
}
begin
fSourceAnalyser.AddSnippet(Snippet);
end;
procedure TSourceGen.IncludeSnippets(const Snips: TSnippetList);
{Includes a one or more snippets in the source code.
@param Routines [in] List of snippets to be included.
}
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;
{Checks if a file name is valid as basis for a unit name.
@param FileName [in] Name of file to be checked.
@return True if file name is valid for unit name, false if not.
}
begin
Result := IsValidIdent(UnitNameFromFileName(FileName));
end;
function TSourceGen.UnitAsString(const UnitName: string;
const CommentStyle: TCommentStyle = csNone;
const HeaderComments: IStringList = nil): string;
{Generates source code of a unit containing all specified routines and
routines depended upon by the included routines.
@param UnitName [in] Name of unit.
@param CommentStyle [in] Style of commenting used in documenting routines.
@param HeaderComments [in] List of comments to be included at top of unit.
@return Unit source code.
}
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, Snippet)
);
skClass:
Writer.AppendLine(
TClassFormatter.FormatClassDeclaration(CommentStyle, Snippet)
);
end;
Writer.AppendLine;
end;
// routine prototypes
for Snippet in fSourceAnalyser.IntfRoutines do
begin
Writer.AppendLine(
TRoutineFormatter.FormatRoutinePrototype(Snippet, CommentStyle)
);
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, 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;
{Creates a unit name from a file name. The unit name is the base file name
with any extension removed.
@param FileName [in] Name of file.
@return Unit name.
}
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);
{Adds a user-specified routine to list of routines specified by user.
Duplicates ignored.
@param Routine [in] Routine to be added.
}
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);
{Adds a user-specified snippet to the analysis. Freeform snippets are ignored.
@param Snippet [in] Snippet to be added.
}
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);
{Adds a user specified or required type or constant to the analysis.
@param TypeOrConst [in] Type of constant snippet to be added.
}
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;
{Constructor. Sets up object.
}
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;
{Destructor. Tears down object.
}
begin
fTypesAndConsts.Free;
fIntfRoutines.Free;
fAllRoutines.Free;
fForwardRoutines.Free;
fRequiredRoutines.Free;
fUnits.Free;
inherited;
end;
procedure TSourceAnalyser.Generate;
{Generates the analysis.
}
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);
{Adds a routine from a dependency list to the analysis. Duplicates ignored.
@param Routine [in] Routine to be added.
}
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);
{Process a snippet from a dependency list.
@param Snippet [in] Snippet to be processed.
@except Exception raised if attempt is made to require freeform snippet
}
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: // add type or const allowing for dependencies
AddTypeOrConst(Snippet);
skFreeform: // can't require a freeform snippet
raise ECodeSnip.CreateFmt(sCantDependOnFreeform, [Snippet.Name]);
end;
end;
procedure TSourceAnalyser.RequireSnippets(const Snips: TSnippetList);
{Process all snippets in a dependency list.
@param Snips [in] List of snippets to process.
}
var
Snippet: TSnippet; // iterates through snippets list
begin
for Snippet in Snips do
RequireSnippet(Snippet);
end;
procedure TSourceAnalyser.RequireUnit(const UnitName: string);
{Add a unit to list of required units. Duplicates ignored.
@param UnitName [in] Name of required unit.
}
begin
if fUnits.IndexOf(UnitName) = -1 then
fUnits.Add(UnitName);
end;
procedure TSourceAnalyser.RequireUnits(const Units: TStringList);
{Adds a list of units to required units list. Duplicates ignored.
@param Units [in] List of units.
}
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;
{Extracts a routine's prototype from source code.
@param Routine [in] Routine whose source code to be processed.
@return Routine prototype.
}
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 Routine: TSnippet): string;
{Formats a routine's whole source code, documented by the routine's
description in a comment.
@param Routine [in] Routine whose source code is to be formatted.
@param CommentStyle [in] Style of commenting used in documenting routine.
@return Formatted prototype.
}
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, Routine) + EOL +
StrTrim(Body);
end;
csBefore:
// Format is: comment - routine
Result := RenderDescComment(CommentStyle, 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): string;
{Formats a routine's prototype, documented by the routine's description in a
comment.
@param Routine [in] Routine whose prototype is to be formatted.
@param CommentStyle [in] Style of commenting used in documenting routine.
@return Formatted prototype.
}
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, Routine);
csBefore:
// comments preceed prototype
Result := RenderDescComment(CommentStyle, Routine) + EOL +
Prototype;
else
// no comments: just return prototype
Result := Prototype;
end;
end;
class function TRoutineFormatter.RenderDescComment(
CommentStyle: TCommentStyle; const Routine: TSnippet): string;
{Creates comment in required style that contains routine's description.
@param CommentStyle [in] Required commenting style.
@param Routine [in] Routine for which comments required.
@return Formatted comments.
}
begin
Assert(Routine.Kind = skRoutine,
ClassName + '.RenderDescComment: Routine must have kind skRoutine');
// Format the output
Result := TSourceComments.FormatSnippetComment(
CommentStyle, Routine.Description
);
end;
class procedure TRoutineFormatter.Split(const Routine: TSnippet; out Head,
Body: string);
{Splits source code of a routine into the head (routine prototype) and body
code.
@param Routine [in] Routine whose source code to be split.
@param Head [out] Routine prototype.
@param Body [out] Remainder of routine without prototype.
}
// ---------------------------------------------------------------------------
function IsDirective(const Symbol: string): Boolean;
{Checks if a symbol is a calling convention directive.
@param Symbol [in] Symbol to be checked.
@return True if symbol is a calling convention, False otherwise.
}
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 ConstOrType: TSnippet): string;
{Formats a constant or type's source code, documented by the snippet's
description in a comment.
@param ConstOrType [in] Constant or type whose source code is to be
formatted.
@param CommentStyle [in] Style of commenting used in documenting constant or
type.
@return Formatted prototype.
}
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 := StrTrim(ConstOrType.Name);
case CommentStyle of
csNone:
Result := StrTrim(ConstOrType.SourceCode);
csBefore:
Result := RenderDescComment(CommentStyle, ConstOrType)
+ EOL
+ StrTrim(ConstOrType.SourceCode);
csAfter:
begin
Split(ConstOrType, Keyword, Body);
if Keyword <> '' then
Result := Keyword
+ EOL
+ RenderDescComment(CommentStyle, ConstOrType)
+ EOL
+ Body
else
Result := ConstOrType.SourceCode;
end;
end;
end;
class function TConstAndTypeFormatter.RenderDescComment(
CommentStyle: TCommentStyle; const ConstOrType: TSnippet): string;
{Creates comment in required style that contains constant or type's
description.
@param CommentStyle [in] Required commenting style.
@param ConstOrType [in] Constant or type for which comments required.
@return Formatted comments.
}
begin
Assert(ConstOrType.Kind in [skConstant, skTypeDef],
ClassName + '.RenderDescComment: ConstOrType must have kind skTypeDef or '
+ 'skConstant');
Result := TSourceComments.FormatSnippetComment(
CommentStyle, ConstOrType.Description
);
end;
class procedure TConstAndTypeFormatter.Split(const ConstOrType: TSnippet;
out Keyword, Body: string);
{Splits source code of a type or constant into the keyword ("const" or
"type") and definition itself (body code).
@param ConstOrType [in] Constant or type whose source code to be split.
@param Keyword [out] "const" or "type" keyword.
@param Body [out] Remainder of constant or type without keyword.
}
// ---------------------------------------------------------------------------
procedure SplitAtKeyword(const SourceCode, KW: string;
out Keyword, Body: string);
{Splits an introductory keyword from following source code.
@param SourceCode [in] Source code to be split.
@param KW [in] Introductory keyword.
@param Keyword [out] Set to KW if KW is present, otherwise ''.
@param Body [out] Source code that follows keyword if KW is present,
otherwise set to SourceCode.
}
begin
if StrStartsStr(KW, SourceCode) then
begin
// KW starts SourceCode - perform split
Keyword := KW;
Body := ' ' + StrTrim(Copy(SourceCode, Length(KW) + 1, MaxInt));
end
else
begin
// KW not present - can't split
Keyword := '';
Body := SourceCode;
end;
end;
// ---------------------------------------------------------------------------
begin
if ConstOrType.Kind = skConstant then
SplitAtKeyword(ConstOrType.SourceCode, 'const', Keyword, Body)
else // if ConstOrType.Kind = skTypeDef
SplitAtKeyword(ConstOrType.SourceCode, 'type', Keyword, Body)
end;
{ TSourceComments }
class function TSourceComments.CommentStyleDesc(
const Style: TCommentStyle): string;
{Gets description of a comment style for use in UI elements.
@param Style [in] Comment style for which description wanted.
@return Required description.
}
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;
{Formats header comment text as Pascal comments.
@param Comments [in] List of comments to format.
@return Formatted comments.
}
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 Text: IActiveText): string;
{Formats a snippet's comment text as Pascal comment according to commenting
style.
@param Style [in] Desired commenting style.
@param Text [in] Active text of comment. Ignored if Style = csNone.
@return Formatted comment. Empty string if Style = csNone.
}
var
Renderer: TActiveTextTextRenderer;
begin
Renderer := TActiveTextTextRenderer.Create;
try
Renderer.DisplayURLs := False;
case Style of
csNone:
Result := '';
csBefore:
Result := '{'
+ EOL
+ FormatCommentLines(Renderer.Render(Text), cIndent)
+ EOL
+ '}';
csAfter:
Result := FormatCommentLines(
'{' + Renderer.Render(Text) + '}', cIndent
);
end;
finally
Renderer.Free;
end;
end;
{ TClassFormatter }
class function TClassFormatter.FormatClassDeclaration(
CommentStyle: TCommentStyle; 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, Snippet)
+ EOL
+ StrTrim(Decl);
csAfter:
begin
if RemoveKeywordFromDecl(Decl, DeclBody) then
Result := 'type'
+ EOL
+ RenderDescComment(CommentStyle, 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 Snippet: TSnippet): string;
begin
Result := TSourceComments.FormatSnippetComment(
CommentStyle, 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];
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('"type" must be first keyword in source code');
SB.Append(Lexer.TokenStr);
// get name of class from following indentifier
while Lexer.NextToken in WhiteSpaceTokens do
SB.Append(Lexer.TokenStr);
if Lexer.Token <> tkIdentifier then
raise ECodeSnip.Create('Class type name expected in source code');
ClassTypeName := Lexer.TokenStr;
SB.Append(Lexer.TokenStr);
while True do
begin
// look for function or procedure
while not (Lexer.NextToken in [tkKeyword, tkEOF])
or not (
StrSameText(Lexer.TokenStr, 'function')
or StrSameText(Lexer.TokenStr, 'procedure')
) do
SB.Append(Lexer.TokenStr);
if Lexer.Token = tkEOF then
raise ECodeSnip.Create('Invalid class or advanced record type');
// check if function is followed by ClassTypeName and a dot => start
// of declaration
// record "function" or "procedure"
Temp := Lexer.TokenStr;
// record following white space
while Lexer.NextToken in WhiteSpaceTokens do
Temp := Temp + Lexer.TokenStr;
// record pascal item after white space
Temp := Temp + Lexer.TokenStr;
if (Lexer.Token <> tkIdentifier)
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 (Lexer.Token <> tkIdentifier) 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.