Metamorphing Machine I rather be this walking metamorphosis
than having that old formed opinion about everything!

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