]> git.donarmstrong.com Git - lilypond.git/blob - scm/markup-macros.scm
Issue 5167/1: Reorganize markup commands to use object properties
[lilypond.git] / scm / markup-macros.scm
1 ;;;; This file is part of LilyPond, the GNU music typesetter.
2 ;;;;
3 ;;;; Copyright (C) 2003--2015 Han-Wen Nienhuys <hanwen@xs4all.nl>
4 ;;;;
5 ;;;; LilyPond is free software: you can redistribute it and/or modify
6 ;;;; it under the terms of the GNU General Public License as published by
7 ;;;; the Free Software Foundation, either version 3 of the License, or
8 ;;;; (at your option) any later version.
9 ;;;;
10 ;;;; LilyPond is distributed in the hope that it will be useful,
11 ;;;; but WITHOUT ANY WARRANTY; without even the implied warranty of
12 ;;;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 ;;;; GNU General Public License for more details.
14 ;;;;
15 ;;;; You should have received a copy of the GNU General Public License
16 ;;;; along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
17
18 "
19 Internally markup is stored as lists, whose head is a function.
20
21   (FUNCTION ARG1 ARG2 ... )
22
23 When the markup is formatted, then FUNCTION is called as follows
24
25   (FUNCTION GROB PROPS ARG1 ARG2 ... )
26
27 GROB is the current grob, PROPS is a list of alists, and ARG1.. are
28 the rest of the arguments.
29
30 The function should return a stencil (i.e. a formatted, ready to
31 print object).
32
33
34 To add a markup command, use the define-markup-command utility.
35
36   (define-markup-command (mycommand layout prop arg1 ...) (arg1-type? ...)
37     \"my command usage and description\"
38     ...function body...)
39
40 The command is now available in markup mode, e.g.
41
42   \\markup { .... \\MYCOMMAND #1 argument ... }
43
44 " ; "
45
46 ;;;;;;;;;;;;;;;;;;;;;;;;;;;
47 ;;; markup definer utilities
48
49 ;; For documentation purposes
50 ;; markup function -> categories
51 (define-public markup-function-category (make-object-property))
52 ;; markup function -> used properties
53 (define-public markup-function-properties (make-object-property))
54
55 (use-modules (ice-9 optargs))
56
57 (defmacro*-public define-markup-command
58   (command-and-args signature
59                     #:key (category '()) (properties '())
60                     #:rest body)
61   "
62 * Define a COMMAND-markup function after command-and-args and body,
63 register COMMAND-markup and its signature,
64
65 * add categories to markup-function-category,
66
67 * sets the markup-signature object property,
68
69 * define a make-COMMAND-markup function.
70
71 Syntax:
72   (define-markup-command (COMMAND layout props . arguments)
73                                  argument-types
74                                  [ #:properties properties ]
75     \"documentation string\"
76     ...command body...)
77
78 where:
79   `argument-types' is a list of type predicates for arguments
80   `properties' a list of (property default-value) lists
81
82 The specified properties are available as let-bound variables in the
83 command body, using the respective `default-value' as fallback in case
84 `property' is not found in `props'.  `props' itself is left unchanged:
85 if you want defaults specified in that manner passed down into other
86 markup functions, you need to adjust `props' yourself.
87
88 The autogenerated documentation makes use of some optional
89 specifications that are otherwise ignored:
90
91 After `argument-types', you may also specify
92                                  [ #:category category ]
93 where:
94   `category' is either a symbol or a symbol list specifying the
95              categories for this markup command in the docs.
96
97 As an element of the `properties' list, you may directly use a
98 COMMANDx-markup symbol instead of a `(prop value)' list to indicate
99 that this markup command is called by the newly defined command,
100 adding its properties to the documented properties of the new
101 command.  There is no protection against circular definitions.
102 "
103   (let* ((command (car command-and-args))
104          (args (cdr command-and-args))
105          (command-name (string->symbol (format #f "~a-markup" command)))
106          (make-markup-name (string->symbol (format #f "make-~a-markup" command))))
107     (while (and (pair? body) (keyword? (car body)))
108            (set! body (cddr body)))
109     `(begin
110        ;; define the COMMAND-markup function
111        ,(let* ((documentation
112                 (format #f "~a\n~a" (cddr args)
113                         (if (string? (car body)) (car body) "")))
114                (real-body (if (or (not (string? (car body)))
115                                   (null? (cdr body)))
116                               body (cdr body))))
117           `(define-public (,command-name ,@args)
118              ,documentation
119              (let ,(map (lambda (prop-spec)
120                           (let ((prop (car prop-spec))
121                                 (default-value (if (null? (cdr prop-spec))
122                                                    #f
123                                                    (cadr prop-spec)))
124                                 (props (cadr args)))
125                             `(,prop (chain-assoc-get ',prop ,props ,default-value))))
126                         (filter pair? properties))
127                ,@real-body)))
128        (set! (markup-command-signature ,command-name) (list ,@signature))
129        ;; Register the new function, for markup documentation
130        (set! (markup-function-category ,command-name) ',category)
131        ;; Used properties, for markup documentation
132        (set! (markup-function-properties ,command-name)
133              (list ,@(map (lambda (prop-spec)
134                             (cond ((symbol? prop-spec)
135                                    prop-spec)
136                                   ((not (null? (cdr prop-spec)))
137                                    `(list ',(car prop-spec) ,(cadr prop-spec)))
138                                   (else
139                                    `(list ',(car prop-spec)))))
140                           properties)))
141        ;; define the make-COMMAND-markup function
142        (define-public (,make-markup-name . args)
143          (let ((sig (list ,@signature)))
144            (make-markup ,command-name ,(symbol->string make-markup-name) sig args))))))
145
146 (defmacro*-public define-markup-list-command
147   (command-and-args signature #:key (properties '()) #:rest body)
148   "Same as `define-markup-command', but defines a command that, when
149 interpreted, returns a list of stencils instead of a single one"
150   (let* ((command (car command-and-args))
151          (args (cdr command-and-args))
152          (command-name (string->symbol (format #f "~a-markup-list" command)))
153          (make-markup-name (string->symbol (format #f "make-~a-markup-list" command))))
154     (while (and (pair? body) (keyword? (car body)))
155            (set! body (cddr body)))
156     `(begin
157        ;; define the COMMAND-markup-list function
158        ,(let* ((documentation
159                 (format #f "~a\n~a" (cddr args)
160                         (if (string? (car body)) (car body) "")))
161                (real-body (if (or (not (string? (car body)))
162                                   (null? (cdr body)))
163                               body (cdr body))))
164           `(define-public (,command-name ,@args)
165              ,documentation
166              (let ,(map (lambda (prop-spec)
167                           (let ((prop (car prop-spec))
168                                 (default-value (if (null? (cdr prop-spec))
169                                                    #f
170                                                    (cadr prop-spec)))
171                                 (props (cadr args)))
172                             `(,prop (chain-assoc-get ',prop ,props ,default-value))))
173                         (filter pair? properties))
174                ,@real-body)))
175        (set! (markup-command-signature ,command-name) (list ,@signature))
176        ;; Used properties, for markup documentation
177        (set! (markup-function-properties ,command-name)
178              (list ,@(map (lambda (prop-spec)
179                             (cond ((symbol? prop-spec)
180                                    prop-spec)
181                                   ((not (null? (cdr prop-spec)))
182                                    `(list ',(car prop-spec) ,(cadr prop-spec)))
183                                   (else
184                                    `(list ',(car prop-spec)))))
185                           properties)))
186        ;; it's a markup-list command:
187        (set! (markup-list-function? ,command-name) #t)
188        ;; define the make-COMMAND-markup-list function
189        (define-public (,make-markup-name . args)
190          (let ((sig (list ,@signature)))
191            (list (make-markup ,command-name
192                               ,(symbol->string make-markup-name) sig args)))))))
193
194 ;;;;;;;;;;;;;;;
195 ;;; Utilities for storing and accessing markup commands signature
196 ;;; Examples:
197 ;;;
198 ;;; (set! (markup-command-signature raise-markup) (list number? markup?))
199 ;;; ==> (#<primitive-procedure number?> #<procedure markup? (obj)>)
200 ;;;
201 ;;; (markup-command-signature raise-markup)
202 ;;; ==> (#<primitive-procedure number?> #<procedure markup? (obj)>)
203 ;;;
204
205 (define-public markup-command-signature (make-object-property))
206
207 ;;;;;;;;;;;;;;;;;;;;;;
208 ;;; markup type predicates
209
210 (define-public (markup-function? x)
211   (and (markup-command-signature x)
212        (not (markup-list-function? x))))
213
214 (define-public markup-list-function? (make-object-property))
215
216 (define-public (markup-command-list? x)
217   "Determine if `x' is a markup command list, ie. a list composed of
218 a markup list function and its arguments."
219   (and (pair? x) (markup-list-function? (car x))))
220
221 (define-public (markup-list? arg)
222   "Return a true value if `x' is a list of markups or markup command lists."
223   (define (markup-list-inner? lst)
224     (or (null? lst)
225         (and (or (markup? (car lst)) (markup-command-list? (car lst)))
226              (markup-list-inner? (cdr lst)))))
227   (not (not (and (list? arg) (markup-list-inner? arg)))))
228
229 (define (markup-argument-list? signature arguments)
230   "Typecheck argument list."
231   (if (and (pair? signature) (pair? arguments))
232       (and ((car signature) (car arguments))
233            (markup-argument-list? (cdr signature) (cdr arguments)))
234       (and (null? signature) (null? arguments))))
235
236
237 (define (markup-argument-list-error signature arguments number)
238   "return (ARG-NR TYPE-EXPECTED ARG-FOUND) if an error is detected, or
239 #f is no error found.
240 "
241   (if (and (pair? signature) (pair? arguments))
242       (if (not ((car signature) (car arguments)))
243           (list number (type-name (car signature)) (car arguments))
244           (markup-argument-list-error (cdr signature) (cdr arguments) (+ 1 number)))
245       #f))
246
247 ;;
248 ;; full recursive typecheck.
249 ;;
250 (define (markup-typecheck? arg)
251   (or (string? arg)
252       (and (pair? arg)
253            (markup-function? (car arg))
254            (markup-argument-list? (markup-command-signature (car arg))
255                                   (cdr arg)))))
256
257 ;;
258 ;;
259 ;;
260 ;;
261 (define (markup-thrower-typecheck arg)
262   "typecheck, and throw an error when something amiss.
263
264 Uncovered - cheap-markup? is used."
265
266   (cond ((string? arg) #t)
267         ((not (pair? arg))
268          (throw 'markup-format "Not a pair" arg))
269         ((not (markup-function? (car arg)))
270          (throw 'markup-format "Not a markup function " (car arg)))
271         ((not (markup-argument-list? (markup-command-signature (car arg))
272                                      (cdr arg)))
273          (throw 'markup-format "Arguments failed  typecheck for " arg)))
274   #t)
275
276 ;;
277 ;; good enough if you only  use make-XXX-markup functions.
278 ;;
279 (define (cheap-markup? x)
280   (or (string? x)
281       (and (pair? x)
282            (markup-function? (car x)))))
283
284 ;;
285 ;; replace by markup-thrower-typecheck for more detailed diagnostics.
286 ;;
287 (define-public markup? cheap-markup?)
288
289 (define-public (make-markup markup-function make-name signature args)
290   " Construct a markup object from MARKUP-FUNCTION and ARGS. Typecheck
291 against SIGNATURE, reporting MAKE-NAME as the user-invoked function.
292 "
293   (let* ((arglen (length args))
294          (siglen (length signature))
295          (error-msg (if (and (> siglen 0) (> arglen 0))
296                         (markup-argument-list-error signature args 1)
297                         #f)))
298     (if (or (not (= arglen siglen)) (< siglen 0) (< arglen 0))
299         (ly:error (string-append make-name ": "
300                                  (_ "Wrong number of arguments.  Expect: ~A, found ~A: ~S"))
301                   siglen arglen args))
302     (if error-msg
303         (ly:error
304          (string-append
305           make-name ": "
306           (_ "Invalid argument in position ~A.  Expect: ~A, found: ~S."))
307          (car error-msg) (cadr error-msg)(caddr error-msg))
308         (cons markup-function args))))
309
310 ;;;;;;;;;;;;;;;;;;;;;;;;;;;
311 ;;; markup constructors
312 ;;; lilypond-like syntax for markup construction in scheme.
313
314 (use-modules (ice-9 receive))
315
316 (define (compile-all-markup-expressions expr)
317   "Return a list of canonical markups expressions, e.g.:
318   (#:COMMAND1 arg11 arg12 #:COMMAND2 arg21 arg22 arg23)
319   ===>
320   ((make-COMMAND1-markup arg11 arg12)
321    (make-COMMAND2-markup arg21 arg22 arg23) ...)"
322   (do ((rest expr rest)
323        (markps '() markps))
324       ((null? rest) (reverse markps))
325     (receive (m r) (compile-markup-expression rest)
326              (set! markps (cons m markps))
327              (set! rest r))))
328
329 (define (keyword->make-markup key)
330   "Transform a keyword, e.g. #:COMMAND, in a make-COMMAND-markup symbol."
331   (string->symbol (string-append "make-" (symbol->string (keyword->symbol key)) "-markup")))
332
333 (define (compile-markup-expression expr)
334   "Return two values: the first complete canonical markup expression
335    found in `expr', e.g. (make-COMMAND-markup arg1 arg2 ...),
336    and the rest expression."
337   (cond ((and (pair? expr)
338               (keyword? (car expr)))
339          ;; expr === (#:COMMAND arg1 ...)
340          (let ((command (symbol->string (keyword->symbol (car expr)))))
341            (if (not (pair? (lookup-markup-command command)))
342                (ly:error (_ "Not a markup command: ~A") command))
343            (let* ((sig (markup-command-signature
344                         (car (lookup-markup-command command))))
345                   (sig-len (length sig)))
346              (do ((i 0 (1+ i))
347                   (args '() args)
348                   (rest (cdr expr) rest))
349                  ((>= i sig-len)
350                   (values (cons (keyword->make-markup (car expr)) (reverse args)) rest))
351                (cond ((eqv? (list-ref sig i) markup-list?)
352                       ;; (car rest) is a markup list
353                       (set! args (cons `(list ,@(compile-all-markup-expressions (car rest))) args))
354                       (set! rest (cdr rest)))
355                      (else
356                       ;; pick up one arg in `rest'
357                       (receive (a r) (compile-markup-arg rest)
358                                (set! args (cons a args))
359                                (set! rest r))))))))
360         ((and (pair? expr)
361               (pair? (car expr))
362               (keyword? (caar expr)))
363          ;; expr === ((#:COMMAND arg1 ...) ...)
364          (receive (m r) (compile-markup-expression (car expr))
365                   (values m (cdr expr))))
366         ((and (pair? expr)
367               (string? (car expr))) ;; expr === ("string" ...)
368          (values `(make-simple-markup ,(car expr)) (cdr expr)))
369         (else
370          ;; expr === (symbol ...) or ((funcall ...) ...)
371          (values (car expr)
372                  (cdr expr)))))
373
374 (define (compile-all-markup-args expr)
375   "Transform `expr' into markup arguments"
376   (do ((rest expr rest)
377        (args '() args))
378       ((null? rest) (reverse args))
379     (receive (a r) (compile-markup-arg rest)
380              (set! args (cons a args))
381              (set! rest r))))
382
383 (define (compile-markup-arg expr)
384   "Return two values: the desired markup argument, and the rest arguments"
385   (cond ((null? expr)
386          ;; no more args
387          (values '() '()))
388         ((keyword? (car expr))
389          ;; expr === (#:COMMAND ...)
390          ;; ==> build and return the whole markup expression
391          (compile-markup-expression expr))
392         ((and (pair? (car expr))
393               (keyword? (caar expr)))
394          ;; expr === ((#:COMMAND ...) ...)
395          ;; ==> build and return the whole markup expression(s)
396          ;; found in (car expr)
397          (receive (markup-expr rest-expr) (compile-markup-expression (car expr))
398                   (if (null? rest-expr)
399                       (values markup-expr (cdr expr))
400                       (values `(list ,markup-expr ,@(compile-all-markup-args rest-expr))
401                               (cdr expr)))))
402         ((and (pair? (car expr))
403               (pair? (caar expr)))
404          ;; expr === (((foo ...) ...) ...)
405          (values (cons 'list (compile-all-markup-args (car expr))) (cdr expr)))
406         (else (values (car expr) (cdr expr)))))
407
408 (define (lookup-markup-command-aux symbol)
409   (let ((proc (catch 'misc-error
410                      (lambda ()
411                        (module-ref (current-module) symbol))
412                      (lambda (key . args) #f))))
413     (and (procedure? proc) proc)))
414
415 (define-public (lookup-markup-command code)
416   (let ((proc (lookup-markup-command-aux
417                (string->symbol (format #f "~a-markup" code)))))
418     (and proc (markup-function? proc)
419          (cons proc (markup-command-signature proc)))))
420
421 (define-public (lookup-markup-list-command code)
422   (let ((proc (lookup-markup-command-aux
423                (string->symbol (format #f "~a-markup-list" code)))))
424     (and proc (markup-list-function? proc)
425          (cons proc (markup-command-signature proc)))))