Excel VBA to OpenOffice Basic converter

YS, 2011-02-19:

Excel VBA to LibreOffice/OpenOffice.org basic converter.

The purpose of the scripts below is to convert Excel VBA macroses to OpenOffice/LibreOffice ones. They were tested on OpenOffice.org 3.2, actually, so may be not up-to-date. Tcl 8.6 is required.

DISCLAIMER: Some conversions are hardcoded (for example, ADO databases access conversion presumes that connection object is called Module1.conn), so read the sources, esp. vba2oo.tcl!

How to use:

0. Save all scripts (convmodule.tcl, convmfix.tcl and vba2oo.tcl) into the same directory.

1. Export modules` sources from Excel (OpenOffice had a bug with exporting modules > 64K).

2. Process exported file with convmodule.tcl:

> tclkit.exe convmodule.tcl module1.bas module1_out.bas

3. Process module1_out.bas with convmfix.tcl (you can skip this step):

> tclkit.exe convmfix.tcl module1_out.bas module1_out2.bas

4. Import module1_out2.bas into LibreOffice.

5. Add 'Option Compatible' to the top of the module (adds Enum support), remove useless event processing Subs.

6. Add these to global definitions:

Public ActiveDialog as Object

Public conn as Object

7. Add this before calling dialogs:

DialogLibraries.LoadLibrary("Standard")

8. Add these functions into module (if needed):

Function CIndex2RGB(idx as Long) as Long
Select Case idx
        Case 0
            CIndex2RGB = -1
        Case 1
            CIndex2RGB = 0
        Case 2
            CIndex2RGB = 16777215
        Case 3
            CIndex2RGB = 16711680
        Case 4
            CIndex2RGB = 65280
        Case 5
            CIndex2RGB = 255
        Case 6
            CIndex2RGB = 16776960
        Case 7
            CIndex2RGB = 16711935
        Case 8
            CIndex2RGB = 65535
        Case 9
            CIndex2RGB = 8388608
        Case 10
            CIndex2RGB = 32768
        Case 11
            CIndex2RGB = 128
        Case 12
            CIndex2RGB = 8421376
        Case 13
            CIndex2RGB = 8388736
        Case 14
            CIndex2RGB = 32896
        Case 15
            CIndex2RGB = 12632256
        Case 16
            CIndex2RGB = 8421504
        Case 17
            CIndex2RGB = 10066431
        Case 18
            CIndex2RGB = 10040166
        Case 19
            CIndex2RGB = 16777164
        Case 20
            CIndex2RGB = 13434879
        Case 21
            CIndex2RGB = 6684774
        Case 22
            CIndex2RGB = 16744576
        Case 23
            CIndex2RGB = 26316
        Case 24
            CIndex2RGB = 13421823
        Case 25
            CIndex2RGB = 128
        Case 26
            CIndex2RGB = 16711935
        Case 27
            CIndex2RGB = 16776960
        Case 28
            CIndex2RGB = 65535
        Case 29
            CIndex2RGB = 8388736
        Case 30
            CIndex2RGB = 8388608
        Case 31
            CIndex2RGB = 32896
        Case 32
            CIndex2RGB = 255
        Case 33
            CIndex2RGB = 52479
        Case 34
            CIndex2RGB = 13434879
        Case 35
            CIndex2RGB = 13434828
        Case 36
            CIndex2RGB = 16777113
        Case 37
            CIndex2RGB = 10079487
        Case 38
            CIndex2RGB = 16751052
        Case 39
            CIndex2RGB = 13408767
        Case 40
            CIndex2RGB = 16764057
        Case 41
            CIndex2RGB = 3368703
        Case 42
            CIndex2RGB = 3394764
        Case 43
            CIndex2RGB = 10079232
        Case 44
            CIndex2RGB = 16763904
        Case 45
            CIndex2RGB = 16750848
        Case 46
            CIndex2RGB = 16737792
        Case 47
            CIndex2RGB = 6710937
        Case 48
            CIndex2RGB = 9868950
        Case 49
            CIndex2RGB = 13158
        Case 50
            CIndex2RGB = 3381606
        Case 51
            CIndex2RGB = 13056
        Case 52
            CIndex2RGB = 3355392
        Case 53
            CIndex2RGB = 10040064
        Case 54
            CIndex2RGB = 10040166
        Case 55
            CIndex2RGB = 3355545
        Case 56
            CIndex2RGB = 3355443
End Select
End Function

Function FixType(Value as string) as Variant
if IsNumeric(Value) then 
   FixType = CDbl(Value)
   elseif IsDate(Value) then
   FixType=CDate(Value)
   else
   FixType=Value
end if
End Function

Sub RecSetOpen (RS as Object, SQL as string, S1 as any, S2 as any, S3 as any, S4 as any)
Dim Statement As Object
Statement = conn.createStatement()
Statement.QueryTimeOut = 0
RS = Statement.executeQuery(SQL)
RS.first
End Sub

Function Round(dVal As Variant, Optional iPrecision As Integer) As Double
dim iPrec as integer
Dim roundStr As String
Dim WholeNumberPart As String
Dim DecimalPart As String
Dim i As Integer
Dim RoundUpValue As Double

roundStr = CStr(dVal)

if isMissing( iPrecision ) then
  iPrec = 0
  else
  iPrec = iPrecision
endif

If InStr(1, roundStr, ",") = -1 Then
    Round = dVal
    Exit Function
End If
WholeNumberPart = Mid(roundStr, 1, InStr(1, roundStr, ",") - 1)
DecimalPart = Mid(roundStr, (InStr(2, roundStr, ",")))
If Len(DecimalPart) > iPrec + 1 Then
    Select Case Mid(DecimalPart, iPrec + 2, 1)
        Case "0", "1", "2", "3", "4"
            DecimalPart = Mid(DecimalPart, 1, iPrec + 1)
        Case "5", "6", "7", "8", "9"
            RoundUpValue = 0.1
            For i = 1 To iPrec - 1
                RoundUpValue = RoundUpValue * 0.1
            Next
            DecimalPart = CStr(cdbl(Mid(DecimalPart, 1, iPrec + 1)) + RoundUpValue)
            If Mid(DecimalPart, 1, 1) <> "1" Then
                DecimalPart = Mid(DecimalPart, 1)
            Else
                WholeNumberPart = CStr(cDbl(WholeNumberPart) + 1)
                DecimalPart = ""
            End If
    End Select
End If
Round = cDbl(WholeNumberPart & DecimalPart)
End Function 

Function GetComboIndex (ComboName as string) as long
Dim f as Long
Dim Combobox as object
Combobox = ActiveDialog.getControl(ComboName)
GetComboIndex = -1
For f = 0 to ComboBox.ItemCount - 1
   If ComboBox.Items(f) = ComboBox.Text Then
      GetComboIndex = f
      exit function
   end if
next f
end function

9. Also, I suggest to:

a. Wrap long-running controls' events with:

ActiveDialog.model.enabled = false 
... event source ...
ActiveDialog.model.enabled = true

b. Replace FixType (produced by conversion if it can't recognize value type) with correct getXXX, if possible.

c. If you use Arrays to output information to sheet (as you should), then clear your arrays before using them, like this:

for f = 1 to 50000
    for g = 1 to 4
        DataArray(f, g) = ""
        next g
next f

Add 'ReDim Preserve DataArray' for EXACT size of sheet area you want to fill before using SetDataArray (doesn't work otherwise in OO 3.2).


convmodule.tcl:

#convmodule.tcl
#Converts exported module. Can use its form.
#Helper proc for HIDING quoted strings:
proc S {} {
global G_CQStrs G_CQStrsIndex
set s [lindex $G_CQStrs $G_CQStrsIndex]
incr G_CQStrsIndex
return $s
}

proc CR {CC} {
if {[string is integer -strict $CC]} {
   set CNum [scan $CC %d]
   incr CNum
   return $CNum
   } else {
   #If it's like "something + 2" OR "something - 3":
   if {[regexp { [-+] \d+$} $CC]} {
      set Idxs [regexp -inline -indices {( [-+]) (\d+)$} $CC]
      set ss [lindex $Idxs 1 0]
      set se [lindex $Idxs 1 1]
      set ns [lindex $Idxs 2 0]
      set ne [lindex $Idxs 2 1]
      set CNum [scan [string index $CC $se][string range $CC $ns $ne] %d]
      incr CNum
      if {$CNum == 0} {return [string replace $CC $ss $ne ""]}
      if {$CNum>0} {set sign "+"} else {set sign "-"}
      return [string replace $CC $se $ne "$sign [expr {abs($CNum)}]"]
      }

   #If it's like "2 + something" 
   if {[regexp {^\d+ \+ } $CC]} {
      regexp -indices {^\d+ \+ } $CC Idxs
      set ei [lindex $Idxs 1] 
      set CNum [scan $CC %d]
      incr CNum
      if {$CNum == 0} {return [string replace $CC 0 $ei ""]}
      return [string replace $CC 0 $ei "$CNum + "]
      }

   if {[regexp {^\w+$} $CC]} {
      return "$CC + 1"
      }

   return "($CC) + 1"
   }
}

#Must convert cell coordinate into OO's:
proc C {CC} {
if {[string is integer -strict $CC]} {
   set CNum [scan $CC %d]
   incr CNum -1
   return $CNum
   } else {
   #If it's like "something + 2" OR "something - 3":
   if {[regexp { [-+] \d+$} $CC]} {
      set Idxs [regexp -inline -indices {( [-+]) (\d+)$} $CC]
      set ss [lindex $Idxs 1 0]
      set se [lindex $Idxs 1 1]
      set ns [lindex $Idxs 2 0]
      set ne [lindex $Idxs 2 1]
      set CNum [scan [string index $CC $se][string range $CC $ns $ne] %d]
      incr CNum -1
      if {$CNum == 0} {return [string replace $CC $ss $ne ""]}
      if {$CNum>0} {set sign "+"} else {set sign "-"}
      return [string replace $CC $se $ne "$sign [expr {abs($CNum)}]"]
      }

   #If it's like "2 + something" 
   if {[regexp {^\d+ \+ } $CC]} {
      regexp -indices {^\d+ \+ } $CC Idxs
      set ei [lindex $Idxs 1] 
      set CNum [scan $CC %d]
      incr CNum -1
      if {$CNum == 0} {return [string replace $CC 0 $ei ""]}
      return [string replace $CC 0 $ei "$CNum + "]
      }

   if {[regexp {^\w+$} $CC]} {
      return "$CC - 1"
      }

   return "($CC) - 1"
   }
}

#.Rows("7:60001").Delete Shift:=xlUp
#.Rows(CStr(psttot - 4) & ":60000").Delete Shift:=xlUp
proc RD {args} {
set RS [join $args]
foreach p [split $RS :] {
    if {[regexp {CStr\((.+)\)} $p All InBrace]} {
       #Try to get expression from there:
       set res [C $InBrace]
       } else {
       #Try to scan number from there:
       regexp {\d+} $p CNumStr
       set res [scan $CNumStr %d]
       incr res -1
       }
    lappend reslst $res
    }
#First's correct, convert second (FROM end row number TO number of rows):
foreach {f s} $reslst break
if {[catch {set s [expr {$s-$f}]}]} {
   set s "$s - ($f)"
   }
return "$f, $s"
}

#Convert Row to range address:
proc RA {args} {
set RS "\"\$A\$\" & [join $args]"
set RS [string map {: {:$AMJ$}} $RS]
return $RS 
}

#Uppercase string
proc U {args} {
set Str [join $args]
string toupper $Str
}

#Convert Format (Numbers):
#They aren't correct, but close for now.
proc CF {Str} {
switch -exact $Str {
    {"#,##0.000"} {return 2}
    {"#,##0.00$"} {return 4}
    {"#,##0.00"} {return 4}
    {"#,##0"} {return 3}
    {"# ##0.000"} {return 4}
    default {return $Str}
    }
}

proc SW {spec} {
set ::G_FixNextWith $spec 
return ""
}

proc EW {} {
set res ""
if {$::G_FixNextWith ne ""} {
   set res $::G_FixNextWith
   set ::G_FixNextWith ""
   }
return $res
}

#Must process list of variables separately here:
proc AR {RS} {
foreach vp [split $RS ,] {
    set RN [string trim $vp]
    AddSubst "$RN\\.Open" "RecSetOpen $RN,"
    }
return ""
}

#This must process ALL possible "DIM" forms:
proc CDim {Head Rest} {
#1. Hack: substitute [AR] in rest:
set Rest [subst -novariables $Rest]
set res ""
set s 0
while {1} {
    if {![regexp -start $s -indices -nocase {As .+?(?:,|$)} $Rest All]} break
    #So, we got one part:
    set ns [lindex $All 0]
    set ne [lindex $All 1]
    set Type [string trim [string range $Rest $ns $ne] ,]
    set Vars [string trim [string range $Rest $s $ns-1]]
    #Process Vars now:
    set vs 0
    while {1} {
        if {![regexp -start $vs -indices {\w+(?:\([^()]+\))?(?:,|$)} $Vars All]} break
        set nvs [lindex $All 0]
        set nve [lindex $All 1]
        set VName [string trim [string range $Vars $nvs $nve] ,]
        append res "$Head $VName $Type\n"
        set vs [expr {$nve+1}]
        }
    set s [expr {$ne+1}]
    }

string replace $res end end 
}

proc AddSubst {RE REsub} {
global G_RE_Substs
lappend G_RE_Substs $RE $REsub
}

proc ConvNum {number} {
format %.0f [expr {$number/35.27778}]
}

proc ConvModuleFile {fname ofname} {
global G_RE_Substs G_CQStrs G_CQStrsIndex
set fd [open $fname]
fconfigure $fd -encoding cp1251
set ofd [open $ofname w]
fconfigure $ofd -encoding cp1251

while {[gets $fd nline] >= 0} {
      set idnt ""
      regexp {^[ \t]+} $nline idnt
      set idnt "\n$idnt"
      set trmline [string trim $nline]
      if {[string index $trmline 0] eq "'"} {
         puts $ofd $nline
         continue
         }
      set G_CQStrsIndex 0
      set G_CQStrs [regexp -inline -all {"[^\"]*"} $nline]
      set nline [regsub -all {[\[\]\\]} $nline {\\\0}] 
      set nline [regsub -all {"[^\"]*"} $nline {[S]}]
      #As strings are hidden now, take out comment:
      set Comment ""
      set CSt [string first ' $nline]
      if {$CSt != -1} {
         set Comment [string range $nline $CSt end]
         set nline [string range $nline 0 $CSt-1]
         }

      foreach {RE RESub} $G_RE_Substs {
         set nline [regsub -all -nocase $RE $nline $RESub]
         }
      set nline [subst -novariables $nline]
      append nline [subst -novariables $Comment]
      #Skip lines that became empty:
      if {([string trim $nline] eq "") && ($trmline ne "")} continue
      #Fix identation here:
      set nline [regsub -all "\n" $nline $idnt]
      puts $ofd $nline
      }
close $fd
close $ofd
}

proc GetFormData {fname} {
set fd [open $fname]
fconfigure $fd -encoding utf-8

while {[gets $fd line] >= 0} {
    if {[regexp {dlg:id=} $line]} {
       regexp {dlg:id="(\w+)"} $line All Name
       switch -glob $line {
            "*dlg:checkbox*" {
                 AddSubst "(.+) = $Name.Value\$" "If ActiveDialog.getControl(\"$Name\").State = 1 Then \\1 = True Else \\1 = False"
                 AddSubst "If $Name.Value Then" "If ActiveDialog.getControl(\"$Name\").State = 1 Then"
                 AddSubst "$Name.Value = False" "ActiveDialog.getControl(\"$Name\").State = 0"
                 AddSubst "$Name.Value = True" "ActiveDialog.getControl(\"$Name\").State = 1"
                 }
            "*dlg:menulist*" {
                 AddSubst "$Name.AddItem (.+)\$" "ActiveDialog.getControl(\"$Name\").addItem(\\1, ActiveDialog.getControl(\"$Name\").ItemCount)"
                 AddSubst "$Name.Clear" "ActiveDialog.getControl(\"$Name\").removeItems(0, ActiveDialog.getControl(\"$Name\").ItemCount)"
                 #Check: from zero in OO, in Excel ?
                 AddSubst "$Name.ListIndex" "ActiveDialog.getControl(\"$Name\").SelectedItemPos"
                 AddSubst "$Name.Text" "ActiveDialog.getControl(\"$Name\").SelectedItem"
                 }
            "*dlg:text*" {
                 AddSubst "$Name.Caption = (.+)\$" "ActiveDialog.getControl(\"$Name\").Text = \\1"
                 }
            "*dlg:radio*" {
                 AddSubst "$Name.Value" "ActiveDialog.getControl(\"$Name\").State"
                 }
            "*dlg:combobox*" {
                 AddSubst "$Name.AddItem (.+)\$" "ActiveDialog.getControl(\"$Name\").addItem(\\1, ActiveDialog.getControl(\"$Name\").ItemCount)"
                 AddSubst "$Name.Clear" "ActiveDialog.getControl(\"$Name\").removeItems(0, ActiveDialog.getControl(\"$Name\").ItemCount)"
                 #Check: from zero in OO, in Excel ?
                 AddSubst "$Name.ListIndex = (.+)\$" "ActiveDialog.getControl(\"$Name\").text = ActiveDialog.getControl(\"$Name\").getItem(\\1)"
                 AddSubst "$Name.ListIndex" "GetComboIndex(\"$Name\")"
                 AddSubst "$Name.Text" "ActiveDialog.getControl(\"$Name\").Text"
                 }
            "*dlg:datefield*" {
                 AddSubst "$Name.Value = (.+)\$" "ActiveDialog.getControl(\"$Name\").Date = CdateToIso(\\1)"
                 AddSubst "(.+) = $Name.Value\$" "\\1 = CDateFromIso(ActiveDialog.getControl(\"$Name\").Date)"
                 }
            }
       }
    }

close $fd
}

set ::G_FixNextWith ""

set L [llength $argv]
if {$L==3} {
   GetFormData [lindex $argv 1]
   source VBA2OO.tcl
   ConvModuleFile [lindex $argv 0] [lindex $argv 2]
   } else {
   source VBA2OO.tcl
   ConvModuleFile [lindex $argv 0] [lindex $argv 1]
   }

VBA2OO.tcl:

#What is NOT translatable:
#1. Multiple ranges (they are easy to convert by hand).
#   like: "A1:B2, C4:C6, A16:B18"
#2. PageSetup --- deleted on translation, not so important.
#3. Creating toolbars --- can be done directly (without code) in OpenOffice.
#4. SQL command (INSERT/UPDATE) semantic's wrong --- it's executed
#   on using ".CommandText" property.
#5. "Range(" with two string parametes is not translated:
#     example: Range(v(G, 3), v(G, 4)).Select
#6. FormulaR1C1 is unsupported in OOBasic, so no conversion.
#7. "Range.Value = " is unsupported in OOBasic, so no conversion.
#8. Defining Enums is supported in OOBasic ONLY with 'Option Compatible'.
#============================================================================
#Destroy these lines:
AddSubst {^.+\.PageSetup\..+$} {}
AddSubst {^.+\.FitToPagesWide.+$} {}
AddSubst {^.+\.FitToPagesTall.+$} {}
AddSubst {^.+\.PrintArea.+$} {}
AddSubst {^.+\.ConnectionTimeout .+$} {}
AddSubst {^.+\.CommandTimeout .+$} {}
AddSubst {^.*\.Execute.*$} {}
AddSubst {Set .+ = New ADODB\.(\w+)$} {}
AddSubst {^.+\.ActiveConnection = .+$} {}
AddSubst {^.+\.BeginTrans$} {}
AddSubst {^.+\.CommitTrans$} {}
AddSubst {^.+\.Calculation = .+$} {}
AddSubst {^.+\.Calculate$} {}
AddSubst {^.+\.LineStyle = .+$} {}

#General substs:
AddSubst {Application\.UserName} {Environ("USERNAME")}
AddSubst {Application\.} {ThisComponent.}
AddSubst {CCur\(} {CDbl(}

#Formatting:
#Borders are next to impossible, who could think!?
AddSubst {With (.+)\.Borders\(xlEdgeLeft\)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.LeftBorder\nWith OOtmpobj2\[SW \"\nOOtmpobj1.LeftBorder = OOtmpobj2\"\]"
AddSubst {With (.+)\.Borders\(xlEdgeRight\)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.RightBorder\nWith OOtmpobj2\[SW \"\nOOtmpobj1.RightBorder = OOtmpobj2\"\]"
AddSubst {With (.+)\.Borders\(xlEdgeTop\)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.TopBorder\nWith OOtmpobj2\[SW \"\nOOtmpobj1.TopBorder = OOtmpobj2\"\]"
AddSubst {With (.+)\.Borders\(xlEdgeBottom\)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.BottomBorder\nWith OOtmpobj2\[SW \"\nOOtmpobj1.BottomBorder = OOtmpobj2\"\]"
AddSubst {With (.+)\.Borders\(xlInsideHorizontal\)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.TableBorder\nOOtmpobj3 = OOtmpobj2.HorizontalLine\nWith OOtmpobj3\[SW \"\nOOtmpobj2.HorizontalLine = OOtmpobj3\nOOtmpobj1.TableBorder = OOtmpobj2\"\]"
AddSubst {With (.+)\.Borders\(xlInsideVertical\)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.TableBorder\nOOtmpobj3 = OOtmpobj2.VerticalLine\nWith OOtmpobj3\[SW \"\nOOtmpobj2.VerticalLine = OOtmpobj3\nOOtmpobj1.TableBorder = OOtmpobj2\"\]"
#This works just by sequence:
AddSubst {(.+)\.Borders\.(.+)} "OOtmpobj1 = \\1\nOOtmpobj2l = OOtmpobj1.LeftBorder\nOOtmpobj2r = OOtmpobj1.RightBorder\nOOtmpobj2t = OOtmpobj1.TopBorder\nOOtmpobj2b = OOtmpobj1.BottomBorder\nOOtmpobj2l.\\2\nOOtmpobj2r.\\2\nOOtmpobj2t.\\2\nOOtmpobj2b.\\2\nOOtmpobj1.LeftBorder = OOtmpobj2l\nOOtmpobj1.RightBorder = OOtmpobj2r\nOOtmpobj1.TopBorder = OOtmpobj2t\nOOtmpobj1.BottomBorder = OOtmpobj2b"
AddSubst {(.+)\.Borders\(xlEdgeLeft\)(.+)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.LeftBorder\nOOtmpobj2\\2\nOOtmpobj1.LeftBorder = OOtmpobj2"
AddSubst {(.+)\.Borders\(xlEdgeRight\)(.+)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.LeftBorder\nOOtmpobj2\\2\nOOtmpobj1.RightBorder = OOtmpobj2"
AddSubst {(.+)\.Borders\(xlEdgeTop\)(.+)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.LeftBorder\nOOtmpobj2\\2\nOOtmpobj1.TopBorder = OOtmpobj2"
AddSubst {(.+)\.Borders\(xlEdgeBottom\)(.+)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.LeftBorder\nOOtmpobj2\\2\nOOtmpobj1.BottomBorder = OOtmpobj2"
AddSubst {(.+)\.Borders\(xlInsideVertical\)(.+)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.TableBorder\nOOtmpobj3 = OOtmpobj2.VerticalLine\nOOtmpobj3\\2\nOOtmpobj2.VerticalLine = OOtmpobj3\nOOtmpobj1.TableBorder = OOtmpobj2"
AddSubst {(.+)\.Borders\(xlInsideHorizontal\)(.+)} "OOtmpobj1 = \\1\nOOtmpobj2 = OOtmpobj1.TableBorder\nOOtmpobj3 = OOtmpobj2.HorizontalLine\nOOtmpobj3\\2\nOOtmpobj2.HorizontalLine = OOtmpobj3\nOOtmpobj1.TableBorder = OOtmpobj2"
AddSubst {End With} {End With[EW]}

#Other formatting:
AddSubst {\.Font\.Size} {.CharHeight}
AddSubst {\.Font\.Bold = True} {.CharWeight = 150}
AddSubst {\.Font\.Bold = False} {.CharWeight = 100}
AddSubst {\.Font\.Italic = True} {.CharPosture = 2}
AddSubst {\.Font\.Italic = False} {.CharPosture = 0}
AddSubst {\.RowHeight = (\d+)} {.Rows.Height = \1 * 35.27778}

AddSubst {\.MergeCells = (\w+)} {.Merge(\1)}
AddSubst {\.Merge$} {.Merge(True)}
AddSubst {\.Interior\.ColorIndex = (.+)$} {.CellBackColor = CIndex2RGB(\1)}
AddSubst {\.Font\.ColorIndex = (.+)$} {.CharColor = CIndex2RGB(\1)}
AddSubst {\.ColorIndex = xlAutomatic} {\.Color = 0}
AddSubst {\.Weight = xlThin} {.OuterLineWidth = 20}
AddSubst {\.HorizontalAlignment = xlRight} {.HoriJustify = 0}
AddSubst {\.HorizontalAlignment = xlLeft} {.HoriJustify = 1}
AddSubst {\.HorizontalAlignment = xlCenter} {.HoriJustify = 2}
AddSubst {\.HorizontalAlignment = xlJustify} {.HoriJustify = 4}

#NumberFormats. Must re-fix them, as result is almost the same:
AddSubst {\.NumberFormat = \[S\]} {.NumberFormat = [CF [S]]}

#Selection:
AddSubst {Selection\.Delete Shift:=xlUp$} {ThisComponent.CurrentController.ActiveSheet.RemoveRange(ThisComponent.getCurrentSelection.RangeAddress, com.sun.star.sheet.CellDeleteMode.UP)}
AddSubst {Selection\.Delete Shift:=xlToLeft$} {ThisComponent.CurrentController.ActiveSheet.RemoveRange(ThisComponent.getCurrentSelection.RangeAddress, com.sun.star.sheet.CellDeleteMode.LEFT)}
AddSubst {(\S+)\.Select$} {ThisComponent.CurrentController.Select(\1)}

#WorkSheets:
AddSubst {^Sheets\(\[S\]\)} {ThisComponent.Sheets.getByName([S])}
AddSubst {([ \(])Sheets\(\[S\]\)} {\1ThisComponent.Sheets.getByName([S])}
AddSubst {^Sheets\((\w+)\)} {ThisComponent.Sheets.getByIndex([C \1])}
AddSubst {([ \(])Sheets\((\w+)\)} {\1ThisComponent.Sheets.getByIndex([C \2])}
AddSubst {\.Worksheets\(\[S\]\)} {.Sheets.getByName([S])}
AddSubst {\.Worksheets\((\w+)\)} {.Sheets.getByIndex([C \1])}

#Rows access is tricky:
AddSubst {\.Rows\((.+)\)\.Delete Shift:=xl.+$} {.GetRows().RemoveByIndex([RD \1])}
#This is quite another thing:
AddSubst {\.Rows\((.+?)\)\.} {.getCellRangeByName([RA \1]).}
AddSubst {\.Columns\((.+?)\)\.} {.Columns([C \1]).}

#Ranges:
AddSubst {As Range} {As Object}
AddSubst {\.Range\((.+)\)\.Value = (\w+)$} {.Range(\1).setDataArray(\2)}
#Deletion first:
AddSubst {(.+)\.Range\((.+)\)\.Delete Shift:=xlToLeft} {\1.RemoveRange(\1.Range(\2).RangeAddress, com.sun.star.sheet.CellDeleteMode.LEFT)}
AddSubst {(.+)\.Range\((.+)\)\.Delete Shift:=xlUp} {\1.RemoveRange(\1.Range(\2).RangeAddress, com.sun.star.sheet.CellDeleteMode.UP)}
#Multiple ranges:
AddSubst {Set (.+) = Union\(\1, (.+)\)$} {\1.addRangeAddress(\2.RangeAddress, false)}
#ANY range assigment -> multiple range:
AddSubst {Set (.+) = (.+\.Range\(.+\))$} "\\1 = ThisComponent.createInstance(\"com.sun.star.sheet.SheetCellRanges\")\n\\1.addRangeAddress(\\2.RangeAddress, false)"
#Usual manipulation then:
AddSubst { Range\(} { ThisComponent.CurrentController.ActiveSheet.Range(}
AddSubst {^Range\(} {ThisComponent.CurrentController.ActiveSheet.Range(}
AddSubst {\.Range\(\.?Cells\((.+?), (.+?)\), \.?Cells\((.+?), (.+?)\)\)} {.getCellRangeByPosition([C {\2}], [C {\1}], [C {\4}], [C {\3}])}
AddSubst {\.Range\(} {.getCellRangeByName(}

#Cell values must be DECREASED by ONE to please OO:
AddSubst {([ \(])Cells\.} {\1ThisComponent.CurrentController.ActiveSheet.}
AddSubst {([ \(])Cells\)} {\1ThisComponent.CurrentController.ActiveSheet)}
AddSubst {([ \(])Cells\(} {\1ThisComponent.CurrentController.ActiveSheet.Cells(}
AddSubst {^Cells\(} {ThisComponent.CurrentController.ActiveSheet.Cells(}
AddSubst {\.Cells\((.+?), (.+?)\)\.Value} {.getCellByPosition([C {\2}], [C {\1}]).FormulaLocal}
AddSubst {\.Cells\((.+?), (.+?)\)\.} {.getCellByPosition([C {\2}], [C {\1}]).}

#Misc:
AddSubst {.Clear$} {.clearContents(com.sun.star.sheet.CellFlags.VALUE + com.sun.star.sheet.CellFlags.STRING + com.sun.star.sheet.CellFlags.DATETIME + com.sun.star.sheet.CellFlags.ANNOTATION + com.sun.star.sheet.CellFlags.FORMULA + com.sun.star.sheet.CellFlags.HARDATTR + com.sun.star.sheet.CellFlags.STYLES + com.sun.star.sheet.CellFlags.OBJECTS + com.sun.star.sheet.CellFlags.EDITATTR)}
AddSubst {.ClearContents$} {.clearContents(com.sun.star.sheet.CellFlags.VALUE + com.sun.star.sheet.CellFlags.STRING +com.sun.star.sheet.CellFlags.DATETIME)}

AddSubst {ThisComponent\.ScreenUpdating = False} "ThisComponent.LockControllers\nThisComponent.CurrentController.Frame.ContainerWindow.Enable = False"
AddSubst {ThisComponent\.ScreenUpdating = True} "ThisComponent.UnLockControllers\nThisComponent.CurrentController.Frame.ContainerWindow.Enable = True" 
AddSubst {(\w+)\.Show} "ActiveDialog = CreateUnoDialog(DialogLibraries.Standard.\\1)\n\\1.UserForm_Activate()\nActiveDialog.execute()"
AddSubst {Unload Me} {ActiveDialog.EndExecute()}
AddSubst {UserForm\w+\.Caption = } {ActiveDialog.Title = }
AddSubst {\.StatusBar = (.+)$} {.CurrentController.StatusIndicator.Start(\1,0)}
#This also registers subst for recordset:
AddSubst {Dim (.+) As ADODB\.Recordset} {Dim \1 As Object[AR {\1}]}
AddSubst {As ADODB\.\w+} {As Object}
AddSubst {As Currency} {As Double}
AddSubst {As WorkSheet} {As Object}
AddSubst {As MSComctlLib\.\w+} {As Object}
#This fixes frequent error in VBA programs, but can introduce it instead:
AddSubst {(Dim|Public|Private) (?!Sub|Function)(.+,.+ As .+)$} {[CDim \1 {\2}]}

AddSubst {ActiveWorkbook.Name} {FileNameOutOfPath(ThisComponent.getURL)}
AddSubst {ActiveWorkbook.Path} {ConvertFromURL(DirectoryNameoutofPath(ThisComponent.getURL, "/"))}
AddSubst {\.Visible} {.isVisible}

#Databases:
AddSubst {conn\.Open (.+)$} "DataSource = createUnoService(\"com.sun.star.comp.dba.ODatabaseSource\")\nDataSource.URL = \"sdbc:ado:\" \\& \\1\nconn = DataSource.GetConnection(\"\", \"\")"
#Not good, but still acceptable:
AddSubst {Module1.conn.ConnectionString = (.+)$} "DataSource = createUnoService(\"com.sun.star.comp.dba.ODatabaseSource\")\nDataSource.URL = \"sdbc:ado:\" \\& \\1\nModule1.conn = DataSource.GetConnection(\"\", \"\")"
#Destroyed, as ConnectionString must be set before:
AddSubst {.+conn\.Open$} {} 
AddSubst {conn\.State = adStateClosed} {conn.isClosed()}
AddSubst {conn\.State <> adStateClosed} {Not conn.isClosed()}
AddSubst {Module1\.conn\.State <> adStateClosed} {Not Module1.conn.isClosed()}
AddSubst {\.EOF} {.isAfterLast}
AddSubst {\.MoveNext} {.next}
#Field values (by name):
AddSubst {CDbl\((\w+?)\.Fields\.Item\(\[S\]\)\.Value\)} {\1.Columns.getbyName([U [S]]).Double}
AddSubst {CDate\((\w+?)\.Fields\.Item\(\[S\]\)\.Value\)} {\1.Columns.getbyName([U [S]]).Date}
AddSubst {CStr\((\w+?)\.Fields\.Item\(\[S\]\)\.Value\)} {\1.Columns.getbyName([U [S]]).String}
AddSubst {CInt\((\w+?)\.Fields\.Item\(\[S\]\)\.Value\)} {\1.Columns.getbyName([U [S]]).Int}
AddSubst {CLng\((\w+?)\.Fields\.Item\(\[S\]\)\.Value\)} {\1.Columns.getbyName([U [S]]).Int}
#Field values (by index):
AddSubst {CDbl\((\w+?)\.Fields\.Item\(([^)]+?)\)\.Value\)} {\1.getDouble([CR {\2}])}
AddSubst {CDate\((\w+?)\.Fields\.Item\(([^)]+?)\)\.Value\)} {\1.getDate([CR {\2}])}
AddSubst {CStr\((\w+?)\.Fields\.Item\(([^)]+?)\)\.Value\)} {\1.getString([CR {\2}])}
AddSubst {CLng\((\w+?)\.Fields\.Item\(([^)]+?)\)\.Value\)} {\1.getInt([CR {\2}])}
AddSubst {CInt\((\w+?)\.Fields\.Item\(([^)]+?)\)\.Value\)} {\1.getInt([CR {\2}])}
#This uses accessory function to shimmer to number or date, if possible:
AddSubst {(\w+)\.Fields\.Item\(\[S\]\)\.Value} {FixType(\1.Columns.getbyName([U [S]]).string)}
AddSubst {(\w+)\.Fields\.Item\(([^)]+?)\)\.Value} {FixType(\1.getString([CR {\2}]))}
#Count of fields:
AddSubst {\.Fields\.Count} {.Columns.Count}
#Not correct, but can be tolerated (multiline fails):
AddSubst {\.CommandText = (.+)$} "Statement = conn.createStatement()\nStatement.QueryTimeOut = 0\nStatement.executeUpdate(\\1)\nStatement.close()"

convmfix.tcl:

#convmfix.tcl
#Tries to fix converted module:
#1. Removes useless 'With / End With'
#2. Deletes definitions of unused variables.
#3. Tries to replace FixType with something sensible.
#----------------------------------------------------------------
#AST is a list of nodes with structure: {root {children} {properties}}
#B -- binary operator, U -- unary/binary operator, E -- end of expression,
#UP --- unary preceeding. E -- end-of-expr.
array set G_TokenTypes {+ U - U if E then E else E do E while E set E ^ B \
                        * B and B or B xor B mod B not UP & B imp B eqv B \
                        / B \\ B < B > B <= B >= B <> B = B ( ( ) ) , , : E \
                        with E case E select E do E new U . U dim E as B \
                        public E const E to B sub E function E on E end E \
                        error E resume E := B private E for E call E step E \
                        elseif E each E in E enum E exit E optional U}

#Node types: A U B V ( () , P F( F() T
array set G_OpPriorities {A 0 ( 0 F( 0 P 0 U 20 () 20 V 20 F() 20 to 1 \
                          imp 2 eqv 3 xor 4 or 5 and 6 not 7 \
                          < 8 <= 8 = 8 >= 8 > 8 <> 8 := 8 as 8 \
                          & 9 + 10 - 10 mod 11 \\ 12 * 13 / 13 ^ 14 . 15}

#---------------------------------------------------------
#For it to work, should be no "Operator" eq "Token type"
proc GetNodePriority {Node} {
upvar AST AST
foreach {NodeType OP} [lindex $AST $Node 2] break
if {$NodeType eq "B"} {
   set Probe [string tolower $OP]
   } else {
   set Probe $NodeType
   }
return $::G_OpPriorities($Probe)
}

proc DoUOpOrVConst {} {
upvar AST AST ctype ctype Token Token CNode CNode CNodeType CNodeType
set nnode [TreeCreateNode AST [list [string index $ctype 0] $Token]]
#If after VConst, ReMake CNode into Proc:
if {$CNodeType eq "V"} {lset AST $CNode 2 0 P}
TreeLinkChild AST $CNode $nnode
set CNode $nnode
return
}

proc DoBinOp {} {
upvar AST AST Token Token CNode CNode
set nnode [TreeCreateNode AST [list B $Token]]
set COPPriority [GetNodePriority $nnode]

while {1} {
    set CNodeRoot [lindex $AST $CNode 0]
    set CRootNodePriority [GetNodePriority $CNodeRoot]
    if {$COPPriority>$CRootNodePriority} break
    set CNode $CNodeRoot
    }

TreeInsertNodeAsRoot AST $CNode $nnode
set CNode $nnode
return
}

proc Line2AST {line} {
global G_TokenTypes
set InStrFlag 0
set sidx 0
#Parser flags and vars:
set AST [list]
set CNode [TreeCreateNode AST [list A A]]

while {1} {
   if {![regexp -indices -start $sidx \
            {:=|\<\>|\<=|\>=|[^ ]|[0-9]+?\.?[0-9]*?|\.[0-9]+?|[0-9a-zA-Z_]+?} $line Res]} break
   set Token [string trim [string range $line {*}$Res]]
   #Set current index after this token:
   set sidx [expr {[lindex $Res 1]+1}]
   if {$Token eq "\""} {
      #Must output string constant as whole at once:
      while {1} {
          set eidx [string first "\"" $line $sidx]
          if {$eidx==-1} {error "Unterminated string constant"}
          append Token [string range $line $sidx $eidx]
          set sidx [expr {$eidx+1}]
          if {[string index $line $sidx] eq "\""} {
             append Token "\""
             incr sidx
             } else break
          }
      }
    if {$Token eq "'"} {
       set nnode [TreeCreateNode AST [list ' [string range $line $sidx-1 end]]]
       TreeLinkChild AST 0 $nnode
       break
       }

    #Now get token type:
    set lToken [string tolower $Token]
    if {[info exists G_TokenTypes($lToken)]} {
       set ctype $G_TokenTypes($lToken)
       } else {
       set ctype V
       }

    #Process token here:
    #Token types: U UP B V ( ) E ,
    #Node types: A U B V ( () , P F( F() T
    set CNodeType [lindex $AST $CNode 2 0]
    switch -exact -- $ctype {
       UP -
        V {
          if {$CNodeType in [list () F()]} {error "Misplaced uop/vconst $Token"}
          DoUOpOrVConst
          }
        U {
          if {$CNodeType in [list V () F()]} {
             DoBinOp
             } else {
             DoUOpOrVConst
             }
          }
        B {
          if {$CNodeType ni [list V () F()]} {error "Misplaced binop $Token"}
          DoBinOp
          }
        ( {
          if {$CNodeType in [list () F()]} {error "Misplaced ("}
          if {$CNodeType eq "V"} {
             #Remake CNode into function:
             lset AST $CNode 2 0 F(
             } else {
             set nnode [TreeCreateNode AST [list ( (]]
             TreeLinkChild AST $CNode $nnode
             set CNode $nnode
             }
          }
        ) {
          if {$CNodeType in [list U B]} {error "Misplaced )"}
          while {[lindex $AST $CNode 2 0] ni [list ( F(]} {
              set CNode [lindex $AST $CNode 0]
              if {$CNode==-1} {
                 #error "Unmatched )" ; #Relaxed
                 TreeLinkChild AST 0 [TreeCreateNode AST [list T $Token]]
                 set CNode 0
                 break
                 }
              }
          if {$CNode>0} {
             set CNType [lindex $AST $CNode 2 0]
             lset AST $CNode 2 0 "$CNType)"
             }
          }
        E {
          if {$CNodeType eq "B"} {
             set ctype V
             DoUOpOrVConst
             } else {
             #Just add it to AST root:
             TreeLinkChild AST 0 [TreeCreateNode AST [list T $Token]]
             set CNode 0
             }
          }
        , {
          if {$CNodeType in [list U B]} {error "Misplaced ,"}
          while {[lindex $AST $CNode 2 0] ni [list A P ( F(]} {
              set CNode [lindex $AST $CNode 0]
              }
          #If 'A' is reached, turn "," into terminal:
          if {[lindex $AST $CNode 2 0] eq "A"} {
             TreeLinkChild AST $CNode [TreeCreateNode AST [list T ,]]
             }
          }
        default {
          error "Unknown TOKEN type $ctype"
          }
        }
    }
if {[lindex $AST $CNode 2 0] in [list U B]} {
   error "Operator at end of expression"
   }
return $AST
}

#-----
proc TreeCreateNode {treename props} {
upvar $treename tree
set idx [llength $tree]
lappend tree [list -1 [list] $props]
return $idx
}

proc TreeLinkChild {treename parent child} {
upvar $treename tree
if {$parent>[llength $tree]} {error "Parent $parent > nodes in tree"}
if {$child>[llength $tree]} {error "Child $child > nodes in tree"}
#Add child node into list of root nodes:
set pchildren [lindex $tree $parent 1]
if {[lsearch -exact $pchildren $child]!=-1} {
   error "$child is already child of $parent"
   }
lappend pchildren $child
lset tree $parent 1 $pchildren
if {[lindex $tree $child 0] != -1} {error "Child $child already has parent"}
lset tree $child 0 $parent
return
}

# ->1     ->
#A->2 => A->
# ->5     -> + ->5
proc TreeInsertNodeAsRoot {treename node newnode} {
upvar $treename tree
if {$node>[llength $tree]} {error "Node $node > nodes in tree"}
if {$newnode>[llength $tree]} {error "Newnode $newnode > nodes in tree"}
if {[llength [lindex $tree $newnode 1]]>0} {error "Newnode $newnode has children"}
set NodeRoot [lindex $tree $node 0]
if {$NodeRoot == -1} {error "Node $node has no root"}

lset tree $newnode 0 $NodeRoot
lset tree $node 0 $newnode
lset tree $NodeRoot 1 end $newnode
lset tree $newnode 1 $node
return
}

proc AST2Line {AST} {
set Line ""
AST2LineWalkNode 0
return $Line
}

proc AST2LineWalkNode {nodeidx} {
upvar AST AST Line Line

foreach {Type Token} [lindex $AST $nodeidx 2] break

switch -exact -- $Type {
    U {
      append Line $Token
      if {$Token ni [list . - +]} {append Line " "}
      AST2LineWalkNode [lindex $AST $nodeidx 1 0]
      }
    B {
      AST2LineWalkNode [lindex $AST $nodeidx 1 0]
      if {$Token in [list . := :]} {
         append Line $Token
         if {$Token eq ":"} {append Line " "}
         } else {
         append Line " " $Token " "
         }
      AST2LineWalkNode [lindex $AST $nodeidx 1 1]
      }
    T {
      if {$Token in [list , : )]} {set Line [string range $Line 0 end-1]}
      append Line $Token
      }
    ' -
    V {
      append Line $Token
      }
  F() {
      append Line $Token "("
      set eidx [lindex $AST $nodeidx 1 end]
      foreach childidx [lindex $AST $nodeidx 1] {
         AST2LineWalkNode $childidx
         if {$childidx!=$eidx} {
            append Line ", "
            }
         }
      append Line ")"
      }
   F( {
      append Line $Token "("
      set eidx [lindex $AST $nodeidx 1 end]
      foreach childidx [lindex $AST $nodeidx 1] {
         AST2LineWalkNode $childidx
         if {$childidx!=$eidx} {
            append Line ", "
            }
         }
      }
    P {
      #Don't add "," before comment:
      append Line $Token " "
      #Flag not first:
      set fnf 0
      foreach childidx [lindex $AST $nodeidx 1] {
         if {$fnf } {
            if {[lindex $AST $childidx 2 0] ne "'"} {
               append Line ","
               }
            append Line " "
            } else {
            set fnf 1
            }
         AST2LineWalkNode $childidx
         }
      }
    A {
      set eidx [lindex $AST $nodeidx 1 end]
      foreach childidx [lindex $AST $nodeidx 1] {
         AST2LineWalkNode $childidx
         if {$childidx!=$eidx} {
            append Line " "
            }
         }
      }
    ( {
      append Line "("
      AST2LineWalkNode [lindex $AST $nodeidx 1 0]
      }
   () {
      append Line "("
      AST2LineWalkNode [lindex $AST $nodeidx 1 0]
      append Line ")"
      }
   default {
      error "Unknown node type $Type"
      }
    }
return
}

#Must find types of nodes, as much as possible.
#Basic types are: Double (Int), String, Date and Unknown.
#Types are created by some nodes and moved down and up the tree.
proc InferTypesAST {AST} {
set NodeTypes [lrepeat [llength $AST] U]
InferASTNodeType 0 U
return $NodeTypes
}

#Result type, then argument types:
set G_FunTypes(fixtype) [list U S]
set G_FunTypes(cstr)  [list S U]
set G_FunTypes(cdbl)  [list N U]
set G_FunTypes(cdate) [list D U]
set G_FunTypes(cint)  [list N U]
set G_FunTypes(getdouble) [list N N]
set G_FunTypes(getint) [list N N]
set G_FunTypes(getstring) [list S N]
set G_FunTypes(getdate) [list D N]
set G_FunTypes(year) [list N D]
set G_FunTypes(month) [list N D]
set G_FunTypes(day) [list N D]
set G_FunTypes(weekday) [list N D N]
set G_FunTypes(round) [list N N N]
set G_FunTypes(datediff) [list N S D D]
set G_FunTypes(ubound) [list N U]
set G_FunTypes(abs) [list N N]

#E is error, otherwise it's root type.
#If absent in the table, root type is unknown.
set G_InferRoot(-,D,D) N
set G_InferRoot(-,D,N) D
set G_InferRoot(-,N,D) E
set G_InferRoot(-,N,N) N
set G_InferRoot(+,S,S) S
set G_InferRoot(+,N,N) N
set G_InferRoot(+,N,D) D
set G_InferRoot(+,D,N) D
set G_InferRoot(+,D,D) E

proc InferASTNodeType {nodeidx RootType} {
upvar AST AST NodeTypes NodeTypes
global G_VarTypes G_FunTypes G_LocalVarTypes G_InferRoot

foreach {CNodeType Token} [lindex $AST $nodeidx 2] break
set Token [string tolower $Token]
switch -exact $CNodeType {
    V {
      #VConst --- accepts type from root, and passes its type up (can
      #have type if Dim'ed or string/numeric constant).
      set SelfType U
      if {[info exists G_VarTypes($Token)]} {
         set SelfType $G_VarTypes($Token)
         } elseif {[info exists G_LocalVarTypes($Token)]} {
         set SelfType $G_LocalVarTypes($Token)
         } elseif {[string is double -strict $Token]} {
         set SelfType N
         } elseif {[string index $Token 0] eq "\""} {
         set SelfType S
         }

      if {$SelfType eq "U"} {
         lset NodeTypes $nodeidx $RootType
         return $RootType
         }
      if {$RootType eq "U"} {
         lset NodeTypes $nodeidx $SelfType
         return $SelfType
         }
      if {$SelfType!=$RootType} {
         error "Conflicting type for node $nodeidx: [lindex $AST $nodeidx]\n\
                Root:$RootType\n $SelfType\n"
         }
      lset NodeTypes $nodeidx $SelfType
      return $SelfType
      }
    U {
      #Unary operators: + and - create, pass and return Numeric.
      #/CHECK/ What if line starts with "+"?
      if {$Token in [list + -]} {
         lset NodeTypes $nodeidx N
         if {$RootType ni [list N U]} {
            error "Conflicting type for node\
                $nodeidx: [lindex $AST $nodeidx]\n\
                Root:$RootType\n N\n"
            }
         set ChildType [InferASTNodeType [lindex $AST $nodeidx 1 0] N]
         if {$ChildType ne "N"} {
            error "Conflicting type for node\
                $nodeidx: [lindex $AST $nodeidx]\n\
                Child:$ChildType\n N\n"
            }
         return N
         } else {
         #/CHECK/ IT:
         InferASTNodeType [lindex $AST $nodeidx 1 0] U
         lset NodeTypes $nodeidx U
         return U
         }
      }
    B {
      # Operators + and - require complex processing.
      switch -exact $Token {
          +   {
              foreach {ch1idx ch2idx} [lindex $AST $nodeidx 1] break
              if {$RootType eq "S"} {
                 #Both children must be strings:
                 set ChildType1 [InferASTNodeType $ch1idx S]
                 set ChildType2 [InferASTNodeType $ch2idx S]
                 set res S
                 } elseif {$RootType eq "N"} {
                 #Both children must be numbers:
                 set ChildType1 [InferASTNodeType $ch1idx N]
                 set ChildType2 [InferASTNodeType $ch2idx N]
                 set res N
                 } else {
                 set ChildType1 [InferASTNodeType $ch1idx U]
                 set ChildType2 [InferASTNodeType $ch2idx U]
                 #Infer children:
                 #If S,U or U,S -> set another=S
                 if {$ChildType1 eq "S" && $ChildType2 eq "U"} {
                    set ChildType2 [InferASTNodeType $ch2idx S]
                    }
                 if {$ChildType1 eq "U" && $ChildType2 eq "S"} {
                    set ChildType1 [InferASTNodeType $ch1idx S]
                    }
                 #If RootType is D, can infer other child:
                 if {$RootType eq "D"} {
                    if {$ChildType1 eq "U" && $ChildType2 eq "D"} {
                       set ChildType1 [InferASTNodeType $ch1idx N]
                       }
                    if {$ChildType1 eq "D" && $ChildType2 eq "U"} {
                       set ChildType2 [InferASTNodeType $ch2idx N]
                       }
                    }
                 #Infer/check RootType:
                 if {($ChildType1 eq "S" && $ChildType2 ne "S") ||
                    ($ChildType1 ne "S" && $ChildType2 eq "S")} {
                    error "Conflicting type for node $nodeidx:\
                           [lindex $AST $nodeidx]\n\
                           1:$ChildType1\n 2:$ChildType2\n"
                    }
                 set res U
                 if {[info exists G_InferRoot(+,$ChildType1,$ChildType2)]} {
                    set res $G_InferRoot(+,$ChildType1,$ChildType2)
                    if {$res eq "E"} {
                       error "Conflicting type for node $nodeidx:\
                              [lindex $AST $nodeidx]\n\
                              1:$ChildType1\n 2:$ChildType2\n"
                       }
                    }
                 }
              #Check if RootType conflicts infered:
              if {$RootType ne "U" && $res ne "U" && $RootType ne $res} {
                 error "Conflicting type for node $nodeidx:\
                        [lindex $AST $nodeidx]\n\
                        RootType:$RootType\n Inf.Root:$res\n"
                 }
              lset NodeTypes $nodeidx $res
              return $res
              }
          -   {
              if {$RootType eq "S"} {
                 error "Conflicting type for node $nodeidx:\
                        [lindex $AST $nodeidx]\n\
                        Root:$RootType\n"
                 }
              foreach {ch1idx ch2idx} [lindex $AST $nodeidx 1] break
              #So, if RootType is D, use it to infer:
              if {$RootType eq "D"} {
                 set ChildType1 [InferASTNodeType $ch1idx D]
                 set ChildType2 [InferASTNodeType $ch2idx N]
                 set res D
                 } else {
                 set ChildType1 [InferASTNodeType $ch1idx U]
                 set ChildType2 [InferASTNodeType $ch2idx U]
                 #In this case: U,D => D,D->N
                 if {$ChildType1 eq "U" && $ChildType2 eq "D"} {
                    set ChildType1 [InferASTNodeType $ch1idx D]
                    }
                 #In this case: N,U => N,N->N
                 if {$ChildType1 eq "N" && $ChildType2 eq "U"} {
                    set ChildType2 [InferASTNodeType $ch2idx N]
                    }
                 #Check/infer root type:
                 if {$ChildType1 eq "S" || $ChildType2 eq "S"} {
                    error "Conflicting type for node $nodeidx:\
                           [lindex $AST $nodeidx]\n\
                           1:$ChildType1\n 2:$ChildType2\n"
                    }
                 set res U
                 if {[info exists G_InferRoot(-,$ChildType1,$ChildType2)]} {
                    set res $G_InferRoot(-,$ChildType1,$ChildType2)
                    if {$res eq "E"} {
                       error "Conflicting type for node $nodeidx:\
                              [lindex $AST $nodeidx]\n\
                              1:$ChildType1\n 2:$ChildType2\n"
                       }
                    }
                 }
              #Check if RootType conflicts infered:
              if {$RootType ne "U" && $res ne "U" && $RootType ne $res} {
                 error "Conflicting type for node $nodeidx:\
                        [lindex $AST $nodeidx]\n\
                        RootType:$RootType\n Inf.Root:$res\n"
                 }
              lset NodeTypes $nodeidx $res
              return $res
              }
          ^ - * - \\ - to - and - or - xor - imp - eqv - mod - / {
              #Root must be U or N:
              if {$RootType ni [list U N]} {
                 error "Conflicting type for node $nodeidx: [lindex $AST $nodeidx]\n\
                        Root:$RootType\n N\n"
                 }
              lset NodeTypes $nodeidx N
              foreach childidx [lindex $AST $nodeidx 1] {
                  set ChildType [InferASTNodeType $childidx N]
                  if {$ChildType ne "N"} {
                     error "Conflicting type returned: $ChildType"
                     }
                  }
              return N
              }
          & {
            #Root must be U or S:
            if {$RootType ni [list U S]} {
               error "Conflicting type for node $nodeidx: [lindex $AST $nodeidx]\n\
                      Root:$RootType\n S\n"
               }
            lset NodeTypes $nodeidx S
            foreach childidx [lindex $AST $nodeidx 1] {
                set ChildType [InferASTNodeType $childidx S]
                if {$ChildType ne "S"} {
                   error "Conflicting type returned: $ChildType"
                   }
                }
            return S
            }
          . {
            #Must transfer type of root to right child, and raise from it:
            foreach {ch1idx ch2idx} [lindex $AST $nodeidx 1] break
            InferASTNodeType $ch1idx U
            set ChildType2 [InferASTNodeType $ch2idx $RootType]
            if {$RootType ne "U" && $ChildType2 ne "U" \
                && $RootType ne $ChildType2} {
               error "Conflicting type for node $nodeidx: [lindex $AST $nodeidx]\n\
                     Root:$RootType\n ChildType2:$ChildType2\n"
               }
            lset NodeTypes $nodeidx $ChildType2
            return $ChildType2
            }
          < - > - <= - >= - <> - = {
              #Root must be U or N:
              if {$RootType ni [list U N]} {
                 error "Conflicting type for node $nodeidx: [lindex $AST $nodeidx]\n\
                        Root:$RootType\n N\n"
                 }
              lset NodeTypes $nodeidx N
              #All children must be of the same type:
              foreach {ch1idx ch2idx} [lindex $AST $nodeidx 1] break
              set ChildType1 [InferASTNodeType $ch1idx U]
              #Use infered type right now:
              set ChildType2 [InferASTNodeType $ch2idx $ChildType1]
              if {$ChildType1 ne "U" && $ChildType2 ne "U"} {
                 if {$ChildType1 ne $ChildType2} {
                    error "Conflicting type for children of\
                           node $nodeidx: [lindex $AST $nodeidx]\n\
                           1:$ChildType1\n 2:$ChildType2\n"
                    }
                 return N
                 }
              if {$ChildType1 eq "U" && $ChildType2 eq "U"} {return N}
              #So, just one of them is "U":
              if {$ChildType1 eq "U"} {
                 set ChildType1 [InferASTNodeType $ch1idx $ChildType2]
                 } else {
                 set ChildType2 [InferASTNodeType $ch2idx $ChildType1]
                 }
              if {$ChildType1 ne "U" && $ChildType2 ne "U"} {
                 if {$ChildType1 ne $ChildType2} {
                    error "Conflicting type for children of\
                           node $nodeidx: [lindex $AST $nodeidx]\n\
                           1:$ChildType1\n 2:$ChildType2\n"
                    }
                 }
              return N
              }
          default {
            #Don't care, just pass inferation down:
            foreach chidx [lindex $AST $nodeidx 1] {
                InferASTNodeType $chidx U
                }
            return U
            }
          }
      }
  F() -
   F( {
      #Some functions set types of their arguments (use lists of types).
      #Types DON'T pass thru them, but can be set on it if return type is U.
      #Examples: Date DateSerial(Int, Int, Int), Variant FixType(Variant)
      if {[info exists G_FunTypes($Token)]} {
         set FType [lindex $G_FunTypes($Token) 0]
         set NumChildren [llength [lindex $AST $nodeidx 1]]
         set ArgTypes [lrange $G_FunTypes($Token) 1 $NumChildren]
         if {$FType eq "U"} {
            lset NodeTypes $nodeidx $RootType
            } else {
            lset NodeTypes $nodeidx $FType
            if {$FType ne "U" && $RootType ne "U" && $FType ne $RootType} {
                error "Conflicting type for node\
                    $nodeidx: [lindex $AST $nodeidx]\n\
                    Root: $RootType\n $FType\n"
                }
            }
         foreach childidx [lindex $AST $nodeidx 1] childtype $ArgTypes {
             InferASTNodeType $childidx $childtype
             }
         return [lindex $NodeTypes $nodeidx]
         } else {
         #Accepts and returns root type:
         lset NodeTypes $nodeidx $RootType
         foreach childidx [lindex $AST $nodeidx 1] {
             InferASTNodeType $childidx U
             }
         return $RootType
         }
      }
    ( -
   () {
      #Just pass types up and down:
      set ChildType [InferASTNodeType [lindex $AST $nodeidx 1 0] $RootType]
      if {$ChildType ne "U" && $RootType ne "U" && $ChildType ne $RootType} {
         error "Conflicting type for node $nodeidx: [lindex $AST $nodeidx]\n\
                Root:$RootType\n $ChildType\n"
         }
      if {$RootType ne "U"} {
         set SelfType $RootType
         } else {
         set SelfType $ChildType
         }
      lset NodeTypes $nodeidx $SelfType
      return $SelfType
      }
    A -
    P -
    T {
      #Root, Terminals and Procs generate unknowns, passing them down.
      #They are impassable up.
      lset NodeTypes $nodeidx U
      foreach childidx [lindex $AST $nodeidx 1] {
          InferASTNodeType $childidx U
          }
      return U
      }
    }
}

#This must return new string with FixType replaced, if possible:
proc RemoveFixType {AST NodeTypes} {
RemoveFixNode 0
return $AST
}

proc RemoveFixNode {nodeidx} {
upvar AST AST NodeTypes NodeTypes

foreach {Type Token} [lindex $AST $nodeidx 2] break
if {$Type in [list F() F(] && [string tolower $Token] eq "fixtype" \
    && [lindex $NodeTypes $nodeidx] ne "U"} {
   #So, this FixType can be replaced:
   set FtRoot [lindex $AST $nodeidx 0]
   set FtChild [lindex $AST $nodeidx 1 0]
   set NewFtRootChildren [list]
   foreach nidx [lindex $AST $FtRoot 1] {
       if {$nidx == $nodeidx} {set nidx $FtChild}
       lappend NewFtRootChildren $nidx
       }
   lset AST $FtRoot 1 $NewFtRootChildren
   lset AST $FtChild 0 $FtRoot
   #"getstring" node index:
   set getstridx [lindex $AST $FtChild 1 1]
   set NewType [lindex $NodeTypes $nodeidx]
   set NewFunc getString
   switch -exact -- $NewType {
       N {set NewFunc getDouble}
       D {set NewFunc getDate}
       }
   lset AST $getstridx 2 1 $NewFunc
   }

#Remove unneeded conversions, too:
if {$Type in [list F() F(] \
    && [string tolower $Token] in [list cdbl cstr cdate]} {
   set FunType [lindex $::G_FunTypes([string tolower $Token]) 0]
   set ConvChild [lindex $AST $nodeidx 1 0]

   if {[lindex $NodeTypes $ConvChild] eq $FunType} {
      lset AST $nodeidx 2 [list () (]
      }
   }

foreach chidx [lindex $AST $nodeidx 1] {
    RemoveFixNode $chidx
    }
return
}

#----------------------------------------------------------------

proc StartWith {line} {
upvar WithsLevel WithsLevel NoWithUseFlag NoWithUseFlag WithsStack WithsStack
upvar WithStart WithStart CLines CLines

lappend WithsStack $WithStart $CLines $NoWithUseFlag
incr WithsLevel
set WithStart $line
set NoWithUseFlag 1
set CLines ""
}

proc EndWith {line} {
upvar WithsLevel WithsLevel NoWithUseFlag NoWithUseFlag WithsStack WithsStack
upvar WithStart WithStart CLines CLines

if {!$WithsLevel} {error "Unclosed WITH!"}

set CClines $CLines
set CNoWithUseFlag $NoWithUseFlag
set CWithStart $WithStart
lassign [lrange $WithsStack end-2 end] WithStart CLines NoWithUseFlag
set WithsStack [lrange $WithsStack 0 end-3]

if {$CNoWithUseFlag} {
   append CLines $CClines
   } else {
   append CLines $CWithStart "\n"
   append CLines $CClines
   append CLines $line "\n"
   }

incr WithsLevel -1
}

proc FixModuleFile {fname ofname} {
global G_Cfdata
set fd [open $fname]
fconfigure $fd -encoding cp1251
set fdata [read $fd]
close $fd
set G_Cfdata ""

set WithsLevel 0
set NoWithUseFlag 1
set CLines ""
set WithStart ""
set WithsStack [list]

foreach nline [split $fdata "\n"] {
      #Check first, as it can be: "With .Something"
      if {[regexp {[ \t(]\.} $nline]} {set NoWithUseFlag 0}
      if {[regexp {^[ \t]*With } $nline]} {
         StartWith $nline
         continue
         }
      if {[regexp {^[ \t]*End With$} $nline]} {
         EndWith $nline
         if {!$WithsLevel} {
            append G_Cfdata $CLines
            set CLines ""
            }
         continue
         }
      #It's usual line here:
      if {$WithsLevel} {
         append CLines $nline "\n"
         } else {
         append G_Cfdata $nline "\n"
         }
      }

RemoveUnusedVariables
ReplaceFixTypes
ReIndentModule

set ofd [open $ofname w]
fconfigure $ofd -encoding cp1251
puts -nonewline $ofd $G_Cfdata
close $ofd
}

proc ConvTypeName2Type {TypeName} {
switch -exact -- $TypeName {
    Double {set VarType N}
    Integer {set VarType N}
    Byte {set VarType N}
    Long {set VarType N}
    Boolean {set VarType N}
    String {set VarType S}
    Date {set VarType D}
    default {set VarType U}
    }
return $VarType
}

proc ReplaceFixTypes {} {
global G_Cfdata G_VarTypes G_LocalVarTypes G_FunTypes

set State 0
set odata ""
set FlagCollectFun 0
set funline ""
foreach nline [split $G_Cfdata "\n"] {
    #Start processing the line:
    #Try to collect multiline functions:
    if {$FlagCollectFun \
        || [regexp -nocase {^\s*(?:Private )?Function } $nline]} {
       set State 1
       #Get this function name and types here, if possible:
       append funline " " $nline
       if {[string index $nline end] eq "_"} {
          set FlagCollectFun 1
          } else {
          set FlagCollectFun 0
          set fargs [list]
          #So, function ended, process it:
          #Get its name first:
          regexp {Function (\w+)} $funline All FName
          set FName [string tolower $FName]
          #Basic units are: "VarName as SomeType"
          set sidx 0
          while {1} {
             if {![regexp -indices -start $sidx \
                          {(?:\w+) As (\w+)} $funline All VTypeI]} break
             set sidx [expr {[lindex $All 1]+1}]
             set VType [string range $funline {*}$VTypeI]
             lappend fargs [ConvTypeName2Type $VType]
             }
          #Not get function type: ") as SomeType"
          regexp -start $sidx {\) As (\w+)} $funline All VType
          set G_FunTypes($FName) [linsert $fargs 0 [ConvTypeName2Type $VType]]
          set funline ""
          }
       #Anyway:
       append odata $nline "\n"
       continue
       }

    if {[regexp -nocase {^\s*(?:Private )?Sub } $nline]} {
       set State 1
       append odata $nline "\n"
       continue
       }

    if {[regexp {^\s*End (?:Function|Sub)} $nline]} {
       unset -nocomplain G_LocalVarTypes
       set State 0
       append odata $nline "\n"
       continue
       }

    #Arrays are defined as functions (can cause subtle bugs if global
    #array is redefined as local in some sub/function):
    if {[regexp -nocase {^\s*(?:Dim|Public) (\w+)\(.*\) As (\w+)} \
                        $nline All VName VType]} {
       set FName [string tolower $VName]
       set FType [ConvTypeName2Type $VType]
       set G_FunTypes($FName) [list $FType N N N N N]
       append odata $nline "\n"
       continue
       }

    if {[regexp -nocase {^\s*(?:Dim|Public|Private) (\w+)[^()]* As (\w+)} \
                        $nline All VName VType]} {
       set VName [string tolower $VName]
       set VarType [ConvTypeName2Type $VType]
       if {$VarType ne "U"} {
          if {$State} {
             set G_LocalVarTypes($VName) $VarType
             } else {
             set G_VarTypes($VName) $VarType
             }
          }
       append odata $nline "\n"
       continue
       }

    if {[regexp -nocase {fixtype\(} $nline]} {
       set AST [Line2AST $nline]
       set NodeTypes [InferTypesAST $AST]
       set AST [RemoveFixType $AST $NodeTypes]
       set nline [AST2Line $AST]
       }

    append odata $nline "\n"
    }

set G_Cfdata $odata
}

proc RemoveUnusedVariables {} {
global G_Cfdata
set CLineNum 0
set LinesToRemove [list]

#In global definitions:
set State 0
#Global and local REs:
set GRE ""
set LRE ""
foreach nline [split $G_Cfdata "\n"] {
    incr CLineNum
    #Remove string constants:
    set nline [regsub -all {"[^\"]*"} $nline {}]
    #As strings are removed now, take out comment:
    set CSt [string first ' $nline]
    if {$CSt != -1} {
       set nline [string range $nline 0 $CSt-1]
       }
    #Start processing the line:
    if {[regexp -nocase {^\s*(?:Function|Sub) } $nline]} {
       set State 1
       continue
       }
    if {[regexp {^\s*End (?:Function|Sub)} $nline]} {
       #Must mark all lines, clear LocalArray and LRE:
       foreach {Name Idx} [array get LocalArray] {
           lappend LinesToRemove $Idx
           }
       array unset LocalArray
       set LRE ""
       set State 0
       continue
       }

    if {[regexp -nocase {^\s*(?:Dim|Public) (\w+).* As .+} $nline All VName]} {
       set VName [string tolower $VName]
       if {$State} {
          #Local:
          set LocalArray($VName) $CLineNum
          if {$LRE eq ""} {
             set LRE "\\m$VName\\M"
             } else {
             append LRE "|\\m$VName\\M"
             }
          } else {
          #Global:
          set GlobalArray($VName) $CLineNum
          if {$GRE eq ""} {
             set GRE "\\m$VName\\M"
             } else {
             append GRE "|\\m$VName\\M"
             }
          }
       continue
       }

    if {$GRE ne ""} {
       foreach VName [regexp -nocase -inline -all $GRE $nline] {
          unset -nocomplain GlobalArray([string tolower $VName])
          }
       }
    if {$LRE ne ""} {
       foreach VName [regexp -nocase -inline -all $LRE $nline] {
          unset -nocomplain LocalArray([string tolower $VName])
          }
       }
    }

foreach {Name Idx} [array get GlobalArray] {
   lappend LinesToRemove $Idx
   }
#Sort, then strip lines marked for deletion:
set LinesToRemove [lsort -integer $LinesToRemove]
set odata ""
set CLineNum 0
set CIndex 0
set CLineToRemove [lindex $LinesToRemove $CIndex]
foreach nline [split $G_Cfdata "\n"] {
    incr CLineNum
    if {$CLineNum == $CLineToRemove} {
       incr CIndex
       set CLineToRemove [lindex $LinesToRemove $CIndex]
       continue
       }
    append odata $nline "\n"
    }
#Set result as new data:
set G_Cfdata $odata
}

FixModuleFile [lindex $argv 0] [lindex $argv 1]