# Guide to changes to ScriptFu v3 for script authors

*Draft, until PIKA 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 PIKA 3.

This is only about changes to ScriptFu proper.
The PIKA PDB, which you can call in scripts, has also changed.
That also may require you to edit scripts.

  - For changes in signatures of PDB procedures,
    see devel-docs/PIKA3-plug-in-porting-guide/porting_scriptfu_scripts.md
  - For added, removed, and replaced PDB procedures,
    see devel-docs/PIKA3-plug-in-porting-guide/removed_functions.md

## Quickstart

A lucky few existing scripts may work in PIKA v3.

Some changes are most likely to break an existing plugin.:

  - SF-VALUE is obsolete
  - TRUE and FALSE are obsolete
  - 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 PIKA v2.

Other changes:

  - you can install scripts like plugins in other languages
  - scripts can use the new mult-layer selection feature of PIKA
  - 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 PIKA.

A word of explanation: the PIKA developers understand these changes may be onerous.
Script developers might need to maintain two different versions of their scripts.
Some users will stick with PIKA 2 for a while and some will switch to PIKA 3.
But PIKA 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.


### SF-VALUE kind of argument is obsolete

The symbol SF-VALUE is obsolete.
You can edit v2 scripts and replace that symbol.

In v2, SF-VALUE declared a formal argument that is an unquoted, arbitrary string.
Usually, SF-VALUE was used for an integer valued argument.
In the dialog for a script, ScriptFu showed a text entry widget.
Usually the widget showed a default integer literal,
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.

A script that has been edited to replace SF-VALUE with SF-ADJUSTMENT
will remain compatible with PIKA 2.

Example:

    SF-VALUE      "Font size (pixels)"  "50"
    =>
    SF-ADJUSTMENT "Font size (pixels)" '(50 1 1000 1 10 0 SF-SPINNER)

Here, in the seven-tuple, the 0 denotes: no decimal places.

Another example, where you formerly
used SF-VALUE to declare a formal argument that is float valued:

    SF-VALUE      "Lighting (degrees)"  "45.0"
    =>
    SF-ADJUSTMENT "Lighting (degrees)" '(45.0 0 360 5 10 1 SF-SLIDER)

Here, the 1 denotes: show 1 decimal place, for example "45.0",
in the dialog widget.

#### 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,
to be injected into a Scheme call to a plugin.
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

In v2, a SF-VALUE argument let a user enter a float with arbitrary precision,
e.g. "0.00000001"

That is no longer possible.  You as a script author must use SF-ADJUSTMENT
and specify the maximum precision that makes sense.  The user won't be able to
enter a value with more precision (more digits after the decimal point.)
You should understand the math of your algorithm and know what precision
is excess in terms of visible results.

Example:

    SF-ADJUSTMENT "Lighting (degrees)" '(45.0 0 360 5 10 4 SF-SLIDER)

Here, the user will only be able to enter four decimal places,
for example by typing "0.0001" into the widget.

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

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 obsolete

FALSE and TRUE symbols are no longer defined symbols in the ScriptFu language.
They never were in the Scheme language.
Instead, the Scheme language has 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

Registering a script:

    SF-TOGGLE     "Gradient reverse"   FALSE
    =>
    SF-TOGGLE     "Gradient reverse"   #f

Calling a PDB procedure taking a boolean:

    (pika-context-set-feather TRUE)
    =>
    (pika-context-set-feather #t)

Logically examining a variable for truth:

    (if (= shadow TRUE) ...
    =>
    (if shadow ...

### 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 (pika-selection-is-empty theImage))) ...
    =>
    (if (car (pika-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 PikaPDBStatus.
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 PIKA app,
the PIKA 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 pika-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.")
      (pika-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 PikaPDBStatus.

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 register PDB procedures
that return values to the caller.
That is, you can only register 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 PIKA status bar
        (pika-message "Failing")
        ; since last expression, is the result, and will mean error
        #f
      )
    )

### You can optionally install scripts like plugins in other languages

In v3 you can install ScriptFu scripts to a /plug-ins directory.
You must edit the script to include a shebang in the first line:

    #!/usr/bin/env pika-script-fu-interpreter-3.0

In v2 all ScriptFu scripts were usually installed in a /scripts directory.
In v3 you may install ScriptFu scripts with a shebang
in a subdirectory of a /plug-ins directory.

Installation of scripts with a shebang must follow rules for interpreted plugins.
A script file must:

- have a shebang on the first line
- be in a directory having the same base name
- have executable permission
- have a file suffix corresponding to an interpreter

An example path to a script:

    ~/.config/PIKA/2.99/plug-ins/myScript/myScript.scm

Such a script will execute in its own process.
If it crashes, it doesn't affect PIKA or other scripts.
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 PIKA app, and you should restart PIKA.

### Use script-fu-register-filter to register PDB procedures that take images

The function *script-fu-register-filter* is new to v3.
It lets you declare a script that:

    - is multi-layer capable filter, taking an image and many drawables
    - can save its settings between sessions

You don't specify the first two arguments "image" and "drawable"
as you do with script-fu-register in v2.
Those arguments are implicit.
As a convenience, ScriptFu and PIKA registers those arguments in the PDB for you.

The run func that you define in your script
must have those formal arguments.  For example:

    (define script-fu-my-plugin (image drawables arg1 arg2) body)

ScriptFu passes a Scheme vector of drawables, not just one, to a script
registering with script-fu-register-filter.

#### Multi-layer capabilily

script-fu-register-filter has an argument "multilayer-capability".
Some documents may refer to the argument as "drawable arity."
The argument follows the "image types" argument
and precedes the argument triples that declare formally the "other" arguments
of your plugin.

Here is an abbreviated example:

    (script-fu-register-filter "script-fu-test-sphere-v3"
      "Sphere v3..."
      "Test script-fu-register-filter: needs 2 selected layers."
      "authors"
      "copyright holders"
      "copyright dates"
      "*"  ; image types any
      SF-TWO-OR-MORE-DRAWABLE  ; multi-layer capability argument
      SF-ADJUSTMENT "Radius (in pixels)" (list 100 1 5000 1 10 0 SF-SPINNER)

The "multilayer-capability" argument can have the following values:

    SF_ONE_DRAWABLE           expects exactly one drawable
    SF_ONE_OR_MORE_DRAWABLE   expects and will process one or more drawables
    SF_TWO_OR_MORE_DRAWABLE   expects and will process two or more drawables

This is only a declaration; whether your run func does what it promises is another matter.

A script declaring SF_ONE_DRAWABLE still receives a vector of drawables,
but the vector should be of length one.

These do not specify how the script will process the drawables.
Typically, SF_ONE_OR_MORE_DRAWABLE means a script will filter
the given drawables independently and sequentially.
Typically, SF_TWO_OR_MORE_DRAWABLE means a script will
combine the given drawables, say into another drawable by a binary operation.

The "multilayer-capability" argument tells PIKA to enable the script's menu item
when a user has selected the appropriate count of drawables.

#### Settings are handled by PIKA, not ScriptFu

Scripts declared with script-fu-register-filter have settings that are persisted
within and between Pika sessions.  That is, the next time a user chooses the filter,
the dialog will show the same settings as the last time they chose the filter.
This is not true for v2 script-register-filter,
where settings are only kept during a PIKA session.

The dialog for a script declared with script-fu-register-filter
will also have buttons for resetting to initial or factory values of settings.

#### A script should check how many drawables were passed

A well-written script should throw an error if a caller does not pass the expected number of drawables, either more or fewer than declared.  See below.

### Deprecated: using script-fu-register-filter to register PDB procedures that take images

Existing scripts that use script-fu-register to declare a procedure
that takes an image and single drawable,
are deprecated.
They will still work and have a correct dialog in v3.
In some future version of PIKA,
such scripts may become obsolete.
All newly written scripts taking an image and one or more drawables
should use script-fu-register-filter.

PIKA enables the menu item for such deprecated scripts if and only if a user
selects exactly one drawable (layer or other.)

### ScriptFu plugins are expected to throw errors for improper count of drawables

Starting with PIKA 3,
a plugin that takes an image takes a container of possibly many drawables.
This is the so-called "multi-layer selection" feature.
Existing plugins that don't are deprecated,
and may become obsoleted in a future version of PIKA.

Plugins should declare how many drawables they can process,
also called the "multi-layer capability" or "drawable arity" of the algorithm.

The declared drawable arity
only describes how many drawables the algorithm is able to process.
The declared drawable arity does not denote the signature of the PDB procedure.
Well-written image procedures always receive a container of drawables.

For calls invoked by a user, the drawable arity describes
how many drawables the user is expected to select.
PIKA disables/enables the menu item for a plugin procedure
according to its declared drawable arity.
So a plugin procedure invoked directly by a user should never receive
a count of drawables that the plugin can't handle.

*But PDB procedures are also called from other PDB procedures.*
A call from another procedure may in fact
pass more drawables than declared for drawable arity.
That is a programming error on behalf of the caller.

A well-written callee plugin that is passed more drawables than declared
should return an error instead of processing any of the drawables.
Similarly for fewer than declared.

A ScriptFu plugin can use script-fu-script-abort to declare an error
when passed an improper count of drawables.
