Build a tested language workbench
Write original, executable examples of functions, enums, loops, arrays, collections, dictionaries, classes and structured errors.
Start with Greeting as a pure function, then add each calculation before wiring the Run button. Main.bas owns the public enum; callers in a different module qualify it as Main.TaskState. Counter is a class with independent per-instance state. FillReadings sizes a dynamic array through its ByRef parameter; declaring the caller with fixed bounds now fails Check. TimerSeconds exercises CDbl(Timer()). Use the array reference’s five-step repair exercise to observe MCB2102 and restore the valid declaration. The test module is declared in the manifest and contains nine test procedures; Check alone does not execute those assertions.
Step 1Prepare a safe practice project#
Extract the archive into a new writable folder, separate from your own application. Open LanguageWorkbench.mcproj in McBasic™. Keep every declared source, layout, resource and migration file together.
To reconstruct it yourself, create a DesktopApp project and add the files declared below. In the complete project, follow these steps as a guided rebuild: replace each named source file once and Check after all its procedures are present.
Exact project configuration
{
"schemaVersion": 1,
"projectId": "mcbasic-web-language-workbench",
"name": "LanguageWorkbench",
"kind": "DesktopApp",
"language": {
"dialect": "McBasic1",
"requireExplicit": true,
"compare": "Binary",
"defaultArrayBase": 0
},
"application": {
"bundleIdentifier": "org.mcbasic.web.languageworkbench",
"version": "1.0.0",
"buildNumber": "1",
"startupFormId": "main",
"shutdownMode": "OnStartupFormClosed"
},
"forms": [
{
"id": "main",
"typeName": "frmMain",
"layout": "frmMain.form.json",
"code": "frmMain.bas"
}
],
"modules": [
{
"name": "Main",
"path": "Main.bas"
}
],
"classes": [
{
"name": "Counter",
"path": "Counter.cls"
}
],
"tests": [
{
"name": "LanguageTests",
"path": "LanguageTests.bas",
"kind": "unit"
}
],
"targets": [
"osx-arm64"
]
}Step 2Build the Form and set its properties#
frmMain
Select the Form first and set its properties. Add containers before their children. These are initial Designer values, not statements to paste into a procedure.
| Form property | Value |
|---|---|
Caption | "Language Workbench" |
Width | 500 |
Height | 240 |
| Type | Name | Parent |
|---|---|---|
| CommandButton | cmdRun | Form |
| Label | lblStatus | Form |
cmdRun · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 24 |
Width | 200 |
Height | 36 |
Caption | "Run demonstrations" |
TabIndex | 0 |
lblStatus · exact Inspector values
| Property | Value |
|---|---|
Left | 24 |
Top | 90 |
Width | 450 |
Height | 70 |
Caption | "Open the IDE output, then run the demonstrations." |
WordWrap | True |
Step 3Connect the events explicitly#
frmMain
| Object | Event | Procedure | Parameters |
|---|---|---|---|
cmdRun | Click | cmdRun_Click | Click() |
Step 4Enter the complete behavior#
Open the named source file. Enter declarations first, then every procedure. Expand a procedure for its full body, or copy the complete file. Helpers are required: do not stop after entering the first Click handler.
frmMain.bas
Enter these declarations at file scope, before the procedures.
Option ExplicitPrivate Sub cmdRun_Click()
Enter the complete action, including its input checks and calls to helper procedures.
Private Sub cmdRun_Click()
Main.Main
lblStatus.Caption = "Demonstrations completed. Read Debug.Print results in the IDE output."
End SubCopy the complete frmMain.bas file
Replace the file once; do not append duplicate procedures.
Option Explicit
Private Sub cmdRun_Click()
Main.Main
lblStatus.Caption = "Demonstrations completed. Read Debug.Print results in the IDE output."
End SubMain.bas
Enter these declarations at file scope, before the procedures.
Option Explicit
Public Enum TaskState
Ready = 0
Running = 1
Complete = 2
End EnumPublic Function Greeting(ByVal name As String) As String
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function Greeting(ByVal name As String) As String
Dim clean As String
clean = Trim$(name)
If Len(clean) = 0 Then
Greeting = "Enter a name."
Exit Function
End If
Greeting = "Hello, " & clean
End FunctionPublic Function StateCaption(ByVal state As TaskState) As String
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function StateCaption(ByVal state As TaskState) As String
Select Case state
Case TaskState.Ready
StateCaption = "Ready"
Case TaskState.Running
StateCaption = "Working"
Case Else
StateCaption = "Complete"
End Select
End FunctionPublic Function SumReadings() As Long
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function SumReadings() As Long
Dim readings(-1 To 1) As Long
Dim index As Long
Dim total As Long
readings(-1) = 10
readings(0) = 20
readings(1) = 30
For index = LBound(readings) To UBound(readings)
total = total + readings(index)
Next index
SumReadings = total
End FunctionPublic Function GrowReadings() As Long
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function GrowReadings() As Long
Dim readings() As Long
ReDim readings(0 To 1)
readings(0) = 4
readings(1) = 5
ReDim Preserve readings(0 To 2)
readings(2) = 6
GrowReadings = readings(0) + readings(1) + readings(2)
End FunctionPublic Sub FillReadings(ByRef readings() As Long)
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub FillReadings(ByRef readings() As Long)
ReDim readings(0 To 2)
readings(0) = 20
readings(1) = 30
readings(2) = 40
End SubPublic Function ReadingsFromProcedure() As Long
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function ReadingsFromProcedure() As Long
Dim readings() As Long
FillReadings readings
ReadingsFromProcedure = readings(0) + readings(1) + readings(2)
End FunctionPublic Function TimerSeconds() As Double
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function TimerSeconds() As Double
TimerSeconds = CDbl(Timer())
End FunctionPublic Function CollectionCount() As Long
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function CollectionCount() As Long
Dim names As Collection
Set names = New Collection
names.Add "Ada", "first"
names.Add "Grace", "second"
names.Remove "first"
CollectionCount = names.Count
End FunctionPublic Function LookupName() As String
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function LookupName() As String
Dim names As Dictionary
Set names = New Dictionary
names.Add "lead", "Ada"
If names.Exists("lead") Then
LookupName = CStr(names.Item("lead"))
Else
LookupName = "Unknown"
End If
End FunctionPublic Function CountDown(ByVal start As Long) As Long
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function CountDown(ByVal start As Long) As Long
Dim value As Long
value = start
Do While value > 0
value = value - 1
Loop
CountDown = value
End FunctionPublic Function CatchFailure() As Long
Follow the success, failure and cleanup paths together. Do not omit a guard or Finally block when entering this procedure.
Public Function CatchFailure() As Long
Try
Err.Raise 513, "LanguageWorkbench", "A deliberate practice error."
Catch failure As McException
CatchFailure = failure.Number
Finally
Debug.Print "Error cleanup completed"
End Try
End FunctionPublic Sub Main()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Main()
Dim counter As Counter
Set counter = New Counter
counter.Add 3
counter.Add 2
Debug.Print Greeting(" Ada ")
Debug.Print StateCaption(TaskState.Running)
Debug.Print "Array total: " & CStr(SumReadings())
Debug.Print "Grown array total: " & CStr(GrowReadings())
Debug.Print "Callee-sized array total: " & CStr(ReadingsFromProcedure())
Debug.Print "Seconds since midnight: " & CStr(TimerSeconds())
Debug.Print "Collection count: " & CStr(CollectionCount())
Debug.Print "Lookup: " & LookupName()
Debug.Print "Counter: " & CStr(counter.Value)
Debug.Print "Countdown: " & CStr(CountDown(3))
Debug.Print "Caught error: " & CStr(CatchFailure())
End SubPublic Function LineTotal(ByVal quantity As Long, ByVal unitPrice As Currency) As Currency
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function LineTotal(ByVal quantity As Long, ByVal unitPrice As Currency) As Currency
Dim total As Currency
total = quantity * unitPrice
LineTotal = total
End FunctionPublic Function Welcome(ByVal name As String, Optional ByVal prefix As String = "Hello") As String
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Function Welcome(ByVal name As String, Optional ByVal prefix As String = "Hello") As String
Welcome = prefix & ", " & name
End FunctionPublic Sub TryWelcome()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub TryWelcome()
Debug.Print Welcome("Ada")
Debug.Print Welcome(prefix:="Welcome", name:="Ada")
End SubPublic Sub TryCounter()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub TryCounter()
Dim counter As Counter
Set counter = New Counter
counter.Add 3
counter.Add 2
Debug.Print counter.Value
End SubPublic Sub ReadLocalJson()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub ReadLocalJson()
Dim document As JsonValue
Dim name As String
document = Json.Parse("{""name"":""Ada""}")
If Json.Has(document, "name") Then
name = CStr(Json.Value(Json.Get(document, "name")))
Debug.Print name
End If
End SubCopy the complete Main.bas file
Replace the file once; do not append duplicate procedures.
Option Explicit
Public Enum TaskState
Ready = 0
Running = 1
Complete = 2
End Enum
Public Function Greeting(ByVal name As String) As String
Dim clean As String
clean = Trim$(name)
If Len(clean) = 0 Then
Greeting = "Enter a name."
Exit Function
End If
Greeting = "Hello, " & clean
End Function
Public Function StateCaption(ByVal state As TaskState) As String
Select Case state
Case TaskState.Ready
StateCaption = "Ready"
Case TaskState.Running
StateCaption = "Working"
Case Else
StateCaption = "Complete"
End Select
End Function
Public Function SumReadings() As Long
Dim readings(-1 To 1) As Long
Dim index As Long
Dim total As Long
readings(-1) = 10
readings(0) = 20
readings(1) = 30
For index = LBound(readings) To UBound(readings)
total = total + readings(index)
Next index
SumReadings = total
End Function
Public Function GrowReadings() As Long
Dim readings() As Long
ReDim readings(0 To 1)
readings(0) = 4
readings(1) = 5
ReDim Preserve readings(0 To 2)
readings(2) = 6
GrowReadings = readings(0) + readings(1) + readings(2)
End Function
Public Sub FillReadings(ByRef readings() As Long)
ReDim readings(0 To 2)
readings(0) = 20
readings(1) = 30
readings(2) = 40
End Sub
Public Function ReadingsFromProcedure() As Long
Dim readings() As Long
FillReadings readings
ReadingsFromProcedure = readings(0) + readings(1) + readings(2)
End Function
Public Function TimerSeconds() As Double
TimerSeconds = CDbl(Timer())
End Function
Public Function CollectionCount() As Long
Dim names As Collection
Set names = New Collection
names.Add "Ada", "first"
names.Add "Grace", "second"
names.Remove "first"
CollectionCount = names.Count
End Function
Public Function LookupName() As String
Dim names As Dictionary
Set names = New Dictionary
names.Add "lead", "Ada"
If names.Exists("lead") Then
LookupName = CStr(names.Item("lead"))
Else
LookupName = "Unknown"
End If
End Function
Public Function CountDown(ByVal start As Long) As Long
Dim value As Long
value = start
Do While value > 0
value = value - 1
Loop
CountDown = value
End Function
Public Function CatchFailure() As Long
Try
Err.Raise 513, "LanguageWorkbench", "A deliberate practice error."
Catch failure As McException
CatchFailure = failure.Number
Finally
Debug.Print "Error cleanup completed"
End Try
End Function
Public Sub Main()
Dim counter As Counter
Set counter = New Counter
counter.Add 3
counter.Add 2
Debug.Print Greeting(" Ada ")
Debug.Print StateCaption(TaskState.Running)
Debug.Print "Array total: " & CStr(SumReadings())
Debug.Print "Grown array total: " & CStr(GrowReadings())
Debug.Print "Callee-sized array total: " & CStr(ReadingsFromProcedure())
Debug.Print "Seconds since midnight: " & CStr(TimerSeconds())
Debug.Print "Collection count: " & CStr(CollectionCount())
Debug.Print "Lookup: " & LookupName()
Debug.Print "Counter: " & CStr(counter.Value)
Debug.Print "Countdown: " & CStr(CountDown(3))
Debug.Print "Caught error: " & CStr(CatchFailure())
End Sub
Public Function LineTotal(ByVal quantity As Long, ByVal unitPrice As Currency) As Currency
Dim total As Currency
total = quantity * unitPrice
LineTotal = total
End Function
Public Function Welcome(ByVal name As String, Optional ByVal prefix As String = "Hello") As String
Welcome = prefix & ", " & name
End Function
Public Sub TryWelcome()
Debug.Print Welcome("Ada")
Debug.Print Welcome(prefix:="Welcome", name:="Ada")
End Sub
Public Sub TryCounter()
Dim counter As Counter
Set counter = New Counter
counter.Add 3
counter.Add 2
Debug.Print counter.Value
End Sub
Public Sub ReadLocalJson()
Dim document As JsonValue
Dim name As String
document = Json.Parse("{""name"":""Ada""}")
If Json.Has(document, "name") Then
name = CStr(Json.Value(Json.Get(document, "name")))
Debug.Print name
End If
End SubCounter.cls
Enter these declarations at file scope, before the procedures.
Option Explicit
Private mValue As LongPublic Property Get Value() As Long
Keep this public property accessor with its complementary accessor and the fields or controls it uses.
Public Property Get Value() As Long
Value = mValue
End PropertyPublic Sub Add(ByVal amount As Long)
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Add(ByVal amount As Long)
mValue = mValue + amount
End SubCopy the complete Counter.cls file
Replace the file once; do not append duplicate procedures.
Option Explicit
Private mValue As Long
Public Property Get Value() As Long
Value = mValue
End Property
Public Sub Add(ByVal amount As Long)
mValue = mValue + amount
End SubLanguageTests.bas
Enter these declarations at file scope, before the procedures.
Option ExplicitPublic Sub Test_Greeting()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_Greeting()
Assert.AreEqual "Hello, Ada", Greeting(" Ada ")
Assert.AreEqual "Enter a name.", Greeting(" ")
End SubPublic Sub Test_ReferenceFunctions()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_ReferenceFunctions()
Assert.AreEqual CCur(15), LineTotal(3, CCur(5))
Assert.AreEqual "Hello, Ada", Welcome("Ada")
Assert.AreEqual "Welcome, Ada", Welcome(prefix:="Welcome", name:="Ada")
End SubPublic Sub Test_JsonValues()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_JsonValues()
Dim document As JsonValue
document = Json.Parse("{""name"":""Ada""}")
Assert.IsTrue Json.Has(document, "name")
Assert.AreEqual "Ada", CStr(Json.Value(Json.Get(document, "name")))
End SubPublic Sub Test_Arrays()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_Arrays()
Assert.AreEqual 60, SumReadings()
Assert.AreEqual 15, GrowReadings()
End SubPublic Sub Test_ArrayParameters()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_ArrayParameters()
Dim readings() As Long
FillReadings readings
Assert.AreEqual 0, LBound(readings)
Assert.AreEqual 2, UBound(readings)
Assert.AreEqual 90, ReadingsFromProcedure()
End SubPublic Sub Test_TimerValue()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_TimerValue()
Dim seconds As Double
seconds = TimerSeconds()
Assert.IsTrue seconds >= 0
Assert.IsTrue seconds < 86400
End SubPublic Sub Test_Containers()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_Containers()
Assert.AreEqual 1, CollectionCount()
Assert.AreEqual "Ada", LookupName()
End SubPublic Sub Test_ControlFlow()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_ControlFlow()
Assert.AreEqual "Working", StateCaption(Main.TaskState.Running)
Assert.AreEqual 0, CountDown(3)
Assert.AreEqual 513, CatchFailure()
End SubPublic Sub Test_InstanceState()
Enter this helper in the named source file, outside every other procedure. Its callers are included in the same project.
Public Sub Test_InstanceState()
Dim first As Counter
Dim second As Counter
Set first = New Counter
Set second = New Counter
first.Add 5
second.Add 2
Assert.AreEqual 5, first.Value
Assert.AreEqual 2, second.Value
Assert.IsFalse first Is second
End SubCopy the complete LanguageTests.bas file
Replace the file once; do not append duplicate procedures.
Option Explicit
Public Sub Test_Greeting()
Assert.AreEqual "Hello, Ada", Greeting(" Ada ")
Assert.AreEqual "Enter a name.", Greeting(" ")
End Sub
Public Sub Test_ReferenceFunctions()
Assert.AreEqual CCur(15), LineTotal(3, CCur(5))
Assert.AreEqual "Hello, Ada", Welcome("Ada")
Assert.AreEqual "Welcome, Ada", Welcome(prefix:="Welcome", name:="Ada")
End Sub
Public Sub Test_JsonValues()
Dim document As JsonValue
document = Json.Parse("{""name"":""Ada""}")
Assert.IsTrue Json.Has(document, "name")
Assert.AreEqual "Ada", CStr(Json.Value(Json.Get(document, "name")))
End Sub
Public Sub Test_Arrays()
Assert.AreEqual 60, SumReadings()
Assert.AreEqual 15, GrowReadings()
End Sub
Public Sub Test_ArrayParameters()
Dim readings() As Long
FillReadings readings
Assert.AreEqual 0, LBound(readings)
Assert.AreEqual 2, UBound(readings)
Assert.AreEqual 90, ReadingsFromProcedure()
End Sub
Public Sub Test_TimerValue()
Dim seconds As Double
seconds = TimerSeconds()
Assert.IsTrue seconds >= 0
Assert.IsTrue seconds < 86400
End Sub
Public Sub Test_Containers()
Assert.AreEqual 1, CollectionCount()
Assert.AreEqual "Ada", LookupName()
End Sub
Public Sub Test_ControlFlow()
Assert.AreEqual "Working", StateCaption(Main.TaskState.Running)
Assert.AreEqual 0, CountDown(3)
Assert.AreEqual 513, CatchFailure()
End Sub
Public Sub Test_InstanceState()
Dim first As Counter
Dim second As Counter
Set first = New Counter
Set second = New Counter
first.Add 5
second.Add 2
Assert.AreEqual 5, first.Value
Assert.AreEqual 2, second.Value
Assert.IsFalse first Is second
End SubStep 5Keep resources and storage reproducible#
No external resource or migration files are declared. Any inline migration, seed data or local JSON is present in the code above. Runtime application data and private preferences are not included in the archive.
Step 6Check, run and test the result#
Save all files and run Check. Resolve diagnostics before Run. Developer CLI users can work from the extracted folder:
mcbasic check LanguageWorkbench.mcproj
mcbasic run LanguageWorkbench.mcproj
mcbasic test LanguageWorkbench.mcprojAcceptance checklist
- Run Check on the whole project.
- Run the demonstrations and inspect the IDE output: array totals 60, 15 and 90, seconds since midnight from 0 up to (but not including) 86400, collection count 1, lookup Ada, counter 5 and countdown 0.
- Run Test: all nine test procedures must pass.
- Change the expected greeting in the test to an incorrect value, confirm the test fails, then restore it.
Step 7Make one change and repeat the tests#
Add a pure function that clamps a Long to a minimum and maximum. Add tests for below, inside and above the range.
Save a working Git checkpoint first. Change one behavior, Check again and repeat the acceptance checklist. Compare with the downloaded solution if a regression appears.
20f4bfed6b · Build manifestSources & maintenance
Original web explanation, reviewed against these repository sources. API tables and example files follow the implementation.
docs/manual/21-language-reference.mddocs/manual/11-language-toolkit.mdsite/examples/LanguageWorkbench/Main.bassrc/McBasic.Compiler/ArrayResizeAnalysis.cssrc/McBasic.Compiler/Binder.cssrc/McBasic.Compiler/CallArguments.cssrc/McBasic.Runtime/BasicRuntime.cssite/examples/LanguageWorkbench/LanguageTests.bassite/examples/LanguageWorkbench/LanguageWorkbench.mcprojsite/examples/LanguageWorkbench/Counter.cls