The main task that the seq
command performs is to print number ranges, so it makes sense to start there.
There are three numbers we are going to use: first, last and increment. They are all going to be floating-point numbers. Since f64
is the default in Odin, let’s just use that.
First code
Here is a starting point:
|
|
At this point, you should have a list of sequence invocations that you can use to test that everything is working. In the next part of this series, we will look at formal testing. For now put tother a list of tests for all the possible sequences that seq
should be able to produce.
Dealing with some special cases
You may have come across some situations where the program just kept counting and you had to force the program to stop via CTRL+C
. There are two case in which the original seq
doesn’t produce any output, namely:
- if first > last AND increment > 0
- if first < last AND increment < 0
The exit status in these cases should be 0. Here is the updated code:
1package main
2
3import "core:fmt"
4import "core:os"
5
6main :: proc() {
7 first: f64 = 1
8 last: f64 = 10
9 increment: f64 = 1
10
11 if first > last && increment > 0 {
12 os.exit(0)
13 } else if first < last && increment < 0 {
14 os.exit(0)
15 }
16
17 for i := first; i <= last; i += increment {
18 fmt.println(i)
19 }
20}
Counting down
If you chose a case where seq is supposed to count down, such as first = 100, increment - 2, last = 80, you will have noted that that currently doesn’t work. The culprit is here. Can you see it?
for i := first; i <= last; i += increment {
fmt.println(i)
}
That’s right: i <= last
is going to exit immediately is a case where first > last. In that case we need to switch the inequality operator to >=
instead. Let’s summarize and see if we can build a statement from it.
- if first < last then loop as long as i <= last
- if first > last then loop as long as i >= last
It may look a bit convoluted when you see it, but the following does exactly what we have just stated.
for i := first; (first < last && i <= last) || (first > last && i >= last); i += increment {
fmt.println(i)
}
If you really don’t like the look of that conditional, you can achieve the same thing with an if/else statment:
if first < last {
for i := first; i <= last; i += increment {
fmt.println(i)
}
} else {
for i := first; i >= last; i += increment {
fmt.println(i)
}
}
Remember that you should be able to maintain code that you have written. If something looks complex now, wait until you haven’t looked at the code for several weeks. I suggest you alsways aim for readable code (whatever that means to you).
Command-line arguments
We are going to have to deal with command-line options, like --help
and --version
, but for now let’s only allow 1-3 arguments. The last argument is required, first and increment are optional. If two arguments are passed in, they will be first and last.
For now, we can use something similar to the following:
switch len(os.args[1:]) {
case 0:
fmt.eprintfln("%s: missing operand", os.args[0])
os.exit(1)
case 1:
// start and increment are 1, last is read from command-line
case 2:
// increment is 1, first and last are read from command-line
case 3:
// first, increment and last are read from command-line
case:
fmt.eprintfln("%s: extra operand: %s", os.args[0], os.args[4])
os.exit(1)
}
Converting strings to f64s
Will create a helper procedure to convert a string to an f64. If conversion fails, we will just exit with a status code of 1. I’ll tack on _or_fail
to the procedure name to indicate that this is a procedure that might never return.
parse_f64_or_fail :: proc(s: string) -> f64 {
if f, ok := strconv.parse_f64(s); !ok {
fmt.eprintfln("%s: invalid floating point argument: '%s'", os.args[0], s)
os.exit(-1)
} else {
return f
}
}
Putting it together
Now that we have put together the pieces, we should be able to put it all together into a program that does
1package main
2
3import "core:fmt"
4import "core:os"
5import "core:strconv"
6
7main :: proc() {
8 first, increment, last: f64
9
10 switch len(os.args[1:]) {
11 case 0:
12 fmt.eprintfln("%s: missing operand", os.args[0])
13 os.exit(1)
14 case 1:
15 first = 1
16 increment = 1
17 last = parse_f64_or_fail(os.args[1])
18 case 2:
19 first = parse_f64_or_fail(os.args[1])
20 increment = 1
21 last = parse_f64_or_fail(os.args[2])
22 case 3:
23 first = parse_f64_or_fail(os.args[1])
24 increment = parse_f64_or_fail(os.args[2])
25 last = parse_f64_or_fail(os.args[3])
26 case:
27 fmt.eprintfln("%s: extra operand: %s", os.args[0], os.args[4])
28 os.exit(1)
29 }
30
31 if first > last && increment > 0 {
32 os.exit(0)
33 } else if first < last && increment < 0 {
34 os.exit(0)
35 }
36
37 for i := first; (first < last && i <= last) || (first > last && i >= last); i += increment {
38 fmt.println(i)
39 }
40}
41
42parse_f64_or_fail :: proc(s: string) -> f64 {
43 if f, ok := strconv.parse_f64(s); !ok {
44 fmt.eprintfln("%s: invalid floating point argument: '%s'", os.args[0], s)
45 os.exit(-1)
46 } else {
47 return f
48 }
49}
Make sure to test this program against all test sequences you wrote down earlier. When you are done and feel satisfied that things are working so far, let’s try formalize the tests.