#!/usr/pkg/bin/perl

=encoding utf8

=begin metadata

Name: seq
Description: print a numeric sequence
Author: Michael Mikonos
License: artistic2

=end metadata

=cut

use strict;

use File::Basename qw(basename);
use POSIX qw(floor);

use constant EX_SUCCESS => 0;
use constant EX_FAILURE => 1;

my $Program = basename($0);

my $begin = 1;
my $step = 1;
my $ender;
my $format = "%g";
my $term = "\n";

sub usage {
    warn "usage: $Program [-f format] [-s string] [begin [step]] end\n";
    exit EX_FAILURE;
}

while (@ARGV && $ARGV[0] =~ /^-/) {
    my $opt = shift;
    if ($opt eq '--') {
        last;
    } elsif ($opt eq '-s') {
        $term = shift;
    } elsif ($opt eq '-f') {
        $format = shift;
    } elsif ($opt =~ m/\A\-?[0-9]/) {
        unshift @ARGV, $opt;
        last;
    } else {
        warn "$Program: unexpected option: '$opt'\n";
        usage();
    }
}
if (@ARGV == 0) {
    usage();
} elsif (@ARGV == 1) {
    $ender = getnum($ARGV[0]);
} elsif (@ARGV == 2) {
    $begin = getnum($ARGV[0]);
    $ender = getnum($ARGV[1]);
} elsif (@ARGV == 3) {
    $begin = getnum($ARGV[0]);
    $step  = getnum($ARGV[1]);
    $ender = getnum($ARGV[2]);
} else {
    warn "$Program: extra argument '$ARGV[3]'\n";
    usage();
}

if ($step == 0) {
    warn "$Program: illegal step value of zero\n";
    exit EX_FAILURE;
}
if ($ender < $begin) {
    if (@ARGV != 3) {
        $step = -$step;
    } elsif ($step > 0) {
        warn "$Program: needs negative decrement\n";
        exit EX_FAILURE;
    }
} else {
    if ($step < 0) {
        warn "$Program: needs positive increment\n";
        exit EX_FAILURE;
    }
}

my $head = 1;
my $count = floor(($ender - $begin) / $step);
for (0 .. $count) {
    if ($head) {
        $head = 0;
    } else {
        print $term;
    }
    printf $format, $begin + $_ * $step;
}
print "\n";
exit EX_SUCCESS;

sub getnum {
    my $n = shift;
    if ($n !~ m/\A[\+\-]?[0-9]+(\.[0-9]+)?\Z/) {
        warn "$Program: invalid number '$n'\n";
        exit EX_FAILURE;
    }
    return $n;
}

__END__

=head1 NAME

seq - print a numeric sequence

=head1 SYNOPSIS

    seq [OPTIONS] LAST
    seq [OPTIONS] FIRST LAST
    seq [OPTIONS] FIRST INCR LAST

=head1 DESCRIPTION

seq writes a list of numbers to standard output separated by a newline character.
If only LAST is provided the sequence starts from 1 and the increment is 1.
LAST may be negative, in which case the sequence starts from 1 with the increment of -1.

When only FIRST and LAST are specified the increment will be either 1 or -1 based on whether FIRST is greater.
Sequences are inclusive of FIRST and LAST, so "seq 3 3" results in the sequence "3".

When an increment is needed other than 1 or -1, the INCR argument should be used.
Zero is not a valid increment.
Positive numbers may optionally include a '+' prefix.
Floating point numbers may be entered in decimal notation (e.g. 0.2223).

=head2 OPTIONS

The following options are available:

=over 4

=item -f FORMAT

Set a printf format specifier instead of the default '%g'

=item -s STRING

Separate each number with STRING instead of the newline character

=back

=head1 BUGS

Corrupt printf format specifiers may be entered.

=head1 AUTHOR

Written by Michael Mikonos.

=head1 COPYRIGHT

Copyright (c) 2023 Michael Mikonos.

This code is licensed under the Artistic License 2.

=cut
