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

Let's build a transpiler! Part 24

This is the twenty-fourth post in a series of building a transpiler.
You can find the previous ones here.

Last time I said we would parse Implements and variable declaration.
If you followed my last posts, parsing Implements is trivial, the only additional step now is to add the proper messages to Messages module.
Here is the code:

Rem Add to project:
Public Class ImplementsConstruct
Option Explicit

Public Property Get Id() As Identifier
Static Hidden As New Identifier

Set Id = Hidden
End Property
End Class


Rem Add to Entity class:
Private Impls_ As KeyedList
(...)

Private Sub Class_Initialize()
(...)
Set Impls_ = New KeyedList
Impls_.CompareMode = vbTextCompare
End Sub

Public Property Get Impls() As KeyedList
Set Impls = Impls_
End Property


Rem Add to ParseDeclarationArea:
Private Function ParseDeclarationArea(ByVal Entity As Entity) As Token
(...)

Case kwImplements
If Not Entity.IsClass Then Fail Token, Msg016
If Access <> AccessLocal Then Fail Token, Msg008, Msg003
ParseImplements Entity

Case kwEnd
Exit Do
(...)
End Function


Add to Parse class:
Private Sub ParseImplements(ByVal Entity As Entity)
Dim Token As Token
Dim Impls As ImplementsConstruct

Set Token = SkipLineBreaks
If Token.Kind <> tkIdentifier Then Fail Token, Msg058, Msg059

Set Impls = New ImplementsConstruct
Set Impls.Id.Name = Token

Set Token = NextToken

If IsOp(Token, opDot) Then
Set Token = NextToken
If Token.Kind <> tkIdentifier Then Fail Token, Msg058, Msg003

Set Impls.Id.Name = Token
Set Token = NextToken
End If

If Not IsBreak(Token) Then Fail Token, Msg058, Msg031
Entity.Impls.Add Token, NameOf(Token)
End Sub


Rem Add to Messages module:
Public Property Get Msg058()
Msg058 = "Rule: Implements [project_name.]identifier line_break"
End Property

Public Property Get Msg059()
Msg059 = "Project name or identifier"
End Property

Parsing variable declaration is a little more involved due to its syntax:

(Public | Private | Static | Dim) [WithEvents] identifier[([[n To] m[, ...]])][type_declaration_character] [As (data_type [= expression] | New class_name)] [, ...]

Its rules are: I'm not sure whether I like the last rule or not, so I will not enforce it for now.
Let's create a Variable class:

Public Class Variable
Option Explicit

Public Access As Accessibility
Public IsStatic As Boolean
Public HasWithEvents As Boolean
Public HasNew As Boolean
Public DataType As DataType
Public Init As Token

Public Property Get Id() As Identifier
Static Hidden As New Identifier

Set Id = Hidden
End Property
End Class

Adapt Entity to have variables:

Private Vars_ As KeyedList

Private Sub Class_Initialize()
(...)

Set Vars_ = New KeyedList
Vars_.CompareMode = vbTextCompare
End Sub

Public Property Get Vars() As KeyedList
Set Vars = Vars_
End Property

Change ParseDeclarationArea to parse variables. These are the main changes:

Private Function ParseDeclarationArea(ByVal Entity As Entity) As Token
(...)

Do
Set Token = SkipLineBreaks

If Token.Kind = tkKeyword Then
Select Case Token.Code
Case kwOption
(...)

Case kwWithEvents
If Access = AccessLocal Then Access = AccessPublic
ParseDim Access, Entity, Token:=Token
Access = AccessLocal

Case kwDim
If Access = AccessLocal Then Access = AccessPublic
ParseDim Access, Entity
Access = AccessLocal

Case kwEnd
Exit Do

Case Else
Fail Token, Msg018
End Select

ElseIf IsProperId(Token, CanHaveSuffix:=True) Then
ParseDim Access, Entity, Token:=Token
Access = AccessLocal

Else
Fail Token, Msg018
End If
Loop

Set ParseDeclarationArea = Token
End Function

Add the missing messages since the last post:

Public Property Get Msg061()
Msg061 = "(Public | Private | Static | Dim) [WithEvents] identifier[type_declaration_character][()] [As (data_type [= expression] | New class_name)] [, ...]"
End Property

Public Property Get Msg062()
Msg062 = "Invalid use of New"
End Property

Public Property Get Msg063()
Msg063 = "Invalid inside Sub, Function, or Property"
End Property

Public Property Get Msg064()
Msg064 = "Invalid use of New with array"
End Property

Finally, ParseDim procedure:

Private Sub ParseDim( _
ByVal Access As Accessibility, _
ByVal Entity As Entity, _
Optional ByVal InsideProc As Boolean, _
Optional ByVal Token As Token _
)
Dim WasArray As Boolean
Dim Var As Variable

If InsideProc Then: If Access = AccessPublic Or Access = AccessPrivate Then Fail Token, Msg063

Rem Token is Nothing when parsing Dim.
Rem It is not Nothing when parsing Public or Private.
If Token Is Nothing Then Set Token = NextToken

Do
Set Var = New Variable
Var.Access = Access

If IsKw(Token, kwWithEvents) Then
If Not Entity.IsClass Then Fail Token, Msg016
If InsideProc Then Fail Token, Msg063

Var.HasWithEvents = True
Set Token = NextToken
End If

If Not IsProperId(Token, CanHaveSuffix:=True) Then Fail Token, Msg061, Msg003
Set Var.Id.Name = Token

Set Token = NextToken
WasArray = False

If Token.Kind = tkLeftParenthesis Then
Set Token = NextToken
If Token.Kind <> tkRightParenthesis Then Fail Token, Msg057

Rem Storing here because we do not have an instantiated DataType yet.
WasArray = True
Set Token = NextToken
End If

If IsKw(Token, kwAs) Then
If Var.Id.Name.Suffix <> vbNullChar Then Fail Token, Msg024
Set Token = NextToken

If IsOp(Token, opNew) Then
Var.HasNew = True
Set Token = NextToken
End If

If Not IsProperDataType(Token) Then Fail Token, Msg061, Msg025
Set Var.DataType = NewDataType(Token)

Rem Example: Dim Abc As New Integer. This is invalid.
If Var.HasNew And Var.DataType.Id.Name.Kind = tkKeyword Then Fail Token, Msg062, Msg059

Set Token = NextToken

If IsOp(Token, opDot) Then
Set Token = NextToken

If Not IsProperDataType(Token) Then Fail Token, Msg061, Msg003
Set Var.DataType.Id.Name = Token

Set Token = NextToken
End If

ElseIf Var.Id.Name.Suffix <> vbNullChar Then
Set Var.DataType = FromChar(Var.Id.Name.Suffix)

Else
Set Var.DataType = Entity.DefTypes(NameOf(Token))
End If

Var.DataType.IsArray = WasArray
If Var.HasNew And Var.DataType.IsArray Then Fail Token, Msg064

If IsOp(Token, opEq) Then
Set Token = GetExpression
Set Var.Init = Token

Set Token = NextToken
End If

If Entity.Vars.Exists(NameOf(Var.Id.Name)) Then Fail Token, Msg006 & NameOf(Var.Id.Name)
Entity.Vars.Add Var, NameOf(Var.Id.Name)

If IsBreak(Token) Then Exit Do
If Token.Kind <> tkListSeparator Then Fail Token, Msg061, ","
Set Token = NextToken
Loop
End Sub

You may have noticed that we did not parse array's subscripts.
That's what we'll do next week.

Andrej Biasic
2021-02-03