The Perl Toolchain Summit needs more sponsors. If your company depends on Perl, please support this very important event.
#include "EXTERN.h"
#include "perl.h"
#include "callchecker0.h"
#include "callparser.h"
#include "XSUB.h"

#define DEMAND_IMMEDIATE 0x00000001
#define DEMAND_NOCONSUME 0x00000002
#define demand_unichar(c, f) THX_demand_unichar(aTHX_ c, f)
static void
THX_demand_unichar (pTHX_ I32 c, U32 flags)
{
  if (!(flags & DEMAND_IMMEDIATE))
    lex_read_space(0);

  if (lex_peek_unichar(0) != c)
    croak("syntax error");

  if (!(flags & DEMAND_NOCONSUME))
    lex_read_unichar(0);
}

#define parse_idword(word) THX_parse_idword(aTHX_ word)
static bool
THX_parse_idword(pTHX_ char const *word)
{
  char *start, *s, c;

  s = start = PL_parser->bufptr;
  c = *s;

  if (!isIDFIRST(c))
    return 0;

  do {
    c = *++s;
  } while (isALNUM(c));

  if (strnNE(word, start, s-start))
    return 0;

  lex_read_to(s);
    return 1;
}

#define parse_blk() THX_parse_blk(aTHX)
static OP *
THX_parse_blk(pTHX)
{
  int blk_floor;
  OP *blkop;

  demand_unichar('{', DEMAND_NOCONSUME);
  blk_floor = Perl_block_start(aTHX_ 1);
  blkop = parse_block(0);
  return op_scope(Perl_block_end(aTHX_ blk_floor, blkop));
}

static OP *
myck_entersub_cond (pTHX_ OP *entersubop, GV *namegv, SV *ckobj)
{
  OP *pushop, *condop, *rv2cvop;

  PERL_UNUSED_ARG(namegv);
  PERL_UNUSED_ARG(ckobj);

  pushop = cUNOPx(entersubop)->op_first;
  if (!pushop->op_sibling)
    pushop = cUNOPx(pushop)->op_first;

  condop = pushop->op_sibling;

  rv2cvop = condop->op_sibling;
  condop->op_sibling = NULL;
  pushop->op_sibling = rv2cvop;

  op_free(entersubop);
  return condop;
}

static OP *
myparse_args_cond (pTHX_ GV *namegv, SV *psobj, U32 *flagsp)
{
  AV *exprs, *blks;
  OP *condop;

  PERL_UNUSED_ARG(namegv);
  PERL_UNUSED_ARG(psobj);
  PERL_UNUSED_ARG(flagsp);

  exprs = newAV();
  blks = newAV();
  AvREAL_off(exprs);
  AvREAL_off(blks);

  while (1) {
    lex_read_space(0);

    switch (lex_peek_unichar(0)) {
    case '(':
      demand_unichar('(', 0);
      av_push(exprs, (SV *)parse_fullexpr(0));
      demand_unichar(')', 0);

      av_push(blks, (SV *)parse_blk());
      continue;
    case 'o':
      if (parse_idword("otherwise")) {
        av_push(exprs, (SV *)newSVOP(OP_CONST, 0, &PL_sv_yes));
        av_push(blks, (SV *)parse_blk());
      }
      continue;
    default:
      break;
    }

    break;
  }

  condop = newOP(OP_STUB, 0);
  while (av_len(exprs) >= 0)
    condop = newCONDOP(0,
                       (OP *)av_pop(exprs),
                       (OP *)av_pop(blks),
                       condop);

  SvREFCNT_inc(exprs);
  SvREFCNT_inc(blks);

  return condop;
}

MODULE = Cond::Expr  PACKAGE = Cond::Expr

void
cond (...)
  CODE:
    PERL_UNUSED_ARG(items);
    croak("cond called as a function");

BOOT:
{
  CV *cond_cv;

  cond_cv = get_cv("Cond::Expr::cond", 0);

  cv_set_call_parser(cond_cv, myparse_args_cond, &PL_sv_undef);
  cv_set_call_checker(cond_cv, myck_entersub_cond, &PL_sv_undef);
}