-;;;; define-syntax.scm -- Defines functions for syntax expressions
+;;;; This file is part of LilyPond, the GNU music typesetter.
;;;;
-;;;; source file of the GNU LilyPond music typesetter
-;;;;
-;;;; (c) 2006 Erik Sandberg <mandolaerik@gmail.com>
+;;;; Copyright (C) 2006--2015 Erik Sandberg <mandolaerik@gmail.com>
+;;;;
+;;;; LilyPond 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.
+;;;;
+;;;; LilyPond 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 LilyPond. If not, see <http://www.gnu.org/licenses/>.
+
+(define-module (scm ly-syntax-constructors)
+ #:use-module (lily)
+ #:use-module (srfi srfi-1)
+ #:use-module (scm display-lily))
+
+(define-public (music-function-call-error fun m)
+ (let* ((sigcar (car (ly:music-function-signature fun)))
+ (pred? (if (pair? sigcar) (car sigcar) sigcar)))
+ (ly:parser-error
+ (format #f (_ "~a function cannot return ~a")
+ (type-name pred?)
+ (value->lily-string m))
+ (*location*))
+ (and (pair? sigcar)
+ (if (ly:music? (cdr sigcar))
+ (ly:music-deep-copy (cdr sigcar) (*location*))
+ (cdr sigcar)))))
+
+;; Music function: Apply function and check return value.
+;; args are in reverse order.
+;;
+;; If args is not a proper list, an error has been flagged earlier
+;; and no fallback value had been available. In this case,
+;; we don't call the function but rather return the general
+;; fallback.
+(define-public (music-function fun args)
+ (let* ((sigcar (car (ly:music-function-signature fun)))
+ (pred? (if (pair? sigcar) (car sigcar) sigcar))
+ (good (list? args))
+ (m (and good (apply (ly:music-function-extract fun) (reverse! args)))))
+ (if good
+ (if (pred? m)
+ (if (ly:music? m) (ly:set-origin! m) m)
+ (music-function-call-error fun m))
+ (and (pair? sigcar)
+ (if (ly:music? (cdr sigcar))
+ (ly:music-deep-copy (cdr sigcar) (*location*))
+ (cdr sigcar))))))
+
+(define-public (argument-error n pred arg)
+ (ly:parser-error
+ (format #f
+ (_ "wrong type for argument ~a. Expecting ~a, found ~s")
+ n (type-name pred) (music->make-music arg))
+ (*location*)))
+
+;; Used for chaining several music functions together. `final'
+;; contains the last argument and still needs typechecking.
+(define (music-function-chain call final)
+ (let* ((fun (car call))
+ (siglast (last (ly:music-function-signature fun)))
+ (pred? (if (pair? siglast) (car siglast) siglast)))
+ (if (pred? final)
+ (music-function fun (cons final (cdr call)))
+ (begin
+ (argument-error (length call) pred? final)
+ ;; call music function just for the error return value
+ (music-function fun #f)))))
+
+(define-public (partial-music-function call-list)
+ (let* ((good (every list? call-list))
+ (sig (ly:music-function-signature (caar call-list))))
+ (and good
+ (ly:make-music-function
+ (cons (car sig) (list-tail sig (length (car call-list))))
+ (lambda rest
+ ;; Every time we use music-function, it destructively
+ ;; reverses its list of arguments. Changing the calling
+ ;; convention would be non-trivial since we do error
+ ;; propagation to the reversed argument list by making it
+ ;; a non-proper list. So we just create a fresh copy of
+ ;; all argument lists for each call. We also want to
+ ;; avoid reusing any music expressions without copying and
+ ;; want to let them point to the location of the music
+ ;; function call rather than its definition.
+ (let ((call-list (ly:music-deep-copy call-list (*location*))))
+ (fold music-function-chain
+ (music-function (caar call-list)
+ (reverse! rest (cdar call-list)))
+ (cdr call-list))))))))
+
+(define-public (void-music)
+ (ly:set-origin! (make-music 'Music)))
+
+(define-public (sequential-music mlist)
+ (ly:set-origin! (make-sequential-music mlist)))
+
+(define-public (simultaneous-music mlist)
+ (ly:set-origin! (make-simultaneous-music mlist)))
+
+(define-public (event-chord mlist)
+ (ly:set-origin! (make-music 'EventChord
+ 'elements mlist)))
+
+(define-public (unrelativable-music mus)
+ (ly:set-origin! (make-music 'UnrelativableMusic
+ 'element mus)))
+
+(define-public (context-change type id)
+ (ly:set-origin! (make-music 'ContextChange
+ 'change-to-type type
+ 'change-to-id id)))
+
+(define-public (tempo text . rest)
+ (let* ((unit (and (pair? rest)
+ (car rest)))
+ (count (and unit
+ (cadr rest)))
+ (range-tempo? (pair? count))
+ (tempo-change (ly:set-origin! (make-music 'TempoChangeEvent
+ 'text text
+ 'tempo-unit unit
+ 'metronome-count count)))
+ (tempo-set
+ (and unit
+ (context-spec-music
+ (make-property-set 'tempoWholesPerMinute
+ (ly:moment-mul
+ (ly:make-moment
+ (if range-tempo?
+ (round (/ (+ (car count) (cdr count))
+ 2))
+ count)
+ 1)
+ (ly:duration-length unit)))
+ 'Score))))
+
+ (if tempo-set
+ (make-sequential-music (list tempo-change tempo-set))
+ tempo-change)))
+
+(define-public (repeat type num body alts)
+ (ly:set-origin! (make-repeat type num body alts)))
+
+(define (script-to-mmrest-text music)
+ "Extract @code{'direction} and @code{'text} from @var{music}, and transform
+into a @code{MultiMeasureTextEvent}."
+
+ (if (music-is-of-type? music 'script-event)
+ (make-music 'MultiMeasureTextEvent music)
+ music))
+
+(define-public (multi-measure-rest duration articulations)
+ (ly:set-origin! (make-music 'MultiMeasureRestMusic
+ 'articulations (map script-to-mmrest-text articulations)
+ 'duration duration)))
+
+(define-public (repetition-chord duration articulations)
+ (ly:set-origin! (make-music 'EventChord
+ 'duration duration
+ 'elements articulations)))
+
+(define-public (context-specification type id ops create-new mus)
+ (let ((csm (context-spec-music mus type id)))
+ (set! (ly:music-property csm 'property-operations) ops)
+ (if create-new (set! (ly:music-property csm 'create-new) #t))
+ (ly:set-origin! csm)))
+
+(define-public (composed-markup-list commands markups)
+ ;; `markups' being a list of markups, eg (markup1 markup2 markup3),
+ ;; and `commands' a list of commands with their scheme arguments, in reverse order,
+ ;; eg: ((italic) (raise 4) (bold)), maps the commands on each markup argument, eg:
+ ;; ((bold (raise 4 (italic markup1)))
+ ;; (bold (raise 4 (italic markup2)))
+ ;; (bold (raise 4 (italic markup3))))
+
+ (define (compose arg)
+ (fold
+ (lambda (cmd prev) (append cmd (list prev)))
+ arg
+ commands))
+ (let loop ((markups markups) (completed '()))
+ (cond ((null? markups) (reverse! completed))
+ ((markup? (car markups))
+ (loop (cdr markups)
+ (cons (compose (car markups)) completed)))
+ (else
+ (call-with-values
+ (lambda () (break! markup? markups))
+ (lambda (complex rest)
+ (loop rest
+ (reverse!
+ (make-map-markup-commands-markup-list
+ compose complex) completed))))))))
+
+(define-public (partial-markup commands)
+ ;; Like composed-markup-list, except that the result is a single
+ ;; markup command that can be applied to one markup
+ (define (compose rest)
+ (fold
+ (lambda (cmd prev) (append cmd (list prev)))
+ (append (car commands) rest)
+ (cdr commands)))
+ (let ((chain (lambda (layout props . rest)
+ (interpret-markup layout props (compose rest)))))
+ (set! (markup-command-signature chain)
+ (list-tail
+ (markup-command-signature (caar commands))
+ (length (cdar commands))))
+ chain))
+
+(define-public (property-set context property value)
+ (ly:set-origin! (context-spec-music
+ (ly:set-origin!
+ (make-music 'PropertySet
+ 'symbol property
+ 'value value))
+ context)))
+
+(define-public (property-unset context property)
+ (ly:set-origin! (context-spec-music
+ (ly:set-origin!
+ (make-music 'PropertyUnset
+ 'symbol property))
+ context)))
+
+(define-public (property-override context path value)
+ (ly:set-origin! (context-spec-music
+ (ly:set-origin!
+ (make-music 'OverrideProperty
+ 'symbol (car path)
+ 'grob-property-path (cdr path)
+ 'grob-value value
+ 'pop-first #t))
+ context)))
+
+(define-public (property-revert context path)
+ (ly:set-origin! (context-spec-music
+ (ly:set-origin!
+ (make-music 'RevertProperty
+ 'symbol (car path)
+ 'grob-property-path (cdr path)))
+ context)))
+
+;; The signature here is slightly fishy since the "fallback return
+;; value" is not actually music but #f. This used to be (void-music)
+;; but triggered "Parsed object should be dead" warnings for music
+;; objects outside of the current parser session/module. The called
+;; functions always deliver music and are used from the parser in a
+;; manner where only the last argument is provided from outside the
+;; parser, and its predicate "scheme?" is always true. So the
+;; fallback value will never get used and its improper type is no
+;; issue.
+(define-public property-override-function
+ (ly:make-music-function
+ (list (cons ly:music? #f) symbol? symbol-list? scheme?)
+ property-override))
-;; TODO: use separate module for parser.
-(define define-ly-syntax define-public)
+(define-public property-set-function
+ (ly:make-music-function
+ (list (cons ly:music? #f) symbol? symbol? scheme?)
+ property-set))
-;; This shorthand adds a location parameter, and uses it to set the
-;; origin. It can be used for most music functions.
-(defmacro define-ly-syntax-loc (args . body)
- (primitive-eval `(define-ly-syntax ,(cons* (car args) 'location (cdr args))
- (let ((m ((lambda ,(cdr args) . ,body) . ,(cdr args))))
- (set! (ly:music-property m 'origin) location)
- m))))
+(define (get-first-context-id! mus)
+ "Find the name of a ContextSpeccedMusic, possibly naming it"
+ (let ((id (ly:music-property mus 'context-id)))
+ (if (eq? (ly:music-property mus 'name) 'ContextSpeccedMusic)
+ (if (and (string? id)
+ (not (string-null? id)))
+ id
+ ;; We may reliably give a new context a unique name, but
+ ;; not an existing one
+ (if (ly:music-property mus 'create-new #f)
+ (let ((id (get-next-unique-voice-name)))
+ (set! (ly:music-property mus 'context-id) id)
+ id)
+ '()))
+ '())))
-(define-ly-syntax-loc (sequential-music mlist)
- (make-sequential-music mlist))
+(define-public (lyric-event text duration)
+ (ly:set-origin! (make-lyric-event text duration)))
-(define-ly-syntax-loc (simultaneous-music mlist)
- (make-simultaneous-music mlist))
+(define-public (lyric-combine sync sync-type music)
+ ;; CompletizeExtenderEvent is added following the last lyric in MUSIC
+ ;; to signal to the Extender_engraver that any pending extender should
+ ;; be completed if the lyrics end before the associated voice.
+ (append! (ly:music-property music 'elements)
+ (list (make-music 'CompletizeExtenderEvent)))
+ (ly:set-origin!
+ (make-music 'LyricCombineMusic
+ 'element music
+ 'associated-context sync
+ 'associated-context-type sync-type)))
-(define-ly-syntax-loc (repeat type num body alts)
- (make-repeat type num body alts))
+(define-public (add-lyrics music addlyrics-list)
+ (let* ((existing-voice-name (get-first-context-id! music))
+ (voice-name (if (string? existing-voice-name)
+ existing-voice-name
+ (get-next-unique-voice-name)))
+ (voice (if (string? existing-voice-name)
+ music
+ (make-music 'ContextSpeccedMusic
+ 'element music
+ 'context-type 'Voice
+ 'context-id voice-name
+ 'origin (ly:music-property music 'origin))))
+ (voice-type (ly:music-property voice 'context-type))
+ (lyricstos (map
+ (lambda (mus+mods)
+ (with-location
+ (ly:music-property (car mus+mods) 'origin)
+ (ly:set-origin! (make-music 'ContextSpeccedMusic
+ 'create-new #t
+ 'context-type 'Lyrics
+ 'property-operations (cdr mus+mods)
+ 'element
+ (lyric-combine
+ voice-name voice-type
+ (car mus+mods))))))
+ addlyrics-list)))
+ (make-simultaneous-music (cons voice lyricstos))))