Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add update-row! and remove-row! and relevant tests #369

Merged
merged 1 commit into from
Oct 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions env/spreadsheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
42 changes: 36 additions & 6 deletions evaldo/builtins.go
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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")
Expand Down
75 changes: 73 additions & 2 deletions evaldo/builtins_spreadsheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand All @@ -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!")
}
Expand Down Expand Up @@ -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 {
Expand Down
4 changes: 2 additions & 2 deletions tests/main.rye
Original file line number Diff line number Diff line change
Expand Up @@ -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 } {
Expand Down Expand Up @@ -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" {
Expand Down
36 changes: 36 additions & 0 deletions tests/spreadsheet_mutations.rye
Original file line number Diff line number Diff line change
@@ -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"

}

}

10 changes: 5 additions & 5 deletions tests/structures.rye
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
Loading