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:
- Public and Private can only be used in a module's or class' declaration area.
- Static can only be used inside Sub, Function, or Property procedures.
- Dim can be used in both situations.
- WithEvents can only be used in a class' declaration area.
- If the identifier has a type-declaration character, it cannot have an As clause.
- WithEvents variables cannot be arrays.
- WithEvents variables cannot have a New clause or an initialization expression.
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