Let's build a transpiler! Part 41
This is the forty first post in a series of building a transpiler.You can find the previous ones here.
The minus-one incident
Since the beginning of times (around 1991), VB understands zero as being false and not-zero as being truthy.True's canon value is -1, though.
Languages derived from the C family have a different tradition. Their true's canonical value is 1.
When the powers-that-be at MS started working on what would become C#, they went the beaten path, semicolons and all. Keep in mind they were aiming at Java devs.
As a second thought, they tried to shove VB in their (.NET) framework and said VB's True would be no longer -1, but 1 because, hey, compatibility!
Mind you that at that time (around 2000), there was no single C# user in the whole world, but estimates were that there were around three million VB devs.
This gratuitous change alone would break hundreds of thousands of programs out there, or so we thought. We were not aware yet that there would be no upgrade path from VB6 to .NET. but, still.
It was the end of the world as we knew it.
After much debate, chagrin, and words being said, I can only suppose an adult stepped in and made the logical thing to do:
Internally, True would still be 1*, but when converting it in VB-land, it would yield -1.
One crisis averted. If only no other hundreds were waiting in line...
*I've checked it by inspecting True's value in memory many moons ago.
Back to business
Last time I said we would assure the correct use of ByVal, New, and named (:=) operators, and also enforce the relationship between elements in the declaration area.We will need more than that, but for now, we'll ensure that - if used - the := operator will be the first one.
If ByVal or New are used, we'll ensure they are the first ones too, or the first ones after := operator.
Here is the plan: We'll have a counter. Any time we hit an operator, we'll increase it. If that operator is :=, then our counter must be 1, else we Fail.
If it is 1, then we'll change it to -1. Any time the counter is negative, we'll decrease it instead of increasing it.
If that operator is not :=, then we check if it is ByVal or New. If it is, then it is better that counter be -2 or 1. -2 means it is the first operator after :=. 1 means it is the first one.
Here is the implementation:
Public Function GetExpression(ByVal Parser As Parser, Optional ByVal Token As Token) As IExpression
(...)
Dim Count As Integer
(...)
Select Case Token.Kind
Case tkOperator
Select Case Token.Code
Case opAddressOf, opAndAlso, opByVal, opIs, opIsNot, opLike, opNew, opNot, opOrElse, opTo, _
opTypeOf, opAnd, opEqv, opImp, opMod, opOr, opXor
GoSub CheckDowngrade
End Select
Rem This check is not redundant. It is verifying if the call to CheckDowngrade reclassified Token.
If Token.Kind = tkOperator Then
Count = Count + IIf(Count < 0, -1, 1)
Select Case Token.Code
Case opSum
Token.Code = opId
Case opSubt
Token.Code = opNeg
Rem Unary operator
Case opNew
Select Case Count
Case -2, 1
Rem OK
Case Else
Parser.Fail Token, x.InvUseOf & NameBank(Token)
End Select
Rem Unary operators
Case opAddressOf, opNot, opTypeOf, opWithBang, opWithDot
Rem OK
Case Else
Exit Do
End Select
(...)
Select Case Token.Kind
Case tkOperator
Down:
Count = Count + IIf(Count < 0, -1, 1)
Rem Unary and compound operators
Select Case Token.Code
Case opNamed
If Count <> 1 Then Parser.Fail Token, x.InvUseOf & NameBank(Token)
Count = -1
Case opByVal
Select Case Count
Case -2, 1
Rem OK
Case Else
Parser.Fail Token, x.InvUseOf & NameBank(Token)
End Select
Case opAddressOf, opNew, opNot, opTypeOf
Parser.Fail Token, x.InvExpr
(...)
End Function
Rem Add to Messages
Public Property Get InvUseOf() As String
InvUseOf = "Invalid use of "
End Property
Regarding that relationship in the declaration area, we'll "complain" if there's a Deftype after a variable declaration or an Option Base after an array declaration:
Public Class ControlPanel
Option Explicit
(...)
Public HadDim As Boolean
Public HadArray As Boolean
(...)
End Class
Private Sub ParseDim( _
ByVal Access As Accessibility, _
ByVal Panel As ControlPanel, _
ByVal Vars As KeyedList, _
Optional ByVal InsideProc As Boolean, _
Optional ByVal IsStatic As Boolean, _
Optional ByVal Token As Token _
)
Dim Name As String
Dim WasArray As Boolean
Dim Var As Variable
Dim Expr As IExpression
Dim Subs As SubscriptPair
Dim Xp As Expressionist
Dim Bin As BinaryExpression
Panel.HadDim = True
(...)
If Token.Kind <> tkRightParenthesis And Xp.LastToken.Kind <> tkRightParenthesis Then _
Fail Token, x.ParensMismatch
Panel.HadArray = True
WasArray = True
Set Token = NextToken
End If
(...)
End Sub
Private Function ParseDeclarationArea(ByVal Entity As Entity) As AccessToken
(...)
Select Case Token.Code
Case cxBase
If Panel.HadArray Then Fail Token, x.ArrayDimed
(...)
Case kwDefBool
If Access <> acLocal Then Fail Token, x.RuleIdHeader, x.IdName
ParseDef vbBoolean, Entity, Panel '<-Repeat it to every ParseDef below
(...)
End Function
Private Sub ParseDef(ByVal VariableType As Integer, ByVal Entity As Entity, ByVal Panel As ControlPanel)
Dim First As String
Dim Last As String
Dim Token As Token
Dim Mark As Token
Do
Set Token = SkipLineBreaks
If Panel.HadDim Then Fail Token, x.DefBeforeDim
(...)
Loop
End Sub
Public Class Messages
(...)
Public Property Get ArrayDimed() As String
ArrayDimed = "Array already dimensioned"
End Property
Public Property Get DefBeforeDim() As String
DefBeforeDim = "Deftype statements must precede declarations"
End Property
End Class
Next week we'll build a symbol table out of our identifiers.
Andrej Biasic
2021-06-30