The London Perl and Raku Workshop takes place on 26th Oct 2024. If your company depends on Perl, please consider sponsoring and/or attending.

NAME

Switch::Plain - a simple switch statement for Perl

SYNOPSIS

use Switch::Plain;

# string version
sswitch (get_me_a_string()) {
  # return value of get_me_a_string() is bound to $_ in this block

  case 'foo': {
    # runs if $_ equals 'foo'
  }

  case 'bar': {
    # runs if $_ equals 'bar'
  }

  case 'melonlord' if $DEBUG: {
    # runs if $_ equals 'melonlord' and $DEBUG is true
  }

  default if $VERBOSE > 1: {
    # runs if nothing else matched so far and $VERBOSE is greater than 1
  }

  default: {
    # runs if nothing else matched so far
  }
}

# number version
nswitch (get_me_a_number()) {
  # return value of get_me_a_number() is bound to $_ in this block

  case 1: {
    # runs if $_ equals 1
  }

  case 2: {
    # runs if $_ equals 2
  }

  case 99 if $DEBUG: {
    # runs if $_ equals 99 and $DEBUG is true
  }

  default if $VERBOSE > 1: {
    # runs if nothing else matched so far and $VERBOSE is greater than 1
  }

  default: {
    # runs if nothing else matched so far
  }
}

DESCRIPTION

This module provides (yet another) switch statement for Perl. Differences between this module and Switch include:

  • It's not a source filter. (It uses perl's pluggable keywords instead.)

  • It generates non-horrible code. If you want to see this for yourself, run some sample code through perl -MO=Deparse.

  • It doesn't try to be smart about matching fancy data structures; it only does simple string or numeric equality tests. This also sets it apart from perl's built in given statement and smartmatch operator ~~.

Syntax

This module understands the following grammar:

switch_statement := switch_keyword switch_scrutinee switch_body

switch_keyword := 'sswitch' | 'nswitch'

switch_scrutinee := '(' EXPR ')'

switch_body := '{' case_clause* '}'

case_clause := case_pattern+ BLOCK

case_pattern := case_keyword case_modifier? ':'

case_keyword := 'default' | 'case' EXPR

case_modifier := 'if' EXPR | 'unless' EXPR

*, +, and ? have their usual regex meaning; BLOCK and EXPR represent standard Perl blocks and expressions, respectively.

Semantics

The meaning of a switch statement is given by the following translation rules:

  • sswitch (FOO) { ... } and nswitch (FOO) { ... } turn into

    do {
      local *_ = \FOO;
      ...
    };

    That is, they alias $_ to FOO within the body of the switch statement.

  • A series of case clauses in the switch body turns into a single if/elsif chain. That is, the first clause becomes an if; every subsequent clause becomes an elsif.

  • case FOO: becomes if ($_ eq FOO) for sswitch and if ($_ == FOO) for nswitch.

    default: becomes if (1).

    case FOO if BAR: becomes if ($_ eq FOO && BAR) for sswitch and if ($_ == FOO && BAR) for nswitch.

    default if BAR: becomes if (BAR).

    ... unless BAR works similarly, but with the condition inverted (!BAR).

    If there are multiple case/defaults before a single block, their conditions are combined with ||.

Here's an example demonstrating all combinations:

sswitch (SCRUTINEE) {
  case FOO0: {
    BODY0
  }

  case FOO1:
  case FOO2 if BAR1:
  case FOO3 unless BAR2:
  default if BAR3:
  default unless BAR4: {
    BODY1
  }

  default: {
    BODY2
  }
}

This is equivalent to:

do {
  # temporarily alias $_ to SCRUTINEE within this block:
  local *_ = \SCRUTINEE;

  if ($_ eq FOO0) {
    BODY0
  }
  elsif (
      $_ eq FOO1 ||
      ($_ eq FOO2 && BAR1) ||
      ($_ eq FOO3 && !BAR2) ||
      BAR3 ||
      !BAR4
  ) {
    BODY1
  }
  elsif (1) {
    BODY2
  }
};

Differences between Switch::Plain and C's switch

  • C's switch is limited to integer scrutinees. Switch::Plain supports any number or string.

  • C's case labels must be compile-time constants. Switch::Plain allows any expression and even additional arbitrary conditions via the if/unless case modifiers.

  • C's case labels are actual labels in that they can appear anywhere within the switch statement's body (even nested). With Switch::Plain all cases must be at the top level.

  • In C the order of the cases does not matter since they're all known at compile time and guaranteed to be distinct. Switch::Plain evaluates them in the order they're written: If you put default: { ... } in the middle of a switch, it will intercept all values, and any following cases will be ignored (this is like writing ... elsif (1) { ... } else { ... }).

  • Since C's case labels are actual labels and C's switch is effectively a dynamic goto, C actually has no concept of a "case clause" or a "case block". switch simply transfers control to one of the cases and that's it. This has the side effect of "fallthrough" behavior if you want to use C's switch to check for multiple distinct cases; that is, you must insert an explicit break; to leave the switch statement when you're done with your case.

    Switch::Plain has nothing of the kind. Because it turns into a single if/elsif chain and every case block is clearly delimited, execution of sswitch/nswitch stops as soon one case pattern matches. However, it is possible to attach multiple case patterns to a single block:

    case 2:
    case 3:
    case 5: {
      ...
    }

    This trivial case works the same way as fallthrough would in C (any value of 2, 3, or 5 is accepted).

  • Since C's break refers to the innermost enclosing switch or loop, you can't use it in switch to leave a surrounding loop (you have to use goto instead). This particular problem would be avoidable in Perl thanks to loop labels; however, this isn't even necessary because sswitch/nswitch work like if: They don't count as loops and last/next/redo ignore them.

Scoping

This module is a lexical pragma, i.e. the effects of use Switch::Plain (turning sswitch and nswitch into keywords) are scoped to the innermost enclosing block (or the whole file if there is no enclosing block).

If you are a module author who wants to wrap Switch::Plain from another module, simply call Switch::Plain->import from your own import method. It will affect whatever scope is currently being compiled (i.e. your caller).

AUTHOR

Lukas Mai, <l.mai at web.de>

COPYRIGHT & LICENSE

Copyright 2012-2013 Lukas Mai.

This program is free software; you can redistribute it and/or modify it under the terms of either: the GNU General Public License as published by the Free Software Foundation; or the Artistic License.

See http://dev.perl.org/licenses/ for more information.