Question
Dominic Chui · Oct 11, 2021

Relatively Quick and Simple Way to Convert from Old Dot Scoping to New Parentheses Scoping?

Does anyone know of a relatively quick and straightforward way of converting code written in the old dot scoping syntax with argumentless do (see here for reference: https://docs.intersystems.com/latest/csp/docbook/DocBook.UI.Page.cls?KEY=RCOS_cdo_legacy) to the modern parentheses scoping syntax? It's not too bad to do it by hand, but it's also easy to make a mistake and leave a "quit" in an if statement by accident for example.

E.g.

do:someCondition

.set value = do ##class(ExampleClass).ExampleFunctionThatReturnsValue()  quit:'value

.do ##class(ExampleClass).ExampleOtherFunction()

Should probably become

if someCondition {

    set value = do ##class(ExampleClass).ExamplesFunctionThatReturnsValue()    

    if value {

            do ##class(ExampleClass).ExampleOtherFunction()

    }

}

where the quit is removed to avoid premature exiting of the larger code block.

0
0 465
Discussion (10)2
Log in or sign up to continue

If this change is to be made in a multitude of classes, I advise writing a computer program that will perform this task.
But is it necessary to do it?

Considering the complexity of the task of parsing, I'm not sure it makes too much sense to write one from scratch. I was hoping to see if someone else had already done the work or tried it in the past.

In terms of is it necessary, not strictly no, but the modern parentheses syntax is much easier to read, maintain and modify.

It´s really curious that nobody yet has create such a converter. I´ve been thinking of it for years, but once I start thinking of all the possibilities a real parser needed for this got to treat, I give up.

Dot syntax (argumentless do) and one letter commands (and a lot of them in the same line) was very tempting and addictive on those old days.

But now, the new generation which got all those more modern equivalent and ubiquitous styles for flow control, which althought much less terse than the one-line program style of old days is much less prone to errors as the code evolves, should not need to learn to read and to write in that cryptic dialet.

Not anyone at all in ISC was ever tempted with the idea of this dot2braces converter?
 

I'm not so convinced that such a converter never existed.
Probably in past  ~20yrs+ back when the improved readable syntax
came in place at the beginning of the millennium.
And taking this time gap the engineers of such tools are
most likely not available anymore in whatever sense. 

Otherwise it is still working and used in system routines like
%R.int, %RI.int, %RIMF.int, %RO.int, %ROMFOLD.int, %ROU.int, %RSET.int, ....

Just to put things in right perspektive, those "one letter commands" and "a lot of them in the same line" were neither tempting nor addictive, they were simply necessity!

At the time of the birth of MUMPS (the core of Cache/IRIS/etc.), more then 50 years ago in the second half of 1960es,  memory (which was a real core memory at the time) was rare and expensive and was measured in units of kilobytes! Just to contrast, today's server have the same amount of RAM, but in gigabytes, that's a factor of one million!

As a consequence of memory shortage and because MUMPS of that time was interpreted (i.e. you loaded the sourcecode into memory), one had to utilize each and every possibility to save memory. One of those possibilities were the ability of the language to short each command to one letter and to put as many commads as possible into one line (thereby saving line-ending bytes).

The tools (to save memory) of that (ancient) time were argumentless IFs and ELSES, short (variable-, global- and routine) names, commands with postcondition and sophisticated programming.

Last but not least, if one aims to "modernize" thos old applications, should be keept in mind, especially, if one is not so familiar with the old fashioned style and methods, there will be many unexpected pitfalls.

Sample1: on old printers, the line with "Total..." will be printed "bold-alike"

 write "last item",?15,$j($fn(val,",",2),10),!
 write ?15,"----------",! do  do  do
 . write $c(13),"Total",?15,$j($fn(sum,",",2),10)
 write !!!,"Due date for payment ....",!

Sample2: converting from:

 ; normal flow
 do
 . ; nested
 . ; commands
 ; normal flow

into:

 ; normal flow
 if 1 {
   ; nestd
   ; commands
 }
 ; normal flow

will be in most cases OK, except, if the nested part uses the current value of $STACK:
this is now one less then in case of argumentless DO!

But don´t you think that code written with one letter commands can be read a liitle bit faster than otherwise?

Considering the last conversion example you forgot the need to treat all que quits (generally with postconditionals) which tend to occur inside argumentless do blocks. I´m dealing with them right now in my first dot2braces procedure. I am going to convert them into If '(condition) { ... }. 

I´ve first tried justo to convert do to do { ... } while 0 so as the quits could be preserved untouched.
But in my first candidate test routine I faced a not unusual "I Condition D" which could be converted by also converting the old style I(f) to new style just adding another pair of braces. But I´ve also run uppon a "I Condition D ... E ", which demands that the Else is also converted to the new style.

Any better idea?



 

When it comes to parsing a MUMPS or COS routine the class %Library.SyntaxColor can be helpful.  It works on Windows machines with Cache or IRIS.  It can be used to convert a routine to what the documentation calls a CSV listing.  This listing separates and clearly identifies each element of the code.

Another piece to remember is that the conversion being discussed is changing the language from MUMPS to Cache Object Script (COS).  So far Cache/IRIS can handle both languages and a mix, but mixed has been discouraged by company coding standards.  Once you start using the braces {} your code will not work on the ANSI standard MUMPS.

One more thing to be aware of, is that the QUIT command works differently in COS.  In many looping situations the CONTINUE command is the correct choice replace the QUIT.  Check out the documentation to determine the difference between QUIT and CONTINUE.

I used the %SyntaxColor help to identify each command in a line and the aproach of converting DO  to DO { ... } While 0 in order to leave the QUITs untouched.

%zDot2Braces(routine,indent=4)
    NEW (routine,indent)
    SET S=$CHAR(127)
    SET lineNumber=0
    KILL level
    SET level=0
    SET rm=##class(%Regex.Matcher).%New("dummy")
    KILL postCommands,whileLoop
    SET sc=##class(%Atelier.v2.Utils.TextServices).GetTextAsArray(routine,0,.moduleTextArray)
    IF 'sc DO $system.OBJ.DisplayError(sc)
    KILL:moduleTextArray(1)?1"ROUTINE [Type=".moduleTextArray(1)
    SET instr=##class(%Stream.TmpCharacter).%New()
    FOR  {
        SET lineNumber=$order(moduleTextArray(lineNumber),1,line) 
        QUIT:lineNumber=""
        QUIT:lineNumber'=+lineNumber
        DO instr.WriteLine(line)
    }
    SET colorer=##class(%SyntaxColor).%New()
    SET outstr=##class(%Stream.TmpCharacter).%New()
    SET sc=colorer.Color(instr,outstr,$select($zconvert($piece(routine,".",*),"U")="CLS":"CLS",1:"COS"),"Q=N",,,.langs,.coloringerrors)
    IF 'sc {
        WRITE "Fatal error: ",colorer.DLLResultCode,!
        Return
    }
    IF coloringerrors 
        WRITE "Syntax error(s)",!
        Return
    }
    Set lastPostCommands=""
    FOR lineNumber=1:1 {
        SET line=$$getParsedLine(.cmdpos)
        QUIT:line=-1
        SET midCode=""
        SET lineLevel=$$lineLevel(line)
        FOR i=1:1:$length(line) quit:" "_$CHAR(9)'[$EXTRACT(line,i)
        SET lineMargin=$extract(line,1,i-1),line=$extract(line,i,*)
        Set postDone=""
        WHILE lineLevel<level {
            Set lastLineMargin=$P(level(level),S,2)
            FOR i=1:1:$P(level(level),S,1) {
                WRITE lastLineMargin,$justify("",(level-1)*indent),$s(lineMargin=""&(level'>1):" ",1:""),"}"
                IF $get(whileLoop(level)) {
                    WRITE " While 0"
                    KILL whileLoop(level)
                    IF $LENGTH($zstrip($get(postCommands(level)),"<>W")) {
                        WRITE !,lastLineMargin,$justify("",(level)*indent),postCommands(level)
                        Set lastPostCommands=postCommands(level)
                        KILL postCommands(level)
                    }
                }    
                WRITE !
            }
            SET level=level-1
            Set postDone=1
        }
        SET line=$$codeQuotedSpaces(line)
        SET posDo=$locate(line,"\b[dD][oO]? ")
        SET:'posDo posDo=$locate(line,"\b[dD][oO]? *$")
        SET:'posDo posDo=$locate(line,"\b[dD][oO]?:[^ ]+ ")
        SET:'posDo posDo=$locate(line,"\b[dD][oO]?:[^ ]+ *$")
        Set nextCmd=""
        SET braceLevel=0
        IF posDo||(cmdpos && ('$G(cmdpos("FCWB"))&&("iIeE"[$extract(line,$o(cmdpos(""))-$length(lineMargin))))) {
            IF cmdpos>1 {
                SET cpos=9999,posEndCmd=$select($data(cmdpos("E"),payloadEnd):payloadEnd,1:$length(lineMargin_line))
                Set originalLine=line
                Set oldElse=""
                Set lastCmd=1
                FOR {
                    SET cpos=$order(cmdpos(cpos),-1)
                    QUIT:cpos=""
                    Set cmd=$extract(line,cpos-$length(lineMargin),posEndCmd-$length(lineMargin))
                    IF ('posDo||((cpos-$length(lineMargin))<posDo))&("iIfF"[$extract(cmd))&('lastCmd!'$locate(cmd,"[iI][fF]? 1")) {
                        SET $extract(line,posEndCmd-$length(lineMargin)) = $extract(line,posEndCmd-$length(lineMargin))_" { "
                        SET braceLevel=braceLevel + 1
                    }
                    IF $locate(cmd,"[eE]([lL][sS][eE])? ") {
                        IF $locate(lastPostCommands,"\b[iI][fF]? ") {
                            IF nextCmd?1(1"i",1"I").{
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)+$length(nextCmd))="If '$Test , "
                            }
                            ELSE {
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)-1)="If '$Test { "
                                SET braceLevel=braceLevel + 1
                            }
                        }
                        ELSE {
                            IF nextCmd?1(1"i",1"I").{
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)+$length(nextCmd))="ElseIf "
                            }
                            ELSE {
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)-1)="Else { "
                                SET braceLevel=braceLevel + 1
                            }
                        }
                        Set lastPostCommands = ""
                        SET:1 oldElse=1
                    }
                    Set nextCmd=""
                    FOR i=0:1 Set carCmd=$extract(line,cpos+i-$length(lineMargin)) q:carCmd'?1A  SET nextCmd=nextCmd_carCmd
                    SET posEndCmd = cpos - 1
                    SET lastCmd = 0
                }
                ; Set:postDone&'posDo&'oldElse line=originalLine,braceLevel=0
            }
            If posDo {
                SET posFor=$locate(line,"\b[fF]([oO][rR])? .*")
                SET:'posFor posFor=$locate(line,"\b[fF]([oO][rR])? [%A-Za-z][A-Za-z0-9]* ?=")
                FOR pat="\b[dD][oO]? ","\b[dD][oO]?:([^ ]+) ","\b[dD][oO]? *$","\b[dD][oO]?:([^ ]+) *$" {
                  SET rm.Pattern=pat
                  SET rm.Text=line
                  SET line=rm.ReplaceFirst($select($find(pat,":"):"If $1 { ",1:"")_"Do {")
                  SET:rm.Locate(1) braceLevel=braceLevel+$select($find(pat,":"):2,1:1)
                  SET whileLoop(level+1)=1
                }
                SET postCommands(level+1) = $translate($piece(line,"{",*),S," ")
                SET line=$piece(line,"{",1,*-1)_"{ "
            }
        }
        SET line=$translate(line,S," ")
        WRITE $replace(lineMargin_$JUSTIFY("",level*indent)_$zstrip($piece(line,".",level+1,*),"<W"),$char(9)," "),!
        WRITE:$LENGTH(midCode) $replace(lineMargin_$JUSTIFY("",(level+1)*indent)_midCode,$CHAR(9)," "),!
        SET:posDo!(braceLevel) level=level+1,level(level)=braceLevel_S_lineMargin
    }
    QUIT
    
lineLevel(line)
    NEW (line)
    SET line=$translate(line," "_$CHAR(9),"")
    FOR i=1:1 quit:$extract(line,i)'="."
    QUIT i-1
codeQuotedSpaces(line)
    NEW (line)
    SET S=$CHAR(127)
    SET quoting=""
    FOR i=1:1:$length(line) 
        SET c=$extract(line,i) 
        IF c="""" {
            SET quoting='quoting
        }
        ElseIf c=" "&quoting {
            SET c=S
        }
        SET $extract(line,i)=c
    }
    QUIT line
getParsedLine(vetpos)
    KILL vetpos
    Set vetpos=0
    SET recLine="",cmdCount=0
    
    Do {
        SET token=$zstrip(outstr.ReadLine(),"<>W")
    WHILE token'="<line>"&'outstr.AtEnd
    
    RETURN:outstr.AtEnd -1
    
    FOR {
        SET token=$zstrip(outstr.ReadLine(),"<>W")
        QUIT:outstr.AtEnd
        QUIT:token="</line>"
        SET rm.Pattern="<([^>]*)>(.*)<\/([^>]*)>$"
        IF rm.Match(token) {
            SET:rm.Group(1)="Command" vetpos($LENGTH(recLine)+1)="",cmdCount = cmdCount+1
            Set:rm.Group(1)="Comment" vetpos("E")=$LENGTH(recLine)
            Set:(cmdCount=1)&(rm.Group(1)="Brace")&(rm.Group(2)="{") vetpos("FCWB")=1
            SET recLine = recLine_$ZCONVERT(rm.Group(2),"I","HTML")
        }
    }
    
    SET vetpos=cmdCount
    
    RETURN recLine

Sample of resulting convertion of JRNDUMP.int:

Two bug fixes:

  1. Reset $Test wherever needed
  2. Adjust position of posts commands in the case of IF ... DO:
%zDot2Braces(routine,indent=4)
    NEW (routine,indent)
    SET S=$CHAR(127)
    SET lineNumber=0
    KILL level
    SET level=0
    SET rm=##class(%Regex.Matcher).%New("dummy")
    KILL postCommands,whileLoop
    SET sc=##class(%Atelier.v2.Utils.TextServices).GetTextAsArray(routine,0,.moduleTextArray)
    IF 'sc DO $system.OBJ.DisplayError(sc)
    KILL:moduleTextArray(1)?1"ROUTINE [Type=".moduleTextArray(1)
    SET instr=##class(%Stream.TmpCharacter).%New()
    FOR  {
        SET lineNumber=$order(moduleTextArray(lineNumber),1,line) 
        QUIT:lineNumber=""
        QUIT:lineNumber'=+lineNumber
        DO instr.WriteLine(line)
    }
    SET colorer=##class(%SyntaxColor).%New()
    SET outstr=##class(%Stream.TmpCharacter).%New()
    SET sc=colorer.Color(instr,outstr,$select($zconvert($piece(routine,".",*),"U")="CLS":"CLS",1:"COS"),"Q=N",,,.langs,.coloringerrors)
    IF 'sc {
        WRITE "Fatal error: ",colorer.DLLResultCode,!
        Return
    }
    IF coloringerrors 
        WRITE "Syntax error(s)",!
        Return
    }
    Set lastPostCommands=""
    FOR lineNumber=1:1 {
        SET line=$$getParsedLine(.cmdpos)
        QUIT:line=-1
        SET midCode=""
        SET lineLevel=$$lineLevel(line)
        FOR i=1:1:$length(line) quit:" "_$CHAR(9)'[$EXTRACT(line,i)
        SET lineMargin=$extract(line,1,i-1),line=$extract(line,i,*)
        Set postDone=""
        WHILE lineLevel<level {
            Set lastLineMargin=$P(level(level),S,2)
            FOR i=1:1:$P(level(level),S,1) {
                WRITE lastLineMargin,$justify("",(level-1)*indent),$select(lastLineMargin=""&(level'>1):" ",1:""),"}"
                IF $get(whileLoop(level)) {
                    WRITE " While 0"
                    KILL whileLoop(level)
                }    
                IF i=($P(level(level),S,1)-1),$LENGTH($zstrip($get(postCommands(level)),"<>W")) {
                    WRITE !,lastLineMargin,$justify("",(level)*indent),postCommands(level)
                    Set lastPostCommands=postCommands(level)
                    KILL postCommands(level)
                }
                WRITE !
            }
            SET level=level-1
            Set postDone=1
        }
        SET line=$$codeQuotedSpaces(line)
        SET posDo=$locate(line,"\b[dD][oO]? ")
        SET:'posDo posDo=$locate(line,"\b[dD][oO]? *$")
        SET:'posDo posDo=$locate(line,"\b[dD][oO]?:[^ ]+ ")
        SET:'posDo posDo=$locate(line,"\b[dD][oO]?:[^ ]+ *$")
        Set nextCmd=""
        SET braceLevel=0
        IF posDo||(cmdpos && ('$G(cmdpos("FCWB"))&&("iIeE"[$extract(line,$o(cmdpos(""))-$length(lineMargin))))) {
            Set ELSEcmd=""
            IF cmdpos>1 {
                SET cpos=9999,posEndCmd=$select($data(cmdpos("E"),payloadEnd):payloadEnd,1:$length(lineMargin_line))
                Set originalLine=line
                Set oldElse=""
                Set lastCmd=1
                FOR {
                    SET cpos=$order(cmdpos(cpos),-1)
                    QUIT:cpos=""
                    Set cmd=$extract(line,cpos-$length(lineMargin),posEndCmd-$length(lineMargin))
                    IF ('posDo||((cpos-$length(lineMargin))<posDo))&("iIfF"[$extract(cmd))&('lastCmd!'$locate(cmd,"[iI][fF]? 1")) {
                        SET $extract(line,posEndCmd-$length(lineMargin)) = $extract(line,posEndCmd-$length(lineMargin))_" { "
                        SET braceLevel=braceLevel + 1
                    }
                    IF $locate(cmd,"[eE]([lL][sS][eE])? ") {
                        IF $locate(lastPostCommands,"\b[iI][fF]? ") {
                            IF nextCmd?1(1"i",1"I").{
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)+$length(nextCmd))="If '$Test , "
                            }
                            ELSE {
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)-1)="If '$Test { "
                                SET braceLevel=braceLevel + 1
                            }
                            Set ELSEcmd=1
                        }
                        ELSE {
                            IF nextCmd?1(1"i",1"I").{
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)+$length(nextCmd))="ElseIf "
                            }
                            ELSE {
                                SET $extract(line,cpos-$length(lineMargin),cpos-$length(lineMargin)+$length(cmd)-1)="Else { "
                                SET braceLevel=braceLevel + 1
                            }
                        }
                        Set lastPostCommands = ""
                        SET:1 oldElse=1
                    }
                    Set nextCmd=""
                    FOR i=0:1 Set carCmd=$extract(line,cpos+i-$length(lineMargin)) q:carCmd'?1A  SET nextCmd=nextCmd_carCmd
                    SET posEndCmd = cpos - 1
                    SET lastCmd = 0
                }
                ; Set:postDone&'posDo&'oldElse line=originalLine,braceLevel=0
            }
            If posDo&cmdpos {
                SET posFor=$locate(line,"\b[fF]([oO][rR])? .*")
                SET:'posFor posFor=$locate(line,"\b[fF]([oO][rR])? [%A-Za-z][A-Za-z0-9]* ?=")
                FOR pat="\b[dD][oO]? ","\b[dD][oO]?:([^ ]+) ","\b[dD][oO]? *$","\b[dD][oO]?:([^ ]+) *$" {
                  SET rm.Pattern=pat
                  SET rm.Text=line
                  SET line=rm.ReplaceFirst($select($find(pat,":"):"If $1 { ",1:"")_"Do {")
                  SET:rm.Locate(1) braceLevel=braceLevel+$select($find(pat,":"):2,1:1)
                  SET whileLoop(level+1)=1
                }
                SET postCommands(level+1) = $translate($piece(line,"{",*),S," ")
                SET line=$piece(line,"{",1,*-1)_"{ "
                IF $locate(postCommands(level+1),"\b[iI][fF]? ")&'ELSEcmd {
                    WRITE $replace(lineMargin_$JUSTIFY("",level*indent)_"Set $Test=0",$char(9)," "),!
                }
            }
        }
        SET line=$translate(line,S," ")
        WRITE $replace(lineMargin_$JUSTIFY("",level*indent)_$zstrip($piece(line,".",level+1,*),"<W"),$char(9)," "),!
        WRITE:$LENGTH(midCode) $replace(lineMargin_$JUSTIFY("",(level+1)*indent)_midCode,$CHAR(9)," "),!
        SET:posDo!(braceLevel) level=level+1,level(level)=braceLevel_S_lineMargin
    }
    QUIT
    
lineLevel(line)
    NEW (line)
    SET line=$translate(line," "_$CHAR(9),"")
    FOR i=1:1 quit:$extract(line,i)'="."
    QUIT i-1
codeQuotedSpaces(line)
    NEW (line)
    SET S=$CHAR(127)
    SET quoting=""
    FOR i=1:1:$length(line) 
        SET c=$extract(line,i) 
        IF c="""" {
            SET quoting='quoting
        }
        ElseIf c=" "&quoting {
            SET c=S
        }
        SET $extract(line,i)=c
    }
    QUIT line
getParsedLine(vetpos)
    KILL vetpos
    Set vetpos=0
    SET recLine="",cmdCount=0
    
    Do {
        SET token=$zstrip(outstr.ReadLine(),"<>W")
    WHILE token'="<line>"&'outstr.AtEnd
    
    RETURN:outstr.AtEnd -1
    
    FOR {
        SET token=$zstrip(outstr.ReadLine(),"<>W")
        QUIT:outstr.AtEnd
        QUIT:token="</line>"
        SET rm.Pattern="<([^>]*)>(.*)<\/([^>]*)>$"
        IF rm.Match(token) {
            SET:rm.Group(1)="Command" vetpos($LENGTH(recLine)+1)="",cmdCount = cmdCount+1
            Set:rm.Group(1)="Comment" vetpos("E")=$LENGTH(recLine)
            Set:(cmdCount=1)&(rm.Group(1)="Brace")&(rm.Group(2)="{") vetpos("FCWB")=1
            SET recLine = recLine_$ZCONVERT(rm.Group(2),"I","HTML")
        }
    }
    
    SET vetpos=cmdCount
    
    RETURN recLine