I don't have a fix for your actual problem, but The reason it refuses to select the ipv4 is because of longest-token matching. NAME matches a longer token than NUMBER, therefore it always wins.
Update: Change NAME to not accept a dot (and update hostname rule) and then it will work as you originally had:
my $rules = <<'END_OF_GRAMMAR';
lexeme default = latm => 1
:default ::= action => [name,values]
:start ::= <entry>
<entry> ::= <op> (SP) <hostaddr4>
<op> ::= 'add' | 'remove'
<ipv4> ::= NUMBER ('.') NUMBER ('.') NUMBER ('.') NUMBER
<hostname> ::= NAME+ separator => DOT
<hostaddr4> ::= <ipv4> | <hostname>
DOT ~ '.'
SP ~ [\s]+
NAME ~ [^\s.]+
NUMBER ~ [\d]+