Code kata: running a DFA

  • Posted on
  • by

Эта задачка началась как regexp matching, но бэктрекингом заниматься желания никакого не было, поэтому сначала сделал автомат, а потом закопался в компиляцию регекспов в NDFA и решил разбить задание на части.

#! /usr/bin/perl
use strict;
use warnings;

use Test::Most;

sub run_automata {
	my ($aut, @symbols) = @_;

	my $cur_state = $aut->{start};

	while (defined(my $symbol = shift @symbols)) {
		$cur_state = $aut->{states}->{$cur_state}->{$symbol}
			// return;
	}

	return $cur_state;
}

my $a1 = {
	states	=> {
		s1	=> { A => 's2', B => 's3' },
		s2	=> { B => 's3' },
		s3	=> { C => 's1' },
	},
	start	=> 's1',
};

is(run_automata($a1, qw/A/), 's2');
is(run_automata($a1, qw/A A/), undef);
is(run_automata($a1, qw//), 's1');
is(run_automata($a1, qw/B/), 's3');
is(run_automata($a1, qw/B C A/), 's2');
is(run_automata($a1, qw/A B C A B C A B C A B C A B/), 's3');
is(run_automata($a1, qw/A X/), undef);
is(run_automata($a1, qw/X/), undef);
is(run_automata($a1, qw/B A/), undef);

sub pairs {
	my ($state, @range) = @_;
	return map { $_ => $state } @range;
}

# [-+]?\d+(\.\d+)?
my $numeric = {
	states	=> {
		nothing		=> { pairs(after_sign => qw/- +/), pairs(in_integer => 0 .. 9) },
		after_sign	=> { pairs(in_integer => 0 .. 9) },
		in_integer	=> { pairs(in_integer => 0 .. 9), '.' => 'decimal', EOF => 'ok' },
		decimal		=> { pairs(in_decimal => 0 .. 9) },
		in_decimal	=> { pairs(in_decimal => 0 .. 9), EOF => 'ok' },
	},
	start	=> 'nothing',
};

is(run_automata($numeric, split('', '-123'), 'EOF'), 'ok', '1st num');
is(run_automata($numeric, split('', '1'), 'EOF'), 'ok');
is(run_automata($numeric, split('', '002'), 'EOF'), 'ok', 'leading zeros');
is(run_automata($numeric, split('', '2.34'), 'EOF'), 'ok');
isnt(run_automata($numeric, split('', '2.'), 'EOF'), 'ok');
isnt(run_automata($numeric, split('', '.3323'), 'EOF'), 'ok');
isnt(run_automata($numeric, split('', 'asd'), 'EOF'), 'ok');
isnt(run_automata($numeric, split('', ''), 'EOF'), 'ok');
isnt(run_automata($numeric, split('', '+.32'), 'EOF'), 'ok');
isnt(run_automata($numeric, split('', '-'), 'EOF'), 'ok');
isnt(run_automata($numeric, split('', '4.4.2'), 'EOF'), 'ok');
done_testing;