Parse an IP Address

grammar IP_Addr {
    token TOP { ^ [ <IPv4> | <IPv6> ] $ }
 
    token IPv4 {
        [ <d8> +% '.' ] <?{ $<d8> == 4 }> <port>?
                { @*by8 = @$<d8> }
    }

    token IPv6 {
        |     <ipv6>
        | '[' <ipv6> ']' <port>
    }

    token ipv6 {
        | <h16> +% ':' <?{ $<h16> == 8 }>
                { @*by16 = @$<h16> }
 
        | [ (<h16>) +% ':']? '::' [ (<h16>) +% ':' ]? <?{ @$0 + @$1 ≤ 8 }>
                { @*by16 = |@$0, |('0' xx 8 - (@$0 + @$1)), |@$1 }
 
        | '::ffff:' <IPv4>
                { @*by16 = |('0' xx 5), 'ffff', |(by8to16 @*by8) }
    }

    token d8  { (\d+) <?{ $0 < 256   }> }
    token d16 { (\d+) <?{ $0 < 65536 }> }
    token h16 { (<xdigit>+) <?{ $0.chars ≤ 4 }> }

    token port {
        ':' <d16> { $*port = +$<d16> }
    }
}

sub by8to16 (@m) { gather for @m -> $a,$b { take ($a * 256 + $b).fmt("%04x") } }

my @cases = <
    127.0.0.1
    127.0.0.1:80
    ::1
    [::1]:80
    2605:2700:0:3::4713:93e3
    [2605:2700:0:3::4713:93e3]:80
    2001:db8:85a3:0:0:8a2e:370:7334
    2001:db8:85a3::8a2e:370:7334
    [2001:db8:85a3:8d3:1319:8a2e:370:7348]:443
    192.168.0.1
    ::ffff:192.168.0.1
    ::ffff:71.19.147.227
    [::ffff:71.19.147.227]:80
    ::
    256.0.0.0
    g::1
    0000
    0000:0000
    0000:0000:0000:0000:0000:0000:0000:0000
    0000:0000:0000::0000:0000
    0000::0000::0000:0000
    ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    ffff:ffff:ffff:fffg:ffff:ffff:ffff:ffff
    fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
    fff:ffff:0:ffff:ffff:ffff:ffff:ffff
>;

for @cases -> $addr {
    my @*by8;
    my @*by16;
    my $*port;

    IP_Addr.parse($addr);

    say $addr;
    if @*by16 {
        say "  IPv6: ", @*by16.map({:16(~$_)})».fmt("%04x").join;
        say "  Port: ", $*port if $*port;
    }
    elsif @*by8 {
        say "  IPv4: ", @*by8».fmt("%02x").join;
        say "  Port: ", $*port if $*port;
    }
    else {
        say "  BOGUS!";
    }
    say '';
}

Output:

127.0.0.1
  IPv4: 7f000001

127.0.0.1:80
  IPv4: 7f000001
  Port: 80

::1
  IPv6: 00000000000000000000000000000001

[::1]:80
  IPv6: 00000000000000000000000000000001
  Port: 80

2605:2700:0:3::4713:93e3
  IPv6: 260527000000000300000000471393e3

[2605:2700:0:3::4713:93e3]:80
  IPv6: 260527000000000300000000471393e3
  Port: 80

2001:db8:85a3:0:0:8a2e:370:7334
  IPv6: 20010db885a3000000008a2e03707334

2001:db8:85a3::8a2e:370:7334
  IPv6: 20010db885a3000000008a2e03707334

[2001:db8:85a3:8d3:1319:8a2e:370:7348]:443
  IPv6: 20010db885a308d313198a2e03707348
  Port: 443

192.168.0.1
  IPv4: c0a80001

::ffff:192.168.0.1
  IPv6: 00000000000000000000ffffc0a80001

::ffff:71.19.147.227
  IPv6: 00000000000000000000ffff471393e3

[::ffff:71.19.147.227]:80
  IPv6: 00000000000000000000ffff471393e3
  Port: 80

::
  IPv6: 00000000000000000000000000000000

256.0.0.0
  BOGUS!

g::1
  BOGUS!

0000
  BOGUS!

0000:0000
  BOGUS!

0000:0000:0000:0000:0000:0000:0000:0000
  IPv6: 00000000000000000000000000000000

0000:0000:0000::0000:0000
  IPv6: 00000000000000000000000000000000

0000::0000::0000:0000
  IPv6: 00000000000000000000000000000000

ffff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
  IPv6: ffffffffffffffffffffffffffffffff

ffff:ffff:ffff:fffg:ffff:ffff:ffff:ffff
  BOGUS!

fff:ffff:ffff:ffff:ffff:ffff:ffff:ffff
  IPv6: 0fffffffffffffffffffffffffffffff

fff:ffff:0:ffff:ffff:ffff:ffff:ffff
  IPv6: 0fffffff0000ffffffffffffffffffff

Last updated