From 7ff72bc11e2eae4edecb5a9dca12e4670ae377ba Mon Sep 17 00:00:00 2001 From: Victor Payno Date: Sat, 2 Sep 2023 22:23:13 -0700 Subject: [PATCH 1/2] awk/grains: download exercise --- awk/grains/.exercism/config.json | 19 + awk/grains/.exercism/metadata.json | 1 + awk/grains/HELP.md | 99 +++++ awk/grains/README.md | 88 ++++ awk/grains/bats-extra.bash | 637 +++++++++++++++++++++++++++++ awk/grains/grains.awk | 4 + awk/grains/test-grains.bats | 79 ++++ 7 files changed, 927 insertions(+) create mode 100644 awk/grains/.exercism/config.json create mode 100644 awk/grains/.exercism/metadata.json create mode 100644 awk/grains/HELP.md create mode 100644 awk/grains/README.md create mode 100644 awk/grains/bats-extra.bash create mode 100644 awk/grains/grains.awk create mode 100644 awk/grains/test-grains.bats diff --git a/awk/grains/.exercism/config.json b/awk/grains/.exercism/config.json new file mode 100644 index 00000000..d70cd2a5 --- /dev/null +++ b/awk/grains/.exercism/config.json @@ -0,0 +1,19 @@ +{ + "authors": [ + "glennj" + ], + "files": { + "solution": [ + "grains.awk" + ], + "test": [ + "test-grains.bats" + ], + "example": [ + ".meta/example.awk" + ] + }, + "blurb": "Calculate the number of grains of wheat on a chessboard given that the number on each square doubles.", + "source": "The CodeRanch Cattle Drive, Assignment 6", + "source_url": "https://coderanch.com/wiki/718824/Grains" +} diff --git a/awk/grains/.exercism/metadata.json b/awk/grains/.exercism/metadata.json new file mode 100644 index 00000000..a362b795 --- /dev/null +++ b/awk/grains/.exercism/metadata.json @@ -0,0 +1 @@ +{"track":"awk","exercise":"grains","id":"1a3045dfcd1642978c12499b7080a7f3","url":"https://exercism.org/tracks/awk/exercises/grains","handle":"vpayno","is_requester":true,"auto_approve":false} \ No newline at end of file diff --git a/awk/grains/HELP.md b/awk/grains/HELP.md new file mode 100644 index 00000000..068808cf --- /dev/null +++ b/awk/grains/HELP.md @@ -0,0 +1,99 @@ +# Help + +## Running the tests + +Each exercise contains a test file. +Run the tests using the `bats` program. +```bash +bats test-hello-world.bats +``` + +`bats` will need to be installed. +See the [Testing on the Bash track][bash] page for instructions to install `bats` for your system. + +### bats is implemented in bash + +The bats file is a bash script, with some special functions recognized by the `bats` command. +You'll see some tests that look like +```sh +gawk -f some-exercise.awk <<< "some,input,here" +``` +That `<<<` syntax is a bash [Here String][here-string]. +It sends the string on the right-hand side into the standard input of the program on the left-hand side. +It is ([approximately][so]) the same as +```sh +echo "some,input,here" | gawk -f some-exercise.awk +``` + +## Help for assert functions + +The tests use functions from the [bats-assert][bats-assert] library. +Help for the various `assert*` functions can be found there. + +## Skipped tests + +Solving an exercise means making all its tests pass. +By default, only one test (the first one) is executed when you run the tests. +This is intentional, as it allows you to focus on just making that one test pass. +Once it passes, you can enable the next test by commenting out or removing the + + [[ $BATS_RUN_SKIPPED == true ]] || skip + +annotations prepending other tests. + +## Overriding skips + +To run all tests, including the ones with `skip` annotations, you can run: +```bash +BATS_RUN_SKIPPED=true bats test-some-exercise.bats +``` + +It can be convenient to use a wrapper function to save on typing: in `bash` you can do: +```bash +bats() { + BATS_RUN_SKIPPED=true command bats *.bats +} +``` +Then run tests with just: +```bash +bats +``` + +[bash]: https://exercism.org/docs/tracks/bash/tests +[bats-assert]: https://github.com/bats-core/bats-assert +[here-string]: https://www.gnu.org/software/bash/manual/bash.html#Here-Strings +[so]: https://unix.stackexchange.com/a/80372/4667 + +## Submitting your solution + +You can submit your solution using the `exercism submit grains.awk` command. +This command will upload your solution to the Exercism website and print the solution page's URL. + +It's possible to submit an incomplete solution which allows you to: + +- See how others have completed the exercise +- Request help from a mentor + +## Need to get help? + +If you'd like help solving the exercise, check the following pages: + +- The [AWK track's documentation](https://exercism.org/docs/tracks/awk) +- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5) +- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs) + +Should those resources not suffice, you could submit your (incomplete) solution to request mentoring. + +Places to look for help for AWK questions: + +* [Stack Overflow `awk` tag][so]. +* check the Resources section of the [Stack Overflow `awk` tag into page][so-info]. +* raise an issue at the [exercism/awk][github] Github repository. +* IRC: `irc://irc.liberachat.net/#awk`, `irc://irc.liberachat.net/#exercism` + * see [Libera.chat][libera] if you're unfamiliar with IRC. + + +[so]: https://stackoverflow.com/tags/awk +[so-info]: https://stackoverflow.com/tags/awk/info +[github]: https://github.com/exercism/awk +[libera]: https://libera.chat \ No newline at end of file diff --git a/awk/grains/README.md b/awk/grains/README.md new file mode 100644 index 00000000..a5750f77 --- /dev/null +++ b/awk/grains/README.md @@ -0,0 +1,88 @@ +# Grains + +Welcome to Grains on Exercism's AWK Track. +If you need help running the tests or submitting your code, check out `HELP.md`. + +## Instructions + +Calculate the number of grains of wheat on a chessboard given that the number on each square doubles. + +There once was a wise servant who saved the life of a prince. +The king promised to pay whatever the servant could dream up. +Knowing that the king loved chess, the servant told the king he would like to have grains of wheat. +One grain on the first square of a chess board, with the number of grains doubling on each successive square. + +There are 64 squares on a chessboard (where square 1 has one grain, square 2 has two grains, and so on). + +Write code that shows: + +- how many grains were on a given square, and +- the total number of grains on the chessboard + +## GNU awk and Arbitrary Precision Arithmetic + +By default, numbers in `awk` are represented as double-precision floating +point numbers. This provides 53 bits of precision (see [Table +16.3][table-16.3] in the manual). Unfortunately, this is insufficient to +complete this exercise (spoiler alert, one test requires 63 bits of +precision). + +"Insufficient precision" looks like this: +```sh +$ gawk 'BEGIN { + n = 2 ^ 54 + if (n == n + 1) + print "not enough precision" + else + print "OK" +}' +``` +```none +not enough precision +``` + +GNU `awk` has the capability to use arbitrary-precision numbers, if the +relevant libraries have been compiled into the `awk` executable. Check +your awk version, and look for "GNU MPFR": +```sh +$ gawk --version +GNU Awk 5.0.1, API: 2.0 (GNU MPFR 4.0.2, GNU MP 6.2.0) +... +``` + +If it's present, enable the capability using the `-M` option: +```sh +$ gawk -M 'BEGIN { + n = 2 ^ 54 + if (n == n + 1) + print "not enough precision" + else + print "OK" +}' +``` +```none +OK +``` + +What if my `awk` doesn't have `-M`? You'll have to call out to an external +program that can do arbitrary precision arithmetic, like `bc`. See the +section [Using getline into a Variable from a Pipe][getline-pipe] for an +example how to do this. + + +Further reading: [Arbitrary Precision Arithmetic][arbitrary] + + +[table-16.3]: https://www.gnu.org/software/gawk/manual/html_node/Math-Definitions.html#table_002dieee_002dformats +[arbitrary]: https://www.gnu.org/software/gawk/manual/html_node/Arbitrary-Precision-Arithmetic.html +[getline-pipe]: https://www.gnu.org/software/gawk/manual/html_node/Getline_002fVariable_002fPipe.html + +## Source + +### Created by + +- @glennj + +### Based on + +The CodeRanch Cattle Drive, Assignment 6 - https://coderanch.com/wiki/718824/Grains \ No newline at end of file diff --git a/awk/grains/bats-extra.bash b/awk/grains/bats-extra.bash new file mode 100644 index 00000000..54d48070 --- /dev/null +++ b/awk/grains/bats-extra.bash @@ -0,0 +1,637 @@ +# This is the source code for bats-support and bats-assert, concatenated +# * https://github.com/bats-core/bats-support +# * https://github.com/bats-core/bats-assert +# +# Comments have been removed to save space. See the git repos for full source code. + +############################################################ +# +# bats-support - Supporting library for Bats test helpers +# +# Written in 2016 by Zoltan Tombol +# +# To the extent possible under law, the author(s) have dedicated all +# copyright and related and neighboring rights to this software to the +# public domain worldwide. This software is distributed without any +# warranty. +# +# You should have received a copy of the CC0 Public Domain Dedication +# along with this software. If not, see +# . +# + +fail() { + (( $# == 0 )) && batslib_err || batslib_err "$@" + return 1 +} + +batslib_is_caller() { + local -i is_mode_direct=1 + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -i|--indirect) is_mode_direct=0; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + # Arguments. + local -r func="$1" + + # Check call stack. + if (( is_mode_direct )); then + [[ $func == "${FUNCNAME[2]}" ]] && return 0 + else + local -i depth + for (( depth=2; depth<${#FUNCNAME[@]}; ++depth )); do + [[ $func == "${FUNCNAME[$depth]}" ]] && return 0 + done + fi + + return 1 +} + +batslib_err() { + { if (( $# > 0 )); then + echo "$@" + else + cat - + fi + } >&2 +} + +batslib_count_lines() { + local -i n_lines=0 + local line + while IFS='' read -r line || [[ -n $line ]]; do + (( ++n_lines )) + done < <(printf '%s' "$1") + echo "$n_lines" +} + +batslib_is_single_line() { + for string in "$@"; do + (( $(batslib_count_lines "$string") > 1 )) && return 1 + done + return 0 +} + +batslib_get_max_single_line_key_width() { + local -i max_len=-1 + while (( $# != 0 )); do + local -i key_len="${#1}" + batslib_is_single_line "$2" && (( key_len > max_len )) && max_len="$key_len" + shift 2 + done + echo "$max_len" +} + +batslib_print_kv_single() { + local -ir col_width="$1"; shift + while (( $# != 0 )); do + printf '%-*s : %s\n' "$col_width" "$1" "$2" + shift 2 + done +} + +batslib_print_kv_multi() { + while (( $# != 0 )); do + printf '%s (%d lines):\n' "$1" "$( batslib_count_lines "$2" )" + printf '%s\n' "$2" + shift 2 + done +} + +batslib_print_kv_single_or_multi() { + local -ir width="$1"; shift + local -a pairs=( "$@" ) + + local -a values=() + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + values+=( "${pairs[$i]}" ) + done + + if batslib_is_single_line "${values[@]}"; then + batslib_print_kv_single "$width" "${pairs[@]}" + else + local -i i + for (( i=1; i < ${#pairs[@]}; i+=2 )); do + pairs[$i]="$( batslib_prefix < <(printf '%s' "${pairs[$i]}") )" + done + batslib_print_kv_multi "${pairs[@]}" + fi +} + +batslib_prefix() { + local -r prefix="${1:- }" + local line + while IFS='' read -r line || [[ -n $line ]]; do + printf '%s%s\n' "$prefix" "$line" + done +} + +batslib_mark() { + local -r symbol="$1"; shift + # Sort line numbers. + set -- $( sort -nu <<< "$( printf '%d\n' "$@" )" ) + + local line + local -i idx=0 + while IFS='' read -r line || [[ -n $line ]]; do + if (( ${1:--1} == idx )); then + printf '%s\n' "${symbol}${line:${#symbol}}" + shift + else + printf '%s\n' "$line" + fi + (( ++idx )) + done +} + +batslib_decorate() { + echo + echo "-- $1 --" + cat - + echo '--' + echo +} + +############################################################ + +assert() { + if ! "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion failed' \ + | fail + fi +} + +assert_equal() { + if [[ $1 != "$2" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$2" \ + 'actual' "$1" \ + | batslib_decorate 'values do not equal' \ + | fail + fi +} + +assert_failure() { + : "${output?}" + : "${status?}" + + (( $# > 0 )) && local -r expected="$1" + if (( status == 0 )); then + batslib_print_kv_single_or_multi 6 'output' "$output" \ + | batslib_decorate 'command succeeded, but it was expected to fail' \ + | fail + elif (( $# > 0 )) && (( status != expected )); then + { local -ir width=8 + batslib_print_kv_single "$width" \ + 'expected' "$expected" \ + 'actual' "$status" + batslib_print_kv_single_or_multi "$width" \ + 'output' "$output" + } \ + | batslib_decorate 'command failed as expected, but status differs' \ + | fail + fi +} + +assert_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + : "${lines?}" + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Arguments. + local -r expected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if ! [[ ${lines[$idx]} =~ $expected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression does not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} != *"$expected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$expected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line does not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} != "$expected" ]]; then + batslib_print_kv_single 8 \ + 'index' "$idx" \ + 'expected' "$expected" \ + 'actual' "${lines[$idx]}" \ + | batslib_decorate 'line differs' \ + | fail + fi + fi + else + # Contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} =~ $expected ]] && return 0 + done + { local -ar single=( 'regexp' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'no output line matches regular expression' \ + | fail + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == *"$expected"* ]] && return 0 + done + { local -ar single=( 'substring' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'no output line contains substring' \ + | fail + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + [[ ${lines[$idx]} == "$expected" ]] && return 0 + done + { local -ar single=( 'line' "$expected" ) + local -ar may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + batslib_print_kv_single_or_multi "$width" "${may_be_multi[@]}" + } \ + | batslib_decorate 'output does not contain line' \ + | fail + fi + fi +} + +assert_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + local -i is_mode_nonempty=0 + local -i use_stdin=0 + : "${output?}" + + # Handle options. + if (( $# == 0 )); then + is_mode_nonempty=1 + fi + + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + -|--stdin) use_stdin=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + return $? + fi + + # Arguments. + local expected + if (( use_stdin )); then + expected="$(cat -)" + else + expected="${1-}" + fi + + # Matching. + if (( is_mode_nonempty )); then + if [ -z "$output" ]; then + echo 'expected non-empty output, but output was empty' \ + | batslib_decorate 'no output' \ + | fail + fi + elif (( is_mode_regexp )); then + if [[ '' =~ $expected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$expected'" \ + | batslib_decorate 'ERROR: assert_output' \ + | fail + elif ! [[ $output =~ $expected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression does not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output != *"$expected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$expected" \ + 'output' "$output" \ + | batslib_decorate 'output does not contain substring' \ + | fail + fi + else + if [[ $output != "$expected" ]]; then + batslib_print_kv_single_or_multi 8 \ + 'expected' "$expected" \ + 'actual' "$output" \ + | batslib_decorate 'output differs' \ + | fail + fi + fi +} + +assert_success() { + : "${output?}" + : "${status?}" + + if (( status != 0 )); then + { local -ir width=6 + batslib_print_kv_single "$width" 'status' "$status" + batslib_print_kv_single_or_multi "$width" 'output' "$output" + } \ + | batslib_decorate 'command failed' \ + | fail + fi +} + +refute() { + if "$@"; then + batslib_print_kv_single 10 'expression' "$*" \ + | batslib_decorate 'assertion succeeded, but it was expected to fail' \ + | fail + fi +} + +refute_line() { + local -i is_match_line=0 + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + : "${lines?}" + + # Handle options. + while (( $# > 0 )); do + case "$1" in + -n|--index) + if (( $# < 2 )) || ! [[ $2 =~ ^([0-9]|[1-9][0-9]+)$ ]]; then + echo "\`--index' requires an integer argument: \`$2'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + is_match_line=1 + local -ri idx="$2" + shift 2 + ;; + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Arguments. + local -r unexpected="$1" + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_line' \ + | fail + return $? + fi + + # Matching. + if (( is_match_line )); then + # Specific line. + if (( is_mode_regexp )); then + if [[ ${lines[$idx]} =~ $unexpected ]]; then + batslib_print_kv_single 6 \ + 'index' "$idx" \ + 'regexp' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'regular expression should not match line' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + batslib_print_kv_single 9 \ + 'index' "$idx" \ + 'substring' "$unexpected" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should not contain substring' \ + | fail + fi + else + if [[ ${lines[$idx]} == "$unexpected" ]]; then + batslib_print_kv_single 5 \ + 'index' "$idx" \ + 'line' "${lines[$idx]}" \ + | batslib_decorate 'line should differ' \ + | fail + fi + fi + else + # Line contained in output. + if (( is_mode_regexp )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} =~ $unexpected ]]; then + { local -ar single=( 'regexp' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'no line should match the regular expression' \ + | fail + return $? + fi + done + elif (( is_mode_partial )); then + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == *"$unexpected"* ]]; then + { local -ar single=( 'substring' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'no line should contain substring' \ + | fail + return $? + fi + done + else + local -i idx + for (( idx = 0; idx < ${#lines[@]}; ++idx )); do + if [[ ${lines[$idx]} == "$unexpected" ]]; then + { local -ar single=( 'line' "$unexpected" 'index' "$idx" ) + local -a may_be_multi=( 'output' "$output" ) + local -ir width="$( batslib_get_max_single_line_key_width "${single[@]}" "${may_be_multi[@]}" )" + batslib_print_kv_single "$width" "${single[@]}" + if batslib_is_single_line "${may_be_multi[1]}"; then + batslib_print_kv_single "$width" "${may_be_multi[@]}" + else + may_be_multi[1]="$( printf '%s' "${may_be_multi[1]}" | batslib_prefix | batslib_mark '>' "$idx" )" + batslib_print_kv_multi "${may_be_multi[@]}" + fi + } \ + | batslib_decorate 'line should not be in output' \ + | fail + return $? + fi + done + fi + fi +} + +refute_output() { + local -i is_mode_partial=0 + local -i is_mode_regexp=0 + local -i is_mode_empty=0 + local -i use_stdin=0 + : "${output?}" + + # Handle options. + if (( $# == 0 )); then + is_mode_empty=1 + fi + + while (( $# > 0 )); do + case "$1" in + -p|--partial) is_mode_partial=1; shift ;; + -e|--regexp) is_mode_regexp=1; shift ;; + -|--stdin) use_stdin=1; shift ;; + --) shift; break ;; + *) break ;; + esac + done + + if (( is_mode_partial )) && (( is_mode_regexp )); then + echo "\`--partial' and \`--regexp' are mutually exclusive" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Arguments. + local unexpected + if (( use_stdin )); then + unexpected="$(cat -)" + else + unexpected="${1-}" + fi + + if (( is_mode_regexp == 1 )) && [[ '' =~ $unexpected ]] || (( $? == 2 )); then + echo "Invalid extended regular expression: \`$unexpected'" \ + | batslib_decorate 'ERROR: refute_output' \ + | fail + return $? + fi + + # Matching. + if (( is_mode_empty )); then + if [ -n "$output" ]; then + batslib_print_kv_single_or_multi 6 \ + 'output' "$output" \ + | batslib_decorate 'output non-empty, but expected no output' \ + | fail + fi + elif (( is_mode_regexp )); then + if [[ $output =~ $unexpected ]]; then + batslib_print_kv_single_or_multi 6 \ + 'regexp' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'regular expression should not match output' \ + | fail + fi + elif (( is_mode_partial )); then + if [[ $output == *"$unexpected"* ]]; then + batslib_print_kv_single_or_multi 9 \ + 'substring' "$unexpected" \ + 'output' "$output" \ + | batslib_decorate 'output should not contain substring' \ + | fail + fi + else + if [[ $output == "$unexpected" ]]; then + batslib_print_kv_single_or_multi 6 \ + 'output' "$output" \ + | batslib_decorate 'output equals, but it was expected to differ' \ + | fail + fi + fi +} diff --git a/awk/grains/grains.awk b/awk/grains/grains.awk new file mode 100644 index 00000000..bca5f438 --- /dev/null +++ b/awk/grains/grains.awk @@ -0,0 +1,4 @@ +BEGIN { + print "Implement this solution" > "/dev/stderr" + exit 1 +} diff --git a/awk/grains/test-grains.bats b/awk/grains/test-grains.bats new file mode 100644 index 00000000..e15ed387 --- /dev/null +++ b/awk/grains/test-grains.bats @@ -0,0 +1,79 @@ +#!/usr/bin/env bats +load bats-extra + +@test "1" { + #[[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 1 + assert_success + assert_output "1" +} + +@test "2" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 2 + assert_success + assert_output "2" +} + +@test "3" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 3 + assert_success + assert_output "4" +} + +@test "4" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 4 + assert_success + assert_output "8" +} + +@test "16" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 16 + assert_success + assert_output "32768" +} + +@test "32" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 32 + assert_success + assert_output "2147483648" +} + +@test "64" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 64 + assert_success + assert_output "9223372036854775808" +} + +@test "square 0 raises an exception" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 0 + assert_failure + assert_output "square must be between 1 and 64" +} + +@test "negative square raises an exception" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< -1 + assert_failure + assert_output "square must be between 1 and 64" +} + +@test "square greater than 64 raises an exception" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -f grains.awk <<< 65 + assert_failure + assert_output "square must be between 1 and 64" +} + +@test "returns the total number of grains on the board" { + [[ $BATS_RUN_SKIPPED == "true" ]] || skip + run gawk -M -f grains.awk <<< total + assert_success + assert_output "18446744073709551615" +} From f50a5da192351bbb703c92195a7dba4db31439ce Mon Sep 17 00:00:00 2001 From: Victor Payno Date: Sat, 2 Sep 2023 22:53:54 -0700 Subject: [PATCH 2/2] awk/grains: 1st iteration --- awk/README.md | 1 + awk/grains/.lint_default_vars | 1 + awk/grains/README.md | 9 +- awk/grains/awkunit.awk | 1 + awk/grains/grains.awk | 41 +++++++- awk/grains/grains_test.awk | 117 +++++++++++++++++++++ awk/grains/run-tests-awk.txt | 191 ++++++++++++++++++++++++++++++++++ awk/grains/test-cases.awk | 23 ++++ 8 files changed, 381 insertions(+), 3 deletions(-) create mode 100644 awk/grains/.lint_default_vars create mode 120000 awk/grains/awkunit.awk create mode 100644 awk/grains/grains_test.awk create mode 100644 awk/grains/run-tests-awk.txt create mode 100644 awk/grains/test-cases.awk diff --git a/awk/README.md b/awk/README.md index c358e225..aefafb64 100644 --- a/awk/README.md +++ b/awk/README.md @@ -25,3 +25,4 @@ - [raindrops](./raindrops/README.md) - [leap](./leap/README.md) - [armstrong-numbers](./armstrong-numbers/README.md) +- [grains](./grains/README.md) diff --git a/awk/grains/.lint_default_vars b/awk/grains/.lint_default_vars new file mode 100644 index 00000000..a3f20a2c --- /dev/null +++ b/awk/grains/.lint_default_vars @@ -0,0 +1 @@ + -M \ No newline at end of file diff --git a/awk/grains/README.md b/awk/grains/README.md index a5750f77..9066e521 100644 --- a/awk/grains/README.md +++ b/awk/grains/README.md @@ -85,4 +85,11 @@ Further reading: [Arbitrary Precision Arithmetic][arbitrary] ### Based on -The CodeRanch Cattle Drive, Assignment 6 - https://coderanch.com/wiki/718824/Grains \ No newline at end of file +The CodeRanch Cattle Drive, Assignment 6 - https://coderanch.com/wiki/718824/Grains + +### My Solution + +- [my solution](./grains.awk) +- [awkunit tests](./grains_test.awk) +- [test cases](./test-cases.awk) +- [run-tests](./run-tests-awk.txt) diff --git a/awk/grains/awkunit.awk b/awk/grains/awkunit.awk new file mode 120000 index 00000000..38a08adf --- /dev/null +++ b/awk/grains/awkunit.awk @@ -0,0 +1 @@ +../.lib/awkunit.awk \ No newline at end of file diff --git a/awk/grains/grains.awk b/awk/grains/grains.awk index bca5f438..f6268df4 100644 --- a/awk/grains/grains.awk +++ b/awk/grains/grains.awk @@ -1,4 +1,41 @@ +#!/usr/bin/gawk -M --lint --file + +function total() { + return 2^64 -1 +} + +function grains(square) { + if (square == "total") { + return total() + } + + if (square < 1 || square > 64) { + return "error:square must be between 1 and 64" + } + + return lshift(1, square-1) +} + BEGIN { - print "Implement this solution" > "/dev/stderr" - exit 1 +} + +{ + if (match($0, /^total$/)) { + print total() + + exit 0 + } + + result = grains($0) + + if (match(result, /^error:/)) { + _ = split(result, part, ":") + print part[2] + exit 1 + } + + print result +} + +END { } diff --git a/awk/grains/grains_test.awk b/awk/grains/grains_test.awk new file mode 100644 index 00000000..b61e07c8 --- /dev/null +++ b/awk/grains/grains_test.awk @@ -0,0 +1,117 @@ +#!/usr/bin/gawk --lint --file + +@include "awkunit" +@include "test-cases" +@include "grains" + +passed = 0 +testCount = 0 + +function _debugTestPre() { + printf "Test %s:\n", (passed + 1) + printf " input -> [%s]\n", input +} + +function _debugTestPost() { + passed = passed + 1 + printf " output -> [%s]\n", got + printf " result -> passed\n\n" +} + +function testGrains_total() { + input = "" + want = "18446744073709551615" + + # _ = split(input, a, " ") + + _debugTestPre() + got = total() + + assertEquals(got, want) + _debugTestPost() +} + +function testGrains_0() { + input = "0" + want = "error:square must be between 1 and 64" + + # _ = split(input, a, " ") + + _debugTestPre() + got = grains(input) + + assertEquals(got, want) + _debugTestPost() +} + +function testGrains_1() { + input = "1" + want = "1" + + # _ = split(input, a, " ") + + _debugTestPre() + got = grains(input) + + assertEquals(got, want) + _debugTestPost() +} + +function testGrains_64() { + input = "64" + want = "9223372036854775808" + + # _ = split(input, a, " ") + + _debugTestPre() + got = grains(input) + + assertEquals(got, want) + _debugTestPost() +} + +function casesGrains() { + printf "Running %d test cases\n\n", length(cases) + caseNum = 0 + + # Associative arrays don't preserve insert order. + for (key in cases) { + input = key + want = cases[key] + + # _ = split(input, a, " ") + + _debugTestPre() + got = grains(input) + + assertEquals(got, want) + _debugTestPost() + } +} + +BEGIN { + exit 0 +} + +END { + cmd = "grep --no-filename --count ^function\\ test *_test.awk" + cmd | getline testCount + + printf "\nRunning %d tests...\n\n", testCount + + testCount = testCount + length(cases) + + # running tests with a lot of duplication + testGrains_0() + testGrains_1() + testGrains_64() + testGrains_total() + + # running tests with reduced duplication + casesGrains() + + print "\n" passed " out of " testCount " tests passed!" + + # add exit here to keep it from looping + exit 0 +} diff --git a/awk/grains/run-tests-awk.txt b/awk/grains/run-tests-awk.txt new file mode 100644 index 00000000..e2b39cb8 --- /dev/null +++ b/awk/grains/run-tests-awk.txt @@ -0,0 +1,191 @@ +Running automated test file(s): + + +=============================================================================== + +AWKLIBPATH=/usr/lib/x86_64-linux-gnu/gawk:../.lib + +/usr/lib/x86_64-linux-gnu/gawk +filefuncs.so +fnmatch.so +fork.so +inplace.so +intdiv.so +ordchr.so +readdir.so +readfile.so +revoutput.so +revtwoway.so +rwarray.so +time.so + +../.lib +awkunit.awk +awkunit.so + +gawk -M --lint --file=./awkunit.awk < /dev/null > /dev/null +gawk: ./awkunit.awk:3: warning: `load' is a gawk extension +gawk: warning: function `assertEquals' defined but never called directly +gawk: warning: function `assert' defined but never called directly +gawk: ./awkunit.awk:26: warning: reference to uninitialized variable `_assert_exit' + +real 0m0.002s +user 0m0.002s +sys 0m0.000s + +gawk -M --lint --file=./grains.awk < /dev/null > /dev/null +gawk: ./grains.awk:16: warning: `lshift' is a gawk extension + +real 0m0.002s +user 0m0.001s +sys 0m0.001s + +gawk -M --lint --file=./test-cases.awk < /dev/null > /dev/null + +real 0m0.002s +user 0m0.002s +sys 0m0.000s + +exit 0 + +=============================================================================== + +Running: bats ./test-grains.bats +1..11 +ok 1 1 +ok 2 2 +ok 3 3 +ok 4 4 +ok 5 16 +ok 6 32 +ok 7 64 +ok 8 square 0 raises an exception +ok 9 negative square raises an exception +ok 10 square greater than 64 raises an exception +ok 11 returns the total number of grains on the board + +real 0m0.372s +user 0m0.227s +sys 0m0.166s + +exit 0 + +=============================================================================== + +AWKLIBPATH=/usr/lib/x86_64-linux-gnu/gawk:../.lib + +/usr/lib/x86_64-linux-gnu/gawk +filefuncs.so +fnmatch.so +fork.so +inplace.so +intdiv.so +ordchr.so +readdir.so +readfile.so +revoutput.so +revtwoway.so +rwarray.so +time.so + +../.lib +awkunit.awk +awkunit.so + +Running: gawk -M --file ./grains_test.awk && printf \n%s\n Tests Passed! || printf \n%s\n Tests Failed! + +Running 4 tests... + +Test 1: + input -> [0] + output -> [error:square must be between 1 and 64] + result -> passed + +Test 2: + input -> [1] + output -> [1] + result -> passed + +Test 3: + input -> [64] + output -> [9223372036854775808] + result -> passed + +Test 4: + input -> [] + output -> [18446744073709551615] + result -> passed + +Running 10 test cases + +Test 5: + input -> [4] + output -> [8] + result -> passed + +Test 6: + input -> [64] + output -> [9223372036854775808] + result -> passed + +Test 7: + input -> [65] + output -> [error:square must be between 1 and 64] + result -> passed + +Test 8: + input -> [total] + output -> [18446744073709551615] + result -> passed + +Test 9: + input -> [0] + output -> [error:square must be between 1 and 64] + result -> passed + +Test 10: + input -> [32] + output -> [2147483648] + result -> passed + +Test 11: + input -> [1] + output -> [1] + result -> passed + +Test 12: + input -> [2] + output -> [2] + result -> passed + +Test 13: + input -> [16] + output -> [32768] + result -> passed + +Test 14: + input -> [3] + output -> [4] + result -> passed + + +14 out of 14 tests passed! + +real 0m0.007s +user 0m0.004s +sys 0m0.004s + +Tests Passed! + +exit 0 + +=============================================================================== + +Running: misspell . + +real 0m0.026s +user 0m0.034s +sys 0m0.010s + +=============================================================================== + diff --git a/awk/grains/test-cases.awk b/awk/grains/test-cases.awk new file mode 100644 index 00000000..6a5699c7 --- /dev/null +++ b/awk/grains/test-cases.awk @@ -0,0 +1,23 @@ +#!/usr/bin/gawk --lint --file +# test-cases.awk + +# key: input +# value: output + +BEGIN { + cases["0"]="error:square must be between 1 and 64" + cases["65"]="error:square must be between 1 and 64" + + cases["1"]="1" + cases["2"]="2" + cases["3"]="4" + cases["4"]="8" + cases["16"]="32768" + cases["32"]="2147483648" + cases["64"]="9223372036854775808" + + cases["total"]="18446744073709551615" + + # add exit here to keep it from waiting for input + exit 0 +}