ScriptFu: update devel docs re version 3 changes

This commit is contained in:
lloyd konneker 2024-10-13 14:03:08 -04:00
parent 1384bf8c28
commit ff1ad6d605
2 changed files with 355 additions and 259 deletions

View file

@ -1,14 +1,62 @@
# Guide to changes to ScriptFu v3 for script authors
*Draft, until GIMP 3 is final. FIXME: rearrange and rename the cited documents*
## About
The audience is authors of Scriptfu plugins.
This discusses how to edit v2 scripts so they will run in GIMP 3.
It also discusses enhancements to ScriptFu and its language.
This is only about changes to ScriptFu proper.
The GIMP PDB, which you can call in scripts, has also changed.
## Quickstart
A lucky few existing scripts may work in GIMP v3 without change.
### Required changes
Some changes are *required* in existing plugins:
- many PDB procedures are obsolete or renamed, or their signature changed
- the symbol SF-VALUE is obolete
Once you edit a script for these changes, the script won't work in GIMP v2.
Most scripts will require edits due to changed PDB API.
### Optional changes
Other changes to scripts are *optional*.
Such changes use improved features of ScriptFu.
In approximate order of importance:
- you can install scripts like plugins in other languages
- you can use new, specific registration functions instead of the generic script-fu-register
- scripts can use the new mult-layer selection feature of GIMP
- scripts can use a v3 dialect having more natural binding of return values from PDB calls
- the symbolic constants TRUE and FALSE are deprecated, except for SF-TOGGLE
- a script can *quit* with an error code
- a script can call *display* to print to the terminal
Enhancements to ScriptFu might not affect an existing plugin.
You need only understand those enhancements when you want to use them.
### Why these changes now?
GIMP 3 is a major version change with new features.
A major version allows breaking API,
meaning old plugins and scripts might not work.
The GIMP developers understand these changes may be onerous.
Script developers might need to maintain two different versions of their scripts.
Some users will stick with GIMP 2 for a while and some will switch to GIMP 3.
A major version is necessary to move forward.
The situation is similar to the disruption caused by the move from Python 2 to 3.
## Changed PDB API
*You must edit your scripts to conform to version 3 of the PDB API.*
The GIMP PDB (Programmers Data Base), that you can call in scripts, has changed in GIMP version 3.
That also may require you to edit scripts.
- For changes in signatures of PDB procedures,
@ -16,40 +64,13 @@ That also may require you to edit scripts.
- For added, removed, and replaced PDB procedures,
see devel-docs/GIMP3-plug-in-porting-guide/removed_functions.md
## Quickstart
Some of the changes are for the multi-layer select feature of GIMP 3.
Many PDB procedures now take a container (a Scheme vector) of drawables
instead of a single drawable.
A lucky few existing scripts may work in GIMP v3.
## SF-VALUE type of argument is obsolete
Some changes are most likely to break an existing plugin.:
- many PDB procedures are obsolete or renamed, or their signature changed
Once you edit a script for these changes, the script won't work in GIMP v2.
Other changes:
- you can use new, specific registration functions instead of the generic script-fu-register
- you can install scripts like plugins in other languages
- scripts can use the new mult-layer selection feature of GIMP
- a script can abort with an error message
- a script's settings are more fully saved
Those changes might not affect an existing plugin.
You need only understand those changes when you want to use new features of GIMP.
A word of explanation: the GIMP developers understand these changes may be onerous.
Script developers might need to maintain two different versions of their scripts.
Some users will stick with GIMP 2 for a while and some will switch to GIMP 3.
But GIMP 3 is a major version change with new features.
A clean break is necessary to move forward with improvements.
The situation is similar to the disruption caused by the move from Python 2 to 3.
## Deprecated ScriptFu constants
### SF-VALUE kind of argument is deprecated
The symbol SF-VALUE is deprecated.
You can edit v2 scripts and replace that symbol.
*You must edit v2 scripts and replace the symbol SF-VALUE.*
In v2, SF-VALUE declared a formal argument that is an unquoted, arbitrary string.
Usually, SF-VALUE was used for an integer valued argument.
@ -60,9 +81,9 @@ but the widget let you enter any text into the string.
You usually will replace it with an SF-ADJUSTMENT kind of formal argument,
where the "digits" field of the SF-ADJUSTMENT is 0,
meaning no decimal places, i.e. integer valued.
You must also add the other fields, e.g. the lower and upper limits.
You must also provide other fields, e.g. the lower and upper limits.
A script that has been edited to replace SF-VALUE with SF-ADJUSTMENT
A script that has SF-VALUE replaced with SF-ADJUSTMENT
will remain compatible with GIMP 2.
Example:
@ -83,7 +104,7 @@ used SF-VALUE to declare a formal argument that is float valued:
Here, the 1 denotes: show 1 decimal place, for example "45.0",
in the dialog widget.
#### Use SF-STRING for some use cases
### Use SF-STRING for some use cases
In v2, a SF-VALUE argument let a user enter executable Scheme code,
say "'(1 g 1)", which is a list literal,
@ -92,7 +113,7 @@ That use is no longer possible.
If you must do that, use SF_STRING to get a string,
and then your plugin can eval the string.
#### Arbitrary precision floats
### Arbitrary precision floats
In v2, a SF-VALUE argument let a user enter a float with arbitrary precision,
e.g. "0.00000001"
@ -114,204 +135,33 @@ If you actually need arbitrary precision, use SF_STRING to get a string,
and then your plugin can eval the string to get a Scheme numeric
of the maximum precision that Scheme supports.
#### Rationale
### Rationale
Formerly, a SF-VALUE argument let a user enter garbage for an argument,
which caused an error in the script.
SF-ADJUSTMENT is more user-friendly.
### FALSE and TRUE symbols deprecated
FALSE and TRUE symbols are deprecated in the ScriptFu language.
They never were in the Scheme language.
Instead, you can use the Scheme language symbols #f and #t.
In ScriptFu v2, FALSE was equivalent to 0
and TRUE was equivalent to 1.
But FALSE was not equivalent to #f.
Formerly, you could use the = operator to compare to FALSE and TRUE.
The = operator in Scheme is a numeric operator, not a logical operator.
Now you can use the eq? or eqv? operators.
In Scheme, all values are truthy except for #f.
The empty list is truthy.
The numeric 0 is truthy.
Only #f is not truthy.
A PDB procedure returning a single Boolean (a predicate)
returns a list containing one element, for example (#f) or (#t).
#### Rationale
The ScriptFu language is simpler and smaller; TRUE and FALSE duplicated concepts already in the Scheme language.
#### Example changes
Declaring a script:
SF-TOGGLE "Gradient reverse" FALSE
=>
SF-TOGGLE "Gradient reverse" #f
Calling a PDB procedure taking a boolean:
(gimp-context-set-feather TRUE)
=>
(gimp-context-set-feather #t)
Logically examining a variable for truth:
(if (= shadow TRUE) ...
=>
(if shadow ...
## v3 binding of PDB return values
TODO this needs rewrite. When first written, we anticipated
a breaking change to the binding.
Now the binding version is a choice,
settable at runtime.
Scripts can opt to use the new binding.
### In scripts, calls to PDB procedures that return boolean yield (#t) or (#f)
In ScriptFu v2, PDB procedures returning a boolean returned 1 or 0 to a script,
that is, numeric values.
Those were equal to the old TRUE and FALSE values.
Remember that a call to the PDB returns a list to the script.
So in ScriptFu v3,
a PDB procedure that returns a single boolean (a predicate function)
returns (#t) or (#f), a list containing one boolean element.
#### Rationale
#t and #f are more precise representations of boolean values.
0 and 1 are binary, but not strictly boolean.
The ScriptFu language is smaller if concepts of truth are not duplicated.
#### Example changes
Calling a PDB procedure that is a predicate function:
(if (= FALSE (car (gimp-selection-is-empty theImage))) ...
=>
(if (car (gimp-selection-is-empty theImage)) ...
Here, the call to the PDB returns a list of one element.
The "car" function returns that element.
The "if" function evaluates that element for truthy.
Note that to evaluate the result of a PDB call for truth,
you should just use as above, and not use "eq?" or "eqv?".
Such a result is always a list, and a list is truthy,
but not equivalent to #t.
In the ScriptFu console:
>(eq? #t '())
#f
>(eqv? #t '())
#f
## Use script-fu-script-abort to throw an error
The function "script-fu-script-abort" is new to ScriptFu v3.
It causes the interpreter to stop evaluating a script
and yield an error of type GimpPDBStatus.
That is, it immediately returns an error to the caller.
It is similar to the "return" statement in other languages,
but the Scheme language has no "return" statement.
The function takes an error message string.
When the caller is the GIMP app,
the GIMP app will show an error dialog
having the message string.
When the caller is another PDB procedure (a plugin or script)
the caller must check the result of a call to the PDB
and propagate the error.
ScriptFu itself always checks the result of a call to the PDB
and propagates the error,
concatenating error message strings.
The function can be used anywhere in a script,
like you would a "return" statement in other languages.
Alternatively, a script can yield #f to yield a PDB error.
See below.
#### Rationale
Formerly, scripts usually called gimp-message on errors,
without yielding an error to the caller.
It was easy for a user to overlook the error message.
An abort shows an error message that a user must acknowledge
by choosing an OK button.
#### Example
This script defines a PDB procedure that aborts:
(define (script-fu-abort)
(script-fu-script-abort "Too many drawables.")
(gimp-message "this never evaluated")
)
...
## A script can yield #f to throw an error
Here we use the word "yield" instead of the word "return".
Neither "yield" nor "return" are reserved words in the Scheme language.
A Scheme text evaluates to, or yields, the value of its last expression.
Any value other than #f, even the empty list or the list containing #f,
is truthy.
A ScriptFu plugin
(the PDB procedure that a script defines in its run func)
whose last evaluated expression is #f
will yield an error of type GimpPDBStatus.
If you don't want a ScriptFu plugin to yield an error,
it must not evaluate to #f.
Most existing plugins won't, since their last evaluated expression
is usually a call to the PDB yielding a list, which is not equivalent to #f.
*Remember that ScriptFu does not yet let you declare PDB procedures
that return values to the caller.
That is, you can only declare a void procedure, having only side effects.
So to yield #f does not mean to return a boolean to the caller.*
*Also, you can define Scheme functions internal to a script
that yield #f but that do not signify errors.
It is only the "run func" that defines a PDB procedure that,
yielding #f, yields a PDB error to the caller.*
#### Examples
(define (script-fu-always-fail)
(begin
; this will be evaluated and show a message in GIMP status bar
(gimp-message "Failing")
; since last expression, is the result, and will mean error
#f
)
)
SF-ADJUSTMENT or SF-STRING is more user-friendly.
## You can optionally install scripts like plugins in other languages
In v3 you can install ScriptFu scripts to a /plug-ins directory.
*We recommend this for new plugins.*
In v3 you can install ScriptFu scripts to a /plug-ins directory,
like plugins in other languages.
This helps isolate plugins.
You must edit the script to include a shebang in the first line:
#!/usr/bin/env gimp-script-fu-interpreter-3.0
;!# Close comment started on first line.
In v2 all ScriptFu scripts were usually installed in a /scripts directory.
If the script has translatable strings, it should also have a second line
as above, to accomodate parsing by gettext.
(Note that ScriptFu doesn't actually support multi-line comments using #!,
and this is a trick to fool gettext, which parses a script as if
it did support multi-line comments.)
In v2 all ScriptFu scripts were installed in a /scripts directory.
In v3 you may install ScriptFu scripts with a shebang
in a subdirectory of a /plug-ins directory.
@ -325,37 +175,43 @@ A script file must:
An example path to a script:
~/.config/GIMP/2.99/plug-ins/myScript/myScript.scm
~/.config/GIMP/2.99/plug-ins/script-fu-my/script-fu-my.scm
Such a script will execute in its own process.
The advantage of installing a script in the /plug-ins directory is that
such a script will execute in its own process.
If it crashes, it doesn't affect GIMP or other scripts.
In v2, all scripts in the /scripts directory are executed by the long-lived
process "extension-script-fu."
Conversely, in v2, all scripts in the /scripts directory
are executed by the long-lived process "extension-script-fu."
If one of those scripts crash, menu items implemented by ScriptFu dissappear
from the GIMP app, and you should restart GIMP.
## Changes in registration functions
It is only a convention to name scripts starting with "script-fu-".
ScriptFu defines "registration" functions, letting you declare a plugin PDB procedure.
## Using new registration functions
ScriptFu v2 has only *script-fu-register*.
It was a generic function used to declare procedures that are either:
*We recommend this.*
* generic procedures (operating without an image.)
* filters/renderers (operating on an image)
New registration functions let a plugin dialog have a new look-and-feel
and improved settings.
ScriptFu "registration" functions let you declare a plugin PDB procedure,
and how it's dialog will look.
The new registration functions are:
ScriptFu v3 has two new registration functions:
* *script-fu-register-procedure*
* *script-fu-register-filter*
The new registration functions let a plugin have a new look-and-feel
and improved settings.
Terminology: you *declare* a plugin's attributes and signature using a registration function.
You *define* a run func with a similar signature.
ScriptFu *registers* the plugin in the PDB.
ScriptFu v2 has only *script-fu-register*.
It is a generic function used to declare procedures that are either:
* generic procedures (operating without an image.)
* filters/renderers (operating on an image)
### Registration function *script-fu-register* is now deprecated.
**You should not use script-fu-register in new ScriptFu scripts.**
@ -604,4 +460,166 @@ That is a programming error in the calling procedure.
A well-written called plugin that is passed more drawables than declared
should return an error instead of processing any of the drawables.
Similarly for fewer than declared.
Similarly for fewer than declared.
## Using the v3 dialect for PDB return values
*We recommend this for new scripts.*
ScriptFu in GIMP version 3 will optionally interpret a v3 dialect.
The dialect is more natural to the Scheme language.
The dialect affects the binding of return values from calls to the PDB.
A script opts into the v3 dialect at runtime by calling script-fu-use-v3
at the beginning of the run func..
The advantage of the v3 dialect is that scripts are smaller,
with fewer calls to car.
Also, you can more naturally examine boolean values.
For more information, see the document in the GIMP source /plugins/script-fu/docs/using-v3-binding.md
## FALSE and TRUE symbols are deprecated
*We recommend not using TRUE and FALSE in new scripts, except for an SF-TOGGLE argument.*
FALSE and TRUE symbols are deprecated in the ScriptFu language.
They never were in the Scheme language.
Instead, you should use the v3 dialect
and use the Scheme language symbols #f and #t.
In ScriptFu, FALSE is 0
and TRUE is 1.
But FALSE is not equivalent to #f.
You must use the = operator to compare to FALSE and TRUE.
FALSE and TRUE are numeric.
The = operator in Scheme is a numeric operator, not a logical operator.
In Scheme, all values are truthy except for #f.
The empty list is truthy.
The numeric 0 is truthy.
Only #f is not truthy.
In the v3 dialect, a PDB procedure returning a single Boolean (a predicate)
returns one element, for example #f or #t.
In the v2 dialect, it still returns (0) or (1),
that is, numeric 0 or 1 wrapped in a list.
*You still need to compare boolean SF_TOGGLE arguments to a script with TRUE and FALSE.
They are not #t and #f*.
#### Rationale
The ScriptFu language is simpler and smaller; TRUE and FALSE duplicated concepts already in the Scheme language.
#### Example changes
Declaring a script's SF_TOGGLE arguments can still use TRUE and FALSE.
SF-TOGGLE "Gradient reverse" FALSE
Comparing a boolean argument to a script *must* remain as in v2 dialect:
(if (= should-invert TRUE)
; do invert )
Calling a PDB procedure taking a boolean:
(gimp-context-set-feather TRUE)
=>
(gimp-context-set-feather #t)
Logically examining a variable for truth:
(if (= shadow TRUE) ...
=>
(if shadow ...
## Using the quit function to abort a script
*We recommend using quit to declare errors* along with gimp-message.
The function "quit" is in Scheme.
It causes a script to exit i.e. return to the system.
The function can be used anywhere in a script,
like you would a "return" statement in other languages.
In ScriptFu v3 it causes the interpreter to stop interpreting a script
and yield an error of type GimpPDBStatus.
That is, it immediately returns an error to the GIMP app or other caller.
The GIMP app will show an error message including the phrase
"script quit with error code x."
When the caller is another PDB procedure (a plugin or script)
the caller should check the result of a call to the script
and propagate the error.
ScriptFu itself always checks the result of a call to the PDB
and propagates the error,
concatenating error message strings.
In ScriptFu v2, a call such as (quit -1) did abort the script,
but did not return an error to the caller
(usually the GIMP app)
and did not return the error code.
### A script doesn't return a value
In ScriptFu, a script returns void.
It is not possible to define a plugin script that returns useful values.
Note that script registration functions do not declare return values.
Scripts only have side effects.
The last expression in a script plugin is *not* the value of the plugin,
as it is in Scheme.
As a PDB procedure, a script can succeed or fail.
But success does not depend on the value of the last evaluated expression.
To make a script return failure, you should call the quit function.
#### Rationale
Formerly, scripts usually called gimp-message on errors,
without yielding an error to the caller.
It was easy for a user to overlook the error message.
You should still call gimp-message just before calling quit,
with an informative error message.
The numeric code that quit prints is not informative to users,
only to the script author.
#### Example
This script quits early:
(define (script-fu-my-plugin)
...
(gimp-message "Too many drawables.")
(quit -1)
(display "this never evaluated")
)
...
## Using the display function to write to the terminal
*We recommend using the display function to help debug a script or to log,
but not to prompt for user input.*
Display is a Scheme function, in the R5RS standard.
It should write a string to any open terminal (e.g stdout on Linux.)
In ScriptFu v3, it does.
You will see the output in any terminal in which the GIMP app was started
(which is optional, and not usually done on a graphical desktop.)
In ScriptFu v2, it did not write to a terminal.
Display wrote to a stream which was returned to the GIMP app, and usually discarded (unless an error occurred, and then the stream
was the prefix of the error message.)
Example:
(display "The script is here") (newline)
Note that displayln is not in ScriptFu.

View file

@ -1,11 +1,13 @@
# The version 3 dialect of the ScriptFu language
# Version 3 of the ScriptFu language
## About
This describes a new dialect of ScriptFu,
used when a script calls: (script-fu-use-v3).
This describes a new dialect of the ScriptFu language.
ScriptFu interprets the dialect after 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.
ScriptFu inteprets the old version 2 by default,
unless a script calls script-fu-use-v3.
The audience is script authors and other developers.
@ -14,13 +16,20 @@ This describes the new dialect and how to port old scripts to the new dialect.
## Quick Start
ScriptFu inteprets the old version 2 by default,
unless a script calls script-fu-use-v3.
After a call to script-fu-use-v3,
ScriptFu interprets the new dialect until the script finishes,
or until the script calls script-fu-use-v2.
A script can switch back and forth between dialects at runtime.
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,
1. PDB procedures that return single values (atoms) return just that single value,
*not wrapped in a list.*
Formerly, every PDB call returned a list (possibly nesting more lists.)
@ -39,12 +48,13 @@ 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.
This sets the dialect version for the remaining interpretation
of the script.
!!! 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.
This has no effect, since it is only executed in the query phase
of a plugin.
The interpreter starts at the run function during the run phase.
*The interpreter always starts interpreting each script in the v2 dialect.
@ -60,24 +70,26 @@ Example:
(let* (
...
```
### Technically speaking
### Scope
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.
You only need to call script-fu-use-v3 once,
not in every defined function.
## 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.
you cannot call plugin Scheme scripts or other library scripts that are in the v2 dialect.
And vice versa.
(When a script calls a PDB procedure that is a script,
(When a script calls a PDB procedure that is a Scheme 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
script-fu-add-bevel because it is a Scheme script in v2 dialect
and for example has:
```
@ -121,6 +133,30 @@ In the ScriptFu Console:
TRUE and FALSE symbols may become obsolete in the future.
## An argument of type SF-TOGGLE is FALSE or TRUE, not #f or #t
The v3 dialect does not affect the binding of arguments to a script.
So the value of an argument of type SF-TOGGLE is zero or one,
not #f or #t.
You must continue to check boolean arguments to a script like this:
```
(define script-fu-my-plugin (should-invert)
(if (= should-invert TRUE)
(
; do invert
)))
(script-fu-register-procedure "script-fu-my-plugin"
"My plugin..."
...
SF-TOGGLE "Invert?" FALSE
)
```
This may change when in the future we obsolete v2 dialect
and the symbols TRUE and FALSE.
## Plans for the future
This dialect is shorter and more natural for Scheme programmers.
@ -133,9 +169,51 @@ 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.
since eventually ScriptFu may obolete the v2 dialect.
## Example conversions from v2 to v3 dialects
### An example script using v3 dialect
```
; !!! Usually not call (script-fu-use-v3) here in global scope
(define script-fu-my-plugin (should-invert)
; the body switches to the v3 dialect
(script-fu-use-v3)
(let* (
; don't need a car, unlike v2
(angle gimp-context-get-brush-angle)))
; call PDB returning boolean
; don't need (= (car (gimp-context-get-feather)) TRUE)
(if (gimp-context-get-feather)
; do feather
)
; boolean arg to script is still C notion of truth
(if (= should-invert TRUE)
(
; do invert
))
; calling a v2 plugin, temporarily switch to v2 dialect
(script-fu-use-v2)
(script-fu-add-bevel ...)
; rest of script in v3 dialect
(script-fu-use-v3)
...
)
(script-fu-register-procedure "script-fu-my-plugin"
"My plugin..."
...
SF-TOGGLE "Invert?" FALSE
)
```
## Example conversions from v2 to v3
### A call to a PDB procedure returning a single value
@ -211,7 +289,7 @@ 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
eliminate most 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,
@ -234,7 +312,7 @@ Return Values
Additional Information
```
This also returns a single value.
The single value is a list i.e. container.
But the single value is a list i.e. container, of strings.
In the v2 dialect, this returned a list wrapped in a list,
for example (("foo" "bar")).
@ -247,7 +325,7 @@ You should not need to change scripts on calls to PDB procedures returning C voi
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.
Calls to PDB procedures that return C void return (#t) in v2 and the empty list () i.e. nil in v3 dialect.
## Details of the implementation