Expression Evaluator
sorry for the late reply Xombie, I just got back from work, here's my mod.
Code: Select all
Procedure.l IsNumber(inString.s, DecimalCharacter.b, ThousandsSeparator.b, *ReturnValue.l)
; TODO: Update to test for international decimal value and then check for IsNumeric calls to make sure they are
; international ready. Maybe need a StringToSingle call first?
;
; *ReturnValue (if a LONG address is passed) will contain a 'cleaned' version of the number. eg, no commas.
; IT MUST BE INITIALZED TO EMPTY OR OTHERWISE OR ELSE NOTHING WILL BE PASSED BACK.
;
iLoop.l
;
HoldCleaned.s
; String used to hold the 'cleaned' value.
CountDecimal.l : CountThousands.l : CountNumeric.l : CountDecimalNumerics.l
; The count of the decimal/thousands separator. Also the count of the numbers.
isHex.b = #False
isSci.b = #False
;
PositionDecimal.l : PositionThousands.l
; The location of the decimal/thousands separator.
IsNegative.b
; True if the value is negative.
HoldChar.b
; This will store an individual character to test if it's numeric.
*MemPosition.l = @inString
;
HoldLength.l = Len(inString)
; This will store the length of our string in characters.
Repeat
;
HoldChar = PeekB(*MemPosition)
; Store the current character.
If HoldChar > 47 And HoldChar < 58
; Numeral 0 to 9.
If CountDecimal : CountDecimalNumerics + 1 : Else : CountNumeric + 1 : EndIf
;
HoldCleaned + PeekS(*MemPosition, 1)
; Add the number to the 'clean' string.
ElseIf (HoldChar = '-') Or (HoldChar = '+')
; - (negative sign)
If iLoop > 0 : ProcedureReturn #False : EndIf
;
If (isSci = #False) And (HoldChar = '-') :IsNegative = #True : EndIf
; If the minus sign is not at the front, it's not a numeral.
HoldCleaned + PeekS(*MemPosition, 1)
; Add the negative sign to the 'clean' string.
ElseIf HoldChar = 36
; $ (hex sign)
If iLoop > 0 : ProcedureReturn #False : EndIf
; If the $ isn't at the front of the string, it's not a hex value.
isHex = #True
;
ElseIf HoldChar = ThousandsSeparator ; 44
; , (comma)
If CountDecimal Or isHex : ProcedureReturn #False : EndIf
; Never thousands after a decimal. Also, hex values never use a comma.
If CountThousands And CountNumeric < 3 : ProcedureReturn #False : EndIf
; Thousands separator requires at least three numbers.
CountNumeric = 0
; Reset the number count.
CountThousands + 1
;
ElseIf HoldChar = DecimalCharacter ; 46
; . (decimal sign)
If CountDecimal > 1 : ProcedureReturn #False : EndIf
; If there is more than one decimal, it's not an integer.
If isHex : ProcedureReturn #False : EndIf
; A hexidecimal will never have a decimal.
If CountThousands And CountNumeric < 3 : ProcedureReturn #False : EndIf
; Thousands separator requires at least three numbers.
PositionDecimal = *MemPosition - @inString
; Store the location of the decimal character.
CountDecimal + 1
; Increment our decimal count.
HoldCleaned + PeekS(*MemPosition, 1)
; Add the decimal to the 'clean' string.
ElseIf (HoldChar='e') Or (HoldChar='E')
HoldCleaned + PeekS(*MemPosition, 1)
isSci = #True
Else
;
ProcedureReturn #False
; Unknown character, non-numeral.
EndIf
;
*MemPosition + 1
;
Until *MemPosition - @inString = HoldLength
;
If CountThousands And CountNumeric < 3 : ProcedureReturn #False : EndIf
; Thousands separator requires at least three numbers.
If (CountDecimal And CountDecimalNumerics) Or isSci
;
If *ReturnValue : PokeS(*ReturnValue, HoldCleaned) : EndIf
; Return a 'cleaned' float value.
If IsNegative : ProcedureReturn -2 : Else : ProcedureReturn 2 : EndIf
; Return 2 for a float or -2 for a negative float. There must be numbers after the decimal to be considered a float.
EndIf
;
If CountNumeric
;
If *ReturnValue : PokeS(*ReturnValue, HoldCleaned) : EndIf
; Return a 'cleaned' integer value.
If IsNegative : ProcedureReturn -1 : Else : ProcedureReturn 1 : EndIf
; Return -1 for a negative integer or 1 for a positive integer.
EndIf
;
ProcedureReturn #False
; If we got this far, it must be a string.
EndProcedure
s.s=Space(200)
Debug IsNumber("1.23e+5",'.',0,@s)
Debug s
hi Xombie, I realize that adding scientific notation to your parser is not easy, but being that it uses strings as a stack to store intermediate results, I fear for loss of precision.
perhaps in the future you may want to rewrite the evaluator as a byte-code compiler, where you have the compiler taking care of parsing the expression, extracting literal constants and storing them in merory thus making the interpreter much simpler and faster.
perhaps in the future you may want to rewrite the evaluator as a byte-code compiler, where you have the compiler taking care of parsing the expression, extracting literal constants and storing them in merory thus making the interpreter much simpler and faster.

...byte-code compiler? ('_' ) Eh? ( '_') You'll have to explain that one to me. What exactly is it and how would I go about doing it so that it works with PB? And would it even be worth it for this function?jack wrote:...perhaps in the future you may want to rewrite the evaluator as a byte-code compiler, where you have the compiler taking care of parsing the expression, extracting literal constants and storing them in merory thus making the interpreter much simpler and faster.
New update. This is my first asm code that I've released. Well, aside from that IsOdd() code a long time (which was slow anyway
)
I've removed the ability to "clean" a numeric string since I wasn't using it at all.
So, now it is roughly 7 times faster than my original code since I've moved it to ASM. And not only that but it's also... err... actually producing correct results. The last code I had would return valid for numbers that were not.
This may still miss some things but it should be much more correct now. Let me know if you spot some errors on it.
The asm part of it makes it faster but it is one of my first tries so I'm sure it's still unoptimized. So don't laugh too hard >_>
I'll work on converting the expression evaluator to asm next and see how that goes

Code: Select all
Procedure.l IsNumber(InString.s, DecimalCharacter.b, ThousandsSeparator.b)
; +0, +4, +5
CaughtSpace.b ; + 6
; True if an empty space exists in the string.
CaughtDecimal.b ; + 7
; True if a decimal character exists.
CountNumeric.l ; + 8
; The count of numbers before the decimal place.
CountDecimal.l ; + 12
; The count of numbers after the decimal place.
IsNegative.b ; + 16
; True if the number is negative.
CaughtThousand.b ; + 17
; True if a thousands character exists.
!MOV esi, dword [esp]
; Store the address of the string in esi.
!DEC esi
; Decrement to the previous byte. This is only because I wanted to store the INC call at the start of the
; loop instead of having multiple "INC esi" calls within the code.
;/ Character loop start
!Start:
; This is the beginning of the character loop.
!INC esi
; Increment the character pointer. The characters are stored as bytes.
!MOVZX eax, byte [esi]
; Store the current character.
!CMP eax, 0
!JE Finished
; Check for EOL character.
!MOVZX edi, byte [esp + 4]
; Store the decimal character as a dword value so the compare operation will work.
!CMP eax, edi
!JE CharDecimal
; Check for the decimal charater.
!CMP eax, 32
!JE CharSpace
; Check for the empty character.
!CMP eax, 45
!JE CharNegative
; Check for the negative sign character.
!MOVZX edi, byte [esp + 5]
; Store the thousands separator character as a dword value so the compare operation will work.
!CMP eax, edi
!JE CharThousand
; Check for the thousands separator charater.
!CMP eax, 48
!JB NonNumeric
!CMP eax, 57
!JA NonNumeric
; Check for a non-numeric character.
;/ Character is a numeric character.
!CMP byte [esp + 6], 1
!JE NonNumeric
; Check if a space exists mid-string. If so, the string is non-numeric.
!CMP byte [esp + 7], 0
!JE @f
; Check if a decimal character exists.
!INC dword [esp + 12]
; Increase the count of numbers after the decimal place.
!JMP Start
; Process the next character.
!@@:
; A decimal character does not exist in the string. This number is a non-decimal numeric.
!CMP byte [esp + 17], 0
!JE @f
; Check if a thousands separator exists.
!CMP dword [esp + 8], 3
!JA NonNumeric
; There can be no more than 3 numbers after a thousands separator.
!@@:
; No thousands separator exists.
!INC dword [esp + 8]
; Increase the count of non-decimal numerics.
!JMP Start
; Process the next character.
;/ Character is a decimal.
!CharDecimal:
; Located a decimal character.
!CMP byte [esp + 6], 1
!JE NonNumeric
; Check if a space exists mid-string. If so, the string is non-numeric.
!CMP byte [esp + 7], 1
!JE NonNumeric
; Check if a decimal character already exists. Only one decimal character is allowed per string.
!CMP dword [esp + 8], 0
!JE NonNumeric
; There must be numbers before the decimal place.
!MOV byte [esp + 7], 1
; Set CaughtDecimal to True.
!JMP Start
; Process the next character.
;/ Character is a thousands separator.
!CharThousand:
; Located a thousands separator character.
!CMP byte [esp + 6], 1
!JE NonNumeric
; Check if a space exists mid-string. If so, the string is non-numeric.
!CMP byte [esp + 7], 1
!JE NonNumeric
; Check if a decimal character already exists. The thousands separator is invalid after the decimal.
!CMP byte [esp + 17], 0
!JE @f
; Check if a thousands separator already exists in the string.
!CMP dword [esp + 8], 3
!JNE NonNumeric
; There must be 3 numbers before a thousands separator when a thousands separator already exists in the string.
!@@:
; No thousands separator exist yet.
!CMP dword [esp + 8], 3
!JA NonNumeric
; Check the non-decimal numeric count. There must be no more than three numbers before a thousands character. There
; will be less than 3 for the first thousands separator (typically) - "3,323" or "38,391" or "849,328".
!CMP dword [esp + 8], 0
!JE NonNumeric
; There must be at least 1 number in front of the thousands separator.
!MOV byte [esp + 17], 1
; Set CaughtThousands to true.
!MOV dword [esp + 8], 0
; Reset the non-decimal numeric count.
!JMP Start
;Process the next character.
;/ Character is a negative sign.
!CharNegative:
; Located a negative sign.
!CMP dword [esp], esi
!JNE NonNumeric
; The negative sign must be the first character.
!MOV byte [esp + 16], 1
; The number is negative.
!JMP Start
; Process the next character.
;/ Character is a space.
!CharSpace:
; Located an empty character.
!CMP byte [esp + 7], 1
!JE @f
!CMP byte [esp + 17], 1
!JE @f
!CMP dword [esp + 8], 0
!JNE @f
; Check if a decimal, thousand or numeric character exists. If so, the space is either a trailing space or a space mid-string.
!JMP Start
; The space is a leading space. Ignore it and process the next character.
!@@:
; The space exists mid-string and is not a leading space.
!MOV byte [esp + 6], 1
; Set CaughtSpace to True.
!JMP Start
; Process the next character.
;/ Finished processing the string.
!Finished:
; Reached the end of the string.
!CMP dword [esp + 8], 0
!JE NonNumeric
; There must be some numbers in the string.
!CMP byte [esp + 17], 0
!JE @f
!CMP dword [esp + 8], 3
!JNE NonNumeric
; There must be three numbers after the last thousands separator.
!@@:
; No thousands character exists or there are 3 numbers after the thousand character.
!CMP byte [esp + 7], 0
!JE NonDecimal
!CMP dword [esp + 12], 0
!JE NonNumeric
; Check if a decimal character exists. If it does, ensure there are numbers after the decimal place.
!CMP byte [esp + 16], 0
!JE @f
; Check if the number is negative.
!MOV eax, -2
ProcedureReturn
; -2 signifies a negative decimal number.
!@@:
; The decimal number is positive.
!MOV eax, 2
ProcedureReturn
; 2 signifies a positive decimal number.
!NonDecimal:
; No decimal character exists.
!CMP byte [esp + 16], 0
!JE @f
; Check if the number is negative.
!MOV eax, -1
ProcedureReturn
; -1 signifies a negative integer.
!@@:
; The integer is positive.
!MOV eax, 1
ProcedureReturn
; 1 signifies a positive integer.
!NonNumeric:
; The string contains a non-numeric character.
!MOV eax, 0
; The string is non-numeric. Return False.
ProcedureReturn
;
EndProcedure
So, now it is roughly 7 times faster than my original code since I've moved it to ASM. And not only that but it's also... err... actually producing correct results. The last code I had would return valid for numbers that were not.

The asm part of it makes it faster but it is one of my first tries so I'm sure it's still unoptimized. So don't laugh too hard >_>
I'll work on converting the expression evaluator to asm next and see how that goes

-
- Addict
- Posts: 1648
- Joined: Mon Sep 20, 2004 3:52 pm
- Contact:
Hey Xombie, I'm using your evaluator modified a bit for my personal scripting language I'm releasing under the LGPL. I found an inconsistancy in your evaluator:
If you evaluate something like this: "Hi" & "Sup", the returned string is:
HiSup
However if you evaluate this: "HiSup", the returned string is:
"HiSup"
(Notice it includes the quotes)
Now in my case I don't want those quotes so I added this to the end of Evaluate to fix my version:
If you DO want the quotes on anything that had a string operation it'll have to be fixed another way. And even this way is a bit of a hack since strings could have the quote in them after all the processing.
But I'm just letting you all know.
Btw thanks for the code Xombie, really saved me a lot of time and debugging!
If you evaluate something like this: "Hi" & "Sup", the returned string is:
HiSup
However if you evaluate this: "HiSup", the returned string is:
"HiSup"
(Notice it includes the quotes)
Now in my case I don't want those quotes so I added this to the end of Evaluate to fix my version:
Code: Select all
If Left(HoldExpression,1)="'" ; for your version use chr(34)
HoldExpression=Right(HoldExpression,Len(HoldExpression)-1)
EndIf
If Right(HoldExpression,1)="'" ; for your version use chr(34)
HoldExpression=Left(HoldExpression,Len(HoldExpression)-1)
EndIf
But I'm just letting you all know.
Btw thanks for the code Xombie, really saved me a lot of time and debugging!
-
- Addict
- Posts: 1648
- Joined: Mon Sep 20, 2004 3:52 pm
- Contact:
Also to ensure it does not crash when running execute on a blank string, add this to the very top of Evaluate():
Code: Select all
If Trim(expression)=""
ProcedureReturn ""
EndIf
Ah. Thanks for the input but I had this other code here that's a complete rewrite. It's at the beginning stages so be careful.
At the very least it should be faster than my old code. It's for PB4 and it should be unicode compatible.
EDITED: Code removed. Check out links in my later post.
Let me know if you spot something else. As it's just starting, there is only one function. Also, I changed adding strings together to use "+" rather than "&". Have a good day ^_^
At the very least it should be faster than my old code. It's for PB4 and it should be unicode compatible.
EDITED: Code removed. Check out links in my later post.
Let me know if you spot something else. As it's just starting, there is only one function. Also, I changed adding strings together to use "+" rather than "&". Have a good day ^_^
Last edited by Xombie on Tue Mar 07, 2006 7:31 pm, edited 1 time in total.
-
- Addict
- Posts: 1648
- Joined: Mon Sep 20, 2004 3:52 pm
- Contact:
-
- Addict
- Posts: 1648
- Joined: Mon Sep 20, 2004 3:52 pm
- Contact:
Ah yeah. Those darn macros. So easy to forget.
http://www.seijin.net/Storage/xSolve/xSolve-394.pb
http://www.seijin.net/Storage/xSolve/xSolve-400.pb
There are two separate versions now. One for 3.94 and one for 4.00b5.
Let me know if that 3.94 one works out for you. I'd be curious to see how it does in your speed testing. It uses pure PB floats so decimal stuff will be sketchy. It'd probably take about 5 minutes to convert it to use something like jack's F64 library but I'll leave it as floats for now.
Take care
(Edit: I uploaded again since I noticed a few bugs. Download from the same spot.)
http://www.seijin.net/Storage/xSolve/xSolve-394.pb
http://www.seijin.net/Storage/xSolve/xSolve-400.pb
There are two separate versions now. One for 3.94 and one for 4.00b5.
Let me know if that 3.94 one works out for you. I'd be curious to see how it does in your speed testing. It uses pure PB floats so decimal stuff will be sketchy. It'd probably take about 5 minutes to convert it to use something like jack's F64 library but I'll leave it as floats for now.
Take care

(Edit: I uploaded again since I noticed a few bugs. Download from the same spot.)