From 86d0d254ba2cd898ceb3a548e314816e0fd344ba Mon Sep 17 00:00:00 2001 From: Mitchell Paulus Date: Mon, 9 Dec 2024 10:06:08 -0600 Subject: [PATCH] Finish all awk examples, add multiplication and division --- .github/workflows/dotnet.yml | 3 ++ README.md | 9 ++-- examples/1.msh | 23 -------- examples/1.sh | 38 ------------- examples/awk.msh | 101 ----------------------------------- examples/awk/19.awk | 2 + examples/awk/19.data | 3 ++ examples/awk/19.msh | 1 + examples/awk/2.data | 20 +++++++ examples/awk/20.awk | 1 + examples/awk/20.data | 3 ++ examples/awk/20.msh | 1 + examples/awk/test.sh | 11 ++-- lib/std.msh | 5 ++ mshell/Evaluator.go | 69 ++++++++++++++++++++++++ tests/arithmetic.msh | 8 +++ tests/arithmetic.msh.stdout | 7 +++ 17 files changed, 131 insertions(+), 174 deletions(-) delete mode 100644 examples/1.msh delete mode 100644 examples/1.sh delete mode 100644 examples/awk.msh create mode 100644 examples/awk/19.awk create mode 100644 examples/awk/19.data create mode 100644 examples/awk/19.msh create mode 100644 examples/awk/2.data create mode 100644 examples/awk/20.awk create mode 100644 examples/awk/20.data create mode 100644 examples/awk/20.msh create mode 100644 tests/arithmetic.msh create mode 100644 tests/arithmetic.msh.stdout diff --git a/.github/workflows/dotnet.yml b/.github/workflows/dotnet.yml index 7ad9083..771983f 100644 --- a/.github/workflows/dotnet.yml +++ b/.github/workflows/dotnet.yml @@ -22,3 +22,6 @@ jobs: - name: Test mshell-go run: cd tests && ./test.sh + + - name: Run awk example tests + run: cd examples/awk && ./test.sh diff --git a/README.md b/README.md index a34db84..2a09143 100644 --- a/README.md +++ b/README.md @@ -118,11 +118,11 @@ wt ((toFloat) map sum str wl) each # 19. Add up all fields in all lines and print the sum # { for (i = 1; i <= NF; i = i + 1) sum = sum + $i } # END { print sum } -wt ((toFloat) map sum) sum wl +wt ((toFloat) map sum) sum str wl # 20. Print every line after replacing each field by its absolute value # { for (i = 1; i <= NF; i = i + 1) $i = ($i < 0) ? -$i : $i; print } -wt ((toFloat abs) map wjoin wl) each +wt ((toFloat abs str) map wjoin wl) each ``` @@ -139,10 +139,11 @@ wt ((toFloat abs) map wjoin wl) each | Objective | `sh` | `mshell` | |-----------|-----|----------| -| Print the number of files in the current directory | `ls \| wc -l` | `* glob len wl` | +| Print the number of files in the current directory | `ls \| wc -l` | `"*" glob len wl` | | `find`/`xargs` | `find . -t x -name '*.sh' -print0 \| xargs -0 mycommand` | `[mycommand [find . -t x -name "*.sh"]]o;` | # TODO -- Floating point numbers +- Type checking +- Improved error messages - Dictionaries diff --git a/examples/1.msh b/examples/1.msh deleted file mode 100644 index 82e4ef6..0000000 --- a/examples/1.msh +++ /dev/null @@ -1,23 +0,0 @@ -#!/usr/bin/env mshell - -# TODO: something like 'stoponerror' for 'set -e' -( - [echo "Starting at " [date]o; +]; - "/home/azureuser/deltek" @dir - [(dir! isdir not) ("Directory not found: " dir! + wl 1 exit)] if - dir! "/" [date "+%Y-%m-%d"].; @outfile - - # Add minutes and hours if file already exists - [(outfile! isfile) (dir! "/" [date "+%Y-%m-%d %H%M%S"].; @outfile)] if - - [~/.local/bin/ccllc-deltek projDump] /tmp/deltek.tsv > ; - - "Dump finished. Cleaning and writing to Excel." wl - - [python3 dir! "/" clean.py + +] /tmp/deltek.tsv < tmp > ? - [(not) ("Error cleaning file." wl 1 exit)] if - [mv tmp /tmp/deltek.xlsx]; - - [HOME env "/.local/bin/xlwrite" + -e block A1 /tmp/deltek.tsv outfile!] - -) "/tmp/deltek_project.log" > diff --git a/examples/1.sh b/examples/1.sh deleted file mode 100644 index d0e4c05..0000000 --- a/examples/1.sh +++ /dev/null @@ -1,38 +0,0 @@ - -#!/bin/bash -set -e - -# Redirect all output to a log file. -exec 1> >(tee /tmp/deltek_project.log) 2>&1 - -printf 'Starting at %s\n' "$(date)" - -# Absolute paths here because this is in a cron job that doesn't have my full PATH. -DIR='/home/azureuser/deltek' - -if ! test -d "$DIR"; then - echo "Directory $DIR does not exist" - exit 1 -fi -OUTFILE="$DIR"/"$(date +%Y-%m-%d)".xlsx - -if test -f "$OUTFILE"; then - OUTFILE="$DIR"/"$(date '+%Y-%m-%d %H%M%S')".xlsx -fi - -"$HOME"/.local/bin/ccllc-deltek projDump > /tmp/deltek.tsv - -printf 'Dump finished. Cleaning and writing to Excel.\n' -# python3 "$DIR"/to_html.py < /tmp/deltek.tsv > "$DIR"/memos.html -python3 "$DIR"/clean.py < /tmp/deltek.tsv > tmp && mv tmp /tmp/deltek.tsv - -"$HOME"/.local/bin/xlwrite -e block A1 /tmp/deltek.tsv "$OUTFILE" -# pandoc -o "$DIR"/memos.docx "$DIR"/memos.html - -python3 "$DIR"/marketing.py | "$HOME"/.local/bin/xlwrite -c -w "Marketing" block A1 - "$OUTFILE" - -printf 'Finished writing to "%s" at %s.\n' "$OUTFILE" "$(date)" - -"$HOME"/.local/bin/CCLLCParser --blob-rename --subject 'Deltek Project Data Dump' upload "$OUTFILE" - -rm "$OUTFILE" diff --git a/examples/awk.msh b/examples/awk.msh deleted file mode 100644 index 0ff27b2..0000000 --- a/examples/awk.msh +++ /dev/null @@ -1,101 +0,0 @@ -#!/usr/bin/env mshell - -# 1. Print the total nubmer of input lines: -# END { print NR } -.. len wl - -# 2. Print the 10th input line: -# NR == 10 -.. :10: wl - -# 3. Print the last field of every input line: -# { print $NF } -wt (:-1: wl) each - -# 4. Print the last field of the last input line: -# { field = $NF } -# END { print field } -wt :-1: :-1: wl - -# 5. Print every input line with more than four fields -# NF > 4 -.. (wsplit len 4 >) filter (wl) each - -# 6. Print every input line in which the last field is more than 4 -# $NF > 4 -.. (wsplit :-1: toFloat 4 >) filter (wl) each - -# 7. Print the total number of fields in all input lines -# { nf = nf + $NF } -# END { print nf } -.. (wsplit len) map sum wl - -# 8. Print the total number of lines that contain 'Beth' -# /Beth/ { nlines = nlines + 1 } -# END { print nlines } -.. ("Beth" in) filter len wl - -# 9. Print the largest first field and the line that contains it (assumes some $1 is positive): -# $1 > max { max = $1; line = $0 } -# END { print max, line } --99999999 max! "" max-line! -.. -( - dup line! # Store line - wsplit :0: toFloat dup first-item! # Store first item - [(@max >) (@first-item max! @line max-line!)] if -) each -@max str w " " w @max-line wl - - -# 10. Print every line that has at least one field -# NF > 0 -.. (wsplit len 0 >) filter (wl) each - -# 11. Print every line longer than 80 characters -# length($0) > 80 -stdin lines (len 80 >) filter (wl) each - -# 12. Print the number of fields in every line followed by the line itself -# { print NF, $0 } -stdin lines (dup " " split len w " " w wl) each - -# 13. Print the first two fields in opposite order, of every line -# { print $2, $1 } -stdin lines (" " split :1: :2: w w) each - -# 14. Exchange the first two fields of every line and then print the line -# { temp = $1; $1 = $2; $2 = temp; print } -# Need a way to write value into an index. -# stdin lines (dup " " split :1: :2: swap w w) each - -# 15. Print every line with the first field replaced by the line number -# { $1 = NR; print } -# Need a way to write value into an index. -# stdin lines (dup w 1 w w) each - -# 16. Print every line after erasing the second field -# { $2 = ""; print } -.. (ws split 1 del " " join wl) each - -# 17. Print in reverse order the fields of every line -# { for (i = NF; i > 0; i = i - 1) printf "%s ", $i -# printf "\n" -# } -stdin lines (" " split reverse " " join wl) each - -# 18. Print the sums of the fields of every line -# { sum = 0 -# for (i = 1; i <= NF; i = i + 1) sum = sum + $i -# print sum -# } -=> (" " split (toFloat) map sum wl) each - -# 19. Add up all fields in all lines and print the sum -# { for (i = 1; i <= NF; i = i + 1) sum = sum + $i } -# END { print sum } -stdin lines (" " split (toFloat) map sum) map sum wl - -# 20. Print every line after replacing each field by its absolute value -# { for (i = 1; i <= NF; i = i + 1) $i = ($i < 0) ? -$i : $i; print } -stdin lines (" " split (toFloat abs) map " " join wl) each diff --git a/examples/awk/19.awk b/examples/awk/19.awk new file mode 100644 index 0000000..7e68d5d --- /dev/null +++ b/examples/awk/19.awk @@ -0,0 +1,2 @@ +{ for (i = 1; i <= NF; i = i + 1) sum = sum + $i } +END { print sum } diff --git a/examples/awk/19.data b/examples/awk/19.data new file mode 100644 index 0000000..50ecbb5 --- /dev/null +++ b/examples/awk/19.data @@ -0,0 +1,3 @@ +1 2 3 +4 5 6 +7 8 9 diff --git a/examples/awk/19.msh b/examples/awk/19.msh new file mode 100644 index 0000000..64146de --- /dev/null +++ b/examples/awk/19.msh @@ -0,0 +1 @@ +wt ((toFloat) map sum) map sum str wl diff --git a/examples/awk/2.data b/examples/awk/2.data new file mode 100644 index 0000000..0ff3bbb --- /dev/null +++ b/examples/awk/2.data @@ -0,0 +1,20 @@ +1 +2 +3 +4 +5 +6 +7 +8 +9 +10 +11 +12 +13 +14 +15 +16 +17 +18 +19 +20 diff --git a/examples/awk/20.awk b/examples/awk/20.awk new file mode 100644 index 0000000..4781dd0 --- /dev/null +++ b/examples/awk/20.awk @@ -0,0 +1 @@ +{ for (i = 1; i <= NF; i = i + 1) $i = ($i < 0) ? -$i : $i; print } diff --git a/examples/awk/20.data b/examples/awk/20.data new file mode 100644 index 0000000..d8426f7 --- /dev/null +++ b/examples/awk/20.data @@ -0,0 +1,3 @@ +1 -2 3 +-4 5 -6 +7 -8 9 diff --git a/examples/awk/20.msh b/examples/awk/20.msh new file mode 100644 index 0000000..0aba540 --- /dev/null +++ b/examples/awk/20.msh @@ -0,0 +1 @@ +wt ((toFloat abs str) map wjoin wl) each diff --git a/examples/awk/test.sh b/examples/awk/test.sh index 1f5d707..a59769d 100755 --- a/examples/awk/test.sh +++ b/examples/awk/test.sh @@ -21,14 +21,7 @@ data_test() { FAIL=0 emp_test 1 - -if diff <(seq 1 20 | awk -f '2.awk' ) <(seq 1 20 | mshell 2.msh); then - printf "2. pass\n" -else - printf "2. fail\n" - FAIL=1 -fi - +data_test 2 emp_test 3 emp_test 4 data_test 5 @@ -45,5 +38,7 @@ emp_test 15 emp_test 16 emp_test 17 data_test 18 +data_test 19 +data_test 20 exit "$FAIL" diff --git a/lib/std.msh b/lib/std.msh index fc1d67c..4f9a8c7 100644 --- a/lib/std.msh +++ b/lib/std.msh @@ -110,3 +110,8 @@ def reverse ) loop nip # Drop original list end + +# abs (int|float -- int|float) +def abs + [(dup 0 <) (-1 *)] if +end diff --git a/mshell/Evaluator.go b/mshell/Evaluator.go index f1f2927..43e2666 100644 --- a/mshell/Evaluator.go +++ b/mshell/Evaluator.go @@ -719,6 +719,75 @@ MainLoop: } stack.Push(&MShellBool{strings.Contains(totalStringText, substringText)}) + } else if t.Lexeme == "/" { + obj1, err := stack.Pop() + if err != nil { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot do '/' operation on an empty stack.\n", t.Line, t.Column)) + } + + obj2, err := stack.Pop() + if err != nil { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot do '/' operation on a stack with only one item.\n", t.Line, t.Column)) + } + + if !obj1.IsNumeric() || !obj2.IsNumeric() { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot divide a %s and a %s.\n", t.Line, t.Column, obj2.TypeName(), obj1.TypeName())) + } + + switch obj1.(type) { + case *MShellInt: + if obj1.(*MShellInt).Value == 0 { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot divide by zero.\n", t.Line, t.Column)) + } + switch obj2.(type) { + case *MShellInt: + stack.Push(&MShellInt{obj2.(*MShellInt).Value / obj1.(*MShellInt).Value}) + case *MShellFloat: + stack.Push(&MShellFloat{float64(obj2.(*MShellFloat).Value) / float64(obj1.(*MShellInt).Value)}) + } + case *MShellFloat: + if obj1.(*MShellFloat).Value == 0 { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot divide by zero.\n", t.Line, t.Column)) + } + + switch obj2.(type) { + case *MShellInt: + stack.Push(&MShellFloat{float64(obj2.(*MShellInt).Value) / obj1.(*MShellFloat).Value}) + case *MShellFloat: + stack.Push(&MShellFloat{obj2.(*MShellFloat).Value / obj1.(*MShellFloat).Value}) + } + } + } else if t.Lexeme == "*" { + obj1, err := stack.Pop() + if err != nil { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot do '*' operation on an empty stack.\n", t.Line, t.Column)) + } + + obj2, err := stack.Pop() + if err != nil { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot do '*' operation on a stack with only one item.\n", t.Line, t.Column)) + } + + if !obj1.IsNumeric() || !obj2.IsNumeric() { + return FailWithMessage(fmt.Sprintf("%d:%d: Cannot multiply a %s and a %s. If you are looking for wildcard glob, you want `\"*\" glob`.\n", t.Line, t.Column, obj2.TypeName(), obj1.TypeName())) + } + + switch obj1.(type) { + case *MShellInt: + switch obj2.(type) { + case *MShellInt: + stack.Push(&MShellInt{obj2.(*MShellInt).Value * obj1.(*MShellInt).Value}) + case *MShellFloat: + stack.Push(&MShellFloat{obj2.(*MShellFloat).Value * float64(obj1.(*MShellInt).Value)}) + } + case *MShellFloat: + switch obj2.(type) { + case *MShellInt: + stack.Push(&MShellFloat{float64(obj2.(*MShellInt).Value) * float64(obj1.(*MShellFloat).Value)}) + case *MShellFloat: + stack.Push(&MShellFloat{obj2.(*MShellFloat).Value * obj1.(*MShellFloat).Value}) + } + } } else if t.Lexeme == "toFloat" { obj, err := stack.Pop() if err != nil { diff --git a/tests/arithmetic.msh b/tests/arithmetic.msh new file mode 100644 index 0000000..671bf9b --- /dev/null +++ b/tests/arithmetic.msh @@ -0,0 +1,8 @@ +2 3 * str wl +1.5 2.5 * str wl +5 2 / str wl +5.0 2.0 / str wl +5 2.0 / str wl +5.0 2 / str wl + +-3 abs str wl diff --git a/tests/arithmetic.msh.stdout b/tests/arithmetic.msh.stdout new file mode 100644 index 0000000..adbbc53 --- /dev/null +++ b/tests/arithmetic.msh.stdout @@ -0,0 +1,7 @@ +6 +3.75 +2 +2.5 +2.5 +2.5 +3