-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathl7codeblock.PRG
More file actions
3230 lines (2855 loc) · 115 KB
/
l7codeblock.PRG
File metadata and controls
3230 lines (2855 loc) · 115 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
* L7CodeBlock.PRG
* -------------------------------------------------------- *
* CodeBlock.PRG : Code Block "Wrapper"
*
* Visual FoxPro Code Block Interpreter
*
* Created by: J. Randy Pearson, CYCLA Corporation
* Revision 3.2(b), December 3, 2001
* Record of Revision at End Of File.
* Public Domain
*
* Calling "wrapper" program for class cusCodeBlock in
* Accepts same parameter calls
* as FP 2.6 version (same author).
* See notes at end of file for theory about how "runtime
* interpreter" works.
* To use JUST THE INTERPRETER in another framework, do one of these:
*
* 1) Cut everything between CUT HERE .. CUT HERE (search below) and
* Paste into your application.
* - or -
*
* 2) SET PROCEDURE TO CodeBlck ADDITIVE, and then you can either use
* this wrapper, or make direct calls to CLASS CusCodeBlock.
*
* You can also test out code interactively in a FORM by calling this
* PRG with no parameters: = CodeBlck()
* (IMPORTANT: See notes at end of file on optional TEXTMERGE strategies!)
* ================================================================= *
* Wrapper code for Class CusCodeBlock below:
LPARAMETERS _0_qcCode, _0_qlFile, _0_qlEdit
* _0_qcCode : Text of code to run OR File name with code.
* If blank, user gets screen to type code.
* _0_qlFile : .T. if 1st parameter is a file name. Internally
* passed as -1 when recursive call made.
* _0_qlEdit : .T. if user gets to edit code before running.
* Calling Examples:
* 1) Allow direct typing of code to run:
* DO CodeBlck
* 2) Run the code contained in memo field "TheCode":
* DO CodeBlck WITH TheCode
* 3) Same as 2, but allow review/edit first:
* DO CodeBlck WITH TheCode, .f., .T.
* 4) Run the code found in file "TESTRUN.PRG":
* DO CodeBlck WITH "TESTRUN.PRG", .T.
* 5) Same as 4, but allow user to review/edit:
* DO CodeBlck WITH "TESTRUN.PRG", .T., .T.
* [NOTE: The file doesn't get changed.]
LOCAL _0_qoCb
* object reference to code block or editor
* --- Deal with different calling methods:
IF m._0_qlFile
* File name as 1st parameter.
DO CASE
CASE EMPTY( m._0_qcCode) OR NOT TYPE("m._0_qcCode") == 'C'
* Process a file, but no file name passed.
_0_qcCode = GETFILE( 'PRG|TXT', 'Select File', 'Execute')
CASE '*' $ m._0_qcCode OR '?' $ m._0_qcCode
_0_qcCode = GETFILE( m._0_qcCode, 'Select File', 'Execute')
OTHERWISE
* Explicit file name sent.
ENDCASE
IF EMPTY( m._0_qcCode)
* File not found/selected.
RETURN .F.
ELSE
* Store file contents to memvar.
_0_qcCode = _0_qFile( m._0_qcCode)
ENDIF
ENDIF
IF NOT TYPE( "m._0_qcCode") == 'C'
* No code passed - see if any stored from last run.
IF PROGRAM(1) == "CODEBLCK" AND TYPE( "m._0_qcPrev") == "C"
_0_qcCode = m._0_qcPrev
ELSE
_0_qcCode = SPACE(0)
ENDIF
_0_qlEdit = .T.
ENDIF [no code passed as parameter]
_0_qcCode = ALLTRIM( m._0_qcCode)
IF LEFT( m._0_qcCode, 1) == ";"
* Assume dBW-style code block. Translate each ;
* to Cr-Lf so that this routine will run it.
IF LEN( m._0_qcCode) > 1
_0_qcCode = STRTRAN( SUBSTR( m._0_qcCode, 2), ";", CHR(13) + CHR(10) )
ELSE
_0_qcCode = ""
ENDIF
ENDIF
IF m._0_qlEdit
* Allow user to enter/edit code:
_0_qoCb = CREATEOBJECT( "frmCodeBlockEditor")
_0_qoCb.edtCodeBlock.Value = m._0_qcCode
_0_qoCb.Show( 1)
**READ EVENTS
ELSE
_0_qoCb = CREATEOBJECT( "cusCodeBlock")
_0_qoCb.SetCodeBlock( m._0_qcCode)
_0_qoCb.Execute()
IF _0_qoCB.lError
= MessageBox( "CAUTION: Error " + LTRIM( STR( _0_qoCB.nError)) + ;
" occurred with message " + _0_qoCB.cErrorMessage + ;
", while processing line " + _0_qoCB.cErrorCode + ".")
ENDIF
RETURN _0_qoCb.Result
ENDIF
RETURN
* -------------------------------------------------------- *
FUNCTION _0_qFile
*
* Get file contents.
*
PARAMETER pcFile
IF NOT FILE( m.pcFile)
RETURN SPACE(0)
ENDIF
LOCAL lnSelect, lcCode
lnSelect = SELECT()
SELECT 0
CREATE CURSOR _0_qFile (Contents M)
APPEND BLANK
APPEND MEMO Contents FROM ( m.pcFile)
lcCode = Contents
USE
SELECT (m.lnSelect)
RETURN m.lcCode
ENDFUNC
**** <<<<< --- CUT HERE (START) to copy the main class into your system.
*** ------------------------------------------- ***
* Allow insertion into prg where CR is already defined:
#IFNDEF CR
#DEFINE CR CHR(13) + CHR(10)
#ENDIF
* Debugging/error control settings:
#DEFINE CODEBLOCK_WAIT_ON_ERROR .F. && displays wait window on error
#DEFINE CODEBLOCK_WAIT_TIMEOUT 5 && timeout on above, 0 = forever
#DEFINE CODEBLOCK_DEBUG .F. && set to FALSE unless debugging
#DEFINE CODEBLOCK_DEBUG_LEVEL 0 && set to 0 except in severe debugging
* 0 = normal, 1 = WAIT WINDOW, 2 = SUSPEND before each line
* applies only if CODEBLOCK_DEBUG = .T.
#DEFINE CODEBLOCK_SHOW_TEXT_FLAG .T.
* Compensates for shortcoming in VFP where you cannot check, via SET(), for
* the current setting of SHOW|NOSHOW in SET TEXTMERGE.
* If constant is TRUE, a property "lShowTextmerge" is included, which controls
* whether output from textmerge commands is also sent via ? or ??.
* IF FALSE, all output is sent via ?/?? and you need to use SET CONSOLE OFF
* in your code to prevent output from going to screen.
*
* Recommended setting:
* .F. to avoid any output to screen ever.
* .T. to make environment most like VFP's current settings.
* If you want to override and constants above, create a file named
* CODEBLCK_OVERRIDE.H, and make the changes there with UNDEF/DEFINE
* "pairs". Example:
*
* #UNDEF CODEBLOCK_DEBUG
* #DEFINE CODEBLOCK_DEBUG .T.
#IF FILE( "CODEBLCK_OVERRIDE.H" )
#INCLUDE CODEBLCK_OVERRIDE.H
#ENDIF
*** ------------------------------------------- ***
DEFINE CLASS l7CodeBlock AS RELATION
DIMENSION aRawCode[ 1] && Raw original code, to allow trace back.
DIMENSION aCode[ 1, 3] && Array to store pre-processed code.
DIMENSION aDiag[ 1, 4] && Array to "diagram" structure of code block.
nRawLines = 0
nDiagCount = 0 && Counter for aDiag
nCodeLines = 0 && Counter for aCode
nRawStart = 0 && used internally
nRawEnd = 0 && used internally
oCaller = NULL && calling object reference (see INIT)
cCodeBlock = ""
nRecursionLevel = 0
lPreProcessed = .F.
lScript = .F. && flag for whether code is ASP-style script
* TWO special flags to handle differences in variable
* declaration in non-compiled environment. It is
* strongly recommended that these flags be set to TRUE:
*
lInitializePrivates = .T.
* Takes any PRIVATE statement and adds a "STORE .F. TO <var_list>"
* right afterward, thus avoiding errors when initialization
* occurs in nested code, such as:
*
* PRIVATE i
* IF <condition>
* i = <value_1>
* ELSE
* i = <value_2>
* ENDIF
* ? i && error, without this switch turned on
lLocalToPrivate = .T.
* Interpret any LOCAL statement as a PRIVATE statement,
* to avoid numerous errors that occur otherwise. Always
* inserts a STORE .F. TO as well for maximum compatability
* with how the same compiled code would run.
#IF CODEBLOCK_DEBUG
lDontExecute = .F.
#ENDIF
nCodeCheckingLevel = 2 && recommended setting: 2
* 0 = no optional checking (maximum performance)
* 1 = check and ignore (strip out) illegal/unsupported commands
* 2 = check and raise error if any illegal/unsupported commands
* NOTES:
* a) This replaces #DEFINE CODEBLOCK_TRUST_CODE in previous version.
* b) It's now possible to set this to 1 or 2 and have minimal
* performance impact, since all checks are done only once
* in the PreProcess() method.
* c) You must set this to at least 1 to enable AddIllegalCommand support.
DIMENSION aIllegal[ 1, 2]
aIllegal[ 1, 2] = .F. && array of optional illegal commands to cause code abort
Result = .T. && used internally to establish RETURN value
xErrorReturn = .F. && value to RETURN if an error occurs (default .F.)
cReturnType = "X" && Type-check of return value. (X = any, can be multiple, i.e. "DT")
cExitCode = "" && [internal control variable]
lError = .F. && did error occur flag
nError = 0 && VFP error (1098, user-defined, when program detects error)
cErrorMethod = "" && method in which VFP error occurred
nErrorLine = 0 && line # of in pre-processed code in which VFP error occurred
nRawErrorLine = 0 && line # in original code in which VFP error occurred
cErrorMessage = "" && error message
cErrorCode = "" && errant line of code, or similar message
lMacroError = .F. && did error occur when macro expanding user code?
nErrorRecursionLevel = 0 && level of recursion at which error occurred
* Properties for extended TEXTMERGE capabilities:
*
* (IMPORTANT: See notes at end of file on optional TEXTMERGE strategies!)
*
lAccumulateMergedText = .F. && flag to accumulate all textmerge output
cAltMergeFunction = "" && alternative merge function name
cMergedText = "" && accumulated merged text
cTextmergeVariable = "" && variable to store accumulated text at end
lWriteAccumulatedText = .T. && flag to write accumulated text at end
lClassicTextmerge = .F. && when not accumulating, use classic (slower) line-
* && by-line textmerge (vs. THIS.InternalMergeText function)
#IF CODEBLOCK_SHOW_TEXT_FLAG
lShowTextmerge = .F. && don't send textmerge via ?/??
#ENDIF
* ------------------------------------------------ *
FUNCTION Init( tcCode, toCaller )
THIS.AddIllegalCommand( "QUIT")
THIS.AddIllegalCommand( "CANC")
**THIS.AddIllegalCommand( "FOR EACH")
THIS.ResetProperties()
* 1) establish code block
IF NOT EMPTY( m.tcCode) AND TYPE( "m.tcCode") == "C"
THIS.SetCodeBlock( m.tcCode)
ENDIF
* 2) caller object reference, if passed
IF PCOUNT() >= 2
THIS.SetCallingObject( m.toCaller)
ENDIF
ENDFUNC
* ------------------------------------------------ *
FUNCTION ResetProperties
* Allow object reuse by resetting all properties
* needed to allow a second block to be executed.
THIS.lError = .F.
THIS.nError = 0
THIS.cErrorMethod = ""
THIS.nErrorLine = 0
THIS.nRawErrorLine = 0
THIS.cErrorMessage = ""
THIS.cErrorCode = ""
THIS.lMacroError = .F.
THIS.nErrorRecursionLevel = 0
THIS.cCodeBlock = ""
THIS.cMergedText = ""
THIS.cExitCode = ""
THIS.nRecursionLevel = 0
THIS.lPreProcessed = .F.
THIS.lScript = .F.
DIMENSION THIS.aCode[ 1, 3]
DIMENSION THIS.aDiag[ 1, 4]
DIMENSION THIS.aRawCode[ 1]
THIS.nDiagCount = 0
THIS.nCodeLines = 0
THIS.nRawLines = 0
THIS.nRawStart = 0
THIS.nRawEnd = 0
ENDFUNC && ResetProperties
* ------------------------------------------------ *
FUNCTION BeforePreProcessCode && <<--- Hook!
RETURN .T. && .F. to kill processing
ENDFUNC && BeforePreProcessCode
* ------------------------------------------------ *
FUNCTION PreProcessCode
* Analyze raw code block to facilitate Execute
* method. The following are accomplished:
*
* a) All control structure information is extracted
* into an array to help control loops in Execute().
* b) Errors in control structures and presence of any
* illegal/unsupported commands are identified up front.
* c) Cleaned up code is placed in an array.
* d) All text between TEXT..ENDTEXT is accumulated into
* single strings to improve textmerge performance in loops.
*
IF NOT THIS.BeforePreProcessCode() && Hook
IF NOT THIS.lError
THIS.SetError( "BeforePreprocessCode hook returned FALSE." )
ENDIF
RETURN .F.
ENDIF
LOCAL ARRAY laPoundIf[ 1, 2]
LOCAL ARRAY laWith[ 1]
LOCAL ARRAY laDefines[ 1, 2]
LOCAL ARRAY laIncludeCode[ 1]
LOCAL lnIncludeLines, lnIncludePointer
LOCAL lnWith, lnDefines, lnPoundIf
STORE 0 TO lnWith, lnDefines, lnPoundIf
LOCAL lnOldMemo, lnOldMline, lcOldExact, lcRawLine, lcUpper, lcCleanLine, ;
jj, lnDepth, lnCleanLines, ;
llInText, llContinued, llComment, lnAtPos, llMatch, llProblem, ;
lcTextBlock, llNewNest, lcStr, lcStr2, ;
lnAt, lnAt2, lnAtStart, lcConstant, lcValue
lnOldMemo = SET( "MEMOWIDTH" )
SET MEMOWIDTH TO 8192
*!* lnOldMline = _MLINE
*!* _MLINE = 0
lcOldExact = SET( 'EXACT')
SET EXACT OFF
** IF THIS.lScript OR THIS.cCodeBlock = "<HTML>"
IF THIS.lScript OR "<html" $ THIS.cCodeBlock && cope with XHTML possibilities
THIS.InvertScript()
ENDIF
lnDepth = 0
* Read the code in:
THIS.nRawLines = ALINES( THIS.aRawCode, THIS.cCodeBlock )
* Dimension the processed code array:
DIMENSION THIS.aCode[ THIS.nRawLines, 3]
* Max possible, so we don't re-DIM all the time.
* New: 2nd column is for tracking original code line for error messages.
lcCleanLine = ""
lnCleanLines = 0
SET MEMOWIDTH TO m.lnOldMemo
THIS.nRawStart = 1
THIS.nRawEnd = 0
DO WHILE .T.
* Cannot use FOR..ENDFOR, since nRawLines can grow,
* if a #INCLUDE file is inserted.
THIS.nRawEnd = THIS.nRawEnd + 1
IF THIS.nRawEnd > THIS.nRawLines
EXIT
ENDIF
* Read next line of raw code:
*!* lcRawLine = MLINE( m.lcRawCode, 1, _MLINE )
lcRawLine = THIS.aRawCode[ THIS.nRawEnd]
* This section accumulates any multi-line statements,
* gathers any text between TEXT..ENDTEXT and otherwise
* cleans up the code for further processing:
IF m.llInText
* Within TEXT..ENDTEXT: don't touch,
* unless "ENDTEXT" is found.
* lnCleanLines = m.lnCleanLines + 1
IF LTRIM( STRTRAN( UPPER( m.lcRawLine ), CHR( 9))) = "ENDT"
* First, the accumulated block of text:
lnCleanLines = m.lnCleanLines + 1
THIS.SetCodeLine( m.lnCleanLines, m.lcTextBlock)
* Then, the ENDTEXT:
lnCleanLines = m.lnCleanLines + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "ENDTEXT")
lnDepth = m.lnDepth - 1
llInText = .F.
IF m.lnCleanLines > THIS.nRawLines && unlikely
DIMENSION THIS.aCode[ m.lnCleanLines, 3]
ENDIF
THIS.SetCodeLine( m.lnCleanLines, "ENDTEXT")
ELSE && simply accumulate the text block
lcTextBlock = m.lcTextBlock + m.lcRawLine + CR
ENDIF
LOOP
ENDIF
* Strip out any TAB's in the code (simplifies later processing):
IF m.llContinued
* 2nd or later line in multi-line
* statement, attach but don't LTRIM(),
* since we could be in middle of delimited string.
lcCleanLine = m.lcCleanLine + TRIM( STRTRAN( ;
m.lcRawLine, CHR(9), SPACE(1) ) )
ELSE && new line of code
THIS.nRawStart = THIS.nRawEnd
lcCleanLine = ALLTRIM( STRTRAN( ;
m.lcRawLine, CHR(9), SPACE(1) ) )
ENDIF
IF EMPTY( m.lcCleanLine) && blank line
LOOP
ENDIF
*!* IF LTRIM( m.lcCleanLine) = "*" && comment line
*!* LOOP
*!* ENDIF
IF LTRIM( m.lcCleanLine) = "&" + "&"
* Comment line (note: can't type 2 &'s together in VFP)
LOOP
ENDIF
* Look for "&&" in line:
lnAtPos = AT( "&" + "&", m.lcCleanLine )
* (Note gymnastics used to avoid compile error.)
IF m.lnAtPos > 0
lcCleanLine = TRIM( LEFT( m.lcCleanLine, m.lnAtPos - 1))
IF EMPTY( m.lcCleanLine)
LOOP
ENDIF
llComment = .T.
ELSE
llComment = .F.
ENDIF
* Check for semi-colon at end of line--denoting line continuation:
IF RIGHT( m.lcCleanLine, 1) = CHR( 59) AND THIS.nRawEnd < THIS.nRawLines
IF m.llComment
* Not allowed on same line!
THIS.SetError( "Syntax Error: Semi-Colon and double-& on same line.", ;
m.lcCleanLine )
lcCleanLine = SPACE(0)
EXIT
ELSE
llContinued = .T.
lcCleanLine = LEFT( m.lcCleanLine, LEN( m.lcCleanLine) - 1)
LOOP
ENDIF
ELSE
llContinued = .F.
ENDIF
* Check/ignore for comment lines. [09/18/2001: This check was moved down to
* here so we captured continuation comments correctly.]
IF LTRIM( m.lcCleanLine) = "*" && comment line
LOOP
ENDIF
* NEW SECTION -- PLEASE TEST AND USE WITH CAUTION!
*
* Substitute for any #DEFINE's. For first round, this handles constant
* replacement only. No support for #IF yet--use IF if possible.
FOR jj = 1 TO m.lnDefines
lnAtStart = 1
lcConstant = laDefines[ m.jj, 1]
DO WHILE m.lnAtStart <= LEN( m.lcCleanLine)
lnAt = ATC( m.lcConstant, SUBSTR( m.lcCleanLine, m.lnAtStart)) + ;
m.lnAtStart - 1
*lnAtStart = m.lnAt + LEN( m.lcConstant)
DO CASE
CASE m.lnAt < m.lnAtStart
EXIT
CASE NOT ( ;
m.lnAt = 1 OR ;
SUBSTR( m.lcCleanLine, m.lnAt -1, 1) $ "[ (*-+/!^%$#<>&.=" )
*
lnAtStart = m.lnAtStart + 1
LOOP
CASE NOT ( ;
m.lnAt + LEN( m.lcConstant) - 1 = LEN( m.lcCleanLine) OR ;
SUBSTR( m.lcCleanLine, m.lnAt + LEN( m.lcConstant), 1) $ ;
"] )*-+/!^%$#<>.=" )
*
lnAtStart = m.lnAtStart + 1
LOOP
OTHERWISE
lcCleanLine = STUFF( m.lcCleanLine, m.lnAt, ;
LEN( m.lcConstant), laDefines[ m.jj, 2] )
lnAtStart = m.lnAt + LEN( laDefines[ m.jj, 2] )
ENDCASE
ENDDO
ENDFOR
* Deal with #IF structures:
*
DO CASE
CASE NOT LEFT( m.lcCleanLine, 1) == "#"
= .F.
CASE PADR( UPPER( m.lcCleanLine), 4) == "#IF "
lnPoundIf = m.lnPoundIf + 1
DIMENSION laPoundIf[ m.lnPoundIf, 2]
lcStr = SUBSTR( m.lcCleanLine, 4)
lcType = TYPE( m.lcStr )
DO CASE
CASE m.lcType = "L"
IF EVALUATE( m.lcStr) = .T.
laPoundIf[ m.lnPoundIf, 1 ] = .T.
laPoundIf[ m.lnPoundIf, 2 ] = .T.
ENDIF
OTHERWISE
THIS.SetError( "Invalid #IF directive: " + m.lcStr )
EXIT
ENDCASE
LOOP
CASE PADR( UPPER( m.lcCleanLine), 6) == "#ELIF "
IF m.lnPoundIf <= 0
THIS.SetError( "Mismatched #ELIF directive." )
EXIT
ENDIF
IF laPoundIf[ m.lnPoundIf, 2 ] = .T. && previous TRUE case
laPoundIf[ m.lnPoundIf, 1 ] = .F.
LOOP
ENDIF
lcStr = SUBSTR( m.lcCleanLine, 6)
lcType = TYPE( m.lcStr )
DO CASE
CASE m.lcType = "L"
IF EVALUATE( m.lcStr) = .T.
laPoundIf[ m.lnPoundIf, 1 ] = .T.
laPoundIf[ m.lnPoundIf, 2 ] = .T.
ENDIF
OTHERWISE
THIS.SetError( "Invalid #ELIF directive: " + m.lcStr )
EXIT
ENDCASE
LOOP
CASE PADR( UPPER( m.lcCleanLine), 5) == "#ELSE"
IF m.lnPoundIf <= 0
THIS.SetError( "Mismatched #ELSE directive." )
EXIT
ENDIF
IF laPoundIf[ m.lnPoundIf, 1 ] = .F.
IF laPoundIf[ m.lnPoundIf, 2] = .F.
laPoundIf[ m.lnPoundIf, 1] = .T.
ENDIF
ELSE
laPoundIf[ m.lnPoundIf, 1 ] = .F.
ENDIF
LOOP
CASE PADR( UPPER( m.lcCleanLine), 6) == "#ENDIF"
lnPoundIf = m.lnPoundIf - 1
IF m.lnPoundIf < 0
THIS.SetError( "Mismatched #ENDIF directive." )
EXIT
ENDIF
LOOP
ENDCASE && #IF stuff
IF m.lnPoundIf > 0 AND laPoundIf[ m.lnPoundIf, 1 ] = .F.
* We're in a FALSE part of a #IF -- ignore line.
LOOP
ENDIF
* Insert any #INCLUDE files:
IF PADR( UPPER( m.lcCleanLine), 9) == "#INCLUDE "
lcStr = ALLTRIM( SUBSTR( m.lcCleanLine, 9))
IF FILE( m.lcStr)
lcStr = THIS.FileToStr( m.lcStr )
* Parse the INCLUDE file to an array:
SET MEMOWIDTH TO 8192
lnIncludeLines = ALINES( laIncludeLines, m.lcStr)
SET MEMOWIDTH TO m.lnOldMemo
* Insert the included lines back into the *raw* code:
THIS.nRawLines = THIS.nRawLines + m.lnIncludeLines
DIMENSION THIS.aRawCode[ THIS.nRawLines]
FOR lnIncludePointer = 1 TO m.lnIncludeLines
AINS( THIS.aRawCode, THIS.nRawEnd + m.lnIncludePointer)
THIS.aRawCode[ THIS.nRawEnd + m.lnIncludePointer] = laIncludeLines[ m.lnIncludePointer]
ENDFOR
* Re-dim the processed code if needed for new size:
IF ALEN( THIS.aCode, 1) < THIS.nRawLines
DIMENSION THIS.aCode[ THIS.nRawLines, 3 ]
ENDIF
* Loop back so we process the next line (from include file):
LOOP
ELSE
THIS.SetError( "#INCLUDE file not found: " + m.lcStr )
EXIT
ENDIF
ENDIF
* Process any #DEFINE's:
IF PADR( UPPER( m.lcCleanLine), 8) == "#DEFINE "
lnAt = AT( SPACE(1), m.lcCleanLine )
IF LEN( m.lcCleanLine) = m.lnAt
THIS.SetError( "Illegal #DEFINE format.")
EXIT
ENDIF
lcConstant = LTRIM( SUBSTR( m.lcCleanLine, m.lnAt + 1))
lnAt = AT( SPACE(1), m.lcConstant)
IF m.lnAt = 0 OR m.lnAt = LEN( m.lcConstant)
THIS.SetError( "Illegal #DEFINE format.")
EXIT
ENDIF
lcValue = ALLTRIM( SUBSTR( m.lcConstant, m.lnAt + 1))
IF EMPTY( m.lcValue)
THIS.SetError( "Illegal #DEFINE format.")
EXIT
ENDIF
lcConstant = UPPER( LEFT( m.lcConstant, m.lnAt - 1))
llConstantExists = .F.
FOR jj = 1 TO m.lnDefines
IF laDefines[ m.jj, 1] == m.lcConstant
llConstantExists = .T.
EXIT
ENDIF
ENDFOR
IF m.llConstantExists
* Emulate VFP's annoying behavior:
THIS.SetError( "Constant " + m.lcConstant + ;
" was already created with a #DEFINE.")
EXIT
ENDIF
lnDefines = m.lnDefines + 1
DIMENSION laDefines[ m.lnDefines, 2]
laDefines[ m.lnDefines, 1] = m.lcConstant
laDefines[ m.lnDefines, 2] = m.lcValue
LOOP
ENDIF
* --- Create an upper-case version of the line:
lcUpper = UPPER( m.lcCleanLine)
* --- Process any WITH or ENDWITH statements separately:
IF PADR( m.lcUpper, 5) == "WITH "
* We don't "do" WITH's, we store argument in stack and
* apply them to subsequent lines of code.
lnWith = lnWith + 1
DIMENSION laWith[ m.lnWith]
laWith[ m.lnWith] = ALLTRIM( SUBSTR( m.lcCleanLine, 5))
IF m.lnWith > 1 && WITH's are cascading
laWith[ m.lnWith] = laWith[ m.lnWith - 1] + ;
laWith[ m.lnWith]
ENDIF
LOOP
ENDIF
IF PADR( m.lcUpper, 4) == "ENDW"
* ENDWITH - see note above on WITH
lnWith = MAX( 0, m.lnWith - 1)
IF m.lnWith = 0
DIMENSION laWith[ 1]
laWith[ 1] = ""
ELSE
DIMENSION laWith[ m.lnWith]
ENDIF
LOOP
ENDIF
IF m.lnWith > 0
* We're within WITH..ENDWITH, so STRTRAN() as needed first.
lnAtStart = 1
lcCleanLine = SPACE(1) + m.lcCleanLine
* The SPACE(1) is prepended to handle lines starting with a "."
DO WHILE m.lnAtStart <= LEN( m.lcCleanLine)
lnAt = AT( SPACE(1) + ".", SUBSTR( m.lcCleanLine, m.lnAtStart))
IF m.lnAt = 0
EXIT
ENDIF
lcStr = LTRIM( UPPER( SUBSTR( m.lcCleanLine, m.lnAt + m.lnAtStart - 1)))
IF NOT INLIST( m.lcStr, ".T.", ".F.", ".AND.", ".OR.", ".NOT.")
lcCleanLine = STUFF( m.lcCleanLine, ;
m.lnAt + m.lnAtStart - 1, 2, ;
SPACE(1) + laWith[ m.lnWith] + ".")
ENDIF
lnAtStart = m.lnAt + m.lnAtStart + 1
ENDDO
lcCleanLine = LTRIM( m.lcCleanLine)
lcUpper = UPPER( m.lcCleanLine )
ENDIF
* --- Optional check of code for non-supported commands:
IF THIS.nCodeCheckingLevel > 0
llProblem = .F.
DO CASE
CASE THIS.IllegalCommandFound( m.lcUpper )
llProblem = .T.
* This method can also set THIS.lError for certain commands.
CASE PADR( m.lcUpper, 9) == "CLEAR ALL" OR ;
PADR( m.lcUpper, 8) == "CLEA ALL" OR ;
PADR( m.lcUpper, 10) == "CLEAR MEMO" OR ;
PADR( m.lcUpper, 9) == "CLEA MEMO" OR ;
PADR( m.lcUpper, 7) == "RETU TO" OR ;
PADR( m.lcUpper, 8) == "RETUR TO" OR ;
PADR( m.lcUpper, 9) == "RETURN TO" OR ;
PADR( m.lcUpper, 8) == "RELE ALL" OR ;
PADR( m.lcUpper, 9) == "RELEA ALL" OR ;
PADR( m.lcUpper, 10) == "RELEAS ALL" OR ;
PADR( m.lcUpper, 11) == "RELEASE ALL"
*
* These are known to break the system.
llProblem = .T.
CASE PADR( m.lcUpper, 4) == "REST" AND ;
"FROM " $ m.lcUpper AND ;
NOT "ADDI" $ m.lcUpper
*
* THIS.SetError( "Can't have RESTORE FROM w/o ADDITIVE.", m.lcUpper)
* EXIT
llProblem = .T.
CASE INLIST( PADR( m.lcUpper, 4), "PROC", "FUNC")
llProblem = .T.
ENDCASE
IF m.llProblem
IF THIS.nCodeCheckingLevel >= 2 && raise error
THIS.SetError( "Command not supported.", m.lcUpper )
EXIT
ELSE && ignore the line
LOOP
ENDIF
ENDIF
ENDIF && Optional checking.
lnCleanLines = m.lnCleanLines + 1
llMatch = .T.
llNewNest = .F.
* --- Main part of pre-processing. Create a "code diagram":
DO CASE
CASE m.lcUpper = "TEXT"
llInText = .T.
lnDepth = m.lnDepth + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "TEXT")
lcTextBlock = ""
CASE m.lcUpper = "DO WHIL"
llNewNest = .T.
lnDepth = m.lnDepth + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "DO WHILE")
* Convert any abbreviated (DO WHIL) so Execute method does not
* have to worry about this:
lnAT = ATC( "WHIL", m.lcCleanLine)
lcCleanLine = "DO WHILE " + ;
IIF( SUBSTR( m.lcCleanLine, m.lnAt + 4, 1) $ "eE", ;
LTRIM( SUBSTR( m.lcCleanLine, m.lnAt + 5)), ;
LTRIM( SUBSTR( m.lcCleanLine, m.lnAt + 4)) )
CASE m.lcUpper = "FOR EACH"
llNewNest = .T.
lnDepth = m.lnDepth + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "FOR EACH")
CASE m.lcUpper = "FOR "
llNewNest = .T.
lnDepth = m.lnDepth + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "FOR")
CASE m.lcUpper = "SCAN"
llNewNest = .T.
lnDepth = m.lnDepth + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "SCAN")
CASE m.lcUpper = "IF " OR m.lcUpper = "IF("
llNewNest = .T.
lnDepth = m.lnDepth + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "IF")
CASE m.lcUpper = "DO CASE"
llNewNest = .T.
lnDepth = m.lnDepth + 1
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "DO CASE")
CASE m.lcUpper = "CASE"
llNewNest = .T.
llMatch = THIS.FindMatch( m.lnDepth, "CASE", "DO CASE")
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "CASE")
ENDIF
CASE m.lcUpper = "OTHE"
lcCleanLine = "OTHERWISE"
llNewNest = .T.
llMatch = THIS.FindMatch( m.lnDepth, "CASE")
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "OTHERWISE")
ENDIF
CASE m.lcUpper = "ELSE"
llNewNest = .T.
llMatch = THIS.FindMatch( m.lnDepth, "IF" )
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "ELSE")
ENDIF
CASE m.lcUpper = "ENDC"
lcCleanLine = "ENDCASE"
llMatch = THIS.FindMatch( m.lnDepth, "CASE", "OTHERWISE")
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "ENDCASE")
lnDepth = m.lnDepth - 1
ENDIF
CASE m.lcUpper = "ENDD"
lcCleanLine = "ENDDO"
llMatch = THIS.FindMatch( m.lnDepth, "DO WHILE")
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "ENDDO")
lnDepth = m.lnDepth - 1
ENDIF
CASE m.lcUpper = "ENDI"
lcCleanLine = "ENDIF"
llMatch = THIS.FindMatch( m.lnDepth, "IF", "ELSE")
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "ENDIF")
lnDepth = m.lnDepth - 1
ENDIF
CASE m.lcUpper = "ENDS"
lcCleanLine = "ENDSCAN"
llMatch = THIS.FindMatch( m.lnDepth, "SCAN" )
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "ENDSCAN")
lnDepth = m.lnDepth - 1
ENDIF
CASE m.lcUpper = "ENDF" OR m.lcUpper = "NEXT"
lcCleanLine = "ENDFOR"
llMatch = THIS.FindMatch( m.lnDepth, "FOR")
IF m.llMatch
THIS.AddDiagramItem( m.lnCleanLines, m.lnDepth, "ENDFOR")
lnDepth = m.lnDepth - 1
ENDIF
OTHERWISE
* normal code--no action needed now
ENDCASE
IF m.llMatch = .F.
THIS.SetError( "Nesting Error", m.lcCleanLine )
EXIT
ENDIF
DO CASE
CASE PADR( m.lcUpper, 7) == "PRIVATE" AND ;
THIS.lInitializePrivates
*
IF m.lnCleanLines + 1 > THIS.nRawLines && need 2 lines
DIMENSION THIS.aCode[ m.lnCleanLines + 1, 3]
ENDIF
* First the PRIVATE itself:
THIS.SetCodeLine( m.lnCleanLines, m.lcCleanLine)
lnCleanLines = m.lnCleanLines + 1
lnAtPos = AT( SPACE(1), m.lcCleanLine )
* Create a matching initialization statement:
lcCleanLine = "STORE .F. TO " + ;
ALLTRIM( SUBSTR( m.lcCleanLine, m.lnAtPos))
THIS.SetCodeLine(m.lnCleanLines, m.lcCleanLine)
CASE PADR( m.lcUpper, 5) == "LOCAL" AND ;
( ")" $ m.lcUpper OR "]" $ m.lcUpper ) AND ;
THIS.lLocalToPrivate
*
* LOCAL [ARRAY]. This must be parsed and handled
* separately.
IF m.lnCleanLines + 1 > THIS.nRawLines && need 2 lines
DIMENSION THIS.aCode[ m.lnCleanLines + 1, 3]
ENDIF
* Extract the variable names:
lnAtPos = AT( SPACE(1), m.lcCleanLine )
lcStr = ALLTRIM( SUBSTR( m.lcCleanLine, m.lnAtPos))
* Get rid of optional "ARRAY" word:
IF UPPER( m.lcStr) = "ARRAY" + SPACE(1)
lcStr = ALLTRIM( SUBSTR( m.lcStr, 6))
ENDIF
* Now create a list of variables *without*
* the dimensions included:
lcStr2 = STRTRAN( STRTRAN( m.lcStr, "(", "["), ")", "]" )
DO WHILE "[" $ m.lcStr2
lnAt = AT( "[", m.lcStr2 )
lnAt2 = AT( "]", m.lcStr2 )
IF m.lnAt2 = 0 OR m.lnAt2 < m.lnAt
llProblem = .T.
EXIT
ELSE
* Take out the dimensions:
lcStr2 = STUFF( m.lcStr2, m.lnAt, 1 + m.lnAt2 - m.lnAt, "")
LOOP
ENDIF
ENDDO
IF m.llProblem
THIS.SetError( "Invalid dimension syntax for local arrays." )
EXIT
ENDIF
* Create a PRIVATE statement:
THIS.SetCodeLine( m.lnCleanLines, "PRIVATE " + m.lcStr2)
lnCleanLines = m.lnCleanLines + 1
* Also insert a DIMENSION statement, since LOCAL
* arrays are dimensioned right in their declaration:
THIS.SetCodeLine( m.lnCleanLines, "DIMENSION " + m.lcStr)
CASE PADR( m.lcUpper, 5) == "LOCAL" AND ;
THIS.lLocalToPrivate
*
* (See above CASE for LOCAL ARRAY.)
IF m.lnCleanLines + 1 > THIS.nRawLines && need 2 lines
DIMENSION THIS.aCode[ m.lnCleanLines + 1, 3]
ENDIF
* Extract the variable names:
lnAtPos = AT( SPACE(1), m.lcCleanLine )
* Create a PRIVATE statement:
THIS.SetCodeLine( m.lnCleanLines, "PRIVATE " + ;
ALLTRIM( SUBSTR( m.lcCleanLine, m.lnAtPos)))
lnCleanLines = m.lnCleanLines + 1
* Also create an initialization statement, since LOCAL
* variables all start with .F. value by default:
THIS.SetCodeLine( m.lnCleanLines, "STORE .F. TO " + ;
ALLTRIM( SUBSTR( m.lcCleanLine, m.lnAtPos)))
OTHERWISE
IF m.lnCleanLines > THIS.nRawLines && unlikely
DIMENSION THIS.aCode[ m.lnCleanLines, 3]
ENDIF
THIS.SetCodeLine( m.lnCleanLines, m.lcCleanLine)
ENDCASE
ENDDO
IF m.lcOldExact = "ON"
SET EXACT ON
ENDIF
*!* _MLINE = m.lnOldMline
SET MEMOWIDTH TO m.lnOldMemo
THIS.nCodeLines = m.lnCleanLines
THIS.lPreProcessed = .T.
IF NOT THIS.lError
IF NOT THIS.AfterPreProcessCode()
IF NOT THIS.lError
THIS.SetError( "AfterPreprocessCode hook returned FALSE." )
ENDIF
RETURN .F.
ENDIF
ENDIF
RETURN NOT THIS.lError
ENDFUNC && PreProcessCode
* ------------------------------------------------ *
FUNCTION AfterPreProcessCode && <<-- Hook!
RETURN .T. && .F. to kill processing
ENDFUNC && AfterPreProcessCode
* ------------------------------------------------ *
FUNCTION SetCodeLine( lnLine, lcCode)
THIS.aCode[ m.lnLine, 1] = m.lcCode
THIS.aCode[ m.lnLine, 2] = THIS.nRawStart
THIS.aCode[ m.lnLine, 3] = THIS.nRawEnd
ENDFUNC && SetCodeLine
* ------------------------------------------------ *
FUNCTION InvertScript