Problem

You have phone numbers as text like "555-123-4567" and want them parsed into a typed record with area, prefix, and line fields.

Solution

module phone_book;

pub record Phone {
    area:   text,
    prefix: text,
    line:   text,
}

pub fn parse(s: text) -> Phone {
    let p: Phone = re::capture::<Phone>(
        s,
        /(?<area>\d{3})-(?<prefix>\d{3})-(?<line>\d{4})/
    );
    return p;
}

Each (?<name>...) group in the pattern maps to the record field of the same name. The re::capture::<Record> lowering populates the record field-by-field from the named-group result.

How it lowers

FUNCTION parse(p_s IN VARCHAR2) RETURN t_phone IS
  l_p t_phone;
  l_pell_caps_0 pell_re.t_capture_map;
BEGIN
  l_pell_caps_0 := pell_re.capture_by_name(p_s,
    '(?<area>\d{3})-(?<prefix>\d{3})-(?<line>\d{4})');
  IF l_pell_caps_0.EXISTS('area')   THEN l_p.area   := l_pell_caps_0('area').match_text;   END IF;
  IF l_pell_caps_0.EXISTS('prefix') THEN l_p.prefix := l_pell_caps_0('prefix').match_text; END IF;
  IF l_pell_caps_0.EXISTS('line')   THEN l_p.line   := l_pell_caps_0('line').match_text;   END IF;
  RETURN l_p;
END parse;

If the input doesn’t match the pattern, all three fields stay NULL.

Use it

let p: Phone = phone_book::parse("555-123-4567");
log::info("({p.area}) {p.prefix}-{p.line}");
// → "(555) 123-4567"

Handling failure

When the input doesn’t match, returning a record with NULL fields is a quiet failure mode. To make failure explicit:

pub error InvalidPhone { input: text }

pub fn parse_or_err(s: text) -> Result<Phone, InvalidPhone> {
    let p: Phone = re::capture::<Phone>(
        s, /(?<area>\d{3})-(?<prefix>\d{3})-(?<line>\d{4})/
    );
    if p.area == "" {
        return Err(InvalidPhone { input: s });
    }
    return Ok(p);
}

(Remember: in Oracle, the empty string "" is NULL, so p.area == "" is actually testing for NULL. See Records and types.)

More formats

International numbers — let any format that contains 10 digits be treated as US:

pub fn parse_loose(s: text) -> Phone {
    let p: Phone = re::capture::<Phone>(
        s, /(?<area>\d{3})\D*(?<prefix>\d{3})\D*(?<line>\d{4})/
    );
    return p;
}

\D is “any non-digit.” This handles "555 123 4567", "5551234567", "(555) 123-4567", "555.123.4567".

See also