From b2d3e79a146de7cb2eb2be722a510c0e4fc90fa8 Mon Sep 17 00:00:00 2001 From: David Kastrup Date: Fri, 4 Nov 2011 09:54:17 +0100 Subject: [PATCH] parser.yy: Allow backup and reparse of some more complex arguments --- input/regression/optional-args-backup.ly | 22 ++- lily/parser.yy | 229 +++++++++++++++++------ scm/ly-syntax-constructors.scm | 3 + 3 files changed, 189 insertions(+), 65 deletions(-) diff --git a/input/regression/optional-args-backup.ly b/input/regression/optional-args-backup.ly index 35dadf990c..122e2d0bcd 100644 --- a/input/regression/optional-args-backup.ly +++ b/input/regression/optional-args-backup.ly @@ -4,24 +4,28 @@ texidoc= "Test backup of predicate-based optional music function arguments. Unit expressions like @code{3\cm} can't be parsed as optional -arguments since they would require lookahead after @code{3}. However, -if @code{3} gets rejected as an optional argument based on evaluating -its predicate, it should still be able to participate as part of a unit -expression in a following mandatory argument." +arguments in one go since they would require lookahead after @code{3}. +The predicate is checked after @code{3}, and if it is suitable, +Lilypond commits to parsing as a unit number, and checks the result +again. For the predicate @code{integer?} and @code{3\cm}, you would +actually get a syntax error (since the combination is no longer an +integer) rather than Lilypond trying to see @code{3\cm} as two +separate arguments." + } \layout { ragged-right = ##t } test=#(define-void-function (parser location expect . rest) - (list? (string? "def1") (integer? "def2") (number-pair? "def3") integer?) + (list? (string? "def1") (integer? "def2") (fraction? "def3") integer?) (if (not (equal? expect rest)) (ly:parser-error parser - (format #f "Expected ~a, got ~a.\n" expect rest) + (format #f "Expected ~s, got ~s.\n" expect rest) location))) twice=2 -\test #'("a" 3 (3 . 4) 8) "a" 3 3/4 4\twice -\test #'("a" 3 "def3" 8) "a" 3 4\twice -\test #'("a" 4 "def3" 2) "a" 4\twice +\test #'("x" 3 (3 . 4) 8) x 3 3/4 4\twice +\test #'("x" 3 "def3" 8) x 3 4\twice +\test #'("x" 8 "def3" 10) x 4\twice 5\twice \test #'("def1" "def2" "def3" 8) 4\twice diff --git a/lily/parser.yy b/lily/parser.yy index fbb65f106d..281c7eff13 100644 --- a/lily/parser.yy +++ b/lily/parser.yy @@ -73,16 +73,18 @@ or /* expressions with units are permitted into argument lists */ %right PITCH_IDENTIFIER NOTENAME_PITCH TONICNAME_PITCH - UNSIGNED REAL DURATION_IDENTIFIER + UNSIGNED REAL DURATION_IDENTIFIER ':' /* The above are the symbols that can start optional function arguments that are recognized in the grammar rather than by predicate */ -%nonassoc NUMBER_IDENTIFIER +%nonassoc NUMBER_IDENTIFIER '/' /* Number-unit expressions, where permitted, are concatenated into - * function arguments. + * function arguments, just like fractions and tremoli. Tremoli must + * not have higher precedence than UNSIGNED, or Lilypond will not + * join ':' with a following optional number. */ %left PREC_TOP @@ -146,6 +148,24 @@ do \ } \ while (0) + +#define MYREPARSE(Location, Default, Pred, Token, Value) \ +do \ + if (yychar == YYEMPTY) \ + { \ + PARSER->lexer_->push_extra_token (Token, Value); \ + if (Default != SCM_UNDEFINED) \ + PARSER->lexer_->push_extra_token (REPARSE, \ + scm_cons (Default, Pred)); \ + else \ + PARSER->lexer_->push_extra_token (REPARSE, \ + Pred); \ + } else { \ + PARSER->parser_error \ + (Location, _("Too much lookahead")); \ + } \ +while (0) + %} @@ -154,7 +174,6 @@ while (0) Output_def *outputdef; SCM scm; std::string *string; - Music *music; Score *score; int i; } @@ -171,9 +190,9 @@ while (0) scm_apply_0 (proc, args) /* Syntactic Sugar. */ #define MAKE_SYNTAX(name, location, ...) \ - LOWLEVEL_MAKE_SYNTAX (ly_lily_module_constant (name), scm_list_n (PARSER->self_scm (), make_input (location), ##__VA_ARGS__, SCM_UNDEFINED)); + LOWLEVEL_MAKE_SYNTAX (ly_lily_module_constant (name), scm_list_n (PARSER->self_scm (), make_input (location) , ##__VA_ARGS__, SCM_UNDEFINED)); #define START_MAKE_SYNTAX(name, ...) \ - scm_list_n (ly_lily_module_constant (name), ##__VA_ARGS__, SCM_UNDEFINED) + scm_list_n (ly_lily_module_constant (name) , ##__VA_ARGS__, SCM_UNDEFINED) #define FINISH_MAKE_SYNTAX(start, location, ...) \ LOWLEVEL_MAKE_SYNTAX (scm_car (start), scm_cons2 (PARSER->self_scm (), make_input (location), scm_append_x (scm_list_2 (scm_cdr (start), scm_list_n (__VA_ARGS__, SCM_UNDEFINED))))) @@ -310,6 +329,7 @@ If we give names, Bison complains. %token EXPECT_DURATION "ly:duration?" %token EXPECT_SCM "scheme?" %token BACKUP "(backed-up?)" +%token REPARSE "(reparsed?)" %token EXPECT_MARKUP_LIST "markup-list?" %token EXPECT_OPTIONAL "optional?" /* After the last argument. */ @@ -331,6 +351,8 @@ If we give names, Bison complains. %token EVENT_FUNCTION %token FRACTION %token LYRICS_STRING +%token LYRIC_ELEMENT +%token LYRIC_ELEMENT_P %token LYRIC_MARKUP_IDENTIFIER %token MARKUP_FUNCTION %token MARKUP_LIST_FUNCTION @@ -375,6 +397,7 @@ If we give names, Bison complains. %type closed_music %type music %type music_bare +%type music_arg %type complex_music %type complex_music_prefix %type mode_changed_music @@ -456,6 +479,7 @@ If we give names, Bison complains. %type function_arglist_closed %type function_arglist_closed_optional %type function_arglist_common +%type function_arglist_common_lyric %type function_arglist_closed_common %type function_arglist_keep %type function_arglist_closed_keep @@ -464,6 +488,8 @@ If we give names, Bison complains. %type lilypond_header %type lilypond_header_body %type lyric_element +%type lyric_element_arg +%type lyric_element_music %type lyric_markup %type markup %type markup_braced_list @@ -498,7 +524,6 @@ If we give names, Bison complains. %type property_operation %type property_path property_path_revved %type scalar -%type scalar_bare %type scalar_closed %type scm_function_call %type scm_function_call_closed @@ -649,7 +674,7 @@ embedded_scm: embedded_scm_arg: embedded_scm_bare_arg | scm_function_call - | music + | music_arg ; scm_function_call: @@ -1103,11 +1128,15 @@ braced_music_list: } ; -music: - simple_music +music: simple_music + | lyric_element_music | composite_music %prec COMPOSITE ; +music_arg: + simple_music + | composite_music %prec COMPOSITE + ; repeated_music: REPEAT simple_string unsigned_number music @@ -1241,11 +1270,11 @@ function_arglist_nonbackup: { $$ = check_scheme_arg (PARSER, @4, $1, $4, $3, $2); } - | EXPECT_OPTIONAL EXPECT_SCM function_arglist_closed bare_number_closed + | EXPECT_OPTIONAL EXPECT_SCM function_arglist_closed bare_number { $$ = check_scheme_arg (PARSER, @4, $1, $4, $3, $2); } - | EXPECT_OPTIONAL EXPECT_SCM function_arglist_closed FRACTION + | EXPECT_OPTIONAL EXPECT_SCM function_arglist_closed fraction { $$ = check_scheme_arg (PARSER, @4, $1, $4, $3, $2); } @@ -1269,20 +1298,33 @@ function_arglist_backup: { $$ = scm_cons ($4, $3); } else { - $$ = try_unpack_lyrics ($2, $4); - if (!SCM_UNBNDP ($$)) - $$ = scm_cons ($$, $3); - else { - $$ = scm_cons (loc_on_music (@3, $1), $3); - MYBACKUP (SCM_IDENTIFIER, $4, @4); - } + $$ = scm_cons (loc_on_music (@3, $1), $3); + MYBACKUP (SCM_IDENTIFIER, $4, @4); + } + } + | EXPECT_OPTIONAL EXPECT_SCM function_arglist_keep lyric_element + { + // There is no point interpreting a lyrics string as + // an event, since we don't allow music possibly + // followed by durations or postevent into closed + // music, and we only accept closed music in optional + // arguments at the moment. If this changes, more + // complex schemes might become interesting here as + // well: see how we do this at the mandatory argument + // point. + if (scm_is_true (scm_call_1 ($2, $4))) + $$ = scm_cons ($3, $2); + else { + $$ = scm_cons (loc_on_music (@3, $1), $3); + MYBACKUP (LYRIC_MARKUP_IDENTIFIER, $4, @4); } } | EXPECT_OPTIONAL EXPECT_SCM function_arglist_closed_keep UNSIGNED { if (scm_is_true (scm_call_1 ($2, $4))) { - $$ = scm_cons ($4, $3); + $$ = $3; + MYREPARSE (@4, $1, $2, UNSIGNED, $4); } else { $$ = scm_cons (loc_on_music (@3, $1), $3); MYBACKUP (UNSIGNED, $4, @4); @@ -1292,7 +1334,8 @@ function_arglist_backup: { if (scm_is_true (scm_call_1 ($2, $4))) { - $$ = scm_cons ($4, $3); + $$ = $3; + MYREPARSE (@4, $1, $2, REAL, $4); } else { $$ = scm_cons (loc_on_music (@3, $1), $3); MYBACKUP (REAL, $4, @4); @@ -1331,6 +1374,21 @@ function_arglist_backup: $$ = scm_cons ($1, $3); MYBACKUP(0, SCM_UNDEFINED, @3); } + | function_arglist_backup REPARSE embedded_scm_arg_closed + { + $$ = check_scheme_arg (PARSER, @3, scm_car ($2), + $3, $1, scm_cdr ($2)); + } + | function_arglist_backup REPARSE bare_number + { + $$ = check_scheme_arg (PARSER, @3, scm_car ($2), + $3, $1, scm_cdr ($2)); + } + | function_arglist_backup REPARSE fraction + { + $$ = check_scheme_arg (PARSER, @3, scm_car ($2), + $3, $1, scm_cdr ($2)); + } ; function_arglist: @@ -1355,6 +1413,47 @@ function_arglist_common: $$ = check_scheme_arg (PARSER, @3, SCM_UNDEFINED, $3, $2, $1); } + | function_arglist_common_lyric + ; + +function_arglist_common_lyric: + EXPECT_SCM function_arglist_optional lyric_element + { + // We check how the predicate thinks about a lyrics + // event or about a markup. If it accepts neither, we + // backup the original token. Otherwise we commit to + // taking the token. Depending on what the predicate + // is willing to accept, we interpret as a string, as + // a lyric event, or ambiguously (meaning that if + // something looking like a duration or post event + // follows, we take the event, otherwise the string). + SCM lyric_event = MAKE_SYNTAX ("lyric-event", @3, $3, + PARSER->default_duration_.smobbed_copy ()); + if (scm_is_true (scm_call_1 ($1, $3))) + if (scm_is_true (scm_call_1 ($1, lyric_event))) + { + $$ = $2; + MYREPARSE (@3, SCM_UNDEFINED, $1, LYRIC_ELEMENT_P, $3); + } else { + $$ = scm_cons ($3, $2); + } + else if (scm_is_true (scm_call_1 ($1, lyric_event))) + { + $$ = $2; + MYREPARSE (@3, SCM_UNDEFINED, $1, LYRIC_ELEMENT, $3); + } else { + // This is going to flag a syntax error, we + // know the predicate to be false. + check_scheme_arg (PARSER, @3, SCM_UNDEFINED, + $3, $2, $1); + } + } + | function_arglist_common_lyric REPARSE lyric_element_arg + { + // This should never be false + $$ = check_scheme_arg (PARSER, @3, SCM_UNDEFINED, + $3, $1, $2); + } ; function_arglist_closed: @@ -1379,6 +1478,11 @@ function_arglist_closed_common: $$ = check_scheme_arg (PARSER, @3, SCM_UNDEFINED, $3, $2, $1); } + | EXPECT_SCM function_arglist_optional lyric_element + { + $$ = check_scheme_arg (PARSER, @3, SCM_UNDEFINED, + $3, $2, $1); + } ; function_arglist_optional: @@ -1748,36 +1852,16 @@ simple_string: STRING { } ; -scalar_bare: - string { - $$ = $1; - } - | lyric_element { - $$ = $1; - } - | bare_number { - $$ = $1; - } - | embedded_scm_bare { - $$ = $1; - } - | full_markup { - $$ = $1; - } - | full_markup_list - { - $$ = $1; - } - ; - scalar: - scalar_bare | - scm_function_call + embedded_scm_arg + | bare_number + | lyric_element ; scalar_closed: - scalar_bare | - scm_function_call_closed + embedded_scm_arg_closed + | bare_number + | lyric_element ; @@ -1915,6 +1999,9 @@ embedded_scm_chord_body: $$ = MAKE_SYNTAX ("music-function", @$, $1, $2); } + | bare_number + | fraction + | lyric_element | chord_body_element ; @@ -1945,6 +2032,9 @@ embedded_scm_event: $$ = MAKE_SYNTAX ("music-function", @$, $1, $2); } + | bare_number + | fraction + | lyric_element | post_event ; @@ -2509,15 +2599,6 @@ simple_element: ev->set_property ("duration", $2); $$ = ev->unprotect (); } - | lyric_element optional_notemode_duration { - if (!PARSER->lexer_->is_lyric_state ()) - PARSER->parser_error (@1, _ ("have to be in Lyric mode for lyrics")); - - Music *levent = MY_MAKE_MUSIC ("LyricEvent", @$); - levent->set_property ("text", $1); - levent->set_property ("duration",$2); - $$= levent->unprotect (); - } ; simple_chord_elements: @@ -2545,6 +2626,42 @@ lyric_element: | LYRICS_STRING { $$ = $1; } + | LYRIC_ELEMENT_P + ; + +lyric_element_arg: + lyric_element + | lyric_element multiplied_duration post_events { + SCM lyric_event = MAKE_SYNTAX ("lyric-event", @$, $1, $2); + $$ = MAKE_SYNTAX ("event-chord", @$, + scm_cons (lyric_event, + scm_reverse_x ($3, SCM_EOL))); + } + | lyric_element post_event post_events { + SCM lyric_event = + MAKE_SYNTAX ("lyric-event", @$, $1, + PARSER->default_duration_.smobbed_copy ()); + $$ = MAKE_SYNTAX ("event-chord", @$, + scm_cons2 (lyric_event, $2, + scm_reverse_x ($3, SCM_EOL))); + + } + | LYRIC_ELEMENT optional_notemode_duration post_events { + SCM lyric_event = MAKE_SYNTAX ("lyric-event", @$, $1, $2); + $$ = MAKE_SYNTAX ("event-chord", @$, + scm_cons (lyric_event, + scm_reverse_x ($3, SCM_EOL))); + } + ; + + +lyric_element_music: + lyric_element optional_notemode_duration post_events { + SCM lyric_event = MAKE_SYNTAX ("lyric-event", @$, $1, $2); + $$ = MAKE_SYNTAX ("event-chord", @$, + scm_cons (lyric_event, + scm_reverse_x ($3, SCM_EOL))); + } ; new_chord: diff --git a/scm/ly-syntax-constructors.scm b/scm/ly-syntax-constructors.scm index dbb466b9a0..0fe9aa0f78 100644 --- a/scm/ly-syntax-constructors.scm +++ b/scm/ly-syntax-constructors.scm @@ -210,6 +210,9 @@ into a @code{MultiMeasureTextEvent}." (set! unique-counter (1+ unique-counter)) (call-with-output-string (lambda (p) (format p "uniqueContext~s" unique-counter)))) +(define-ly-syntax-simple (lyric-event text duration) + (make-lyric-event text duration)) + (define (lyric-combine-music sync music loc) ;; CompletizeExtenderEvent is added following the last lyric in MUSIC ;; to signal to the Extender_engraver that any pending extender should -- 2.39.5