Row Sum (Σ) Formula Validation
By
czardas
Much work still needs to be done on my current project, but design concepts often benefit more from early criticism. Although there may be nothing particularly innovative about this project, user experience is everything. I would like you to look at the behaviour of the controls on this GUI. Unless you type something really awful, it allows you to finish typing the formula before testing the input. When typing into an edit control, pressing the enter key activates the okay button.
The program itself, mainly consists of a listview control, which you can't see - just imagine it looks like a stone age version of Excel with a nice paint job, but no bells or whistles. What Row Sum (Σ) is intended to do is loop through all the rows, calculate a sum and print the output in a newly created column. The user is prompted to type a formula into the following GUI. Please try to figure it out, and break it any way you can.
#include <GUIConstants.au3>
#include <GuiEdit.au3>
#include <Misc.au3>
Global $g_iAvailableCols = 24 ; arbitary dev variable [column count will eventually be read from a listview control]
; missing several thousand lines of code...
Formula()
; caution about including columns containing empty fields in the formula
; empty fields are treated as zero
; with addition and subtraction there is never any problem
; multiplication by an empty field (or by zero) returns zero
; division by an empty field (or by zero) returns an empty field [unless - see exception]
; use of the power operator is limited to rows in which all referenced fields contain numbers
; imaginary roots of negative numbers return an empty field [unless - see exception]
; exception: infinity (or an imaginary number) to the power of zero returns a meaningless number
Func FormulaSyntax()
MsgBox(BitOR(64, 8192), "Row Sum (" & ChrW(0x03A3) & ") : Syntax To Use", _ ; $MB_ICONINFORMATION, $MB_TASKMODAL
" mathematical operators :" & @TAB & "+ - * / ^" & @CRLF & _
" column numbers :" & @TAB & @TAB & "c1, c2, c3 etc..." & @CRLF & _
" decimal digits :" & @TAB & @TAB & "0 to 9 and ." & @CRLF & _
" parenthesis :" & @TAB & @TAB & "( )" & @CRLF & _
" minus sign :" & @TAB & @TAB & "-1, -c2, -(c3) etc..." & @CRLF & _
" example :" & @TAB & @TAB & @TAB & "c1 +c2 -c3")
EndFunc ;==> FormulaSyntax
Func Formula() ; ($hParent, $idListView, $hListView) ; [missing parent window]
Local $sTitle = "Row Sum (" & ChrW(0x03A3) & ")", _
$iStyle = BitOR($WS_CAPTION, $WS_POPUP, $WS_SYSMENU), _
$iExStyle = BitOR($WS_EX_MDICHILD, $WS_EX_TOOLWINDOW, $WS_EX_TOPMOST)
Local $hChild = GUICreate($sTitle, 334 +25, 105 +1, Default + 100, Default + 100); , $iStyle, $iExStyle, $hParent)
Local $hLabel = GUICtrlCreateLabel("Formula :", 8, 8, 55, 18)
GUICtrlSetFont(-1, 10)
Local $hSyntax = GUICtrlCreateButton("???", 72, 7, 40, 20)
Local $hLabel = GUICtrlCreateLabel("Header :", 127, 8, 49, 18)
GUICtrlSetFont(-1, 10)
Local $hHeader = GUICtrlCreateInput("", 182, 7, 170, 20)
GUICtrlSetFont(-1, 10)
Local $hInput = GUICtrlCreateInput("", 7, 33, 320 +25, 40, BitOR($WS_TABSTOP, $ES_MULTILINE))
GUICtrlSetFont(-1, 10)
Local $hCheckBox = GUICtrlCreateCheckbox(" Round to", 7, 79, 75, 20)
GUICtrlSetFont(-1, 10)
Local $hPlaces = GUICtrlCreateInput("2", 86, 79 +1, 20, 20, BitOR($WS_TABSTOP, $ES_CENTER, $ES_NUMBER))
GUICtrlCreateLabel("decimal places", 114 -2, 79 +2, 92, 18)
GUICtrlSetFont(-1, 10)
Local $hCancel = GUICtrlCreateButton("Cancel", 210, 79 +1, 66, 20)
Local $hOkay = GUICtrlCreateButton("OK", 285, 79 +1, 66, 20)
GUISetState(@SW_SHOW)
Local $sInput = "", $aArray, $iError = 0, $iCols = $g_iAvailableCols ; $iCols = _GUICtrlListView_GetColumnCount($hListView), _ ; >>> CHANGE THIS LATER
;$iRows = _GUICtrlListView_GetItemCount($hListView), $aColOrder = _GUICtrlListView_GetColumnOrderArray($hListView)
Local $vTemp, $msg2, $iPlaces = '2'
While 1
$msg2 = GUIGetMsg()
If $msg2 = $hCancel Or $msg2 = $GUI_EVENT_CLOSE Then ExitLoop
If $msg2 = $hSyntax Then
FormulaSyntax()
ContinueLoop
EndIf
$vTemp = GUICtrlRead($hPlaces)
If $vTemp <> $iPlaces Then
If $vTemp > 14 Then
GUICtrlSetData($hPlaces, 14)
$iPlaces = 14
_GUICtrlEdit_SetSel($hPlaces, 0, -1)
ElseIf Not $vTemp Then
GUICtrlSetData($hPlaces ,'0')
$iPlaces = '0'
_GUICtrlEdit_SetSel($hPlaces, 0, -1)
EndIf
EndIf
If BitAND(GUICtrlRead($hCheckBox), $GUI_CHECKED) == $GUI_CHECKED _
And BitAND(GUICtrlGetState($hPlaces), $GUI_DISABLE) == $GUI_DISABLE Then
GUICtrlSetState($hPlaces, $GUI_ENABLE)
ElseIf BitAND(GUICtrlRead($hCheckBox), $GUI_CHECKED) <> $GUI_CHECKED _
And BitAND(GUICtrlGetState($hPlaces), $GUI_ENABLE) == $GUI_ENABLE Then
GUICtrlSetState($hPlaces, $GUI_DISABLE)
EndIf
$sFormula = GUICtrlRead($hInput)
If $sFormula <> $sInput Then
If StringRegExp($sFormula, '[^ ]') Then ; spaces between components are ignored
If Not ValidSyntax($sFormula, $iCols) Then
If @extended < 5 Then
If $iError = 0 Then GUICtrlSetBkColor($hInput, 0xFFA090)
$iError = 1
ElseIf $iError Then
GUICtrlSetBkColor($hInput, 0xFFFFFF)
$iError = 0
EndIf
ElseIf $iError Then
GUICtrlSetBkColor($hInput, 0xFFFFFF)
$iError = 0
EndIf
ElseIf $iError Then
GUICtrlSetBkColor($hInput, 0xFFFFFF)
$iError = 0
EndIf
$sInput = $sFormula
EndIf
; BitAND(WinGetState($hChild), $WIN_STATE_ACTIVE) = 8
If $msg2 = $hOkay Or (_IsPressed("0D") And BitAND(WinGetState($hChild), 8) = 8 And (ControlGetFocus($hChild) = "Edit2" Or ControlGetFocus($hChild) = "Edit1")) Then
$sFormula = GUICtrlRead($hInput) ;
If StringRegExp($sFormula, '[^ ]') And ValidSyntax($sFormula, $iCols) Then
; missing formula execution code
MsgBox(0, "VALID SYNTAX", "Well done, your formula can safely be executed, but" & @CRLF & "can't find parent window.")
ExitLoop
Else
Switch @extended
Case 1
$vTemp = "The formula contains illegal characters."
Case 2
$vTemp = "No such column exists."
Case 3
$vTemp = "The formula contains a syntax error."
Case 4
$vTemp = "The formula is missing opening parenthesis."
Case 5
$vTemp = "The formula is unterminated"
case 6
$vTemp = "The formula is missing closing parenthesis."
EndSwitch
MsgBox(262160, "uh-uh!", StringRegExp($sFormula, '[^ ]') ? $vTemp : "Please enter a formula.")
While _IsPressed("0D")
Sleep(50)
WEnd
;$iError = 1
EndIf
EndIf
WEnd
GUIDelete($hChild)
EndFunc ; formula
Func ValidSyntax($sFormula, $iMax) ; [currently requires external check that string <> ""]
; quick test for accepted characters
If StringRegExp($sFormula, '(?i)[^c \d\.\+\-\*/\^\(\)]') Then Return SetExtended(1, False) ; illegal characters
If StringRegExp($sFormula, '(?i)\.\D|c[0\D]|\d\s+[\.\d]') Then Return SetExtended(3, False) ; syntax
$sFormula = StringStripWS($sFormula, 8) ; strip all spaces
; split to separate formula components
Local $aFormula = StringRegExp($sFormula, '(?i)\-?c\d+|\-?c|\-?\d*\.\d*|\-?\d+|\-\(|[\+\-\*/\^\(\)]|.+', 3), _ ; create array [ADDED] ==> |.+
$iLast, $sTest, $sExpect = '(?i)\-?\(|\-?c\d+|\-?c|\-?\d*\.\d*|\-?\d+|\-', _ ; valid first component
$iBracket = 0, $bSyntax = False, $bNoSuch = False, $bTermination = False ; error tracking variables
For $i = 0 To UBound($aFormula) -1
If Not StringRegExp($aFormula[$i], $sExpect) Then ; unexpected code sequence
$bSyntax = True
ExitLoop
EndIf
If StringRegExp($aFormula[$i], '(?i)\-?c\d+') Then ; column number
$sTest = StringRegExpReplace($aFormula[$i], '(?i)[\-c]+', '')
If $sTest > $iMax Or $sTest == 0 Then ; no such column exists
$bNoSuch = True
ExitLoop
EndIf
$sExpect = '[\+\-\*/\^\)]' ; operators / closing brackets
ElseIf StringRegExp($aFormula[$i], '\-?\d*\.\d*') Then ; decimal
If $aFormula[$i] == '.' Or $aFormula[$i] == '-.' Then
If $i = UBound($aFormula) -1 Then
$bTermination = True
ExitLoop
EndIf
$bSyntax = True ; badly formated decimal
ExitLoop
EndIf
$sExpect = '[\+\-\*/\^\)]' ; as above
ElseIf StringRegExp($aFormula[$i], '\-?\d+') Then ; integer
$sExpect = '[\+\-\*/\^\)]'
ElseIf StringRegExp($aFormula[$i], '\A[\+\-\*/\^]\z') Then ; operator
$iLast = UBound($aFormula) -1
If $i = $iLast Or ($i = $iLast -1 And $aFormula[$iLast] == '-') Then
$bTermination = True
ExitLoop
EndIf
$sExpect = '(?i)\-?\(|\-?c\d+|\-?c|\-?\d*\.\d*|\-?\d+'
ElseIf $aFormula[$i] == '(' Or $aFormula[$i] == '-(' Then ; opening bracket
$iBracket += 1
$sExpect = '(?i)\-?\(|\-?c\d+|\-?c|\-?\d*\.\d*|\-?\d+|\-'
ElseIf $aFormula[$i] == ')' Then ; closing bracket
$iBracket -= 1
$sExpect = '[\+\-\*/\^\)]'
ElseIf $aFormula[$i] = 'c' Or $aFormula[$i] = '-c' Then ; unterminated column number
If $i = UBound($aFormula) -1 Then
$bTermination = True
Else
$bSyntax = True
ExitLoop
EndIf
Else ; unexpected exception [this should never happen]
$bSyntax = True
ExitLoop
EndIf
If $iBracket < 0 Then ExitLoop ; closing (unopened) parenthesis error
; [formula looks okay so far]
Next
; better to be a bit wordy [for clarity]
If $bNoSuch Then Return SetExtended(2, False) ; no such column exists
If $bSyntax Then Return SetExtended(3, False) ; syntax error
If $iBracket < 0 Then Return SetExtended(4, False) ; missing opening parenthesis
If $bTermination Then Return SetExtended(5, False) ; formula may not terminate with operator or c
If $iBracket > 0 Then Return SetExtended(6, False) ; missing closing parenthesis
; ConsoleWrite('valid' & @LF)
Return True ; formula is correct
EndFunc ;==> ValidSyntax