This question already has answers here:
VBScript implicit conversion in IF statement different from variable to literals?
(2 answers)
Closed 5 years ago.
I've run into a weird issue (IMHO) which may just be highlighting my ignorance when it comes to VBS vs. other programming languages. Here's what I have at the top of my .VBS file.
Option Explicit
Const MAXROWSFOREXCEL2010 = 1048576
I then have the following conditional check in a loop:
If numLines >= MAXROWSFOREXCEL2010 Then
wscript.echo "Inside the numLines If Then Statement"
wscript.echo "numLines = " & numLines & " >= " & MAXROWSFOREXCEL2010
As an example, let's say numLines is equal to 107563, which is clearly less than the 1 million plus value assigned to MAXROWSFOREXCEL2010 as a global const above.
For whatever reason, the IF statement is being executed, even when numLines is clearly less than the const.
However, if I remove the use of the const in the comparison, and just put the hard coded value of MAXROWSFOREXCEL2010 into the loop such as:
If numLines >= 1048576 Then
wscript.echo "Inside the numLines If Then Statement"
wscript.echo "numLines = " & numLines & " >= " & MAXROWSFOREXCEL2010
Then the IF statement is NOT entered incorrectly.
Can someone clue me in to why this is the case? Should I be somehow declaring the const as a particular data type? Is there some sort of truncation of the const going on?
I would be inclined to suggest the problem is numLines not being numeric rather than there being a problem with the Const value, in which case this question answers the problem.
From A:VBScript implicit conversion in IF statement different from variable to literals? by #cheran-shunmugavel
The documented behavior is that in comparisons, a number is always less than a string. This is mentioned in the documentation for Comparison Operators. Paraphrasing the table near the bottom of the page:
If one expression is numeric and the other is a string, then the numeric expression is less than the string expression.
With that in mind you simply need to make sure the variable numLines is explicitly a numeric value using an explicit cast.
'Explicitly cast variable to Long
numLines = CLng(numLines)
OK, I believe what's going on is a quirk of the way VBS attempts to determine data types. I also believe you're numLines variable is being evaluated as a String.
You can do Wscript.Echo TypeName(numLines) to verify this. I can replicate your issue when I set numLines equal to "107563" instead of 107563.
If a value is "boxed" in VBS, in the case of your const MAXROWSFOREXCEL2010, the engine will attempt to perform the comparison operation differently than if it's not. In this case, because numLines is a string and the comparison is against a boxed variable, it attempts to do a (bitwise) string comparison. If numLines is a string and the comparison is against an explicit Integer the comparison is done as a (bitwise) integer comparison.
If numLines was an Integer this problem doesn't happen.
Here are some more examples:
strOne = "1"
intOne = 1
If "1" = 1 Then WScript.Echo "true" Else WScript.Echo "false"
If "1" = intOne Then WScript.Echo "true" Else wscript.Echo "false"
If strOne = 1 Then WScript.Echo "true" Else wscript.Echo "false"
If strOne = intOne Then WScript.Echo "true" Else wscript.Echo "false"
Output
true
true
true
false
I am having problems separating words from each other when it comes to equations, because I can't separate the equation into two parts if there is a negative variable involved.
set function to "-3x"
return word 1 of function
that would return "3x", because a hyphen is a text item delimiter, but I want it to return "-3x". Is there any way to remove the hyphen from the text item delimiters or any other way to include the hyphen into the string?
To give an idea, here's a simple tokenizer for a very simple Lisp-like language:
-- token types
property StartList : "START"
property EndList : "END"
property ANumber : "NUMBER"
property AWord : "WORD"
-- recognized token chars
property _startlist : "("
property _endlist : ")"
property _number : "+-.1234567890"
property _word : "abcdefghijklmnopqrstuvwxyz"
property _whitespace : space & tab & linefeed & return
to tokenizeCode(theCode)
considering diacriticals, hyphens, punctuation and white space but ignoring case and numeric strings
set i to 1
set l to theCode's length
set tokensList to {}
repeat while i ≤ l
set c to character i of theCode
if c is _startlist then
set end of tokensList to {tokenType:StartList, tokenText:c}
set i to i + 1
else if c is _endlist then
set end of tokensList to {tokenType:EndList, tokenText:c}
set i to i + 1
else if c is in _number then
set tokenText to ""
repeat while character i of theCode is in _number and i ≤ l
set tokenText to tokenText & character i of theCode
set i to i + 1
end repeat
set end of tokensList to {tokenType:ANumber, tokenText:tokenText}
else if c is in _word then
set tokenText to ""
repeat while character i of theCode is in _word and i ≤ l
set tokenText to tokenText & character i of theCode
set i to i + 1
end repeat
set end of tokensList to {tokenType:AWord, tokenText:tokenText}
else if c is in _whitespace then -- skip over white space
repeat while character i of theCode is in _whitespace and i ≤ l
set i to i + 1
end repeat
else
error "Unknown character: '" & c & "'"
end if
end repeat
return tokensList
end considering
end tokenizeCode
The syntax rules for this language are as follows:
A number expression contains one or more digits, "+" or "-" signs, and/or decimal point. (The above code currently doesn't check that the token is a valid number, e.g. it'll happily accept nonsensical input like "0.1.2-3+", but that's easy enough to add.)
A word expression contains one or more characters (a-z).
A list expression begins with a "(" and ends with a ")". The first token in a list expression must be the name of the operator to apply; this may be followed by zero or more additional expressions representing its operands.
Any unrecognized characters are treated as an error.
For example, let's use it to tokenize the mathematical expression "3 + (2.5 * -2)", which in prefix notation is written like this:
set programText to "(add 3 (multiply 2.5 -2))"
set programTokens to tokenizeCode(programText)
--> {{tokenType:"START", tokenText:"("},
{tokenType:"WORD", tokenText:"add"},
{tokenType:"NUMBER", tokenText:"3"},
{tokenType:"START", tokenText:"("},
{tokenType:"WORD", tokenText:"multiply"},
{tokenType:"NUMBER", tokenText:"2.5"},
{tokenType:"NUMBER", tokenText:"-2"},
{tokenType:"END", tokenText:")"},
{tokenType:"END", tokenText:")"}}
Once the text is split up into a list of tokens, the next step is to feed that list into a parser which assembles it into an abstract syntax tree which fully describes the structure of the program.
Like I say, there's a bit of a learning curve to this stuff, but you can write it in your sleep once you've grasped the basic principles. Ask and I'll add an example of how to parse these tokens into usable form later.
Following on from before, here's a parser that turns the tokenizer's output into a tree-based data structure that describes the program's logic.
-- token types
property StartList : "START"
property EndList : "END"
property ANumber : "NUMBER"
property AWord : "WORD"
-------
-- handlers called by Parser to construct Abstract Syntax Tree nodes,
-- simplified here for demonstration purposes
to makeOperation(operatorName, operandsList)
return {operatorName:operatorName, operandsList:operandsList}
end makeOperation
to makeWord(wordText)
return wordText
end makeWord
to makeNumber(numberText)
return numberText as number
end makeNumber
-------
-- Parser
to makeParser(programTokens)
script ProgramParser
property currentToken : missing value
to advanceToNextToken()
if programTokens is {} then error "Found unexpected end of program after '" & currentToken & "'."
set currentToken to first item of programTokens
set programTokens to rest of programTokens
return
end advanceToNextToken
--
to parseOperation() -- parses an '(OPERATOR [OPERANDS ...])' list expression
advanceToNextToken()
if currentToken's tokenType is AWord then -- parse 'OPERATOR'
set operatorName to currentToken's tokenText
set operandsList to {}
advanceToNextToken()
repeat while currentToken's tokenType is not EndList -- parse 'OPERAND(S)'
if currentToken's tokenType is StartList then
set end of operandsList to parseOperation()
else if currentToken's tokenType is AWord then
set end of operandsList to makeWord(currentToken's tokenText)
else if currentToken's tokenType is ANumber then
set end of operandsList to makeNumber(currentToken's tokenText)
else
error "Expected word, number, or list expression but found '" & currentToken's tokenText & "' instead."
end if
advanceToNextToken()
end repeat
return makeOperation(operatorName, operandsList)
else
error "Expected operator name but found '" & currentToken's tokenText & "' instead."
end if
end parseOperation
to parseProgram() -- parses the entire program
advanceToNextToken()
if currentToken's tokenType is StartList then
return parseOperation()
else
error "Found unexpected '" & currentToken's tokenText & "' at start of program."
end if
end parseProgram
end script
end makeParser
-------
-- parse the tokens list produced by the tokenizer into an Abstract Syntax Tree
set programTokens to {{tokenType:"START", tokenText:"("}, ¬
{tokenType:"WORD", tokenText:"add"}, ¬
{tokenType:"NUMBER", tokenText:"3"}, ¬
{tokenType:"START", tokenText:"("}, ¬
{tokenType:"WORD", tokenText:"multiply"}, ¬
{tokenType:"NUMBER", tokenText:"2.5"}, ¬
{tokenType:"NUMBER", tokenText:"-2"}, ¬
{tokenType:"END", tokenText:")"}, ¬
{tokenType:"END", tokenText:")"}}
set parserObject to makeParser(programTokens)
set abstractSyntaxTree to parserObject's parseProgram()
--> {operatorName:"add", operandsList:{3, {operatorName:"multiply", operandsList:{2.5, -2}}}}
The ProgramParser object is a very, very simple recursive descent parser, a collection of handlers, each of which knows how to turn a sequence of tokens into a specific data structure. In fact, the Lisp-y syntax used here is so simple it really only requires two handlers: parseProgram, which gets everything underway, and parseOperation, which knows how to read the tokens that make up a (OPERATOR_NAME [OPERAND1 OPERAND2 ...]) list and turn it into a record that describes a single operation (add, multiply, etc) to be performed.
The nice thing about an AST, especially a very simple regular one like this, is you can manipulate it as data in its own right. For instance, given the program (multiply x y) and a definition of y = (add x 1), you could walk the AST and replace any mention of y with its definition, in this case giving (multiply x (add x 1)). i.e. You can not only do arithmetic calculations (algorithmic programming), but algebraic manipulations (symbolic programming) too. That's a bit heady for here, but I'll see about knocking together a simple arithmetical evaluator for later.
To finish off, here's a simple evaluator for the parser's output:
to makeOperation(operatorName, operandsList)
if operatorName is "add" then
script AddOperationNode
to eval(env)
if operandsList's length ≠ 2 then error "Wrong number of operands."
return ((operandsList's item 1)'s eval(env)) + ((operandsList's item 2)'s eval(env))
end eval
end script
else if operatorName is "multiply" then
script MultiplyOperationNode
to eval(env)
if operandsList's length ≠ 2 then error "Wrong number of operands."
return ((operandsList's item 1)'s eval(env)) * ((operandsList's item 2)'s eval(env))
end eval
end script
-- define more operations here as needed...
else
error "Unknown operator: '" & operatorName & "'"
end if
end makeOperation
to makeWord(wordText)
script WordNode
to eval(env)
return env's getValue(wordText)'s eval(env)
end eval
end script
end makeWord
to makeNumber(numberText)
script NumberNode
to eval(env)
return numberText as number
end eval
end script
end makeNumber
to makeEnvironment()
script EnvironmentObject
property _storedValues : {}
--
to setValue(theKey, theValue)
-- theKey : text
-- theValue : script
repeat with aRef in _storedValues
if aRef's k is theKey then
set aRef's v to theValue
return
end if
end repeat
set end of _storedValues to {k:theKey, v:theValue}
return
end setValue
--
to getValue(theKey)
repeat with aRef in _storedValues
if aRef's k is theKey then return aRef's v
end repeat
error "'" & theKey & "' is undefined." number -1728
end getValue
--
end script
end makeEnvironment
to runProgram(programText, theEnvironment)
set programTokens to tokenizeCode(programText)
set abstractSyntaxTree to makeParser(programTokens)'s parseProgram()
return abstractSyntaxTree's eval(theEnvironment)
end runProgram
This replaces the make... handlers used to test the parser with new handlers that construct full-blown objects representing each type of structure that can make up an Abstract Syntax Tree: numbers, words, and operations. Each object defines an eval handler that knows how to evaluate that particular structure: in a NumberNode it simply returns the number, in a WordNode it retrieves and evaluates the structure stored under that name, in an AddOperationNode it evaluates each operand then sums them, and so on.
For example, to evaluate our original 3 + 2.5 * -2 program:
set theEnvironment to makeEnvironment()
runProgram("(add 3 (multiply 2.5 -2))", theEnvironment)
--> -2.0
In addition, an EnvironmentObject is used to store named values. For example, to store a value named "x" for use by a program:
set theEnvironment to makeEnvironment()
theEnvironment's setValue("x", makeNumber(5))
runProgram("(add 3 x)", theEnvironment)
--> 8
Obviously this will need a bit more work to make it into a proper calculator: a full set of operator definitions, better error reporting, and so on. Plus you'll probably want to replace the parenthesized prefix syntax with a more familiar infix syntax, for which you'll need something like a Pratt parser that can handle precedence, association, etc. But once you've got the basics working it's just a matter of reading up on the various techniques and making changes and improvements one by one until you arrive at the desired solution. HTH.
You can write a calculator in AppleScript if you wish to, but you need to do it as you would in any other language: 1. using a tokenizer to split the input text into a list of tokens, 2. feeding those tokens to a parser which assembles them into an abstract syntax tree, and 3. evaluating that tree to produce a result.
For what you're doing, you could probably write your tokenizer as a regular expression (assuming you don't mind dipping down to NSRegularExpression via the AppleScript-ObjC bridge). For parsing, I recommend reading up on Pratt parsers, which are easy to implement yet powerful enough to support prefix, infix, and posfix operators and operator precedence. For evaluation, a simple recursive AST walking algorithm may well be sufficient, but one step at a time.
These are all well-solved problems, so you won't have any trouble finding tutorials and other online information on how to do it. (Lots of crap, of course, so be prepared to spend some time figuring out how tell the good from the bad.)
Your one problem is that you none will be written specifically for AppleScript, so be prepared to spelunk material written around other languages (Python, Java, etc, etc) and translate from that to AS yourself. That'll require some effort and patience wading through all the programmer-speak, but is eminently doable (I originally cut my teeth on AppleScript and now write my own automation scripting languages) and a great learning exercise for developing your skills.
I ran into an issue with the Classic ASP VbScript InStr() function. As shown below, the second call to InStr() returns 1 when searching for an empty string in a non empty string. I'm curious why this is happening.
' InStr Test
Dim someText : someText = "So say we all"
Dim emptyString : emptyString = ""
'' I expect this to be true
If inStr(1,someText,"so",1) > 0 Then
Response.write ( "I found ""so""<br />" )
End If
'' I expect this to be false
If inStr(1, someText, emptyString, 1) > 0 Then
Response.Write( "I found an empty string<br />" )
End If
EDIT:
Some additional clarification: The reason for the question came up when debugging legacy code and running into a situation like this:
Function Go(value)
If InStr(1, "Option1|Option2|Option3", value, 1) > 0 Then
' Do some stuff
End If
End Function
In some cases function Go() can get called with an empty string. The original developer's intent was not to check whether value was empty, but rather, whether or not value was equal to one of the piped delimited values (Option1,Option2, etc.).
Thinking about this further it makes sense that every string is created from an empty string, and I can understand why a programming language would assume a string with all characters removed still contains the empty string.
What doesn't make sense to me is why programming languages are implementing this. Consider these 2 statements:
InStr("so say we all", "s") '' evaluates to 1
InStr("so say we all", "") '' evaluates to 1
The InStr() function will return the position of the first occurrence of one string within another. In both of the above cases, the result is 1. However, position 1 always contains the character "s", not an empty string. Furthermore, using another string function like Len() or LenB() on an empty string alone will result in 0, indicating a character length of 0.
It seems that there is some inconsistency here. The empty string contained in all strings is not actually a character, but the InStr() function is treating it as one when other string functions are not. I find this to be un-intuitive and un-necessary.
The Empty String is the Identity Element for Strings:
The identity element I (also denoted E, e, or 1) of a group or related
mathematical structure S is the unique element such that Ia=aI=a for
every element a in S. The symbol "E" derives from the German word for
unity, "Einheit." An identity element is also called a unit element.
If you add 0 to a number n the result is n; if you add/concatenate "" to a string s the result is s:
>> WScript.Echo CStr(1 = 1 + 0)
>> WScript.Echo CStr("a" = "a" & "")
>>
True
True
So every String and SubString contains at least one "":
>> s = "abc"
>> For p = 1 To Len(s)
>> WScript.Echo InStr(p, s, "")
>> Next
>>
1
2
3
and Instr() reports that faithfully. The docs even state:
InStr([start, ]string1, string2[, compare])
...
The InStr function returns the following values:
...
string2 is zero-length start
WRT your
However, position 1 always contains the character "s", not an empty
string.
==>
Position 1 always contains the character "s", and therefore an empty
string too.
I'm puzzled why you think this behavior is incorrect. To the extent that asking Does 'abc' contain ''? even makes sense, the answer has to be "yes": All strings contain the empty string as a trivial case. So the answer to your "why is this happening" question is because it's the only sane thing to do.
It is s correct imho. At least it is what I expect that empty string is part of any other string. But maybe this is a philosophical question. ASP does it so, so live with it. Practically speaking, if you need a different behavior write your own Method, InStrNotEmpty or something, which returns false on empty search string.
I'm connecting with LDAP to Active Directory for a corporate phonebook. I'm grabbing the data I want, but I need to do an If...Else statement on some of the data I'm grabbing.
I want to check if the value in AD attribute 'homePhone' begins with "01". If it does, I want to write out its value. If it begins with anything else, I want to either write "" or "Not Valid".
Here's what I've been writing, but isn't working:
Response.Write "<td>"
if objRS("homePhone") = "01*" then
Response.Write objRS("homePhone")
else
Response.Write ""
end if
Response.Write "</td>"
This seems to just go to the Else condition, and the homePhone attribute doesn't get written.
You can't use wildcards in string comparisons and, unfortunately, VBScript doesn't support the Like operator used in VBA/VB. You can use a regular expression but that's overkill for what you need here. Just strip the first two characters and perform your comparison.
If Left(objRS("homePhone"), 2) = "01" Then
If you need to perform case-insensitive string comparisons (not necessary in this situation, but may be helpful in the future), you can convert both strings to upper/lowercase before comparing or use the StrComp() function with the vbTextCompare parameter value.
If StrComp(Left(objRS("homePhone"), 2), "01", vbTextCompare) = 0 Then
I just learned that $ requires an escape character. What other special characters are there in VBScript?
Also is there a boolean function where I can find out if a character is a special character?
Huh? WScript.Echo "$" outputs $ without any escaping. The only special character in a VBScript string literal is the double quote (use two in a row for a literal double quote within a string).
You have to escape bigmoney when using it in VBScript regular expressions, but that is a very specific case. You cannot use it as you are used to in some BASIC flavours, VBA or VB to assign the String primitive to a variable.
(like
10 FOR I = 1024 TO 1063
20 A$ = A$ + CHR$(PEEK(I))
30 NEXT I
40 ? A$;
50 A$ = ""
60 GOTO 10
for the C64 or
Dim i, original$, final$
original$ = "Hello World!"
' Premature optimization rules! xxx$ functions are faster than xxx functions!
final$ = Left$(original$, 3) & Chr$(112) & Chr$(32) & Chr$(109) & Mid$(original$, 2, 1) & Right$(original$, 7)
MsgBox final$
In good ol' VB6)
Just eliminate the $ in the latter example, you don't need them.
If you really, really, really want to use the $ in routine or variable naming, you can always use the brackets like:
Sub [Wow! does thi$ really works? I'm a 1337 h4x0rz!]
MsgBox "Yes it does!"
End Sub
[Wow! does thi$ really works? I'm a 1337 h4x0rz!]
Edit;
Extra-Free-Bonus: A specialcharacter recognition function:
Public Function isSpecialCharacter(byVal myChar)
isSpecialCharacter = (myChar="""")
End Function