Commit message: Fix SEGV in onig_error_code_to_str() (Fix #132)

When onig_new(ONIG_SYNTAX_PERL) fails with ONIGERR_INVALID_GROUP_NAME,
onig_error_code_to_str() crashes.
onig_scan_env_set_error_string() should have been used when returning
ONIGERR_INVALID_GROUP_NAME.
CVE: CVE-2019-16161
CWE: CWE-476
Vulnerability description: Onigmo through 6.2.0 has a NULL pointer dereference in onig_error_code_to_str because of fetch_token in regparse.c.

Project: Onigmo
File: regparse.c

add_code_range_to_buf0(BBuf** pbuf, ScanEnv* env, OnigCodePoint from, OnigCodePoint to,
	int checkdup)
{
  int r, inc_n, pos;
  OnigCodePoint low, high, bound, x;
  OnigCodePoint n, *data;
  BBuf* bbuf;

  if (from > to) {
    n = from; from = to; to = n;
  }

  if (IS_NULL(*pbuf)) {
    r = new_code_range(pbuf);
    if (r) return r;
    bbuf = *pbuf;
    n = 0;
  }
  else {
    bbuf = *pbuf;
    GET_CODE_POINT(n, bbuf->p);
  }
  data = (OnigCodePoint* )(bbuf->p);
  data++;

  bound = (from == 0) ? 0 : n;
  for (low = 0; low < bound; ) {
    x = (low + bound) >> 1;
    if (from - 1 > data[x*2 + 1])
      low = x + 1;
    else
      bound = x;
  }

  high = (to == ONIG_LAST_CODE_POINT) ? n : low;
  for (bound = n; high < bound; ) {
    x = (high + bound) >> 1;
    if (to + 1 >= data[x*2])
      high = x + 1;
    else
      bound = x;
  }
  /* data[(low-1)*2+1] << from <= data[low*2]
   * data[(high-1)*2+1] <= to << data[high*2]
   */

  inc_n = low + 1 - high;
  if (n + inc_n > ONIG_MAX_MULTI_BYTE_RANGES_NUM)
    return ONIGERR_TOO_MANY_MULTI_BYTE_RANGES;

  if (inc_n != 1) {
    if (checkdup && from <= data[low*2+1]
	&& (data[low*2] <= from || data[low*2+1] <= to))
      CC_DUP_WARN(env, from, to);
    if (from > data[low*2])
      from = data[low*2];
    if (to < data[(high - 1)*2 + 1])
      to = data[(high - 1)*2 + 1];
  }

  if (inc_n != 0) {
    int from_pos = SIZE_CODE_POINT * (1 + high * 2);
    int to_pos   = SIZE_CODE_POINT * (1 + (low + 1) * 2);

    if (inc_n > 0) {
      if (high < n) {
	int size = (n - high) * 2 * SIZE_CODE_POINT;
	BBUF_MOVE_RIGHT(bbuf, from_pos, to_pos, size);
      }
    }
    else {
      BBUF_MOVE_LEFT_REDUCE(bbuf, from_pos, to_pos);
    }
  }

  pos = SIZE_CODE_POINT * (1 + low * 2);
  BBUF_ENSURE_SIZE(bbuf, pos + SIZE_CODE_POINT * 2);
  BBUF_WRITE_CODE_POINT(bbuf, pos, from);
  BBUF_WRITE_CODE_POINT(bbuf, pos + SIZE_CODE_POINT, to);
  n += inc_n;
  BBUF_WRITE_CODE_POINT(bbuf, 0, n);

  return 0;
}

Project: Onigmo
File: regparse.c

create_property_node(Node **np, ScanEnv* env, const char* propname)
{
  int r;
  CClassNode* cc;

  *np = node_new_cclass();
  if (IS_NULL(*np)) return ONIGERR_MEMORY;
  cc = NCCLASS(*np);
  r = add_property_to_cc(cc, propname, 0, env);
  if (r != 0)
    onig_node_free(*np);
  return r;
}

Project: Onigmo
File: regparse.c

parse_char_class(Node** np, Node** asc_np, OnigToken* tok, UChar** src, UChar* end,
		 ScanEnv* env)
{
  int r, neg, len, fetched, and_start;
  OnigCodePoint v, vs;
  UChar *p;
  Node* node;
  Node* asc_node;
  CClassNode *cc, *prev_cc;
  CClassNode *asc_cc, *asc_prev_cc;
  CClassNode work_cc, asc_work_cc;

  enum CCSTATE state;
  enum CCVALTYPE val_type, in_type;
  int val_israw, in_israw;

  *np = *asc_np = NULL_NODE;
  env->parse_depth++;
  if (env->parse_depth > ParseDepthLimit)
    return ONIGERR_PARSE_DEPTH_LIMIT_OVER;
  prev_cc = asc_prev_cc = (CClassNode* )NULL;
  r = fetch_token_in_cc(tok, src, end, env);
  if (r == TK_CHAR && tok->u.c == '^' && tok->escaped == 0) {
    neg = 1;
    r = fetch_token_in_cc(tok, src, end, env);
  }
  else {
    neg = 0;
  }

  if (r < 0) return r;
  if (r == TK_CC_CLOSE) {
    if (! code_exist_check((OnigCodePoint )']',
                           *src, env->pattern_end, 1, env))
      return ONIGERR_EMPTY_CHAR_CLASS;

    CC_ESC_WARN(env, (UChar* )"]");
    r = tok->type = TK_CHAR;  /* allow []...] */
  }

  *np = node = node_new_cclass();
  CHECK_NULL_RETURN_MEMERR(node);
  cc = NCCLASS(node);

  if (IS_IGNORECASE(env->option)) {
    *asc_np = asc_node = node_new_cclass();
    CHECK_NULL_RETURN_MEMERR(asc_node);
    asc_cc = NCCLASS(asc_node);
  }
  else {
    asc_node = NULL_NODE;
    asc_cc = NULL;
  }

  and_start = 0;
  state = CCS_START;
  p = *src;
  while (r != TK_CC_CLOSE) {
    fetched = 0;
    switch (r) {
    case TK_CHAR:
      if ((tok->u.code >= SINGLE_BYTE_SIZE) ||
	  (len = ONIGENC_CODE_TO_MBCLEN(env->enc, tok->u.c)) > 1) {
	in_type = CCV_CODE_POINT;
      }
      else if (len < 0) {
	r = len;
	goto err;
      }
      else {
      sb_char:
	in_type = CCV_SB;
      }
      v = (OnigCodePoint )tok->u.c;
      in_israw = 0;
      goto val_entry2;
      break;

    case TK_RAW_BYTE:
      /* tok->base != 0 : octal or hexadec. */
      if (! ONIGENC_IS_SINGLEBYTE(env->enc) && tok->base != 0) {
	UChar buf[ONIGENC_CODE_TO_MBC_MAXLEN];
	UChar* bufe = buf + ONIGENC_CODE_TO_MBC_MAXLEN;
	UChar* psave = p;
	int i, base = tok->base;

	buf[0] = (UChar )tok->u.c;
	for (i = 1; i < ONIGENC_MBC_MAXLEN(env->enc); i++) {
	  r = fetch_token_in_cc(tok, &p, end, env);
	  if (r < 0) goto err;
	  if (r != TK_RAW_BYTE || tok->base != base) {
	    fetched = 1;
	    break;
	  }
	  buf[i] = (UChar )tok->u.c;
	}

	if (i < ONIGENC_MBC_MINLEN(env->enc)) {
	  r = ONIGERR_TOO_SHORT_MULTI_BYTE_STRING;
	  goto err;
	}

	len = enclen(env->enc, buf, buf + i);
	if (i < len) {
	  r = ONIGERR_TOO_SHORT_MULTI_BYTE_STRING;
	  goto err;
	}
	else if (i > len) { /* fetch back */
	  p = psave;
	  for (i = 1; i < len; i++) {
	    (void)fetch_token_in_cc(tok, &p, end, env);
	    /* no need to check the retun value (already checked above) */
	  }
	  fetched = 0;
	}

	if (i == 1) {
	  v = (OnigCodePoint )buf[0];
	  goto raw_single;
	}
	else {
	  v = ONIGENC_MBC_TO_CODE(env->enc, buf, bufe);
	  in_type = CCV_CODE_POINT;
	}
      }
      else {
	v = (OnigCodePoint )tok->u.c;
      raw_single:
	in_type = CCV_SB;
      }
      in_israw = 1;
      goto val_entry2;
      break;

    case TK_CODE_POINT:
      v = tok->u.code;
      in_israw = 1;
    val_entry:
      len = ONIGENC_CODE_TO_MBCLEN(env->enc, v);
      if (len < 0) {
	r = len;
	goto err;
      }
      in_type = (len == 1 ? CCV_SB : CCV_CODE_POINT);
    val_entry2:
      r = next_state_val(cc, asc_cc, &vs, v, &val_israw, in_israw, in_type, &val_type,
			 &state, env);
      if (r != 0) goto err;
      break;

    case TK_POSIX_BRACKET_OPEN:
      r = parse_posix_bracket(cc, asc_cc, &p, end, env);
      if (r < 0) goto err;
      if (r == 1) {  /* is not POSIX bracket */
	CC_ESC_WARN(env, (UChar* )"[");
	p = tok->backp;
	v = (OnigCodePoint )tok->u.c;
	in_israw = 0;
	goto val_entry;
      }
      goto next_class;
      break;

    case TK_CHAR_TYPE:
      r = add_ctype_to_cc(cc, tok->u.prop.ctype, tok->u.prop.not,
			  IS_ASCII_RANGE(env->option), env);
      if (r != 0) return r;
      if (IS_NOT_NULL(asc_cc)) {
	if (tok->u.prop.ctype != ONIGENC_CTYPE_WORD)
	  r = add_ctype_to_cc(asc_cc, tok->u.prop.ctype, tok->u.prop.not,
			      IS_ASCII_RANGE(env->option), env);
	if (r != 0) return r;
      }

    next_class:
      r = next_state_class(cc, asc_cc, &vs, &val_type, &state, env);
      if (r != 0) goto err;
      break;

    case TK_CHAR_PROPERTY:
      {
	int ctype;

	ctype = fetch_char_property_to_ctype(&p, end, env);
	if (ctype < 0) return ctype;
	r = add_ctype_to_cc(cc, ctype, tok->u.prop.not, 0, env);
	if (r != 0) return r;
	if (IS_NOT_NULL(asc_cc)) {
	  if (ctype != ONIGENC_CTYPE_ASCII)
	    r = add_ctype_to_cc(asc_cc, ctype, tok->u.prop.not, 0, env);
	  if (r != 0) return r;
	}
	goto next_class;
      }
      break;

    case TK_CC_RANGE:
      if (state == CCS_VALUE) {
	r = fetch_token_in_cc(tok, &p, end, env);
	if (r < 0) goto err;
	fetched = 1;
	if (r == TK_CC_CLOSE) { /* allow [x-] */
	range_end_val:
	  v = (OnigCodePoint )'-';
	  in_israw = 0;
	  goto val_entry;
	}
	else if (r == TK_CC_AND) {
	  CC_ESC_WARN(env, (UChar* )"-");
	  goto range_end_val;
	}

	if (val_type == CCV_CLASS) {
	  r = ONIGERR_UNMATCHED_RANGE_SPECIFIER_IN_CHAR_CLASS;
	  goto err;
	}

	state = CCS_RANGE;
      }
      else if (state == CCS_START) {
	/* [-xa] is allowed */
	v = (OnigCodePoint )tok->u.c;
	in_israw = 0;

	r = fetch_token_in_cc(tok, &p, end, env);
	if (r < 0) goto err;
	fetched = 1;
	/* [--x] or [a&&-x] is warned. */
	if (r == TK_CC_RANGE || and_start != 0)
	  CC_ESC_WARN(env, (UChar* )"-");

	goto val_entry;
      }
      else if (state == CCS_RANGE) {
	CC_ESC_WARN(env, (UChar* )"-");
	goto sb_char;  /* [!--x] is allowed */
      }
      else { /* CCS_COMPLETE */
	r = fetch_token_in_cc(tok, &p, end, env);
	if (r < 0) goto err;
	fetched = 1;
	if (r == TK_CC_CLOSE) goto range_end_val; /* allow [a-b-] */
	else if (r == TK_CC_AND) {
	  CC_ESC_WARN(env, (UChar* )"-");
	  goto range_end_val;
	}

	if (IS_SYNTAX_BV(env->syntax, ONIG_SYN_ALLOW_DOUBLE_RANGE_OP_IN_CC)) {
	  CC_ESC_WARN(env, (UChar* )"-");
	  goto range_end_val;   /* [0-9-a] is allowed as [0-9\-a] */
	}
	r = ONIGERR_UNMATCHED_RANGE_SPECIFIER_IN_CHAR_CLASS;
	goto err;
      }
      break;

    case TK_CC_CC_OPEN: /* [ */
      {
	Node *anode, *aasc_node;
	CClassNode* acc;

	r = parse_char_class(&anode, &aasc_node, tok, &p, end, env);
	if (r == 0) {
	  acc = NCCLASS(anode);
	  r = or_cclass(cc, acc, env);
	}
	if (r == 0 && IS_NOT_NULL(aasc_node)) {
	  acc = NCCLASS(aasc_node);
	  r = or_cclass(asc_cc, acc, env);
	}
	onig_node_free(anode);
	onig_node_free(aasc_node);
	if (r != 0) goto err;
      }
      break;

    case TK_CC_AND: /* && */
      {
	if (state == CCS_VALUE) {
	  r = next_state_val(cc, asc_cc, &vs, 0, &val_israw, 0, val_type,
			     &val_type, &state, env);
	  if (r != 0) goto err;
	}
	/* initialize local variables */
	and_start = 1;
	state = CCS_START;

	if (IS_NOT_NULL(prev_cc)) {
	  r = and_cclass(prev_cc, cc, env);
	  if (r != 0) goto err;
	  bbuf_free(cc->mbuf);
	  if (IS_NOT_NULL(asc_cc)) {
	    r = and_cclass(asc_prev_cc, asc_cc, env);
	    if (r != 0) goto err;
	    bbuf_free(asc_cc->mbuf);
	  }
	}
	else {
	  prev_cc = cc;
	  cc = &work_cc;
	  if (IS_NOT_NULL(asc_cc)) {
	    asc_prev_cc = asc_cc;
	    asc_cc = &asc_work_cc;
	  }
	}
	initialize_cclass(cc);
	if (IS_NOT_NULL(asc_cc))
	  initialize_cclass(asc_cc);
      }
      break;

    case TK_EOT:
      r = ONIGERR_PREMATURE_END_OF_CHAR_CLASS;
      goto err;
      break;
    default:
      r = ONIGERR_PARSER_BUG;
      goto err;
      break;
    }

    if (fetched)
      r = tok->type;
    else {
      r = fetch_token_in_cc(tok, &p, end, env);
      if (r < 0) goto err;
    }
  }

  if (state == CCS_VALUE) {
    r = next_state_val(cc, asc_cc, &vs, 0, &val_israw, 0, val_type,
		       &val_type, &state, env);
    if (r != 0) goto err;
  }

  if (IS_NOT_NULL(prev_cc)) {
    r = and_cclass(prev_cc, cc, env);
    if (r != 0) goto err;
    bbuf_free(cc->mbuf);
    cc = prev_cc;
    if (IS_NOT_NULL(asc_cc)) {
      r = and_cclass(asc_prev_cc, asc_cc, env);
      if (r != 0) goto err;
      bbuf_free(asc_cc->mbuf);
      asc_cc = asc_prev_cc;
    }
  }

  if (neg != 0) {
    NCCLASS_SET_NOT(cc);
    if (IS_NOT_NULL(asc_cc))
      NCCLASS_SET_NOT(asc_cc);
  }
  else {
    NCCLASS_CLEAR_NOT(cc);
    if (IS_NOT_NULL(asc_cc))
      NCCLASS_CLEAR_NOT(asc_cc);
  }
  if (IS_NCCLASS_NOT(cc) &&
      IS_SYNTAX_BV(env->syntax, ONIG_SYN_NOT_NEWLINE_IN_NEGATIVE_CC)) {
    int is_empty;

    is_empty = (IS_NULL(cc->mbuf) ? 1 : 0);
    if (is_empty != 0)
      BITSET_IS_EMPTY(cc->bs, is_empty);

    if (is_empty == 0) {
#define NEWLINE_CODE    0x0a

      if (ONIGENC_IS_CODE_NEWLINE(env->enc, NEWLINE_CODE)) {
	if (ONIGENC_CODE_TO_MBCLEN(env->enc, NEWLINE_CODE) == 1)
	  BITSET_SET_BIT_CHKDUP(cc->bs, NEWLINE_CODE);
	else {
	  r = add_code_range(&(cc->mbuf), env, NEWLINE_CODE, NEWLINE_CODE);
	  if (r < 0) goto err;
	}
      }
    }
  }
  *src = p;
  env->parse_depth--;
  return 0;

 err:
  if (cc != NCCLASS(*np))
    bbuf_free(cc->mbuf);
  if (IS_NOT_NULL(asc_cc) && (asc_cc != NCCLASS(*asc_np)))
    bbuf_free(asc_cc->mbuf);
  return r;
}

Project: Onigmo
File: regparse.c

add_property_to_cc(CClassNode* cc, const char* propname, int not, ScanEnv* env)
{
  int ctype = propname2ctype(env, propname);
  if (ctype < 0) return ctype;
  return add_ctype_to_cc(cc, ctype, not, 0, env);
}

Project: Onigmo
File: regparse.c

next_state_val(CClassNode* cc, CClassNode* asc_cc,
	       OnigCodePoint *from, OnigCodePoint to,
	       int* from_israw, int to_israw,
	       enum CCVALTYPE intype, enum CCVALTYPE* type,
	       enum CCSTATE* state, ScanEnv* env)
{
  int r;

  switch (*state) {
  case CCS_VALUE:
    if (*type == CCV_SB) {
      BITSET_SET_BIT_CHKDUP(cc->bs, (int )(*from));
      if (IS_NOT_NULL(asc_cc))
	BITSET_SET_BIT(asc_cc->bs, (int )(*from));
    }
    else if (*type == CCV_CODE_POINT) {
      r = add_code_range(&(cc->mbuf), env, *from, *from);
      if (r < 0) return r;
      if (IS_NOT_NULL(asc_cc)) {
	r = add_code_range0(&(asc_cc->mbuf), env, *from, *from, 0);
	if (r < 0) return r;
      }
    }
    break;

  case CCS_RANGE:
    if (intype == *type) {
      if (intype == CCV_SB) {
	if (*from > 0xff || to > 0xff)
	  return ONIGERR_INVALID_CODE_POINT_VALUE;

	if (*from > to) {
	  if (IS_SYNTAX_BV(env->syntax, ONIG_SYN_ALLOW_EMPTY_RANGE_IN_CC))
	    goto ccs_range_end;
	  else
	    return ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS;
	}
	bitset_set_range(env, cc->bs, (int )*from, (int )to);
	if (IS_NOT_NULL(asc_cc))
	  bitset_set_range(env, asc_cc->bs, (int )*from, (int )to);
      }
      else {
	r = add_code_range(&(cc->mbuf), env, *from, to);
	if (r < 0) return r;
	if (IS_NOT_NULL(asc_cc)) {
	  r = add_code_range0(&(asc_cc->mbuf), env, *from, to, 0);
	  if (r < 0) return r;
	}
      }
    }
    else {
      if (*from > to) {
	if (IS_SYNTAX_BV(env->syntax, ONIG_SYN_ALLOW_EMPTY_RANGE_IN_CC))
	  goto ccs_range_end;
	else
	  return ONIGERR_EMPTY_RANGE_IN_CHAR_CLASS;
      }
      bitset_set_range(env, cc->bs, (int )*from, (int )(to < 0xff ? to : 0xff));
      r = add_code_range(&(cc->mbuf), env, (OnigCodePoint )*from, to);
      if (r < 0) return r;
      if (IS_NOT_NULL(asc_cc)) {
	bitset_set_range(env, asc_cc->bs, (int )*from, (int )(to < 0xff ? to : 0xff));
	r = add_code_range0(&(asc_cc->mbuf), env, (OnigCodePoint )*from, to, 0);
	if (r < 0) return r;
      }
    }
  ccs_range_end:
    *state = CCS_COMPLETE;
    break;

  case CCS_COMPLETE:
  case CCS_START:
    *state = CCS_VALUE;
    break;

  default:
    break;
  }

  *from_israw = to_israw;
  *from       = to;
  *type       = intype;
  return 0;
}

Project: Onigmo
File: regparse.c

scan_env_add_mem_entry(ScanEnv* env)
{
  int i, need, alloc;
  Node** p;

  need = env->num_mem + 1;
  if (need > ONIG_MAX_CAPTURE_GROUP_NUM)
    return ONIGERR_TOO_MANY_CAPTURE_GROUPS;
  if (need >= SCANENV_MEMNODES_SIZE) {
    if (env->mem_alloc <= need) {
      if (IS_NULL(env->mem_nodes_dynamic)) {
	alloc = INIT_SCANENV_MEMNODES_ALLOC_SIZE;
	p = (Node** )xmalloc(sizeof(Node*) * alloc);
	CHECK_NULL_RETURN_MEMERR(p);
	xmemcpy(p, env->mem_nodes_static,
		sizeof(Node*) * SCANENV_MEMNODES_SIZE);
      }
      else {
	alloc = env->mem_alloc * 2;
	p = (Node** )xrealloc(env->mem_nodes_dynamic, sizeof(Node*) * alloc);
	CHECK_NULL_RETURN_MEMERR(p);
      }

      for (i = env->num_mem + 1; i < alloc; i++)
	p[i] = NULL_NODE;

      env->mem_nodes_dynamic = p;
      env->mem_alloc = alloc;
    }
  }

  env->num_mem++;
  return env->num_mem;
}

Project: Onigmo
File: regparse.c

propname2ctype(ScanEnv* env, const char* propname)
{
  UChar* name = (UChar* )propname;
  UChar* name_end = name + strlen(propname);
  int ctype = env->enc->property_name_to_ctype(ONIG_ENCODING_ASCII,
      name, name_end);
  if (ctype < 0) {
    onig_scan_env_set_error_string(env, ctype, name, name_end);
  }
  return ctype;
}

Project: Onigmo
File: regparse.c

quantify_property_node(Node **np, ScanEnv* env, const char* propname, char repetitions)
{
  int r;
  int lower = 0;
  int upper = REPEAT_INFINITE;

  r = create_property_node(np, env, propname);
  if (r != 0) return r;
  switch (repetitions) {
    case '?':  upper = 1;          break;
    case '+':  lower = 1;          break;
    case '*':                      break;
    case '2':  lower = upper = 2;  break;
    default :  return ONIGERR_PARSER_BUG;
  }
  return quantify_node(np, lower, upper);
}

Project: Onigmo
File: regparse.c

create_node_from_array(int kind, Node **np, Node **node_array)
{
  Node* tmp = NULL_NODE;
  int i = 0;

  while (node_array[i] != NULL_NODE)  i++;
  while (--i >= 0) {
    *np = kind==LIST ? node_new_list(node_array[i], tmp)
                     : onig_node_new_alt(node_array[i], tmp);
    if (IS_NULL(*np)) {
      while (i >= 0) {
        onig_node_free(node_array[i]);
        node_array[i--] = NULL_NODE;
      }
      onig_node_free(tmp);
      return ONIGERR_MEMORY;
    }
    else
      node_array[i] = NULL_NODE;
    tmp = *np;
  }
  return 0;
}

Project: Onigmo
File: regparse.c

node_extended_grapheme_cluster(Node** np, ScanEnv* env)
{
  Node* tmp = NULL;
  Node* np1 = NULL;
  Node* top_alt = NULL;
  int r = 0;
  int num1;
  int i;
  int any_target_position;
  UChar buf[ONIGENC_CODE_TO_MBC_MAXLEN * 2];
  OnigOptionType option;
  /* node_common is function-global so that we can free all nodes
   * in case of error. Unused slots are set to NULL_NODE at all times. */
  Node *node_common[NODE_COMMON_SIZE];
  Node **alts = node_common+0; /* size: 5 */

  for (i=0; i<NODE_COMMON_SIZE; i++)
    node_common[i] = NULL_NODE;

  /* CRLF, common for both Unicode and non-Unicode */
  /* \x0D\x0A */
  r = ONIGENC_CODE_TO_MBC(env->enc, 0x0D, buf);
  if (r < 0) goto err;
  num1 = r;
  r = ONIGENC_CODE_TO_MBC(env->enc, 0x0A, buf + num1);
  if (r < 0) goto err;
  alts[0] = node_new_str_raw(buf, buf + num1 + r);
  if (IS_NULL(alts[0])) goto err;

#ifdef USE_UNICODE_PROPERTIES
  if (ONIGENC_IS_UNICODE(env->enc)) {  /* UTF-8, UTF-16BE/LE, UTF-32BE/LE */
    CClassNode* cc;

    if (propname2ctype(env, "Grapheme_Cluster_Break=Extend") < 0) goto err;
    /* Unicode 11.0.0
     *   CRLF     (already done)
     * | [Control CR LF]
     * | precore* core postcore*
     * | .      (to catch invalid stuff, because this seems to be spec for String#grapheme_clusters) */

    /* [Control CR LF]    (CR and LF are not in the spec, but this is a conformed fix) */
    alts[1] = node_new_cclass();
    if (IS_NULL(alts[1])) goto err;
    cc = NCCLASS(alts[1]);
    R_ERR(add_property_to_cc(cc, "Grapheme_Cluster_Break=Control", 0, env));
    if (ONIGENC_MBC_MINLEN(env->enc) > 1) { /* UTF-16/UTF-32 */
      R_ERR(add_code_range(&(cc->mbuf), env, 0x000A, 0x000A)); /* CR */
      R_ERR(add_code_range(&(cc->mbuf), env, 0x000D, 0x000D)); /* LF */
    }
    else {
      BITSET_SET_BIT(cc->bs, 0x0a);
      BITSET_SET_BIT(cc->bs, 0x0d);
    }

    /* precore* core postcore* */
    {
      Node **list = alts + 3; /* size: 4 */

      /* precore*; precore := Prepend */
      R_ERR(quantify_property_node(list+0, env, "Grapheme_Cluster_Break=Prepend", '*'));

      /* core := hangul-syllable
       *       | ri-sequence
       *       | xpicto-sequence
       *       | [^Control CR LF] */
      {
        Node **core_alts = list + 2; /* size: 7 */

        /* hangul-syllable :=
         *     L* (V+ | LV V* | LVT) T*
         *   | L+
         *   | T+ */
        /* hangul-syllable is an alternative (would be called H_alt)
         * inside an alternative, but we flatten it into core_alts */

        /* L* (V+ | LV V* | LVT) T* */
        {
          Node **H_list = core_alts + 1; /* size: 4 */
          R_ERR(quantify_property_node(H_list+0, env, "Grapheme_Cluster_Break=L", '*'));

          /* V+ | LV V* | LVT */
          {
            Node **H_alt2 = H_list + 2; /* size: 4 */
            R_ERR(quantify_property_node(H_alt2+0, env, "Grapheme_Cluster_Break=V", '+'));

            /* LV V* */
            {
              Node **H_list2 = H_alt2 + 2; /* size: 3 */

              R_ERR(create_property_node(H_list2+0, env, "Grapheme_Cluster_Break=LV"));
              R_ERR(quantify_property_node(H_list2+1, env, "Grapheme_Cluster_Break=V", '*'));
              R_ERR(create_node_from_array(LIST, H_alt2+1, H_list2));
            }

            R_ERR(create_property_node(H_alt2+2, env, "Grapheme_Cluster_Break=LVT"));
            R_ERR(create_node_from_array(ALT, H_list+1, H_alt2));
          }

          R_ERR(quantify_property_node(H_list+2, env, "Grapheme_Cluster_Break=T", '*'));
          R_ERR(create_node_from_array(LIST, core_alts+0, H_list));
        }

        R_ERR(quantify_property_node(core_alts+1, env, "Grapheme_Cluster_Break=L", '+'));
        R_ERR(quantify_property_node(core_alts+2, env, "Grapheme_Cluster_Break=T", '+'));
        /* end of hangul-syllable */

        /* ri-sequence := RI RI */
        R_ERR(quantify_property_node(core_alts+3, env, "Regional_Indicator", '2'));

        /* xpicto-sequence := \p{Extended_Pictographic} (Extend* ZWJ \p{Extended_Pictographic})* */
        {
          Node **XP_list = core_alts + 5; /* size: 3 */
          R_ERR(create_property_node(XP_list+0, env, "Extended_Pictographic"));

          /* (Extend* ZWJ \p{Extended_Pictographic})* */
          {
            Node **Ex_list = XP_list + 2; /* size: 4 */
            /* assert(Ex_list+4 == node_common+NODE_COMMON_SIZE); */
            R_ERR(quantify_property_node(Ex_list+0, env, "Grapheme_Cluster_Break=Extend", '*'));

            /* ZWJ (ZERO WIDTH JOINER) */
            r = ONIGENC_CODE_TO_MBC(env->enc, 0x200D, buf);
            if (r < 0) goto err;
            Ex_list[1] = node_new_str_raw(buf, buf + r);
            if (IS_NULL(Ex_list[1])) goto err;

            R_ERR(create_property_node(Ex_list+2, env, "Extended_Pictographic"));
            R_ERR(create_node_from_array(LIST, XP_list+1, Ex_list));
          }
          R_ERR(quantify_node(XP_list+1, 0, REPEAT_INFINITE)); /* TODO: Check about node freeing */

          R_ERR(create_node_from_array(LIST, core_alts+4, XP_list));
        }

        /* [^Control CR LF] */
        core_alts[5] = node_new_cclass();
        if (IS_NULL(core_alts[5])) goto err;
        cc = NCCLASS(core_alts[5]);
        if (ONIGENC_MBC_MINLEN(env->enc) > 1) { /* UTF-16/UTF-32 */
          BBuf *inverted_buf = NULL;

          /* Start with a positive buffer and invert at the end.
           * Otherwise, adding single-character ranges work the wrong way. */
          R_ERR(add_property_to_cc(cc, "Grapheme_Cluster_Break=Control", 0, env));
          R_ERR(add_code_range(&(cc->mbuf), env, 0x000A, 0x000A)); /* CR */
          R_ERR(add_code_range(&(cc->mbuf), env, 0x000D, 0x000D)); /* LF */
          R_ERR(not_code_range_buf(env->enc, cc->mbuf, &inverted_buf, env));
          cc->mbuf = inverted_buf; /* TODO: check what to do with buffer before inversion */
        }
        else {
          R_ERR(add_property_to_cc(cc, "Grapheme_Cluster_Break=Control", 1, env));
          BITSET_CLEAR_BIT(cc->bs, 0x0a);
          BITSET_CLEAR_BIT(cc->bs, 0x0d);
        }

        R_ERR(create_node_from_array(ALT, list+1, core_alts));
      }

      /* postcore*; postcore = [Extend ZWJ SpacingMark] */
      R_ERR(create_property_node(list+2, env, "Grapheme_Cluster_Break=Extend"));
      cc = NCCLASS(list[2]);
      R_ERR(add_property_to_cc(cc, "Grapheme_Cluster_Break=SpacingMark", 0, env));
      R_ERR(add_code_range(&(cc->mbuf), env, 0x200D, 0x200D));
      R_ERR(quantify_node(list+2, 0, REPEAT_INFINITE));

      R_ERR(create_node_from_array(LIST, alts+2, list));
    }

    any_target_position = 3;
  }
  else
#endif /* USE_UNICODE_PROPERTIES */
  {
    any_target_position = 1;
  }

  /* PerlSyntax: (?s:.), RubySyntax: (?m:.), common for both Unicode and non-Unicode */
  /* Not in Unicode spec (UAX #29), but added to catch invalid stuff,
   * because this is Ruby spec for String#grapheme_clusters. */
  np1 = node_new_anychar();
  if (IS_NULL(np1)) goto err;

  option = env->option;
  ONOFF(option, ONIG_OPTION_MULTILINE, 0);
  tmp = node_new_option(option);
  if (IS_NULL(tmp)) goto err;
  NENCLOSE(tmp)->target = np1;
  alts[any_target_position] = tmp;
  np1 = NULL;

  R_ERR(create_node_from_array(ALT, &top_alt, alts));

  /* (?>): For efficiency, because there is no text piece
   *       that is not in a grapheme cluster, and there is only one way
   *       to split a string into grapheme clusters. */
  tmp = node_new_enclose(ENCLOSE_STOP_BACKTRACK);
  if (IS_NULL(tmp)) goto err;
  NENCLOSE(tmp)->target = top_alt;
  np1 = tmp;

#ifdef USE_UNICODE_PROPERTIES
  if (ONIGENC_IS_UNICODE(env->enc)) {
    /* Don't ignore case. */
    option = env->option;
    ONOFF(option, ONIG_OPTION_IGNORECASE, 1);
    *np = node_new_option(option);
    if (IS_NULL(*np)) goto err;
    NENCLOSE(*np)->target = np1;
  }
  else
#endif
  {
    *np = np1;
  }
  return ONIG_NORMAL;

 err:
  onig_node_free(np1);
  for (i=0; i<NODE_COMMON_SIZE; i++)
    onig_node_free(node_common[i]);
  return (r == 0) ? ONIGERR_MEMORY : r;
}

Project: Onigmo
File: regparse.c

quantify_node(Node **np, int lower, int upper)
{
  Node* tmp = node_new_quantifier(lower, upper, 0);
  if (IS_NULL(tmp)) return ONIGERR_MEMORY;
  NQTFR(tmp)->target = *np;
  *np = tmp;
  return 0;
}

Project: Onigmo
File: regparse.c

fetch_token(OnigToken* tok, UChar** src, UChar* end, ScanEnv* env)
{
  int r, num;
  OnigCodePoint c;
  OnigEncoding enc = env->enc;
  const OnigSyntaxType* syn = env->syntax;
  UChar* prev;
  UChar* p = *src;
  PFETCH_READY;

 start:
  if (PEND) {
    tok->type = TK_EOT;
    return tok->type;
  }

  tok->type  = TK_STRING;
  tok->base  = 0;
  tok->backp = p;

  PFETCH(c);
  if (IS_MC_ESC_CODE(c, syn)) {
    if (PEND) return ONIGERR_END_PATTERN_AT_ESCAPE;

    tok->backp = p;
    PFETCH(c);

    tok->u.c = c;
    tok->escaped = 1;
    switch (c) {
    case '*':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_ASTERISK_ZERO_INF)) break;
      tok->type = TK_OP_REPEAT;
      tok->u.repeat.lower = 0;
      tok->u.repeat.upper = REPEAT_INFINITE;
      goto greedy_check;
      break;

    case '+':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_PLUS_ONE_INF)) break;
      tok->type = TK_OP_REPEAT;
      tok->u.repeat.lower = 1;
      tok->u.repeat.upper = REPEAT_INFINITE;
      goto greedy_check;
      break;

    case '?':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_QMARK_ZERO_ONE)) break;
      tok->type = TK_OP_REPEAT;
      tok->u.repeat.lower = 0;
      tok->u.repeat.upper = 1;
    greedy_check:
      if (!PEND && PPEEK_IS('?') &&
	  IS_SYNTAX_OP(syn, ONIG_SYN_OP_QMARK_NON_GREEDY)) {
	PFETCH(c);
	tok->u.repeat.greedy     = 0;
	tok->u.repeat.possessive = 0;
      }
      else {
      possessive_check:
	if (!PEND && PPEEK_IS('+') &&
	    ((IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_PLUS_POSSESSIVE_REPEAT) &&
	      tok->type != TK_INTERVAL)  ||
	     (IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_PLUS_POSSESSIVE_INTERVAL) &&
	      tok->type == TK_INTERVAL))) {
	  PFETCH(c);
	  tok->u.repeat.greedy     = 1;
	  tok->u.repeat.possessive = 1;
	}
	else {
	  tok->u.repeat.greedy     = 1;
	  tok->u.repeat.possessive = 0;
	}
      }
      break;

    case '{':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_BRACE_INTERVAL)) break;
      r = fetch_range_quantifier(&p, end, tok, env);
      if (r < 0) return r;  /* error */
      if (r == 0) goto greedy_check;
      else if (r == 2) { /* {n} */
	if (IS_SYNTAX_BV(syn, ONIG_SYN_FIXED_INTERVAL_IS_GREEDY_ONLY))
	  goto possessive_check;

	goto greedy_check;
      }
      /* r == 1 : normal char */
      break;

    case '|':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_VBAR_ALT)) break;
      tok->type = TK_ALT;
      break;

    case '(':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_LPAREN_SUBEXP)) break;
      tok->type = TK_SUBEXP_OPEN;
      break;

    case ')':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_LPAREN_SUBEXP)) break;
      tok->type = TK_SUBEXP_CLOSE;
      break;

    case 'w':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_W_WORD)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_WORD;
      tok->u.prop.not   = 0;
      break;

    case 'W':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_W_WORD)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_WORD;
      tok->u.prop.not   = 1;
      break;

    case 'b':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_B_WORD_BOUND)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_WORD_BOUND;
      tok->u.anchor.ascii_range = IS_ASCII_RANGE(env->option)
		&& ! IS_WORD_BOUND_ALL_RANGE(env->option);
      break;

    case 'B':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_B_WORD_BOUND)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_NOT_WORD_BOUND;
      tok->u.anchor.ascii_range = IS_ASCII_RANGE(env->option)
		&& ! IS_WORD_BOUND_ALL_RANGE(env->option);
      break;

#ifdef USE_WORD_BEGIN_END
    case '<':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_LTGT_WORD_BEGIN_END)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_WORD_BEGIN;
      tok->u.anchor.ascii_range = IS_ASCII_RANGE(env->option);
      break;

    case '>':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_LTGT_WORD_BEGIN_END)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_WORD_END;
      tok->u.anchor.ascii_range = IS_ASCII_RANGE(env->option);
      break;
#endif

    case 's':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_S_WHITE_SPACE)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_SPACE;
      tok->u.prop.not   = 0;
      break;

    case 'S':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_S_WHITE_SPACE)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_SPACE;
      tok->u.prop.not   = 1;
      break;

    case 'd':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_D_DIGIT)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_DIGIT;
      tok->u.prop.not   = 0;
      break;

    case 'D':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_D_DIGIT)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_DIGIT;
      tok->u.prop.not   = 1;
      break;

    case 'h':
      if (! IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_H_XDIGIT)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_XDIGIT;
      tok->u.prop.not   = 0;
      break;

    case 'H':
      if (! IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_H_XDIGIT)) break;
      tok->type = TK_CHAR_TYPE;
      tok->u.prop.ctype = ONIGENC_CTYPE_XDIGIT;
      tok->u.prop.not   = 1;
      break;

    case 'A':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_AZ_BUF_ANCHOR)) break;
    begin_buf:
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_BEGIN_BUF;
      break;

    case 'Z':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_AZ_BUF_ANCHOR)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_SEMI_END_BUF;
      break;

    case 'z':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_AZ_BUF_ANCHOR)) break;
    end_buf:
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_END_BUF;
      break;

    case 'G':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_CAPITAL_G_BEGIN_ANCHOR)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = ANCHOR_BEGIN_POSITION;
      break;

    case '`':
      if (! IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_GNU_BUF_ANCHOR)) break;
      goto begin_buf;
      break;

    case '\'':
      if (! IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_GNU_BUF_ANCHOR)) break;
      goto end_buf;
      break;

    case 'x':
      if (PEND) break;

      prev = p;
      if (PPEEK_IS('{') && IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_X_BRACE_HEX8)) {
	PINC;
	num = scan_unsigned_hexadecimal_number(&p, end, 0, 8, enc);
	if (num < 0) return ONIGERR_TOO_BIG_WIDE_CHAR_VALUE;
	if (!PEND) {
	  if (ONIGENC_IS_CODE_XDIGIT(enc, PPEEK))
	    return ONIGERR_TOO_LONG_WIDE_CHAR_VALUE;
	}

	if ((p > prev + enclen(enc, prev, end)) && !PEND && PPEEK_IS('}')) {
	  PINC;
	  tok->type   = TK_CODE_POINT;
	  tok->u.code = (OnigCodePoint )num;
	}
	else {
	  /* can't read nothing or invalid format */
	  p = prev;
	}
      }
      else if (IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_X_HEX2)) {
	num = scan_unsigned_hexadecimal_number(&p, end, 0, 2, enc);
	if (num < 0) return ONIGERR_TOO_BIG_NUMBER;
	if (p == prev) {  /* can't read nothing. */
	  num = 0; /* but, it's not error */
	}
	tok->type = TK_RAW_BYTE;
	tok->base = 16;
	tok->u.c  = num;
      }
      break;

    case 'u':
      if (PEND) break;

      prev = p;
      if (IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_U_HEX4)) {
	num = scan_unsigned_hexadecimal_number(&p, end, 4, 4, enc);
	if (num < -1) return ONIGERR_TOO_SHORT_DIGITS;
	else if (num < 0) return ONIGERR_TOO_BIG_NUMBER;
	if (p == prev) {  /* can't read nothing. */
	  num = 0; /* but, it's not error */
	}
	tok->type   = TK_CODE_POINT;
	tok->base   = 16;
	tok->u.code = (OnigCodePoint )num;
      }
      break;

    case 'o':
      if (PEND) break;

      prev = p;
      if (PPEEK_IS('{') && IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_O_BRACE_OCTAL)) {
	PINC;
	num = scan_unsigned_octal_number(&p, end, 11, enc);
	if (num < 0) return ONIGERR_TOO_BIG_WIDE_CHAR_VALUE;
	if (!PEND) {
	  OnigCodePoint c = PPEEK;
	  if (ONIGENC_IS_CODE_DIGIT(enc, c) && c < '8')
	    return ONIGERR_TOO_LONG_WIDE_CHAR_VALUE;
	}

	if ((p > prev + enclen(enc, prev, end)) && !PEND && PPEEK_IS('}')) {
	  PINC;
	  tok->type   = TK_CODE_POINT;
	  tok->u.code = (OnigCodePoint )num;
	}
	else {
	  /* can't read nothing or invalid format */
	  p = prev;
	}
      }
      break;

    case '1': case '2': case '3': case '4':
    case '5': case '6': case '7': case '8': case '9':
      PUNFETCH;
      prev = p;
      num = onig_scan_unsigned_number(&p, end, enc);
      if (num < 0 || num > ONIG_MAX_BACKREF_NUM) {
	goto skip_backref;
      }

      if (IS_SYNTAX_OP(syn, ONIG_SYN_OP_DECIMAL_BACKREF) &&
	  (num <= env->num_mem || num <= 9)) { /* This spec. from GNU regex */
	if (IS_SYNTAX_BV(syn, ONIG_SYN_STRICT_CHECK_BACKREF)) {
	  if (num > env->num_mem || IS_NULL(SCANENV_MEM_NODES(env)[num]))
	    return ONIGERR_INVALID_BACKREF;
	}

	tok->type = TK_BACKREF;
	tok->u.backref.num     = 1;
	tok->u.backref.ref1    = num;
	tok->u.backref.by_name = 0;
#ifdef USE_BACKREF_WITH_LEVEL
	tok->u.backref.exist_level = 0;
#endif
	break;
      }

    skip_backref:
      if (c == '8' || c == '9') {
	/* normal char */
	p = prev; PINC;
	break;
      }

      p = prev;
      /* fall through */
    case '0':
      if (IS_SYNTAX_OP(syn, ONIG_SYN_OP_ESC_OCTAL3)) {
	prev = p;
	num = scan_unsigned_octal_number(&p, end, (c == '0' ? 2:3), enc);
	if (num < 0 || 0xff < num) return ONIGERR_TOO_BIG_NUMBER;
	if (p == prev) {  /* can't read nothing. */
	  num = 0; /* but, it's not error */
	}
	tok->type = TK_RAW_BYTE;
	tok->base = 8;
	tok->u.c  = num;
      }
      else if (c != '0') {
	PINC;
      }
      break;

#ifdef USE_NAMED_GROUP
    case 'k':
      if (!PEND && IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_K_NAMED_BACKREF)) {
	PFETCH(c);
	if (c == '<' || c == '\'') {
	  r = fetch_named_backref_token(c, tok, &p, end, env);
	  if (r < 0) return r;
	}
	else {
	  PUNFETCH;
	  onig_syntax_warn(env, "invalid back reference");
	}
      }
      break;
#endif

#if defined(USE_SUBEXP_CALL) || defined(USE_NAMED_GROUP)
    case 'g':
# ifdef USE_NAMED_GROUP
      if (!PEND && IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_G_BRACE_BACKREF)) {
	PFETCH(c);
	if (c == '{') {
	  r = fetch_named_backref_token(c, tok, &p, end, env);
	  if (r < 0) return r;
	}
	else
	  PUNFETCH;
      }
# endif
# ifdef USE_SUBEXP_CALL
      if (!PEND && IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_G_SUBEXP_CALL)) {
	PFETCH(c);
	if (c == '<' || c == '\'') {
	  int gnum = -1, rel = 0;
	  UChar* name_end;
	  OnigCodePoint cnext;

	  cnext = PPEEK;
	  if (cnext == '0') {
	    PINC;
	    if (PPEEK_IS(get_name_end_code_point(c))) {  /* \g<0>, \g'0' */
	      PINC;
	      name_end = p;
	      gnum = 0;
	    }
	  }
	  else if (cnext == '+') {
	    PINC;
	    rel = 1;
	  }
	  prev = p;
	  if (gnum < 0) {
	    r = fetch_name((OnigCodePoint )c, &p, end, &name_end, env, &gnum, 1);
	    if (r < 0) return r;
	  }

	  tok->type = TK_CALL;
	  tok->u.call.name     = prev;
	  tok->u.call.name_end = name_end;
	  tok->u.call.gnum     = gnum;
	  tok->u.call.rel      = rel;
	}
	else {
	  onig_syntax_warn(env, "invalid subexp call");
	  PUNFETCH;
	}
      }
# endif
      break;
#endif

    case 'Q':
      if (IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_CAPITAL_Q_QUOTE)) {
	tok->type = TK_QUOTE_OPEN;
      }
      break;

    case 'p':
    case 'P':
      if (PPEEK_IS('{') &&
	  IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_P_BRACE_CHAR_PROPERTY)) {
	PINC;
	tok->type = TK_CHAR_PROPERTY;
	tok->u.prop.not = (c == 'P' ? 1 : 0);

	if (!PEND && IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_P_BRACE_CIRCUMFLEX_NOT)) {
	  PFETCH(c);
	  if (c == '^') {
	    tok->u.prop.not = (tok->u.prop.not == 0 ? 1 : 0);
	  }
	  else
	    PUNFETCH;
	}
      }
      else {
	onig_syntax_warn(env, "invalid Unicode Property \\%c", c);
      }
      break;

    case 'R':
      if (IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_CAPITAL_R_LINEBREAK)) {
	tok->type = TK_LINEBREAK;
      }
      break;

    case 'X':
      if (IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_CAPITAL_X_EXTENDED_GRAPHEME_CLUSTER)) {
	tok->type = TK_EXTENDED_GRAPHEME_CLUSTER;
      }
      break;

    case 'K':
      if (IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_ESC_CAPITAL_K_KEEP)) {
	tok->type = TK_KEEP;
      }
      break;

    default:
      {
	OnigCodePoint c2;

	PUNFETCH;
	num = fetch_escaped_value(&p, end, env, &c2);
	if (num < 0) return num;
	/* set_raw: */
	if ((OnigCodePoint )tok->u.c != c2) {
	  tok->type = TK_CODE_POINT;
	  tok->u.code = (OnigCodePoint )c2;
	}
	else { /* string */
	  p = tok->backp + enclen(enc, tok->backp, end);
	}
      }
      break;
    }
  }
  else {
    tok->u.c = c;
    tok->escaped = 0;

#ifdef USE_VARIABLE_META_CHARS
    if ((c != ONIG_INEFFECTIVE_META_CHAR) &&
	IS_SYNTAX_OP(syn, ONIG_SYN_OP_VARIABLE_META_CHARACTERS)) {
      if (c == MC_ANYCHAR(syn))
	goto any_char;
      else if (c == MC_ANYTIME(syn))
	goto anytime;
      else if (c == MC_ZERO_OR_ONE_TIME(syn))
	goto zero_or_one_time;
      else if (c == MC_ONE_OR_MORE_TIME(syn))
	goto one_or_more_time;
      else if (c == MC_ANYCHAR_ANYTIME(syn)) {
	tok->type = TK_ANYCHAR_ANYTIME;
	goto out;
      }
    }
#endif

    switch (c) {
    case '.':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_DOT_ANYCHAR)) break;
#ifdef USE_VARIABLE_META_CHARS
    any_char:
#endif
      tok->type = TK_ANYCHAR;
      break;

    case '*':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_ASTERISK_ZERO_INF)) break;
#ifdef USE_VARIABLE_META_CHARS
    anytime:
#endif
      tok->type = TK_OP_REPEAT;
      tok->u.repeat.lower = 0;
      tok->u.repeat.upper = REPEAT_INFINITE;
      goto greedy_check;
      break;

    case '+':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_PLUS_ONE_INF)) break;
#ifdef USE_VARIABLE_META_CHARS
    one_or_more_time:
#endif
      tok->type = TK_OP_REPEAT;
      tok->u.repeat.lower = 1;
      tok->u.repeat.upper = REPEAT_INFINITE;
      goto greedy_check;
      break;

    case '?':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_QMARK_ZERO_ONE)) break;
#ifdef USE_VARIABLE_META_CHARS
    zero_or_one_time:
#endif
      tok->type = TK_OP_REPEAT;
      tok->u.repeat.lower = 0;
      tok->u.repeat.upper = 1;
      goto greedy_check;
      break;

    case '{':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_BRACE_INTERVAL)) break;
      r = fetch_range_quantifier(&p, end, tok, env);
      if (r < 0) return r;  /* error */
      if (r == 0) goto greedy_check;
      else if (r == 2) { /* {n} */
	if (IS_SYNTAX_BV(syn, ONIG_SYN_FIXED_INTERVAL_IS_GREEDY_ONLY))
	  goto possessive_check;

	goto greedy_check;
      }
      /* r == 1 : normal char */
      break;

    case '|':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_VBAR_ALT)) break;
      tok->type = TK_ALT;
      break;

    case '(':
      if (PPEEK_IS('?') &&
	  IS_SYNTAX_OP2(syn, ONIG_SYN_OP2_QMARK_GROUP_EFFECT)) {
	PINC;
	if (PPEEK_IS('#')) {
	  PFETCH(c);
	  while (1) {
	    if (PEND) return ONIGERR_END_PATTERN_IN_GROUP;
	    PFETCH(c);
	    if (c == MC_ESC(syn)) {
	      if (!PEND) PFETCH(c);
	    }
	    else {
	      if (c == ')') break;
	    }
	  }
	  goto start;
	}
#ifdef USE_PERL_SUBEXP_CALL
	/* (?&name), (?n), (?R), (?0), (?+n), (?-n) */
	c = PPEEK;
	if ((c == '&' || c == 'R' || ONIGENC_IS_CODE_DIGIT(enc, c)) &&
	    IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_SUBEXP_CALL)) {
	  /* (?&name), (?n), (?R), (?0) */
	  int gnum;
	  UChar *name;
	  UChar *name_end;

	  if (c == 'R' || c == '0') {
	    PINC;   /* skip 'R' / '0' */
	    if (!PPEEK_IS(')')) {
	      r = ONIGERR_INVALID_GROUP_NAME;
	      onig_scan_env_set_error_string(env, r, p - 1, p + 1);
	      return r;
	    }
	    PINC;   /* skip ')' */
	    name_end = name = p;
	    gnum = 0;
	  }
	  else {
	    int numref = 1;
	    if (c == '&') {     /* (?&name) */
	      PINC;
	      numref = 0;       /* don't allow number name */
	    }
	    name = p;
	    r = fetch_name((OnigCodePoint )'(', &p, end, &name_end, env, &gnum, numref);
	    if (r < 0) return r;
	  }

	  tok->type = TK_CALL;
	  tok->u.call.name     = name;
	  tok->u.call.name_end = name_end;
	  tok->u.call.gnum     = gnum;
	  tok->u.call.rel      = 0;
	  break;
	}
	else if ((c == '-' || c == '+') &&
	    IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_SUBEXP_CALL)) {
	  /* (?+n), (?-n) */
	  int gnum;
	  UChar *name;
	  UChar *name_end;
	  OnigCodePoint cnext;
	  PFETCH_READY;

	  PINC;     /* skip '-' / '+' */
	  cnext = PPEEK;
	  if (ONIGENC_IS_CODE_DIGIT(enc, cnext)) {
	    if (c == '-') PUNFETCH;
	    name = p;
	    r = fetch_name((OnigCodePoint )'(', &p, end, &name_end, env, &gnum, 1);
	    if (r < 0) return r;

	    tok->type = TK_CALL;
	    tok->u.call.name     = name;
	    tok->u.call.name_end = name_end;
	    tok->u.call.gnum     = gnum;
	    tok->u.call.rel      = 1;
	    break;
	  }
	}
#endif /* USE_PERL_SUBEXP_CALL */
#ifdef USE_CAPITAL_P_NAMED_GROUP
	if (PPEEK_IS('P') &&
	    IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_CAPITAL_P_NAMED_GROUP)) {
	  int gnum;
	  UChar *name;
	  UChar *name_end;
	  PFETCH_READY;

	  PINC;     /* skip 'P' */
	  if (PEND) return ONIGERR_UNDEFINED_GROUP_OPTION;
	  PFETCH(c);
	  if (c == '=') {       /* (?P=name): backref */
	    r = fetch_named_backref_token((OnigCodePoint )'(', tok, &p, end, env);
	    if (r < 0) return r;
	    break;
	  }
	  else if (c == '>') {  /* (?P>name): subexp call */
	    name = p;
	    r = fetch_name((OnigCodePoint )'(', &p, end, &name_end, env, &gnum, 0);
	    if (r < 0) return r;

	    tok->type = TK_CALL;
	    tok->u.call.name     = name;
	    tok->u.call.name_end = name_end;
	    tok->u.call.gnum     = gnum;
	    tok->u.call.rel      = 0;
	    break;
	  }
	}
#endif /* USE_CAPITAL_P_NAMED_GROUP */
	PUNFETCH;
      }

      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_LPAREN_SUBEXP)) break;
      tok->type = TK_SUBEXP_OPEN;
      break;

    case ')':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_LPAREN_SUBEXP)) break;
      tok->type = TK_SUBEXP_CLOSE;
      break;

    case '^':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_LINE_ANCHOR)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = (IS_SINGLELINE(env->option)
			       ? ANCHOR_BEGIN_BUF : ANCHOR_BEGIN_LINE);
      break;

    case '$':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_LINE_ANCHOR)) break;
      tok->type = TK_ANCHOR;
      tok->u.anchor.subtype = (IS_SINGLELINE(env->option)
			       ? ANCHOR_SEMI_END_BUF : ANCHOR_END_LINE);
      break;

    case '[':
      if (! IS_SYNTAX_OP(syn, ONIG_SYN_OP_BRACKET_CC)) break;
      tok->type = TK_CC_OPEN;
      break;

    case ']':
      if (*src > env->pattern)   /* /].../ is allowed. */
	CLOSE_BRACKET_WITHOUT_ESC_WARN(env, (UChar* )"]");
      break;

    case '#':
      if (IS_EXTEND(env->option)) {
	while (!PEND) {
	  PFETCH(c);
	  if (ONIGENC_IS_CODE_NEWLINE(enc, c))
	    break;
	}
	goto start;
	break;
      }
      break;

    case ' ': case '\t': case '\n': case '\r': case '\f':
      if (IS_EXTEND(env->option))
	goto start;
      break;

    default:
      /* string */
      break;
    }
  }

#ifdef USE_VARIABLE_META_CHARS
 out:
#endif
  *src = p;
  return tok->type;
}

Project: Onigmo
File: regparse.c

CC_DUP_WARN(ScanEnv *env, OnigCodePoint from ARG_UNUSED, OnigCodePoint to ARG_UNUSED)
{
  if (onig_warn == onig_null_warn || !RTEST(ruby_verbose)) return ;

  if (IS_SYNTAX_BV(env->syntax, ONIG_SYN_WARN_CC_DUP) &&
      !(env->warnings_flag & ONIG_SYN_WARN_CC_DUP)) {
#ifdef WARN_ALL_CC_DUP
    onig_syntax_warn(env, "character class has duplicated range: %04x-%04x", from, to);
#else
    env->warnings_flag |= ONIG_SYN_WARN_CC_DUP;
    onig_syntax_warn(env, "character class has duplicated range");
#endif
  }
}

Project: Onigmo
File: regparse.c

node_new_quantifier(int lower, int upper, int by_number)
{
  Node* node = node_new();
  CHECK_NULL_RETURN(node);

  SET_NTYPE(node, NT_QTFR);
  NQTFR(node)->state  = 0;
  NQTFR(node)->target = NULL;
  NQTFR(node)->lower  = lower;
  NQTFR(node)->upper  = upper;
  NQTFR(node)->greedy = 1;
  NQTFR(node)->target_empty_info = NQ_TARGET_ISNOT_EMPTY;
  NQTFR(node)->head_exact        = NULL_NODE;
  NQTFR(node)->next_head_exact   = NULL_NODE;
  NQTFR(node)->is_referred       = 0;
  if (by_number != 0)
    NQTFR(node)->state |= NST_BY_NUMBER;

#ifdef USE_COMBINATION_EXPLOSION_CHECK
  NQTFR(node)->comb_exp_check_num = 0;
#endif

  return node;
}

Project: Onigmo
File: regparse.c

parse_enclose(Node** np, OnigToken* tok, int term, UChar** src, UChar* end,
	      ScanEnv* env)
{
  int r = 0, num;
  Node *target, *work1 = NULL, *work2 = NULL;
  OnigOptionType option;
  OnigCodePoint c;
  OnigEncoding enc = env->enc;

#ifdef USE_NAMED_GROUP
  int list_capture;
#endif

  UChar* p = *src;
  PFETCH_READY;

  *np = NULL;
  if (PEND) return ONIGERR_END_PATTERN_WITH_UNMATCHED_PARENTHESIS;

  option = env->option;
  if (PPEEK_IS('?') &&
      IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_GROUP_EFFECT)) {
    PINC;
    if (PEND) return ONIGERR_END_PATTERN_IN_GROUP;

    PFETCH(c);
    switch (c) {
    case ':':   /* (?:...) grouping only */
    group:
      r = fetch_token(tok, &p, end, env);
      if (r < 0) return r;
      r = parse_subexp(np, tok, term, &p, end, env);
      if (r < 0) return r;
      *src = p;
      return 1; /* group */
      break;

    case '=':
      *np = onig_node_new_anchor(ANCHOR_PREC_READ);
      break;
    case '!':   /* preceding read */
      *np = onig_node_new_anchor(ANCHOR_PREC_READ_NOT);
      break;
    case '>':   /* (?>...) stop backtrack */
      *np = node_new_enclose(ENCLOSE_STOP_BACKTRACK);
      break;
    case '~':   /* (?~...) absent operator */
      if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_TILDE_ABSENT)) {
	*np = node_new_enclose(ENCLOSE_ABSENT);
      }
      else {
	return ONIGERR_UNDEFINED_GROUP_OPTION;
      }
      break;

#ifdef USE_NAMED_GROUP
    case '\'':
      if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_LT_NAMED_GROUP)) {
	goto named_group1;
      }
      else
	return ONIGERR_UNDEFINED_GROUP_OPTION;
      break;

# ifdef USE_CAPITAL_P_NAMED_GROUP
    case 'P':   /* (?P<name>...) */
      if (!PEND &&
	  IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_CAPITAL_P_NAMED_GROUP)) {
	PFETCH(c);
	if (c == '<') goto named_group1;
      }
      return ONIGERR_UNDEFINED_GROUP_OPTION;
      break;
# endif
#endif

    case '<':   /* look behind (?<=...), (?<!...) */
      if (PEND) return ONIGERR_END_PATTERN_WITH_UNMATCHED_PARENTHESIS;
      PFETCH(c);
      if (c == '=')
	*np = onig_node_new_anchor(ANCHOR_LOOK_BEHIND);
      else if (c == '!')
	*np = onig_node_new_anchor(ANCHOR_LOOK_BEHIND_NOT);
#ifdef USE_NAMED_GROUP
      else {    /* (?<name>...) */
	if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_LT_NAMED_GROUP)) {
	  UChar *name;
	  UChar *name_end;

	  PUNFETCH;
	  c = '<';

	named_group1:
	  list_capture = 0;

# ifdef USE_CAPTURE_HISTORY
	named_group2:
# endif
	  name = p;
	  r = fetch_name((OnigCodePoint )c, &p, end, &name_end, env, &num, 0);
	  if (r < 0) return r;

	  num = scan_env_add_mem_entry(env);
	  if (num < 0) return num;
	  if (list_capture != 0 && num >= (int )BIT_STATUS_BITS_NUM)
	    return ONIGERR_GROUP_NUMBER_OVER_FOR_CAPTURE_HISTORY;

	  r = name_add(env->reg, name, name_end, num, env);
	  if (r != 0) return r;
	  *np = node_new_enclose_memory(env->option, 1);
	  CHECK_NULL_RETURN_MEMERR(*np);
	  NENCLOSE(*np)->regnum = num;
	  if (list_capture != 0)
	    BIT_STATUS_ON_AT_SIMPLE(env->capture_history, num);
	  env->num_named++;
	}
	else {
	  return ONIGERR_UNDEFINED_GROUP_OPTION;
	}
      }
#else
      else {
	return ONIGERR_UNDEFINED_GROUP_OPTION;
      }
#endif
      break;

#ifdef USE_CAPTURE_HISTORY
    case '@':
      if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_ATMARK_CAPTURE_HISTORY)) {
# ifdef USE_NAMED_GROUP
	if (!PEND &&
	    IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_LT_NAMED_GROUP)) {
	  PFETCH(c);
	  if (c == '<' || c == '\'') {
	    list_capture = 1;
	    goto named_group2; /* (?@<name>...) */
	  }
	  PUNFETCH;
	}
# endif
	*np = node_new_enclose_memory(env->option, 0);
	CHECK_NULL_RETURN_MEMERR(*np);
	num = scan_env_add_mem_entry(env);
	if (num < 0) return num;
	if (num >= (int )BIT_STATUS_BITS_NUM)
	  return ONIGERR_GROUP_NUMBER_OVER_FOR_CAPTURE_HISTORY;

	NENCLOSE(*np)->regnum = num;
	BIT_STATUS_ON_AT_SIMPLE(env->capture_history, num);
      }
      else {
	return ONIGERR_UNDEFINED_GROUP_OPTION;
      }
      break;
#endif /* USE_CAPTURE_HISTORY */

    case '(':   /* conditional expression: (?(cond)yes), (?(cond)yes|no) */
      if (!PEND &&
	  IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_LPAREN_CONDITION)) {
	UChar *name = NULL;
	UChar *name_end;
	PFETCH(c);
	if (ONIGENC_IS_CODE_DIGIT(enc, c)) {     /* (n) */
	  PUNFETCH;
	  r = fetch_name((OnigCodePoint )'(', &p, end, &name_end, env, &num, 1);
	  if (r < 0) return r;
#if 0
	  /* Relative number is not currently supported. (same as Perl) */
	  if (num < 0) {
	    num = BACKREF_REL_TO_ABS(num, env);
	    if (num <= 0)
	      return ONIGERR_INVALID_BACKREF;
	  }
#endif
	  if (IS_SYNTAX_BV(env->syntax, ONIG_SYN_STRICT_CHECK_BACKREF)) {
	    if (num > env->num_mem ||
		IS_NULL(SCANENV_MEM_NODES(env)[num]))
	    return ONIGERR_INVALID_BACKREF;
	  }
	}
#ifdef USE_NAMED_GROUP
	else if (c == '<' || c == '\'') {    /* (<name>), ('name') */
	  name = p;
	  r = fetch_named_backref_token(c, tok, &p, end, env);
	  if (r < 0) return r;
	  if (!PPEEK_IS(')')) return ONIGERR_UNDEFINED_GROUP_OPTION;
	  PINC;

	  if (IS_SYNTAX_BV(env->syntax, ONIG_SYN_USE_LEFT_MOST_NAMED_GROUP)) {
	    num = tok->u.backref.ref1;
	  }
	  else {
	    /* FIXME:
	     * Use left most named group for now. This is the same as Perl.
	     * However this should use the same strategy as normal back-
	     * references on Ruby syntax; search right to left. */
	    int len = tok->u.backref.num;
	    num = len > 1 ? tok->u.backref.refs[0] : tok->u.backref.ref1;
	  }
	}
#endif
	else
	  return ONIGERR_INVALID_CONDITION_PATTERN;
	*np = node_new_enclose(ENCLOSE_CONDITION);
	CHECK_NULL_RETURN_MEMERR(*np);
	NENCLOSE(*np)->regnum = num;
	if (IS_NOT_NULL(name)) NENCLOSE(*np)->state |= NST_NAME_REF;
      }
      else
	return ONIGERR_UNDEFINED_GROUP_OPTION;
      break;

#if 0
    case '|':   /* branch reset: (?|...) */
      if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_QMARK_VBAR_BRANCH_RESET)) {
	/* TODO */
      }
      else
	return ONIGERR_UNDEFINED_GROUP_OPTION;
      break;
#endif

    case '^':   /* loads default options */
      if (!PEND && IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_PERL)) {
	/* d-imsx */
	ONOFF(option, ONIG_OPTION_ASCII_RANGE, 1);
	ONOFF(option, ONIG_OPTION_IGNORECASE, 1);
	ONOFF(option, ONIG_OPTION_SINGLELINE, 0);
	ONOFF(option, ONIG_OPTION_MULTILINE,  1);
	ONOFF(option, ONIG_OPTION_EXTEND, 1);
	PFETCH(c);
      }
#if 0
      else if (!PEND && IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_RUBY)) {
	/* d-imx */
	ONOFF(option, ONIG_OPTION_ASCII_RANGE, 0);
	ONOFF(option, ONIG_OPTION_POSIX_BRACKET_ALL_RANGE, 0);
	ONOFF(option, ONIG_OPTION_WORD_BOUND_ALL_RANGE, 0);
	ONOFF(option, ONIG_OPTION_IGNORECASE, 1);
	ONOFF(option, ONIG_OPTION_MULTILINE,  1);
	ONOFF(option, ONIG_OPTION_EXTEND, 1);
	PFETCH(c);
      }
#endif
      else {
	return ONIGERR_UNDEFINED_GROUP_OPTION;
      }
      /* fall through */
#ifdef USE_POSIXLINE_OPTION
    case 'p':
#endif
    case '-': case 'i': case 'm': case 's': case 'x':
    case 'a': case 'd': case 'l': case 'u':
      {
	int neg = 0;

	while (1) {
	  switch (c) {
	  case ':':
	  case ')':
	  break;

	  case '-':  neg = 1; break;
	  case 'x':  ONOFF(option, ONIG_OPTION_EXTEND,     neg); break;
	  case 'i':  ONOFF(option, ONIG_OPTION_IGNORECASE, neg); break;
	  case 's':
	    if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_PERL)) {
	      ONOFF(option, ONIG_OPTION_MULTILINE,  neg);
	    }
	    else
	      return ONIGERR_UNDEFINED_GROUP_OPTION;
	    break;

	  case 'm':
	    if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_PERL)) {
	      ONOFF(option, ONIG_OPTION_SINGLELINE, (neg == 0 ? 1 : 0));
	    }
	    else if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_RUBY)) {
	      ONOFF(option, ONIG_OPTION_MULTILINE,  neg);
	    }
	    else
	      return ONIGERR_UNDEFINED_GROUP_OPTION;
	    break;
#ifdef USE_POSIXLINE_OPTION
	  case 'p':
	    ONOFF(option, ONIG_OPTION_MULTILINE|ONIG_OPTION_SINGLELINE, neg);
	    break;
#endif

	  case 'a':     /* limits \d, \s, \w and POSIX brackets to ASCII range */
	    if ((IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_PERL) ||
		 IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_RUBY)) &&
		(neg == 0)) {
	      ONOFF(option, ONIG_OPTION_ASCII_RANGE, 0);
	      ONOFF(option, ONIG_OPTION_POSIX_BRACKET_ALL_RANGE, 1);
	      ONOFF(option, ONIG_OPTION_WORD_BOUND_ALL_RANGE, 1);
	    }
	    else
	      return ONIGERR_UNDEFINED_GROUP_OPTION;
	    break;

	  case 'u':
	    if ((IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_PERL) ||
		 IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_RUBY)) &&
		(neg == 0)) {
	      ONOFF(option, ONIG_OPTION_ASCII_RANGE, 1);
	      ONOFF(option, ONIG_OPTION_POSIX_BRACKET_ALL_RANGE, 1);
	      ONOFF(option, ONIG_OPTION_WORD_BOUND_ALL_RANGE, 1);
	    }
	    else
	      return ONIGERR_UNDEFINED_GROUP_OPTION;
	    break;

	  case 'd':
	    if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_PERL) &&
		(neg == 0)) {
	      ONOFF(option, ONIG_OPTION_ASCII_RANGE, 1);
	    }
	    else if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_RUBY) &&
		(neg == 0)) {
	      ONOFF(option, ONIG_OPTION_ASCII_RANGE, 0);
	      ONOFF(option, ONIG_OPTION_POSIX_BRACKET_ALL_RANGE, 0);
	      ONOFF(option, ONIG_OPTION_WORD_BOUND_ALL_RANGE, 0);
	    }
	    else
	      return ONIGERR_UNDEFINED_GROUP_OPTION;
	    break;

	  case 'l':
	    if (IS_SYNTAX_OP2(env->syntax, ONIG_SYN_OP2_OPTION_PERL) && (neg == 0)) {
	      ONOFF(option, ONIG_OPTION_ASCII_RANGE, 1);
	    }
	    else
	      return ONIGERR_UNDEFINED_GROUP_OPTION;
	    break;

	  default:
	    return ONIGERR_UNDEFINED_GROUP_OPTION;
	  }

	  if (c == ')') {
	    *np = node_new_option(option);
	    CHECK_NULL_RETURN_MEMERR(*np);
	    *src = p;
	    return 2; /* option only */
	  }
	  else if (c == ':') {
	    OnigOptionType prev = env->option;

	    env->option = option;
	    r = fetch_token(tok, &p, end, env);
	    if (r < 0) {
	      env->option = prev;
	      return r;
	    }
	    r = parse_subexp(&target, tok, term, &p, end, env);
	    env->option = prev;
	    if (r < 0) return r;
	    *np = node_new_option(option);
	    CHECK_NULL_RETURN_MEMERR(*np);
	    NENCLOSE(*np)->target = target;
	    *src = p;
	    return 0;
	  }

	  if (PEND) return ONIGERR_END_PATTERN_IN_GROUP;
	  PFETCH(c);
	}
      }
      break;

    default:
      return ONIGERR_UNDEFINED_GROUP_OPTION;
    }
  }
  else {
    if (ONIG_IS_OPTION_ON(env->option, ONIG_OPTION_DONT_CAPTURE_GROUP))
      goto group;

    *np = node_new_enclose_memory(env->option, 0);
    CHECK_NULL_RETURN_MEMERR(*np);
    num = scan_env_add_mem_entry(env);
    if (num < 0) return num;
    NENCLOSE(*np)->regnum = num;
  }

  CHECK_NULL_RETURN_MEMERR(*np);
  r = fetch_token(tok, &p, end, env);
  if (r < 0) return r;
  r = parse_subexp(&target, tok, term, &p, end, env);
  if (r < 0) {
    onig_node_free(target);
    return r;
  }

  if (NTYPE(*np) == NT_ANCHOR)
    NANCHOR(*np)->target = target;
  else {
    NENCLOSE(*np)->target = target;
    if (NENCLOSE(*np)->type == ENCLOSE_MEMORY) {
      /* Don't move this to previous of parse_subexp() */
      r = scan_env_set_mem_node(env, NENCLOSE(*np)->regnum, *np);
      if (r != 0) return r;
    }
    else if (NENCLOSE(*np)->type == ENCLOSE_CONDITION) {
      if (NTYPE(target) != NT_ALT) {
	/* convert (?(cond)yes) to (?(cond)yes|empty) */
	work1 = node_new_empty();
	if (IS_NULL(work1)) goto err;
	work2 = onig_node_new_alt(work1, NULL_NODE);
	if (IS_NULL(work2)) goto err;
	work1 = onig_node_new_alt(target, work2);
	if (IS_NULL(work1)) goto err;
	NENCLOSE(*np)->target = work1;
      }
    }
  }

  *src = p;
  return 0;

 err:
  onig_node_free(work1);
  onig_node_free(work2);
  onig_node_free(*np);
  *np = NULL;
  return ONIGERR_MEMORY;
}