Skip to content

Add select case statement - #1826

Open
TwitchBronBron wants to merge 1 commit into
masterfrom
select-case
Open

TwitchBronBron wants to merge 1 commit into
masterfrom
select-case

Conversation

@TwitchBronBron

Copy link
Copy Markdown
Member

Closes #15, and follows the VB-style syntax proposed in this comment.

BrighterScript finally gets a switch statement. Since BrightScript is so heavily based on VB, we borrowed VB's select case syntax instead of inventing a C-style switch:

select case number
    case 1
        print "one"
    case 6, 7, 8
        print "between 6 and 8, inclusive"
    case else
        print "no matching case"
end select

It compiles down to exactly what you'd have written by hand:

if number = 1 then
    print "one"
else if number = 6 or number = 7 or number = 8 then
    print "between 6 and 8, inclusive"
else
    print "no matching case"
end if

So it runs on every device, and there's no runtime cost.

The full write-up, with all the edge cases, is in docs/select-case.md. Here are the parts most worth a reviewer's attention.

How it behaves

  • No fallthrough, no break. The first matching case runs, and that's it. To share a body between values, list them together: case "left", "right". Value lists can also wrap across lines if each line ends with a comma.
  • The subject is evaluated once. select case getStatus() doesn't call getStatus() once per case. Local variables and literals are used directly. Anything else is stored in a single temp variable, __bsSelectCase, first. We can reuse that one name even for nested selects, because once a case body starts running, the outer select never reads its subject again.
  • select case true works the way VB devs expect. Each case is a condition, and we emit the conditions directly (if age < 13 then) instead of true = (age < 13).
  • Values that bind looser than = get parentheses. For example, case a and b becomes subject = (a and b), so operator precedence can't quietly change what a case means.
  • Enums and consts get inlined as usual. exit for, continue for (including the goto rewrite for older firmware), and return all behave like they would inside an if.
  • case right after select is optional (select m.state), like in VB. This only works when the subject starts with a name, a literal, or not, so select(x) stays a function call.

Nothing that works today should break

select, case, and endselect were never reserved words, so real code may already use them as names. Variables, fields, methods, AA keys, and function names called select/case all still work, and there are tests for that. The one catch: inside a select case, a line starting with case is read as the next case. Assignment forms (case = 1, case.x = 1, case[0] = 1, case++) are excluded from that.

Editor experience

The parser gets exercised on half-typed code all the time, so I put effort into recovery:

  • A missing subject, a missing value, a trailing comma, or a missing end select each gives one clear error, and the rest of the AST survives.
  • An unterminated block inside a case (like a missing end if) stops at the next case, instead of eating the rest of the select or function.
  • A function expression inside a case body resets that behavior, so case can't accidentally end a function.
  • One test parses every prefix of a sample snippet, character by character, and checks the function is still found each time.

New diagnostics (1158–1170)

  • Warnings:
    • no case else (1158)
    • duplicate case values (1166)
    • literal values whose types can't be compared, since "1" = 1 is a runtime Type Mismatch crash in BrightScript (1167)
    • an empty case followed by another case, which is the classic "I expected this to fall through" bug (1168). Putting a comment in the case marks it as intentional.
    • a select with no cases (1169)
  • Errors:
    • statements before the first case
    • misplaced or duplicate case else
    • orphaned case / end select
    • use inside an inline if
    • use in .brs files

A call for reviewers: the missing-case else warning is the most opinionated one. A lot of people leave out a default on purpose, and it's easy to override with diagnosticSeverityOverrides. Still, if folks would rather it be a hint, that's a one-line change.

A note on the AST shape

SelectCaseStatement holds an array of CaseStatements, and case else is just a CaseStatement with isElse set. TypeScript and ESTree model these as "clauses" that are neither statements nor expressions. Our AST only has statements and expressions, though, and a case holds statements rather than producing a value. So it's a Statement, the same way CatchStatement is for try.

One unrelated fix that came along

AstNode.finalizeClone was setting .parent on the array instead of on each child. That means cloned Block.statements (and every other array of child nodes) never got re-parented. I found it while writing the clone tests, and the fix is one line. The full suite still passes with it.

Not in this PR

These VB features aren't included, and they'd make good follow-ups: case 1 to 5, case is > 5, and exit select. The last one would need a goto label, the same way continue does for older firmware. For now, the docs point people to select case true for ranges.

🤖 Generated with Claude Code

Adds VB-style `select case` / `case` / `case else` / `end select` syntax
(proposed in #15), which transpiles to a plain if/else-if chain.

- parser support with tolerant recovery for partially-typed code
- SelectCaseStatement and CaseStatement AST nodes
- transpile to if/else chain, evaluating the subject exactly once
- validation for syntax errors, missing `case else`, duplicate values,
  mismatched literal types, and empty cases that look like fallthrough
- fix finalizeClone not re-parenting children of cloned arrays
- docs page at docs/select-case.md

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

@markwpearce markwpearce left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Finally.

Thanks for using "v1" Statement constructor syntax. :)

@iObject
iObject self-requested a review September 25, 2026 11:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

support switch statements

3 participants