From 6a8a8e0c99de0712371cdac7b0478cc6dd5cd0f9 Mon Sep 17 00:00:00 2001 From: Andrew Owen Date: Fri, 4 Oct 2024 23:22:51 -0600 Subject: [PATCH] Add update-row! and remove-row! and relevant tests also clean up some tests --- env/spreadsheet.go | 12 ++++++ evaldo/builtins.go | 42 +++++++++++++++--- evaldo/builtins_spreadsheet.go | 75 ++++++++++++++++++++++++++++++++- tests/main.rye | 4 +- tests/spreadsheet_mutations.rye | 36 ++++++++++++++++ tests/structures.rye | 10 ++--- 6 files changed, 164 insertions(+), 15 deletions(-) create mode 100644 tests/spreadsheet_mutations.rye diff --git a/env/spreadsheet.go b/env/spreadsheet.go index 435da6d2..1eb7e6d9 100644 --- a/env/spreadsheet.go +++ b/env/spreadsheet.go @@ -24,6 +24,18 @@ func NewSpreadsheetRow(values []any, uplink *Spreadsheet) *SpreadsheetRow { return &nat } +func SpreadsheetRowFromDict(dict Dict, uplink *Spreadsheet) (bool, string, *SpreadsheetRow) { + row := SpreadsheetRow{make([]any, len(uplink.Cols)), uplink} + for i, v := range uplink.Cols { + if val, ok := dict.Data[v]; ok { + row.Values[i] = val + } else { + return false, v, nil + } + } + return true, "", &row +} + type Spreadsheet struct { Cols []string Rows []SpreadsheetRow diff --git a/evaldo/builtins.go b/evaldo/builtins.go index 6c610a46..55f3faa5 100644 --- a/evaldo/builtins.go +++ b/evaldo/builtins.go @@ -44,16 +44,32 @@ func MakeBuiltinError(env1 *env.ProgramState, msg string, fn string) *env.Error return env.NewError(msg + " in builtin " + fn + ".") } -func MakeArgError(env1 *env.ProgramState, N int, typ []env.Type, fn string) *env.Error { - env1.FailureFlag = true +func NameOfRyeType(t env.Type) string { + if t < 0 || int(t) >= len(env.NativeTypes) { + return "INVALID TYPE (" + strconv.FormatInt(int64(t), 10) + ")" + } + return env.NativeTypes[t] +} + +func MakeArgErrorMessage(N int, allowedTypes []env.Type, fn string) string { types := "" - for i, tt := range typ { + for i, tt := range allowedTypes { if i > 0 { types += ", " } types += env.NativeTypes[tt-1] } - return env.NewError("builtin `" + fn + "` requires argument " + strconv.Itoa(N) + " to be: " + types + ".") + return "builtin `" + fn + "` requires argument " + strconv.Itoa(N) + " to be: " + types + "." +} + +func MakeArgError(env1 *env.ProgramState, N int, typ []env.Type, fn string) *env.Error { + env1.FailureFlag = true + return env.NewError(MakeArgErrorMessage(N, typ, fn)) +} + +func MakeNeedsThawedArgError(env1 *env.ProgramState, fn string) *env.Error { + env1.FailureFlag = true + return env.NewError("builtin `" + fn + "` requires a thawed spreadsheet as the first argument") } func MakeNativeArgError(env1 *env.ProgramState, N int, knd []string, fn string) *env.Error { @@ -6799,8 +6815,22 @@ var builtins = map[string]*env.Builtin{ return MakeBuiltinError(ps, fmt.Sprintf("String has less than %d elements.", num.Value), "nth") } return *env.NewString(string(str[num.Value-1 : num.Value])) - default: - return MakeArgError(ps, 1, []env.Type{env.BlockType}, "nth") + case env.Spreadsheet: + rows := s1.GetRows() + if num.Value > int64(len(rows)) { + return MakeBuiltinError(ps, fmt.Sprintf("Spreadhseet has less than %d rows.", num.Value), "nth") + } + return rows[num.Value-1] + case *env.Spreadsheet: + rows := s1.GetRows() + if num.Value > int64(len(rows)) { + return MakeBuiltinError(ps, fmt.Sprintf("Spreadhseet has less than %d rows.", num.Value), "nth") + } + return rows[num.Value-1] + default: + return MakeArgError(ps, 1, + []env.Type{env.BlockType, env.ListType, env.StringType, env.SpreadsheetType}, + "nth") } default: return MakeArgError(ps, 2, []env.Type{env.IntegerType}, "nth") diff --git a/evaldo/builtins_spreadsheet.go b/evaldo/builtins_spreadsheet.go index 0640a7a3..2652d080 100644 --- a/evaldo/builtins_spreadsheet.go +++ b/evaldo/builtins_spreadsheet.go @@ -233,6 +233,55 @@ var Builtins_spreadsheet = map[string]*env.Builtin{ } }, }, + "update-row!": { + Argsn: 3, // Spreadsheet, index function/dict + Doc: `Update the row at the given index. If given a dict or a spreadsheet row, replace the row with that.` + + `If given a function, pass the row, its index and replace the row with the return value from the function`, + Fn: func(ps *env.ProgramState, arg0 env.Object, arg1 env.Object, arg2 env.Object, arg3 env.Object, arg4 env.Object) env.Object { + switch spr := arg0.(type) { + case *env.Spreadsheet: + switch idx := arg1.(type) { + case env.Integer: + if idx.Value < 1 || (idx.Value-1) > int64(len(spr.Rows)) { + errMsg := fmt.Sprintf("update-row! called with row index %i, but spreadsheet only has %i rows", idx.Value, len(spr.Rows)) + return makeError(ps, errMsg) + } + switch updater := arg2.(type) { + case env.Function: + CallFunctionArgs4(updater, ps, spr.Rows[idx.Value-1], idx, nil, nil, ps.Ctx) + if !ps.ReturnFlag { + return makeError(ps, "Function given to update-row! should have returned a value, but didn't") + } + if ok, err, row := RyeValueToSpreadsheetRow(spr, ps.Res); ok { + spr.Rows[idx.Value-1] = *row + return spr + } else if len(err) > 0 { + return makeError(ps, err) + } else { + return makeError(ps, fmt.Sprintf( + "Function given to update-row! should have returned a Dict or a SpreadsheetRow, but returned a %s instead", + NameOfRyeType(ps.Res.Type()), + )) + } + + default: + if ok, err, row := RyeValueToSpreadsheetRow(spr, updater); ok { + spr.Rows[idx.Value-1] = *row + return ps.Res + } else if len(err) > 0 { + return makeError(ps, err) + } + return MakeArgError(ps, 3, []env.Type{env.DictType, env.SpreadsheetRowType}, "update-row") + } + default: + return MakeArgError(ps, 2, []env.Type{env.IntegerType}, "update-row!") + } + default: + return MakeNeedsThawedArgError(ps, "update-row!") + } + + }, + }, "remove-row!": { Argsn: 2, Doc: "Remove a row from a spreadsheet by index", @@ -241,8 +290,12 @@ var Builtins_spreadsheet = map[string]*env.Builtin{ case *env.Spreadsheet: switch data1 := arg1.(type) { case env.Integer: - spr.RemoveRowByIndex(data1.Value) - return spr + if data1.Value > 0 && data1.Value <= int64(len(spr.Rows)) { + spr.RemoveRowByIndex(data1.Value - 1) + return spr + } else { + return makeError(ps, fmt.Sprintf("Spreadsheet had less then %d rows", data1.Value)) + } default: return MakeArgError(ps, 2, []env.Type{env.BlockType, env.NativeType}, "remove-row!") } @@ -899,6 +952,24 @@ var Builtins_spreadsheet = map[string]*env.Builtin{ }, } +func RyeValueToSpreadsheetRow(spr *env.Spreadsheet, obj env.Object) (bool, string, *env.SpreadsheetRow) { + switch updater := obj.(type) { + case env.Dict: + success, missing, row := env.SpreadsheetRowFromDict(updater, spr) + if !success { + return false, "update-row! given a dict that is missing value for the " + missing + " column!", nil + } else { + return true, "", row + + } + case env.SpreadsheetRow: + return true, "", &updater + default: + return false, "", nil + } + +} + func DropColumnBlock(ps *env.ProgramState, s env.Spreadsheet, names env.Block) env.Object { toDrop := make([]env.String, 0) for _, obj := range names.Series.S { diff --git a/tests/main.rye b/tests/main.rye index 9524c94a..426e096e 100644 --- a/tests/main.rye +++ b/tests/main.rye @@ -20,7 +20,7 @@ test-framework: context { } error: fn { test } { ; try { do\in root-ctx test } - try test :got \type? |= 'error \either { print join { " - OK: " mold got } } { inc! 'failed , print join { " - Error: expected error but got: " mold got } } + try test :got |type? |= 'error |either { print join { " - OK: " mold got } } { inc! 'failed , print join { " - Error: expected error but got: " mold got } } } equal: fn { test res } { @@ -83,7 +83,7 @@ generate-doc-file: fn { filename menu } { } |write* to-file filename + ".html" } -menu: { "basics" "structures" "validation" "misc" "errors" } +menu: { "basics" "structures" "validation" "misc" "spreadsheet_mutations" } switch arg { "test" { diff --git a/tests/spreadsheet_mutations.rye b/tests/spreadsheet_mutations.rye new file mode 100644 index 00000000..15c75b04 --- /dev/null +++ b/tests/spreadsheet_mutations.rye @@ -0,0 +1,36 @@ +section "spreadsheet mutations" +"Functions that mutate thawed spreadsheets" +{ + + group "add-row!" "add-row!" + { { block } } + { + equal { + spreadsheet { "title" "isbn" "price" "quantity" } + { + "Do Androids Dream of Electric Sheep?" "9781608866403" 12.99 4 + "Animal Farm" "9780151002177" 2.49 10 + "An Oxford Anthology of Shakespeare" "9780198129356" 19.99 10 + "And Then There Were None" "9780062073488" 3.99 10 + } |thaw ::cart + cart |nth 1 -> "title" } "Do Androids Dream of Electric Sheep?" + equal { cart |nth 3 -> "price" } 19.99 + + equal { cart .remove-row! 1 cart .nth 1 -> "title" } "Animal Farm" + equal { + rowdict: dict { "title" "Windowlight" "isbn" "0000000000000" "price" 0.99 "quantity" 1 } + probe rowdict + print rowdict .type? + cart .update-row! 1 rowdict + cart .nth 1 -> "isbn" + } "0000000000000" + equal { + updatefn: fn { row } { dict { "title" "I see the moon" "isbn" "0123456789012" "price" 0.99 "quantity" 1 } } + cart .update-row! 3 updatefn + cart .nth 3 -> "isbn" + } "0123456789012" + + } + +} + diff --git a/tests/structures.rye b/tests/structures.rye index 1e029633..690433a7 100644 --- a/tests/structures.rye +++ b/tests/structures.rye @@ -589,7 +589,7 @@ section "Spreadsheet related functions" equal { spreadsheet { "name" "rating" "weight" } { "Enno" 4.3 120 "Enya" 6 132 "Shizuuchi" 7.2 168 "Kunishima" 2 68 } ::spr |type? } 'spreadsheet equal { spr |length? } 4 - equal { spr |columns? |length? } 3 + equal { spr |header? |length? } 3 equal\todo { spr -> 2 -> 'name } "Enya" equal { spr .first -> "name" } "Enno" equal { spr .where-equal 'name "Enya" |length? } 1 @@ -618,8 +618,8 @@ section "Spreadsheet related functions" equal { to-spreadsheet vals { dict { "a" 1 b 2 "c" 3 } dict { "a" 4 "b" 5 } } } spreadsheet { "a" "b" "c" } { 1 2 3 4 5 _ } } - group "add-col" - mold\nowrap ?add-col! + group "add-column" + mold\nowrap ?add-column! { { block } } { equal { try { spreadsheet { "n" } [ 1 ] |add-col! 'm { x } { x } } |type? } 'error @@ -674,12 +674,12 @@ section "Spreadsheet related functions" mold\nowrap ?group-by { { block } } { - equal { spreadsheet { "name" "val" } { "a" 1 "b" 2 } |group-by 'name { } |sort-by! 'name 'asc + equal { spreadsheet { "name" "val" } { "a" 1 "b" 2 } |group-by 'name { } |order-by! 'name 'asc } spreadsheet { "name" } { "a" "b" } equal { spreadsheet { "name" "val" } { "a" 1 "b" 6 "a" 5 "b" 10 "a" 7 } |group-by 'name { 'name count 'val sum 'val min 'val max 'val avg } - |sort-by! 'name 'asc + |order-by! 'name 'asc } spreadsheet { "name" "name_count" "val_sum" "val_min" "val_max" "val_avg" } { "a" 3 13.0 1.0 7.0 4.333333333333333