From 98bf051e7a25147cf27b322752251c927c613a5a Mon Sep 17 00:00:00 2001 From: bootchk Date: Mon, 4 Dec 2023 11:23:01 -0500 Subject: [PATCH] Fix #10044 more natural binding of PDB return values Allow (script-fu-use-v3) in script, or in SF Console. Definitive description is in script-fu/docs/using-v3-binding.md Makes SF interpret v3 of SF dialect. - marshals single return value from PDB without wrapping in list - marshals boolean return value from PDB as #t #f instead of integers - marshals boolean to PDB from #t and #f or TRUE and FALSE - marshals void return from PDB as () instead of (#t), but that is moot. The version of SF dialect is distinct from the version of the PDB API. Dialect v3 is opt-in: the initial dialect of all SF tools remains v2. Commit also allows #t,#f for defaults of SF-TOGGLE instead of TRUE, FALSE but that is an independent enhancement (but closely related.) Affects interpreter state of the current process. Not an attribute per se of a plugin. While in this state, a plugin should not call PDB procedures which are themselves v2 script plugins, or a few utility scripts in script-fu-util.scm, but that is rarely needed. Does not remove symbols TRUE and FALSE from dialect. A script can also call (script-fu-use-v2) to revert. That is also discouraged but useful e.g. for testing. --- plug-ins/script-fu/docs/using-v3-binding.md | 282 ++++++++++++++++++ plug-ins/script-fu/libscriptfu/meson.build | 1 + .../libscriptfu/scheme-marshal-return.c | 81 +++-- .../script-fu/libscriptfu/scheme-wrapper.c | 62 +++- .../script-fu/libscriptfu/script-fu-arg.c | 14 +- .../libscriptfu/script-fu-register.c | 25 +- .../libscriptfu/script-fu-run-func.c | 10 + .../script-fu/libscriptfu/script-fu-version.c | 72 +++++ .../script-fu/libscriptfu/script-fu-version.h | 27 ++ plug-ins/script-fu/scripts/meson.build | 1 + plug-ins/script-fu/scripts/test-sphere-v3.scm | 44 +-- plug-ins/script-fu/scripts/test-v3.scm | 78 +++++ 12 files changed, 635 insertions(+), 62 deletions(-) create mode 100644 plug-ins/script-fu/docs/using-v3-binding.md create mode 100644 plug-ins/script-fu/libscriptfu/script-fu-version.c create mode 100644 plug-ins/script-fu/libscriptfu/script-fu-version.h create mode 100644 plug-ins/script-fu/scripts/test-v3.scm diff --git a/plug-ins/script-fu/docs/using-v3-binding.md b/plug-ins/script-fu/docs/using-v3-binding.md new file mode 100644 index 0000000000..6ee08336ff --- /dev/null +++ b/plug-ins/script-fu/docs/using-v3-binding.md @@ -0,0 +1,282 @@ +# The version 3 dialect of the ScriptFu language + +## About + +This describes a new dialect of ScriptFu, +used when a script calls: (script-fu-use-v3). +The new dialect is more like Scheme and makes scripts shorter. +The dialect only affects calls to the PDB. + +The audience is script authors and other developers. + +This describes the new dialect and how to port old scripts to the new dialect. + + +## Quick Start + +A script that calls: +``` +(script-fu-use-v3) +``` +binds to certain PDB calls differently: + +1. PDB procedures that return single values return just that single value, +*not wrapped in a list.* +Formerly, every PDB call returned a list (possibly nesting more lists.) + +2. You can use #t and #f as arguments to PDB calls taking a boolean. + +3. PDB calls returning a boolean return #t or #f, not TRUE or FALSE (1 or 0.) + +## Script-Fu Console + +The Script-Fu Console always starts in the v2 dialect. + +You can call *script-fu-use-v3* in the console. +Then, the console interprets the v3 dialect. +It continues to interpret the v3 dialect in that session, +until you call *script-fu-use-v2.* + +## Where to call *script-fu-use-v3* in scripts + +Call *script-fu-use-v3* early. +This sets the dialect version for the remaining interpretation of the script. +Call *script-fu-use-v3* at the beginning of the run function. + +!!! Do not call *script-fu-use-v3* at the top level of a script. +This has no effect, since it is only executed in the query phase. +The interpreter starts at the run function during the run phase. + +*The interpreter always starts interpreting each script in the v2 dialect. +This is true even for extension-script-fu, the long-running interpreter.* +There is no need to call *script-fu-use-v2* before returning from a script, +to ensure that the next script is interpreted in v2 dialect. + +Example: + +``` +(define (script-fu-testv3 img drawables ) + (script-fu-use-v3) ; <<< + (let* ( + ... +``` +### Technically speaking + +The dialect version has "execution scope" versus "lexical scope." +Setting the dialect version is effective even for +other functions defined in the same script but lexically +outside the function where the dialect is set. + + +## Don't call v2 scripts from v3 scripts + +When using the v3 dialect, +you can't call plugin scripts or other library scripts that depend on the v2 dialect. +And vice versa. +(When a script calls a PDB procedure that is a script, +a new interpreter process is *NOT* started.) + +For example, a new plugin script should not call the PDB procedure +script-fu-add-bevel because it is implemented in ScriptFu Scheme +and for example has: + +``` +(width (car (gimp-drawable-get-width pic-layer))) +``` +which is v2 dialect and would throw an error such as: +``` +Error: car requires a pair. +``` + +*It is rare that a script calls another plugin script.* +A script usually calls the PDB, +but rarely calls a plugin script of the PDB. + +There are very few, obscure library scripts that call the PDB using v2 dialect. +These are in scripts/script-fu-util.scm. + +## Pure Scheme is unaffected + +The dialect only affects calls the to PDB. + +This means you can usually call most library scripts when using v3, +since most library scripts are pure Scheme, +that is, with no calls to the GIMP PDB. + +## TRUE and FALSE + +TRUE and FALSE are still in v3 dialect and are still numbers 1 and 0. +But we recommend not using them. + +You can still pass them as arguments to PDB calls taking a boolean, +and they are still converted to the C notion of boolean truth. + +FALSE which is 0 is truthy in Scheme!!! +It converts to the C notion of false only in a call the the PDB. +In the ScriptFu Console: +``` +>(equal? FALSE #t) +#t +``` + +TRUE and FALSE symbols may become obsolete in the future. + +## Plans for the future + +This dialect is shorter and more natural for Scheme programmers. + +The long-term goal is for the v3 dialect to be the only dialect of ScriptFu. +For the short term, for backward compatibility, +the default dialect of ScriptFu is the v2 dialect. + +You should write any new scripts in the v3 dialect, +and call *script-fu-use-v3*. + +You should plan on porting existing scripts to the v3 dialect, +since eventually ScriptFu might not support v2 dialect. + +## Example conversions from v2 to v3 + +### A call to a PDB procedure returning a single value + +``` +(set! (width (car (gimp-drawable-get-width pic-layer)))) +``` +*must* become +``` +(set! (width (gimp-drawable-get-width pic-layer))) +``` +The PDB call returns a single integer, +so no *car* is needed. + +### A call to a PDB procedure returning boolean +``` +(if (= (gimp-image-is-rgb image) TRUE) ... +``` +*must* become: +``` +(if (gimp-image-is-rgb image) ... +``` +The PDB procedure returns a boolean, +which is bound to #t or #f, +the usual symbols for Scheme truth. + +### A call to a PDB procedure taking a boolean + +``` +(gimp-context-set-antialias TRUE) +``` +*should* become +``` +(gimp-context-set-antialias #t) +``` +This doesn't *require* conversion because TRUE is 1 and is truthy in Scheme. + +!!! But FALSE is 0 and 0 is truthy in Scheme +``` +(gimp-context-set-antialias FALSE) +``` +This *should* be converted, for clarity, but doesn't *require* conversion. +For now, ScriptFu still binds Scheme 0 to C 0. +So it still does what the script intends. + +### A call to a PDB procedure returning a list + +``` +(set! brushes (car (gimp-brushes-get-list))) +``` +*must* become: +``` +(set! brushes (gimp-brushes-get-list)) +``` +Formerly, the PDB procedure returned a list wrapped in a list, +i.e. ((...)) +In the v3 dialect, it returns just a list, i.e. (...) +which is a single value, a single container. + +### A call to a PDB procedure returning a list, getting the first element + +``` +(set! first-brush (caar (gimp-brushes-get-list))) +``` +Gets the first brush in GIMP. +This *must* become: +``` +(set! first-brush (car (gimp-brushes-get-list))) +``` +The call to *caar* is consecutive calls to *car*, +and you must eliminate the first call to *car*. + + +## Knowing what constructs need conversion + +You *should* (but are not required to) +eliminate all uses of symbols TRUE and FALSE from a script +using v3 dialect. + +You should eliminate many, *but not all*, uses of the *car* function wrapping a call to the PDB, +where the PDB procedure returns a single value. +There are several hundred such PDB procedures in the PDB. +Examine the signature of a PDB procedure using the PDB Browser. + +For example, gimp-brush-get-angle: +``` +Return Values + angle gdouble .... +Additional Information +``` +This returns a single value so no need to use car. + +For example, gimp-brushes-get-list: +``` +Return Values + brush-list GStrv +Additional Information +``` +This also returns a single value. +The single value is a list i.e. container. +In the v2 dialect, this returned a list wrapped in a list, +for example (("foo" "bar")). + +See "Example conversions from v2 to v3" + +### Does not require changes to calls to PDB procedures returning void + +You should not need to change scripts on calls to PDB procedures returning C void because a script should not be examining the result. + +Some scripts might examine the result of a call to a void PDB procedure, thinking that (#t) is the success of the call, +but that is a misconception and should be fixed. + +Calls to PDB procedures that return C void return (#t) in v2 and the empty list (), sometimes called nil, in v3 dialect. + +## Details of the implementation + +There are new functions in the dialect: +``` +script-fu-use-v3 +script-fu-use-v2 +``` +These functions have side-effects on the state of the interpreter. +They always return the empty list (). + +The effect is immediate. +The interpreter interprets a dialect from then on, +until the script returns, +or until the script changes the dialect again. + +A call to *script-fu-use-v3* sets a flag in the state of the interpreter. +When the flag is set, the interpreter binds arguments to PDB calls slightly differently, as described. +Binding of args to PDB calls is done at run time. + +Similarly, a call to *script-fu-use-v2* clears the flag +and restores the interpreter state to binding using the v2 dialect. + +When in the v3 state, +any PDB call constructs using v2 binding will yield errors, +and vice versa. + +Note that the difference in interpretation is only in binding, but both to and from the PDB: + +1. Single return values *from* the PDB are not wrapped in lists. +2. Boolean return values *from* the PDB are bound to #t and #f. +3. Boolean values *to* the PDB can be #t and #f. diff --git a/plug-ins/script-fu/libscriptfu/meson.build b/plug-ins/script-fu/libscriptfu/meson.build index 532becb886..0a71a9f438 100644 --- a/plug-ins/script-fu/libscriptfu/meson.build +++ b/plug-ins/script-fu/libscriptfu/meson.build @@ -21,6 +21,7 @@ libscriptfu_sources = [ 'script-fu-dialog.c', 'script-fu-run-func.c', 'script-fu-command.c', + 'script-fu-version.c', 'script-fu-widgets-custom.c', 'script-fu-color.c', 'script-fu-resource.c', diff --git a/plug-ins/script-fu/libscriptfu/scheme-marshal-return.c b/plug-ins/script-fu/libscriptfu/scheme-marshal-return.c index ed307caa40..59d431b1aa 100644 --- a/plug-ins/script-fu/libscriptfu/scheme-marshal-return.c +++ b/plug-ins/script-fu/libscriptfu/scheme-marshal-return.c @@ -19,6 +19,7 @@ #include "libgimp/gimp.h" #include "tinyscheme/scheme-private.h" #include "script-fu-errors.h" +#include "script-fu-version.h" #include "scheme-marshal.h" #include "scheme-marshal-return.h" @@ -164,8 +165,11 @@ marshal_PDB_return (scheme *sc, * * Returns a scheme "pointer" type referencing the scheme return value. * - * The return value is a list. - * FUTURE: value is either a single value or a list. + * The return value depends on the SF dialect in use (script-fu-use-v3) + * v2: return value is a list. + * v3: value is either a single value for PDB procs returning solitary value, + * or an empty list for void PDB procs, + * or a list for PDB procs returning many values. * * Same error return as marshal_returned_PDB_values. */ @@ -193,22 +197,36 @@ marshal_PDB_return_by_arity (scheme *sc, if (return_arity == 0) { /* PDB procedure returns void. - * Every scheme function must return a value. - * Return (#t) - * FUTURE: return just sc->T, no reason to wrap it. - * result = sc->T; + * But every scheme function must return a value. + * What we return is moot: a caller should not use result of a void PDB procedure. + * This result is NOT an error status. */ - g_debug ("void PDB proc returns (#t)"); - result = sc->vptr->cons (sc, sc->T, sc->NIL); + if (is_interpret_v3_dialect ()) + { + /* Marshal to `() satisfying (null? ) predicate. + * Note is truthy in Scheme, satisfies (if ) + */ + result = sc->NIL; + } + else + { + /* v2 void PDB proc return marshals to (#t) */ + result = sc->vptr->cons (sc, sc->T, sc->NIL); + } } else if (return_arity == 1) { - /* Unary result. - * Return a list wrapping the result. - * FUTURE: return just unwrapped result (which can itself be a list.) - * i.e. just call marshal_returned_PDB_value (singular) - */ - result = marshal_returned_PDB_values (sc, values, &marshalling_error); + if (is_interpret_v3_dialect ()) + { + /* Marshal to single value not wrapped in list. */ + /* The value is second in the GVA, beyond the PDB status value. */ + result = marshal_returned_PDB_value (sc, gimp_value_array_index (values, 1), 2, &marshalling_error); + } + else + { + /* v2 marshal to list of many values. */ + result = marshal_returned_PDB_values (sc, values, &marshalling_error); + } if (marshalling_error != NULL) { /* Propagate error. */ @@ -217,9 +235,7 @@ marshal_PDB_return_by_arity (scheme *sc, } else /* >1 */ { - /* Many result values. - * Return a list wrapping the results. Similar to Python tuple return. - */ + /* Marshal to a list wrapping the results. Similar to Python tuple return.*/ result = marshal_returned_PDB_values (sc, values, &marshalling_error); if (marshalling_error != NULL) { @@ -229,11 +245,8 @@ marshal_PDB_return_by_arity (scheme *sc, } g_assert ( (result == NULL && *error != NULL) || (result != NULL && *error == NULL)); - /* result is: (#t) or sc->NIL i.e. empty list or a non-empty list. */ - /* FUTURE result is: #t or an atom or a vector - * or empty list or a non-empty list. - * A non-empty list is either a single result that itself is a list - * or a list wrapping a multiple result. + /* result is Scheme pointer to a Scheme data structure + * that depends on the dialect being interpreted (script-fu-use-v3) */ return result; } @@ -252,12 +265,8 @@ marshal_PDB_return_by_arity (scheme *sc, * The list can be non-homogenous (elements of different scheme types.) * * The returned list may be empty or have only a single element. - * FUTURE: - * When a PDB procedure returns a single value (which can be a container) - * do not wrap it in a list. - * It will be an error to call this function - * for PDB procedures that return a single value or return void. - * IOW, for PDB procedures of return arity < 2. + * In particular, when v2 dialect is in use, and the called PDB procedure + * returns a solitary value. */ static pointer marshal_returned_PDB_values (scheme *sc, @@ -343,8 +352,9 @@ marshal_returned_PDB_values (scheme *sc, * Currently, does not return atoms of scheme type byte or char * (no PDB procedure returns those types.) * - * !!! Returns a scheme number (0 or 1) for C type boolean. - * FUTURE: return atoms #f and #t. + * !!! For C type boolean, returned scheme type depends on dialect version: + * - v2 returns a scheme integer (0 or 1) + * - v3 returns atom #f or #t. */ static pointer marshal_returned_PDB_value (scheme *sc, @@ -438,7 +448,16 @@ marshal_returned_PDB_value (scheme *sc, else if (G_VALUE_HOLDS_BOOLEAN (value)) { gboolean v = g_value_get_boolean (value); - result = sc->vptr->mk_integer (sc, v); + if (is_interpret_v3_dialect ()) + { + /* Marshal to Scheme #t and #f */ + result = v ? sc->T : sc->F; + } + else + { + /* v2 marshal to integer 0 or 1, same as TRUE FALSE symbols. C idiom */ + result = sc->vptr->mk_integer (sc, v); + } } else if (G_VALUE_HOLDS_STRING (value)) { diff --git a/plug-ins/script-fu/libscriptfu/scheme-wrapper.c b/plug-ins/script-fu/libscriptfu/scheme-wrapper.c index ca77484686..6146c82f9d 100644 --- a/plug-ins/script-fu/libscriptfu/scheme-wrapper.c +++ b/plug-ins/script-fu/libscriptfu/scheme-wrapper.c @@ -43,6 +43,7 @@ #include "script-fu-scripts.h" #include "script-fu-errors.h" #include "script-fu-compat.h" +#include "script-fu-version.h" #include "scheme-wrapper.h" #include "scheme-marshal.h" @@ -78,6 +79,10 @@ static pointer script_fu_register_call_filter (scheme *sc, pointer a); static pointer script_fu_menu_register_call (scheme *sc, pointer a); +static pointer script_fu_use_v3_call (scheme *sc, + pointer a); +static pointer script_fu_use_v2_call (scheme *sc, + pointer a); static pointer script_fu_quit_call (scheme *sc, pointer a); static pointer script_fu_nil_call (scheme *sc, @@ -553,6 +558,7 @@ ts_init_procedures (scheme *sc, ts_define_procedure (sc, "load-extension", scm_load_ext); #endif + /* Define special functions used in scripts. */ if (register_scripts) { ts_define_procedure (sc, "script-fu-register", script_fu_register_call); @@ -566,18 +572,25 @@ ts_define_procedure (sc, "load-extension", scm_load_ext); ts_define_procedure (sc, "script-fu-menu-register", script_fu_nil_call); } + ts_define_procedure (sc, "script-fu-use-v3", script_fu_use_v3_call); + ts_define_procedure (sc, "script-fu-use-v2", script_fu_use_v2_call); ts_define_procedure (sc, "script-fu-quit", script_fu_quit_call); + /* Define wrapper functions, not used in scripts. + * FUTURE: eliminate all but one, deprecated and permissive is obsolete. + */ ts_define_procedure (sc, "gimp-proc-db-call", script_fu_marshal_procedure_call_strict); ts_define_procedure (sc, "-gimp-proc-db-call", script_fu_marshal_procedure_call_permissive); ts_define_procedure (sc, "--gimp-proc-db-call", script_fu_marshal_procedure_call_deprecated); + /* Define each PDB procedure as a scheme func. + * Each call passes through one of the wrapper funcs. + */ proc_list = gimp_pdb_query_procedures (gimp_get_pdb (), ".*", ".*", ".*", ".*", ".*", ".*", ".*", ".*"); num_procs = proc_list ? g_strv_length (proc_list) : 0; - /* Register each procedure as a scheme func */ for (i = 0; i < num_procs; i++) { gchar *buff; @@ -854,11 +867,34 @@ script_fu_marshal_procedure_call (scheme *sc, } else if (G_VALUE_HOLDS_BOOLEAN (&value)) { - if (! sc->vptr->is_number (sc->vptr->pair_car (a))) - return script_type_error (sc, "numeric", i, proc_name); + if (sc->vptr->is_number (sc->vptr->pair_car (a))) + { + /* Bind according to C idiom: 0 is false, other numeric values true. + * This is not strict Scheme: 0 is truthy in Scheme. + * This lets FALSE still work, where FALSE is a deprecated symbol for 0. + */ + g_value_set_boolean (&value, + sc->vptr->ivalue (sc->vptr->pair_car (a))); + } else - g_value_set_boolean (&value, - sc->vptr->ivalue (sc->vptr->pair_car (a))); + { + if (is_interpret_v3_dialect ()) + { + /* Use Scheme semantics: anything but #f is true. + * This allows Scheme expressions yielding any Scheme type. + */ + /* is_false is not exported from scheme.c (but should be.) + * This is the same code: compare Scheme pointers. + */ + gboolean truth_value = ! (sc->vptr->pair_car (a) == sc->F); + g_value_set_boolean (&value, truth_value); + } + else + { + /* v2 */ + return script_type_error (sc, "numeric", i, proc_name); + } + } } else if (G_VALUE_HOLDS_STRING (&value)) { @@ -1509,6 +1545,22 @@ script_fu_menu_register_call (scheme *sc, return script_fu_add_menu (sc, a); } +static pointer +script_fu_use_v3_call (scheme *sc, + pointer a) +{ + begin_interpret_v3_dialect (); + return sc->NIL; +} + +static pointer +script_fu_use_v2_call (scheme *sc, + pointer a) +{ + begin_interpret_v2_dialect (); + return sc->NIL; +} + static pointer script_fu_quit_call (scheme *sc, pointer a) diff --git a/plug-ins/script-fu/libscriptfu/script-fu-arg.c b/plug-ins/script-fu/libscriptfu/script-fu-arg.c index b45bc9da35..8cc8c4a3ca 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-arg.c +++ b/plug-ins/script-fu/libscriptfu/script-fu-arg.c @@ -24,6 +24,7 @@ #include "script-fu-types.h" #include "script-fu-arg.h" #include "script-fu-utils.h" +#include "script-fu-version.h" /* @@ -497,8 +498,12 @@ script_fu_arg_append_repr_from_gvalue (SFArg *arg, break; case SF_TOGGLE: - g_string_append_printf (result_string, (g_value_get_boolean (gvalue) ? - "TRUE" : "FALSE")); + if (is_interpret_v3_dialect ()) + { + g_string_append (result_string, (g_value_get_boolean (gvalue) ? "#t" : "#f")); + } + else + g_string_append (result_string, (g_value_get_boolean (gvalue) ? "TRUE" : "FALSE")); break; case SF_VALUE: @@ -634,7 +639,10 @@ script_fu_arg_append_repr_from_self (SFArg *arg, break; case SF_TOGGLE: - g_string_append (result_string, arg_value->sfa_toggle ? "TRUE" : "FALSE"); + if (is_interpret_v3_dialect ()) + g_string_append (result_string, arg_value->sfa_toggle ? "#t" : "#f"); + else + g_string_append (result_string, arg_value->sfa_toggle ? "TRUE" : "FALSE"); break; case SF_VALUE: diff --git a/plug-ins/script-fu/libscriptfu/script-fu-register.c b/plug-ins/script-fu/libscriptfu/script-fu-register.c index a8c4a9ef1e..e4159a9899 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-register.c +++ b/plug-ins/script-fu/libscriptfu/script-fu-register.c @@ -194,11 +194,26 @@ script_fu_parse_default_spec (scheme *sc, break; case SF_TOGGLE: - if (!sc->vptr->is_integer (default_spec)) - return registration_error (sc, "toggle default must be an integer value"); - - arg->default_value.sfa_toggle = - (sc->vptr->ivalue (default_spec)) ? TRUE : FALSE; + /* Accept scheme boolean or int. + * This does not vary by language version, and makes language v2 more lenient. + */ + /* Note storing internally as a C int, which the widget wants. + * Elsewhere we marshal back to a Scheme data. + * + * Note that is_false is not exported from scheme.c, we compare Scheme pointers. + * + * The default value is from evaluating a Scheme expression. + * More in keeping with Scheme, should convert any value other than #f to C truth. + * Instead, convert only literal #t to C truth. + */ + if (sc->vptr->is_integer (default_spec)) + arg->default_value.sfa_toggle = (sc->vptr->ivalue (default_spec)) ? TRUE : FALSE; + else if (default_spec == sc->T) + arg->default_value.sfa_toggle = 1; + else if (default_spec == sc->F) + arg->default_value.sfa_toggle = 0; + else + return registration_error (sc, "toggle default must yield an integer, #t, or #f"); break; case SF_VALUE: diff --git a/plug-ins/script-fu/libscriptfu/script-fu-run-func.c b/plug-ins/script-fu/libscriptfu/script-fu-run-func.c index 8da79c2ce9..d097c26867 100644 --- a/plug-ins/script-fu/libscriptfu/script-fu-run-func.c +++ b/plug-ins/script-fu/libscriptfu/script-fu-run-func.c @@ -30,6 +30,7 @@ #include "script-fu-script.h" #include "script-fu-scripts.h" /* script_fu_find_script */ #include "script-fu-command.h" +#include "script-fu-version.h" #include "script-fu-run-func.h" @@ -42,6 +43,11 @@ * These return the result of interpretation, * in a GimpValueArray whose only element is a status. * !!! ScriptFu does not let authors define procedures that return values. + * + * A prior script may have called (script-fu-use-v3) to opt in to interpret v3 dialect. + * When this is long-running extension-script-fu process, + * ensure initial dialect is v2, the default. + * FUTURE: default is v3 and script must opt in to v2 dialect. */ /* run_func for a GimpImageProcedure @@ -73,6 +79,8 @@ script_fu_run_image_procedure (GimpProcedure *procedure, /* GimpImageProc ts_set_run_mode (run_mode); + begin_interpret_default_dialect (); + switch (run_mode) { case GIMP_RUN_INTERACTIVE: @@ -154,6 +162,8 @@ script_fu_run_procedure (GimpProcedure *procedure, ts_set_run_mode (run_mode); + begin_interpret_default_dialect (); + switch (run_mode) { case GIMP_RUN_INTERACTIVE: diff --git a/plug-ins/script-fu/libscriptfu/script-fu-version.c b/plug-ins/script-fu/libscriptfu/script-fu-version.c new file mode 100644 index 0000000000..66cf36e996 --- /dev/null +++ b/plug-ins/script-fu/libscriptfu/script-fu-version.c @@ -0,0 +1,72 @@ +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#include "config.h" + +#include + +/* Flag indicating version 3 binding to the PDB. + * while marshalling return values from PDB: + * - not wrap solitary values in list + * - bind c boolean to scheme truth values + * + * Set by a script calling (script-fu-use-v3). + * Cleared by a script calling (script-fu-use-v2). + * + * Initial, default state of all ScriptFu tools is interpret v2 dialect. + * + * New-style scripts call (script-fu-use-v3) at top level. + * Ends with process termination of the interpreter. + * + * Old-style scripts call in run function, but this is discouraged. + * Ends after extension-script-fu finishes interpretation of current command. + * + * Affects interpretation for the duration of the current interpreter process. + * !!! All script interpreted subsequently in the current process, + * especially in called PDB procedures that are themselves scripts, + * must use v3 binding to PDB. + * Note most old script plugins in the PDB use v2 that is affected by this flag. + * Note a very few routines in script-fu-util.scm call the PDB using v2 binding. + * So a script should not call them while this flag is set. + */ +static gboolean language_version_is_3 = FALSE; + +void +begin_interpret_v3_dialect (void) +{ + language_version_is_3 = TRUE; +} + +void +begin_interpret_v2_dialect (void) +{ + language_version_is_3 = FALSE; +} + +/* the default dialect is v2 */ +void +begin_interpret_default_dialect (void) +{ + /* the default dialect is v2 */ + begin_interpret_v2_dialect (); +} + +gboolean +is_interpret_v3_dialect (void) +{ + return language_version_is_3; +} diff --git a/plug-ins/script-fu/libscriptfu/script-fu-version.h b/plug-ins/script-fu/libscriptfu/script-fu-version.h new file mode 100644 index 0000000000..cd216732fd --- /dev/null +++ b/plug-ins/script-fu/libscriptfu/script-fu-version.h @@ -0,0 +1,27 @@ + +/* GIMP - The GNU Image Manipulation Program + * Copyright (C) 1995 Spencer Kimball and Peter Mattis + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +#ifndef __SCRIPT_FU_VERSION_H__ +#define __SCRIPT_FU_VERSION_H__ + +void begin_interpret_v3_dialect (void); +void begin_interpret_v2_dialect (void); +void begin_interpret_default_dialect (void); +gboolean is_interpret_v3_dialect (void); + +#endif /* __SCRIPT_FU_VERSION__ */ \ No newline at end of file diff --git a/plug-ins/script-fu/scripts/meson.build b/plug-ins/script-fu/scripts/meson.build index 8b0f7fc226..3a49fb9fbe 100644 --- a/plug-ins/script-fu/scripts/meson.build +++ b/plug-ins/script-fu/scripts/meson.build @@ -53,6 +53,7 @@ if not stable scripts += [ 'contactsheet.scm', 'test-sphere.scm', + 'test-v3.scm', ] endif diff --git a/plug-ins/script-fu/scripts/test-sphere-v3.scm b/plug-ins/script-fu/scripts/test-sphere-v3.scm index dea3801d94..eb7feb1dbc 100644 --- a/plug-ins/script-fu/scripts/test-sphere-v3.scm +++ b/plug-ins/script-fu/scripts/test-sphere-v3.scm @@ -9,6 +9,11 @@ ; See also test-sphere.scm, for GIMP 2, from which this is derived ; Diffs marked with ; v3 >>> +; Also modified to use script-fu-use-v3 +; I.E. binding of boolean and binding of PDB returns is changed. +; TRUE => #t in many places +; (car (...)) => (...) in many places + ; v3 >>> signature of GimpImageProcedure ; drawables is a vector @@ -37,12 +42,13 @@ unused-layer unused-channel unused-drawable) + (script-fu-use-v3) (let* ( (width (* radius 3.75)) (height (* radius 2.5)) - (img (car (gimp-image-new width height RGB))) - (drawable (car (gimp-layer-new img width height RGB-IMAGE - "Sphere Layer" 100 LAYER-MODE-NORMAL))) + (img (gimp-image-new width height RGB)) ; v3 >>> elide car + (drawable (gimp-layer-new img width height RGB-IMAGE + "Sphere Layer" 100 LAYER-MODE-NORMAL)) (radians (/ (* light *pi*) 180)) (cx (/ width 2)) (cy (/ height 2)) @@ -73,7 +79,8 @@ (if (and (or (and (>= light 45) (<= light 75)) (and (<= light 135) (>= light 105))) - (= shadow TRUE)) + ; v3 >>> conditional doesn't need (= shadow TRUE) + shadow ) (let ((shadow-w (* (* radius 2.5) (cos (+ *pi* radians)))) (shadow-h (* radius 0.5)) (shadow-x cx) @@ -82,21 +89,21 @@ (begin (set! shadow-x (+ cx shadow-w)) (set! shadow-w (- shadow-w)))) - (gimp-context-set-feather TRUE) + (gimp-context-set-feather #t) (gimp-context-set-feather-radius 7.5 7.5) (gimp-image-select-ellipse img CHANNEL-OP-REPLACE shadow-x shadow-y shadow-w shadow-h) (gimp-context-set-pattern pattern) (gimp-drawable-edit-fill drawable FILL-PATTERN))) - (gimp-context-set-feather FALSE) + (gimp-context-set-feather #f) ; v3 >>> FALSE => #f (gimp-image-select-ellipse img CHANNEL-OP-REPLACE (- cx radius) (- cy radius) (* 2 radius) (* 2 radius)) (gimp-context-set-gradient-fg-bg-rgb) (gimp-drawable-edit-gradient-fill drawable GRADIENT-RADIAL offset - FALSE 1 1 - TRUE + #f 1 1 ; v3 >>> and also supersampling enum starts at 1 now + #t light-x light-y light-end-x light-end-y) @@ -108,20 +115,21 @@ (gimp-context-set-gradient-reverse gradient-reverse) (gimp-drawable-edit-gradient-fill drawable GRADIENT-LINEAR offset - FALSE 1 1 - TRUE + #f 1 1 + #t 10 10 30 60) (gimp-selection-none img) (gimp-context-set-foreground '(0 0 0)) - (gimp-floating-sel-anchor (car (gimp-text-font img drawable - x-position y-position - multi-text - 0 TRUE - size - font))) + (gimp-floating-sel-anchor (gimp-text-font + img drawable + x-position y-position + multi-text + 0 #t + size + font)) (if (= orientation 1) (gimp-image-rotate img ROTATE-DEGREES90)) @@ -147,7 +155,7 @@ SF-ONE-OR-MORE-DRAWABLE ; v3 >>> additional argument SF-ADJUSTMENT "Radius (in pixels)" (list 100 1 5000 1 10 0 SF-SPINNER) SF-ADJUSTMENT "Lighting (degrees)" (list 45 0 360 1 10 1 SF-SLIDER) - SF-TOGGLE "Shadow" TRUE + SF-TOGGLE "Shadow" #t ; v3 >>> SF-COLOR "Background color" "white" SF-COLOR "Sphere color" "red" ; v3 >>> only declare name of default brush @@ -156,7 +164,7 @@ SF-TEXT "Multi-line text" "Hello,\nWorld!" SF-PATTERN "Pattern" "Maple Leaves" SF-GRADIENT "Gradient" "Deep Sea" - SF-TOGGLE "Gradient reverse" FALSE + SF-TOGGLE "Gradient reverse" #f ; v3 >>> SF-FONT "Font" "Agate" SF-ADJUSTMENT "Font size (pixels)" '(50 1 1000 1 10 0 1) SF-PALETTE "Palette" "Default" diff --git a/plug-ins/script-fu/scripts/test-v3.scm b/plug-ins/script-fu/scripts/test-v3.scm new file mode 100644 index 0000000000..b31f0fa897 --- /dev/null +++ b/plug-ins/script-fu/scripts/test-v3.scm @@ -0,0 +1,78 @@ +; Old style script testing v3 binding of return values from PDB + +; Modified from clothify.scm +; 1. Calls (script-fu-use-v3) very early +; 2. Uses v3 binding for PDB returns, i.e. elides many (car ()) from original + +; Expect: +; this works +; after this plugin is invoked, all other /scripts still work, when invoked by menu item + +; !!! You can't do this at top level, it affects all scripts in extension-script-fu +; (script-fu-use-v3) + +(define (script-fu-testv3 timg tdrawable bx by azimuth elevation depth) + (script-fu-use-v3) + (let* ( + (width (gimp-drawable-get-width tdrawable)) + (height (gimp-drawable-get-height tdrawable)) + (img (gimp-image-new width height RGB)) +; (layer-two (gimp-layer-new img width height RGB-IMAGE "Y Dots" 100 LAYER-MODE-MULTIPLY)) + (layer-one (gimp-layer-new img width height RGB-IMAGE "X Dots" 100 LAYER-MODE-NORMAL)) + (layer-two 0) + (bump-layer 0) + ) + + (gimp-context-push) + (gimp-context-set-defaults) + + (gimp-image-undo-disable img) + + (gimp-image-insert-layer img layer-one 0 0) + + (gimp-context-set-background '(255 255 255)) + (gimp-drawable-edit-fill layer-one FILL-BACKGROUND) + + (plug-in-noisify RUN-NONINTERACTIVE img layer-one FALSE 0.7 0.7 0.7 0.7) + + (set! layer-two (gimp-layer-copy layer-one 0)) + (gimp-layer-set-mode layer-two LAYER-MODE-MULTIPLY) + (gimp-image-insert-layer img layer-two 0 0) + + (plug-in-gauss-rle RUN-NONINTERACTIVE img layer-one bx TRUE FALSE) + (plug-in-gauss-rle RUN-NONINTERACTIVE img layer-two by FALSE TRUE) + (gimp-image-flatten img) + ; Note the inner call returns ( ) + ; We still need cadr even in v3 language + (set! bump-layer (aref (cadr (gimp-image-get-selected-layers img)) 0)) + + (plug-in-c-astretch RUN-NONINTERACTIVE img bump-layer) + (plug-in-noisify RUN-NONINTERACTIVE img bump-layer FALSE 0.2 0.2 0.2 0.2) + + (plug-in-bump-map RUN-NONINTERACTIVE img tdrawable bump-layer azimuth elevation depth 0 0 0 0 FALSE FALSE 0) + (gimp-image-delete img) + (gimp-displays-flush) + + (gimp-context-pop) + ) +) + + +(script-fu-register "script-fu-testv3" + _"Test script-fu-use-v3..." + _"Test use of script-fu-use-v3 in old scripts" + "lloyd konneker" + "" + "2023" + "RGB* GRAY*" + SF-IMAGE "Input image" 0 + SF-DRAWABLE "Input drawable" 0 + SF-ADJUSTMENT _"Blur X" '(9 3 100 1 10 0 1) + SF-ADJUSTMENT _"Blur Y" '(9 3 100 1 10 0 1) + SF-ADJUSTMENT _"Azimuth" '(135 0 360 1 10 1 0) + SF-ADJUSTMENT _"Elevation" '(45 0 90 1 10 1 0) + SF-ADJUSTMENT _"Depth" '(3 1 50 1 10 0 1) +) + +(script-fu-menu-register "script-fu-testv3" + "/Filters/Development/Script-Fu/Test")