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

Let's build a transpiler! Part 37

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

ParamArray's type

When our transpiler is done, these are the data types we'll support:


(I choose not to support some VB6 valid data types, like Error, for instance. Technically Error is an unsigned Integer, but one cannot perform many operations on it.)

First and second boxes would be classified "value types" in .NET jargon, meaning they are not pointers nor references.
Those parenthesized names are data types per se, but we cannot detect them in VB. We cannot test a filehandle with VarType function, for instance (VarType(#1) is invalid syntax.)
It also returns vbString for both regular and fixed-length strings. We cannot tell a ByRef parameter from a ByVal one using VarType, either.

The other boxes would be classified as "reference values." They "carry" the address where the "true" value resides.

You may have noticed that I classified String as a value type, even though technically it is a reference one. That's because Strings have value semantics in VB. It (apparently) never has an "invalid" value. One does not Set them; we "Let" them.

Except for filehandles, all other types can be turned into arrays (even fixed-length strings.)
Arrays are weird beasts in VB6. Their variables are pointers to a Type/structure having a member that discriminates the type of their elements.

There are a few kinds of arrays. For starters, there are fixed-size arrays and dynamic ones.
When you declare a fixed-size array (i.e. Dim Months(1 To 12) As Integer), there's no way you can change its subscripts. They are fixed.
In our example, it will always have 12 elements. Another consequence is that Erase does not free the memory they occupy.

As for dynamic arrays (i.e. ReDim Months(1 To 12) As Integer), we can change their subscripts at will (ReDim Months(0 To 11)) and Erase frees their memory.

I am therefore asserting that there's a third type of array, and it is ParamArray: It is an array of references. (Pointers and references are "forbidding" words in VB-land, hush!)
My guess is that's the reason one cannot pass it as-is to another ParamArray, but have no idea why VB's powers-that-be thought it would be dangerous.
The last straw is that ParamArrays are "compiler magic": There's no way we, regular users, can create one manually.

Back to business

Last time I said we would parse attributes. I have forgotten about attributes... I do not plan to support VB's specific ones, but I do have use to custom attributes.
Unlike Java or .NET attributes, they are not placed above procedure signatures, but inside procedures.
Let's start with the class:

Public Class AttributeConstruct
Option Explicit

Public Id As Identifier
Public Value As IExpression
End Class

Then, the lines below must be included in SubConstruct, FunctionConstruct, PropertyConstruct, and Entity classes:

(...)
Private Attributes_ As KeyedList

Private Sub Class_Initialize()
(...)
Set Attributes_ = New KeyedList
Set Attributes_.T = NewValidator(TypeName(New AttributeConstruct))
End Sub

Public Property Get Attributes() As KeyedList
Set Attributes = Attributes_
End Property

And the changes below must be done to ParseProperty, ParseSub, and ParseFunction methods.

Rem Include in ParseProperty
Set Token = ParseAttributes(Prop.Attributes)
Set Token = ParseBody(Entity, Prop.Body, LookAhead:=Token)

Rem Include in ParseSub
Set Token = ParseAttributes(Proc.Attributes)
Set Token = ParseBody(Entity, Proc.Body, LookAhead:=Token)

Rem Include in ParseFunction
Set Token = ParseAttributes(Func.Attributes)
Set Token = ParseBody(Entity, Func.Body, LookAhead:=Token)

ParseDeclarationArea also needs a few changes and here it is ParseAttributes:

Private Function ParseDeclarationArea(ByVal Entity As Entity) As AccessToken
(...)
Dim KeepToken As Boolean

(...)

Do
If Not KeepToken Then Set Token = SkipLineBreaks
KeepToken = False

Select Case Token.Code
Case kwAttribute
If Access <> acLocal Then Fail Token, Msg008, Msg003
Set Token = ParseAttributes(Entity.Attributes, Token)
KeepToken = True
(...)
End Function
Private Function ParseAttributes(ByVal Attrs As KeyedList, Optional ByVal Token As Token) As Token
Dim Attr As AttributeConstruct
Dim Xp As New Expressionist

Do
If Token Is Nothing Then Set Token = NextToken
If Not Token.IsKeyword(kwAttribute) Then Exit Do

Set Attr = New AttributeConstruct
Set Token = NextToken
If Not IsProperId(Token) Then Fail Token, Msg128, Msg129
Set Attr.Id = NewId(Token)

Set Token = NextToken

If Token.IsOperator(opDot) Then
Set Token = NextToken
If Not IsProperId(Token) Then Fail Token, Msg129, Msg130

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

If Not Token.IsOperator(opEq) Then Fail Token, Msg129, Msg131
Set Attr.Value = Xp.GetExpression(Me)
Set Token = Xp.LastToken
If Attr.Value Is Nothing Then Fail Token, Msg129, Msg132

If Not IsBreak(Token) Then Exit Do
Set Token = Nothing
Loop

Set ParseAttributes = Token
End Function

Changes to EmitEntity and the EmitAttributes procedure:

Private Sub EmitEntity(ByVal Entity As Entity)

If Entity.OptionExplicit Then .AppendLn "Option Explicit"
.AppendLn
EmitAttributes Entity.Attributes
(...)

EmitAttributes Fnc.Attributes
EmitBody Fnc.Body
(...)

EmitAttributes Prc.Attributes
EmitBody Prc.Body
(...)

EmitAttributes Prp.Attributes
EmitBody Prp.Body
End Sub

Private Sub EmitAttributes(ByVal Attrs As KeyedList)
Dim Attr As AttributeConstruct

For Each Attr In Attrs
Builder.Append "Attribute "

If Not Attr.Id.Project Is Nothing Then
EmitToken Attr.Id.Project
Builder.Append "."
End If

EmitToken Attr.Id.Name
Builder.Append "="
EmitExpression Attr.Value
Builder.AppendLn
Next
End Sub

Finally, the messages:

Public Property Get Msg128() As String
Msg128 = "Rule: Attribute [varname.]identifier = expression"
End Property

Public Property Get Msg129() As String
Msg129 = "Expected: varname or identifier"
End Property

Public Property Get Msg130() As String
Msg130 = "Expected: identifier"
End Property

Public Property Get Msg131() As String
Msg131 = "Expected: ="
End Property

Public Property Get Msg132() As String
Msg132 = "Expected: expression"
End Property

Next week we'll start cleaning up some pending items before starting to transpile for real.

Andrej Biasic
2021-06-02
Update:
Removed Object from value types.

Andrej Biasic
2021-09-15