home
***
CD-ROM
|
disk
|
FTP
|
other
***
search
/
Freesoft 1997 March
/
Freesoft_1997-03_cd.bin
/
nerecenz
/
internet
/
webexplr
/
CODEBLCK.PRG
< prev
next >
Wrap
Text File
|
1997-03-04
|
26KB
|
977 lines
* CodeBlck.PRG
*
* FoxPro 2.5a (or later) Code Block Interpreter
*
* Created by: Randy Pearson, CYCLA Corporation
* Modified by: Ken R. Levy
* Revision 1.0(e), October 26, 1996
*
* DO NOT rename this file! This program uses recursion (i.e.,
* calls itself) to handle nested programming constructs. If
* you must rename this file, be sure to revise all lines of code
* that include CodeBlck() calls.
*
* General Strategy:
* If code were all simple "in-line" code (no SCAN, DO, etc.),
* it is easy to run uncompiled by simply storing each line to
* a memory variable and then macro substituting each line.
*
* Thus, we adopt that approach, but when we encounter any control
* structure, we capture the actual code block within the structure,
* create an artificial simulation of the structure, and pass the
* internal code block recursively to this same routine. Nesting
* is handled automatically by this approach.
*
* Limitations:
* - Does not support embedded subroutines (PROC or FUNC)
* within code passed.
* Performs implied RETURN .T. if subroutine found.
* To use UDF's, capture each PROC/FUNC as its own code
* block and call CODEBLCK repeatedly as needed.
* - Doesn't accept TEXT w/ no ENDTEXT (although FP doc's
* suggest that this is acceptable to FP.
*
* Notes:
* All variables declared for usage by this routine have a
* prefix of _0_q (2 underscores, number zero, letter queue)
* in an attempt to minimize the chances of conflicts with
* variables in running programs.
*
* All FUNCTIONs are similarly named to avoid problems where
* the users code includes a DO <FunctionName> and that name
* matches a subroutine herein, which would be higher in the
* calling stack.
*
* No subroutines are called during _execution_ phases in order
* to minimize the depth of DO calls. FoxPro has a maximum
* of 32. My APPS are usually 10-16 levels deep at any time.
* By keeping all calls at top level, we only use up one level
* for each level of nesting in the code being processed.
*
* If your code block begins with a semicolon ";", the block is
* assumed to be a dBW-style code block, and all semicolons are
* translated to Cr-Lf pairs for the execution in this routine.
* (Existing code in files is not altered.)
*
* You may want to SET TALK OFF when testing this program from
* the Command Window.
PARAMETER _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.]
* Record of Revision
* ==================
* Initials in brackets are credits to testers/users that found
* bugs or provided enhancement requests.
* [KL] = Ken Levy
* [BA] = Bill Anderson
* [TG] = Tom Gehrke
* [RP] = Randy Pearson
* 09/21/1994
* - Supports DO CODEBLCK, etc., from within another code block!
* Pass 2nd parameter as -1 to indicate recursion, rather than
* checking the program stack. Thus a code block can call
* CODEBLCK and it won't look like recursion. [BA, KL]
* - Trap for unsupportable commands CLEAR MEMORY, CLEAR ALL and
* RESTORE FROM w/o ADDITIVE. [BA]
* - Call error routine manually when unsupported statements found. [RP]
* - Adjusted SIZE of Edit Region for _MAC screens. [BA]
* - Added LEFT( , 254) to WAIT WINDOW in error handler, to avoid
* FP error if total expression exceeds 254 characters. [KL]
* - Expanded main CASE structure to detect orphaned END statements
* with no matching beginning statement. [RP]
* - Revised to RETURN .F. if file not found (or selected by user),
* or if user presses <Cancel> or doesn't enter any code. [RP]
* 09/14/1994
* - Revised to RETURN .T. if no RETURN <something> found, EXCEPT
* RETURN .F. if errors occur. [KL]
* - Revised to intercept dBW-style code blocks begining with
* and using semicolons as line breaks, and translating them
* to Cr-Lf pairs for this routine. [KL]
* - Removed bell in on error routine. [KL]
* - Changed all TYPE() function calls to use == operator, because
* dBW's TYPE() function now returns some 2-character codes (dBW
* programmers take note!) such as "CB", and we cannot be certain
* that FP 3.0 won't too. Existing code like TYPE("myvar") = "C"
* may break in dBW under some circumstances. [RP]
* 09/03/1994
* - Made determination of whether called recursively bullet-proof
* by analyzing entire program stack. [RP]
* - Cleaned up handling of FOR..ENDFOR and eliminated several
* unused memory variables. [RP]
* - Made several changes to streamline EXIT process when errors
* are encountered. [RP, KL]
* - Always return .T. if no code passed or file not found. [RP]
* - Made consistent the handling of "null blocks" (e.g., a SCAN
* ENDSCAN with no code in between. [RP]
* - Revised ON ERROR display to clarify that the Line ## is the
* CODEBLCK.PRG Line ##, not the line in the user's code. [TG]
*
* 09/02/1994
* - Changed name of program to CODEBLCK.PRG (from ZZRUNPRG.PRG). [KL]
* - Removed assumption that TEXTMERGE DELIMITERS were set to
* default values << >>. [RP]
* - Fixed bug where TEXT..ENDTEXT only worked correctly when
* SET TEXTMERGE was ON. [RP]
* - Revised font definitions for Mac. [BA]
* - Changed KEYCOMP to WINDOWS during code edit. [BA]
* - Localized setting/resetting of MEMOWIDTH in case users code
* depends on current setting. [RP]
* - Corrected various typos. [RP, TG]
* - Revised ON ERROR to send/receive MESSAGE(1), and optionally
* display it if there is no code being executed (i.e., it's my
* error rather than the users). [RP]
* - Revised routines to work properly if user has SET EXACT ON,
* by SET EXACT OFF when needed and lots of PADR() stuff. [RP]
* - Corrected bug where user's macro substitution didn't work
* within CASE, DO WHILE, and FOR. Lesson: If an expression
* might contain '&', always & it rather than EVAL() it. [BA, RP]
* 10/26/1996
* - Changed syntax error on VFP 5.0 compile. [KL]
* 3/4/1997
* - Added check to ignore HTML comment lines
#DEFINE dnMaxNest 32
* Maximum DO nesting.
#DEFINE dnMemWidth 254
#DEFINE crLf CHR(13) + CHR(10)
* Carriage Return + Line Feed
PRIVATE _0_qnLines ;; # lines of code
PRIVATE _0_qnNext ;; Line # of next line of code
PRIVATE _0_qMemoW ;; previous SET MEMO setting
PRIVATE _0_qcLine1 ;; current line of code being processed
PRIVATE _0_qcUpper ;; UPPER() of same
PRIVATE _0_qxRet ;; Proposed RETURN value
PRIVATE _0_qcExpr ;; Fragment of control code line
PRIVATE _0_qnAtPos ;; Result of misc. AT() function calls.
PRIVATE _0_qcBlk ;; Nested block of code to pass recursively
PRIVATE _0_qnCount ;; Counter for misc. loops
PRIVATE _0_qlTop ;; Flag if top of recursion
IF TYPE( "m._0_qlFile") == "N" AND m._0_qlFile = -1
* Program called recursively.
_0_qlTop = .F.
ELSE
* First call to program.
_0_qlTop = .T.
* Establish "thread" control variables:
PRIVATE _0_qcExit ;; EXIT/LOOP/RETURN passback variable
PRIVATE _0_qcError ;; old ON ERROR process
_0_qcExit = SPACE(0)
_0_qcError = ON( "ERROR")
IF EMPTY(_0_qcError)
ON ERROR DO _0_qError WITH ;
ERROR(), LINENO(), MESSAGE(), MESSAGE(1)
ENDIF
* --- Deal with different calling methods, only
* --- applies to first call (not recursion):
IF m._0_qlFile
* File name as 1st parameter.
DO CASE
CASE EMPTY( m._0_qcCode) OR NOT TYPE("m._0_qcCode") == 'C'
_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.
DO _0_qRestE
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.
_0_qcCode = STRTRAN( m._0_qcCode, ";", CrLf)
ENDIF
IF m._0_qlEdit
* Allow user to enter/edit code:
DO _0_qInput
IF NOT EMPTY( m._0_qcCode) AND ;
PROGRAM(1) == "CODEBLCK"
*
* Run from Command Window - save code
* so user can retry:
IF TYPE("m._0_qcPrev") == "U"
PUBLIC _0_qcPrev
_0_qcPrev = m._0_qcCode
ELSE
IF TYPE("m._0_qcPrev") == "C"
_0_qcPrev = m._0_qcCode
ENDIF
ENDIF [program previously used]
ENDIF [from Command window]
ENDIF [allow user to enter code]
IF EMPTY( m._0_qcCode)
* Still no code.
DO _0_qRestE
RETURN .F.
ENDIF [no code supplied to run]
ENDIF [called recursively]
_0_qMemoW = SET("MEMOWIDTH")
SET MEMOWIDTH TO dnMemWidth
_0_qnLines = MEMLINES( m._0_qcCode)
SET MEMOWIDTH TO m._0_qMemoW
_0_qnNext = 1
_0_qcLine1 = ""
_0_qxRet = .T.
DO WHILE m._0_qnNext <= m._0_qnLines
_0_qcLine1 = _0_qLine()
_0_qcUpper = UPPER( m._0_qcLine1)
DO CASE
CASE EMPTY( m._0_qcLine1)
* Almost assuredly past end.
LOOP
CASE PADR( m._0_qcUpper, 8) == "DO WHILE"
_0_qcExpr = SUBSTR( m._0_qcLine1, 9)
_0_qcBlk = _0_qBlock( 'DO WHILE')
DO WHILE &_0_qcExpr
IF NOT EMPTY( m._0_qcBlk)
_0_qxRet = CodeBlck( m._0_qcBlk, -1)
ENDIF
IF NOT EMPTY( m._0_qcExit)
IF m._0_qcExit = 'LOOP'
_0_qcExit = SPACE(0)
LOOP
ENDIF
IF m._0_qcExit = 'EXIT'
_0_qcExit = SPACE(0)
ENDIF
EXIT
ENDIF
ENDDO
CASE PADR( m._0_qcUpper, 4) == "SCAN"
_0_qcExpr = IIF( ALLTRIM( m._0_qcUpper) == "SCAN", ;
SPACE(0), ALLTRIM( SUBSTR( m._0_qcLine1, 5)))
_0_qcBlk = _0_qBlock( 'SCAN')
SCAN &_0_qcExpr
IF NOT EMPTY( m._0_qcBlk)
_0_qxRet = CodeBlck( m._0_qcBlk, -1)
ENDIF
IF NOT EMPTY( m._0_qcExit)
IF m._0_qcExit = 'LOOP'
_0_qcExit = SPACE(0)
LOOP
ENDIF
IF m._0_qcExit = 'EXIT'
_0_qcExit = SPACE(0)
ENDIF
EXIT
ENDIF
ENDSCAN
CASE PADR( m._0_qcUpper, 3) == "FOR"
_0_qcExpr = SUBSTR( m._0_qcLine1, 4)
_0_qcBlk = _0_qBlock( 'FOR')
FOR &_0_qcExpr
*
IF NOT EMPTY( m._0_qcBlk)
_0_qxRet = CodeBlck( m._0_qcBlk, -1)
ENDIF
IF NOT EMPTY( m._0_qcExit)
IF m._0_qcExit = 'LOOP'
_0_qcExit = SPACE(0)
LOOP
ENDIF
IF m._0_qcExit = 'EXIT'
_0_qcExit = SPACE(0)
ENDIF
EXIT
ENDIF
ENDFOR
CASE PADR( m._0_qcUpper, 2) == "IF"
_0_qcExpr = ALLTRIM( SUBSTR( m._0_qcLine1, 3))
IF &_0_qcExpr
_0_qcBlk = _0_qBlock( "IF")
ELSE
_0_qcBlk = _0_qBlock( "ELSE")
ENDIF
IF NOT EMPTY( m._0_qcBlk)
_0_qxRet = CodeBlck( m._0_qcBlk, -1)
ENDIF
CASE PADR( m._0_qcUpper, 7) == "DO CASE"
_0_qcBlk = _0_qBlock( "DO CASE")
* _0_qBlock() figures out which case to use.
IF NOT EMPTY( m._0_qcBlk)
_0_qxRet = CodeBlck( m._0_qcBlk, -1)
ENDIF
CASE PADR( m._0_qcUpper, 4) == "TEXT"
_0_qcBlk = _0_qBlock( 'TEXT')
_0_qMemoW = SET("MEMOWIDTH")
SET MEMOWIDTH TO dnMemWidth
FOR _0_qnCount = 1 TO MEMLINES( m._0_qcBlk)
_0_qcExpr = "\" + MLINE( m._0_qcBlk, m._0_qnCount)
&_0_qcExpr
ENDFOR
SET MEMOWIDTH TO m._0_qMemoW
CASE PADR( m._0_qcUpper, 4) == "LOOP"
_0_qcExit = "LOOP"
EXIT
CASE PADR( m._0_qcUpper, 4) == "EXIT"
_0_qcExit = "EXIT"
EXIT
* Prototype for statements to disallow. Remove comments or
* re-write portions if you want to disallow these.
*
* CASE INLIST( PADR( m._0_qcUpper, 4), "CANC", "QUIT")
* _0_qcExit = "ILLEGAL"
* _0_qxRet = .F.
* EXIT
CASE PADR( m._0_qcUpper, 9) == "CLEAR ALL" OR ;
PADR( m._0_qcUpper, 8) == "CLEA ALL" OR ;
PADR( m._0_qcUpper, 10) == "CLEAR MEMO" OR ;
PADR( m._0_qcUpper, 9) == "CLEA MEMO" OR ;
PADR( m._0_qcUpper, 7) == "RETU TO" OR ;
PADR( m._0_qcUpper, 8) == "RETUR TO" OR ;
PADR( m._0_qcUpper, 9) == "RETURN TO"
*
* These are known to break the system.
_0_qcExit = "ILLEGAL"
_0_qxRet = .F.
EXIT
CASE PADR( m._0_qcUpper, 4) == "REST" AND ;
"FROM " $ m._0_qcUpper AND ;
NOT "ADDI" $ m._0_qcUpper
*
* Can't have RESTORE FROM w/o ADDITIVE.
_0_qcExit = "ILLEGAL"
_0_qxRet = .F.
EXIT
CASE INLIST( PADR( m._0_qcUpper, 4), "PROC", "FUNC")
* Probably NOT good news, but maybe OK.
* This program does not support embedded PROC's
* and FUNC's. It can only call compiled routines.
_0_qcExit = "RETURN"
_0_qxRet = .T.
CASE INLIST( PADR( m._0_qcUpper, 4), ;
"ENDS", "ENDD", "ENDF", "ENDI", ;
"NEXT", "ENDC", "ENDT", "ELSE", "CASE")
*
* Nesting error in user's code.
_0_qnAtPos = AT( SPACE(1), m._0_qcUpper)
_0_qcExpr = LEFT( m._0_qcUpper, ;
IIF( m._0_qnAtPos = 0, 7, m._0_qnAtPos - 1))
WAIT WINDOW [Nesting Error - "] + m._0_qcExpr + ;
[" statement found, ] + CrLf + ;
[but there was no matching beginning statement.] NOWAIT
_0_qcExit = "ERROR"
_0_qxRet = .F.
EXIT
CASE PADR( m._0_qcUpper, 4) == "RETU"
_0_qcExit = "RETURN"
_0_qxRet = .T.
_0_qnAtPos = AT( SPACE(1), m._0_qcLine1)
IF m._0_qnAtPos > 0
_0_qcExpr = ALLTRIM( SUBSTR( m._0_qcLine1, m._0_qnAtPos))
IF NOT EMPTY( m._0_qcExpr)
* RETURN <something>
_0_qxRet = EVAL( m._0_qcExpr)
ENDIF
ENDIF
OTHERWISE
IF EMPTY( m._0_qcExit)
* Just do it:
&_0_qcLine1
ENDIF
ENDCASE
IF NOT EMPTY( m._0_qcExit)
* Some exit code encountered.
EXIT
ENDIF
ENDDO
SET MEMOWIDTH TO m._0_qMemoW
IF m._0_qcExit = "ILLEGAL"
DO _0_qError WITH 9999, 0, ;
"Unsupported Code Block statement", ;
m._0_qcLine1
ENDIF
IF m._0_qlTop
* leaving for good
DO _0_qRestE
ENDIF
IF m._0_qcExit == "ERROR"
_0_qxRet = .F.
ENDIF
RETURN m._0_qxRet
* -------------------------------------------------------- *
FUNCTION _0_qRestE
*
* Restore environment.
*
IF EMPTY( m._0_qcError)
ON ERROR
ELSE
ON ERROR &_0_qcError
ENDIF
RETURN .T.
* -------------------------------------------------------- *
FUNCTION _0_qFile
*
* Get file contents.
*
PARAMETER pcFile
IF NOT FILE( m.pcFile)
RETURN SPACE(0)
ENDIF
PRIVATE 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
* -------------------------------------------------------- *
PROCEDURE _0_qError
*
* ON ERROR routine
*
PARAMETERS pnError, pnLineNo, pcMessage, pcMessage1
* ?? CHR(7) + CHR(7)
WAIT WINDOW LEFT( ;
"* CODE BLOCK RUNTIME ERROR *" + CrLf + CrLf + ;
"Error: " + LTRIM( STR( m.pnError)) + " occurred." + CrLf + ;
"Mes'g: " + m.pcMessage + CrLf + ;
"Code.: " + LEFT( IIF( TYPE( "m._0_qcLine1") == "C", ;
m._0_qcLine1, m.pcMessage1), 50) + CrLf + ;
"Modul: CodeBlck.PRG, Line: " + LTRIM( STR( m.pnLineNo)), ;
254) NOWAIT
*/ SUSPEND
STORE "ERROR" TO m._0_qcExit
STORE .F. TO m._0_qxRet
RETURN .T.
* -------------------------------------------------------- *
FUNCTION _0_qInput
*
* Allow EDIT of code.
*
PRIVATE lcControl, lcLastKeyC
lcControl = "Execute"
IF _WINDOWS OR _MAC
lcLastKeyC = SET( "KEYCOMP")
SET KEYCOMP TO WINDOWS
ENDIF
DO CASE
CASE _MAC
DEFINE WINDOW _0_qInput ;
AT 1, 0 ;
SIZE 16, 78 ;
TITLE " FoxPro Code Block Interpreter " ;
FONT "Geneva", 10 ;
STYLE "B" ;
COLOR RGB(,,,192,192,192) ;
FLOAT NOMDI
CASE _WINDOWS
DEFINE WINDOW _0_qInput ;
AT 1, 0 ;
SIZE 16, 78 ;
TITLE " FoxPro Code Block Interpreter " ;
FONT "MS Sans Serif", 9 ;
STYLE "B" ;
COLOR RGB(,,,192,192,192) ;
FLOAT NOMDI
OTHERWISE
DEFINE WINDOW _0_qInput ;
AT 1, 0 ;
SIZE 16, 76 ;
TITLE " FoxPro Code Block Interpreter " ;
COLOR SCHEME 1 ;
SHADOW ;
FLOAT NOMDI
ENDCASE
ACTIVATE WINDOW _0_qInput NOSHOW
IF _MAC
@ 1, 1 SAY "Enter code to run:" FONT "Geneva", 10 STYLE [B]
@ 2.2, 2 EDIT m._0_qcCode ;
SIZE 10, 87 ;
FUNCTION [3] ;
FONT "Monaco", 9 ;
SCROLL ;
TAB ;
MESSAGE "Press {Ctrl}+{Tab} to Exit Box"
@ 13, 25 GET m.lcControl ;
PICTURE "@*HT Execute;\?Cancel" ;
SIZE 1.7, 12, 5 ;
FONT "Chicago", 12 ;
MESSAGE "Press EXECUTE to Run Code, CANCEL to Skip"
ELSE
@ 1, 1 SAY "Enter code to run:"
@ 2.2, 2 EDIT m._0_qcCode ;
SIZE 8, 64 ;
FONT "Courier New", 8 ;
SCROLL ;
TAB ;
MESSAGE "Press {Ctrl}+{Tab} to Exit Box"
@ 13, 25 GET m.lcControl ;
PICTURE "@*HT \!Execute;\?Cancel" ;
SIZE 1.7, 12, 5 ;
MESSAGE "Press EXECUTE to Run Code, CANCEL to Skip"
ENDIF
MOVE WINDOW _0_qInput CENTER
SHOW WINDOW _0_qInput
READ CYCLE MODAL
DEACTIVATE WINDOW _0_qInput
IF NOT m.lcControl = 'Execute'
_0_qcCode = SPACE( 0)
ENDIF
RELEASE WINDOW _0_qInput
IF _WINDOWS OR _MAC
IF NOT m.lcLastKeyC = "WINDOWS"
SET KEYCOMP TO &lcLastKeyC
ENDIF
ENDIF
RETURN .T.
* -------------------------------------------------------- *
FUNCTION _0_qBlock
*
* Fetch block of code for recursive call, and increment
* pointer m._0_qnNext to point past end of block (e.g.,
* line after ENDCASE).
*
PARAMETER pcType
* {FOR, DO WHILE, IF, ELSE, DO CASE, SCAN, TEXT}
PRIVATE lcCodeBlk, lcLastExct
lcCodeBlk = SPACE(0)
lcLastExct = SET( 'EXACT')
SET EXACT OFF
PRIVATE laBlkStack, lnDepth
DIMENSION laBlkStack[ 1]
IF m.pcType == "ELSE"
laBlkStack[ 1] = "IF"
ELSE
laBlkStack[ 1] = m.pcType
ENDIF
lnDepth = 1
PRIVATE lcNext, lcUpper, lcSubstr
PRIVATE llSubSect, llTrueCase
llSubSect = NOT INLIST( m.pcType, "ELSE", "DO CASE")
* Flag of whether we're within
* a .T. case (thus code should
* be returned).
llTrueCase = .F.
* Flag of whether a .T. case has
* yet been found (thus don't evaluate
* further CASE's or process OTHERWISE).
DO WHILE NOT m._0_qcExit = "ERROR"
lcNext = _0_qLine( laBlkStack[ m.lnDepth])
IF m._0_qcExit = "ERROR"
* Error discovered by Line function.
EXIT
ENDIF
IF EMPTY( m.lcNext)
WAIT WINDOW "Nesting Error - no matching final END found " + ;
"for " + laBlkStack[ m.lnDepth] + "." NOWAIT
_0_qcExit = "ERROR"
EXIT
ENDIF
lcUpper = UPPER( m.lcNext)
DO CASE
CASE INLIST( m.lcUpper, "END", "NEXT")
* end of control structure
IF ( m.lcUpper = "ENDC" AND ;
INLIST( laBlkStack[ m.lnDepth], ;
"CASE", "OTHERWISE")) OR ;
( m.lcUpper = "ENDD" AND ;
laBlkStack[ m.lnDepth] = "DO WHILE") OR ;
( INLIST( m.lcUpper, "ENDF", "NEXT") AND ;
laBlkStack[ m.lnDepth] = "FOR") OR ;
( m.lcUpper = "ENDS" AND ;
laBlkStack[ m.lnDepth] = "SCAN") OR ;
( m.lcUpper = "ENDT" AND ;
laBlkStack[ m.lnDepth] = "TEXT") OR ;
( m.lcUpper = "ENDI" AND ;
INLIST( laBlkStack[ m.lnDepth], "ELSE", "IF"))
*
lnDepth = m.lnDepth - 1
IF m.lnDepth = 0
* Only valid exit point!
EXIT
ELSE
IF m.llSubSect
lcCodeBlk = m.lcCodeBlk + m.lcNext + CrLf
ENDIF
LOOP
ENDIF
ELSE
WAIT WINDOW "Nesting error. " + CrLf + ;
TRIM( PADR( m.lcUpper, 8)) + ;
" found, when matching begin " + ;
"line was " + laBlkStack[ m.lnDepth] + "." NOWAIT
_0_qcExit = "ERROR"
ENDIF
CASE UPPER( m.lcNext) = "ELSE"
IF laBlkStack[ m.lnDepth] = "IF"
laBlkStack[ m.lnDepth] = "ELSE"
IF m.lnDepth = 1
IF m.pcType == "IF"
m.llSubSect = .F.
ELSE
m.llSubSect = .T.
ENDIF
ELSE
IF m.llSubSect
lcCodeBlk = m.lcCodeBlk + m.lcNext + CrLf
ENDIF
ENDIF
LOOP
ELSE
WAIT WINDOW "ELSE nesting error - no matching IF. " NOWAIT
_0_qcExit = "ERROR"
ENDIF
CASE UPPER( m.lcNext) = "CASE"
IF INLIST( laBlkStack[ m.lnDepth], "DO CASE", "CASE")
laBlkStack[ m.lnDepth] = "CASE"
IF m.lnDepth = 1
IF m.llTrueCase
m.llSubSect = .F.
ELSE
lcSubstr = SUBSTR(m.lcNext, 5)
IF &lcSubstr
m.llTrueCase = .T.
m.llSubSect = .T.
ENDIF
ENDIF
ELSE
IF m.llSubSect
lcCodeBlk = m.lcCodeBlk + m.lcNext + CrLf
ENDIF
ENDIF
LOOP
ELSE
WAIT WINDOW "CASE nesting error - no matching DO CASE." NOWAIT
_0_qcExit = "ERROR"
ENDIF
CASE UPPER( m.lcNext) = "OTHE"
IF INLIST( laBlkStack[ m.lnDepth], "DO CASE", "CASE")
laBlkStack[ m.lnDepth] = "OTHERWISE"
IF m.lnDepth = 1
IF m.llTrueCase
m.llSubSect = .F.
ELSE
m.llSubSect = .T.
ENDIF
ELSE
IF m.llSubSect
lcCodeBlk = m.lcCodeBlk + m.lcNext + CrLf
ENDIF
ENDIF
LOOP
ELSE
WAIT WINDOW "OTHERWISE nesting error - no matching DO CASE." NOWAIT
_0_qcExit = "ERROR"
ENDIF
CASE INLIST( m.lcUpper, "IF", "DO WHIL", "SCAN", ;
"TEXT", "DO CASE", "FOR")
*
IF laBlkStack[ m.lnDepth] = "DO CASE"
WAIT WINDOW "Nesting error - DO CASE w/o CASE. " NOWAIT
_0_qcExit = "ERROR"
ELSE
lnDepth = m.lnDepth + 1
DIMENSION laBlkStack[ m.lnDepth]
DO CASE
CASE UPPER( m.lcNext) = "IF"
laBlkStack[ m.lnDepth] = "IF"
CASE UPPER( m.lcNext) = "DO WHIL"
laBlkStack[ m.lnDepth] = "DO WHILE"
CASE UPPER( m.lcNext) = "SCAN"
laBlkStack[ m.lnDepth] = "SCAN"
CASE UPPER( m.lcNext) = "TEXT"
laBlkStack[ m.lnDepth] = "TEXT"
CASE UPPER( m.lcNext) = "DO CASE"
laBlkStack[ m.lnDepth] = "DO CASE"
CASE UPPER( m.lcNext) = "FOR"
laBlkStack[ m.lnDepth] = "FOR"
OTHERWISE
WAIT WINDOW "Internal CODEBLCK consistency error." NOWAIT
_0_qcExit = "ERROR"
ENDCASE
IF m.llSubSect
lcCodeBlk = m.lcCodeBlk + m.lcNext + CrLf
ENDIF
LOOP
ENDIF
OTHERWISE
* legitmate in-line code
IF m.llSubSect
lcCodeBlk = m.lcCodeBlk + m.lcNext + CrLf
ENDIF
ENDCASE
ENDDO
IF m.lcLastExct == "ON"
SET EXACT ON
ENDIF
IF m._0_qcExit = "ERROR"
lcCodeBlk = SPACE(0)
ENDIF
RETURN m.lcCodeBlk
* -------------------------------------------------------- *
FUNCTION _0_qLine
*
* Return next line of code, ignoring comments and
* blank lines. Leave m._0_qnNext pointing to first
* text line after returned line of code. Return null string
* if no code line found to end of block.
*
* Assume m._0_qnNext points to line to read
* m._0_qnLines counts total # of lines
* m._0_qcCode contains the total code
*
PARAMETER pcType
* Type of inner most block. If "TEXT" skip almost
* all "conditioning" steps and take literally.
PRIVATE lcCode, lcUpper, lnMemoWidt, lcLastExct
lcCode = SPACE(0)
lnMemoWidt = SET( 'MEMOWIDTH')
lcLastExct = SET( 'EXACT')
SET EXACT OFF
PRIVATE llContinued, lnAtPos, llComment, llText
llContinued = .F.
lnAtPos = 0
llComment = .F.
llText = TYPE( "m.pcType") == "C" AND m.pcType == "TEXT"
DO WHILE m._0_qnNext <= m._0_qnLines
SET MEMOWIDTH TO dnMemWidth
DO CASE
CASE m.llText
* Within TEXT...ENDTEXT; leave alone.
lcCode = MLINE( m._0_qcCode, m._0_qnNext)
CASE m.llContinued
* 2nd or later line in multi-line
* statement; attach but don't LTRIM(),
* since we could be in middle of delimited string.
lcCode = m.lcCode + TRIM( ;
MLINE( m._0_qcCode, m._0_qnNext))
OTHERWISE
* Beginning of new line of normal code; LTRIM
* any indentation after removing TAB's.
lcCode = LTRIM( STRTRAN( ;
MLINE( m._0_qcCode, m._0_qnNext), ;
CHR(9), SPACE(1)))
IF EMPTY( m.lcCode) OR ;
INLIST( LTRIM( m.lcCode), "*", "&" + "&", "#", "<!--", "-->")
* Blank or comment line OR compiler directive.
* (Can't type 2 &'s together in FoxPro)
* (Probably if compiler directive, subsequent
* code will fail, but give it a try.)
lcCode = SPACE(0)
ENDIF
ENDCASE
SET MEMOWIDTH TO m.lnMemoWidt
_0_qnNext = m._0_qnNext + 1
IF m.llText
EXIT
ENDIF
IF EMPTY( m.lcCode)
LOOP
ENDIF
lnAtPos = AT( "&" + "&", m.lcCode)
* Note gymnastics to avoid compile error.
IF m.lnAtPos > 0
lcCode = TRIM( LEFT( m.lcCode, m.lnAtPos - 1))
llComment = .T.
ELSE
llComment = .F.
ENDIF
IF RIGHT( m.lcCode, 1) = ";"
IF m.llComment
* Not allowed on same line!
WAIT WINDOW "Syntax Error: Semi-Colon and " + ;
"double-& on same line." NOWAIT
_0_qcExit = "ERROR"
lcCode = SPACE(0)
EXIT
ELSE
llContinued = .T.
lcCode = LEFT( m.lcCode, LEN( m.lcCode) - 1)
LOOP
ENDIF
ELSE
* llContinued = .F.
EXIT
ENDIF
ENDDO
IF NOT m.llText
lcUpper = UPPER( m.lcCode)
IF m.lcUpper = "DO" AND ;
NOT INLIST( m.lcUpper, "DO WHILE", "DO CASE")
*
lcStub = LTRIM( SUBSTR( m.lcCode, 3))
lcUpper = UPPER( m.lcStub)
DO CASE
CASE INLIST( m.lcUpper, "WHILE", "CASE")
lcCode = "DO " + m.lcStub
CASE m.lcUpper = "WHIL"
lcCode = "DO WHILE " + SUBSTR( m.lcStub, 5)
OTHERWISE
* Hopefully DO <SomeLegitProcedure>
* Leave alone.
ENDCASE
ENDIF
ENDIF [NOT m.llText]
IF m.lcLastExct == "ON"
SET EXACT ON
ENDIF
RETURN m.lcCode
* -------------------------------------------------------- *