+(define-safe-public (check-grob-path path #:optional parser location
+ #:key
+ (start 0)
+ default
+ (min 1)
+ max)
+ "Check a grob path specification @var{path}, a symbol list (or a
+single symbol), for validity and possibly complete it. Returns the
+completed specification, or @code{#f} if invalid. If optional
+@var{parser} is given, a syntax error is raised in that case,
+optionally using @var{location}. If an optional keyword argument
+@code{#:start @var{start}} is given, the parsing starts at the given
+index in the sequence @samp{Context.Grob.property.sub-property...},
+with the default of @samp{0} implying the full path.
+
+If there is no valid first element of @var{path} fitting at the given
+path location, an optionally given @code{#:default @var{default}} is
+used as the respective element instead without checking it for
+validity at this position.
+
+The resulting path after possibly prepending @var{default} can be
+constrained in length by optional arguments @code{#:min @var{min}} and
+@code{#:max @var{max}}, defaulting to @samp{1} and unlimited,
+respectively."
+ (let ((path (if (symbol? path) (list path) path)))
+ ;; A Guile 1.x bug specific to optargs precludes moving the
+ ;; defines out of the let
+ (define (unspecial? s)
+ (not (or (object-property s 'is-grob?)
+ (object-property s 'backend-type?))))
+ (define (grob? s)
+ (object-property s 'is-grob?))
+ (define (property? s)
+ (object-property s 'backend-type?))
+ (define (check c p) (c p))
+
+ (let* ((checkers
+ (and (< start 3)
+ (drop (list unspecial? grob? property?) start)))
+ (res
+ (cond
+ ((null? path)
+ ;; tricky. Should we make use of the default when the
+ ;; list is empty? In most cases, this question should be
+ ;; academical as an empty list can only be generated by
+ ;; Scheme and is likely an error. We consider this a case
+ ;; of "no valid first element, and default given".
+ ;; Usually, invalid use cases should be caught later using
+ ;; the #:min argument, and if the user explicitly does not
+ ;; catch this, we just follow through.
+ (if default (list default) '()))
+ ((not checkers)
+ ;; no checkers, so we have a valid first element and just
+ ;; take the path as-is.
+ path)
+ (default
+ (if ((car checkers) (car path))
+ (and (every check (cdr checkers) (cdr path))
+ path)
+ (and (every check (cdr checkers) path)
+ (cons default path))))
+ (else
+ (and (every check checkers path)
+ path)))))
+ (if (and res
+ (if max (<= min (length res) max)
+ (<= min (length res))))
+ res
+ (begin
+ (if parser
+ (ly:parser-error parser
+ (format #f (_ "bad grob property path ~a")
+ path)
+ location))
+ #f)))))
+