]> git.donarmstrong.com Git - lilypond.git/blobdiff - lily/lexer.ll
Run grand-replace (issue 3765)
[lilypond.git] / lily / lexer.ll
index c4ba231c5138927c60be086b4e2ceb9159d35ee2..ab6794a21ef138b8dce8488bb703a0fc375977bc 100644 (file)
@@ -1,8 +1,8 @@
-%{ // -*-Fundamental-*-
+%{ // -*- mode: c++; c-file-style: "linux"; indent-tabs-mode: t -*-
 /*
   This file is part of LilyPond, the GNU music typesetter.
 
-  Copyright (C) 1996--2011 Han-Wen Nienhuys <hanwen@xs4all.nl>
+  Copyright (C) 1996--2014 Han-Wen Nienhuys <hanwen@xs4all.nl>
                  Jan Nieuwenhuizen <janneke@gnu.org>
 
   LilyPond is free software: you can redistribute it and/or modify
   along with LilyPond.  If not, see <http://www.gnu.org/licenses/>.
 */
 
+/* Mode and indentation are at best a rough approximation based on TAB
+ * formatting (reasonable for compatibility with unspecific editor
+ * modes as Flex modes are hard to find) and need manual correction
+ * frequently.  Without a reasonably dependable way of formatting a
+ * Flex file sensibly, there is little point in trying to fix the
+ * inconsistent state of indentation.
+ */
+
 /*
   backup rules
 
@@ -78,13 +86,19 @@ SCM lookup_markup_list_command (string s);
 bool is_valid_version (string s);
 
 
-#define start_quote()  \
-       yy_push_state (quote);\
-       yylval.string = new string
+#define start_quote() do {                      \
+                yy_push_state (quote);          \
+                yylval = SCM_EOL;               \
+        } while (0)
+
+/*
+  The inside of \"violin1" is marked by commandquote mode
+*/
 
-#define start_lyric_quote()    \
-       yy_push_state (lyric_quote);\
-       yylval.string = new string
+#define start_command_quote() do {             \
+                yy_push_state (commandquote);  \
+                yylval = SCM_EOL;               \
+        } while (0)
 
 #define yylval (*lexval_)
 
@@ -109,49 +123,57 @@ SCM (* scm_parse_error_handler) (void *);
 %option never-interactive 
 %option warn
 
-%x extratoken
 %x chords
 %x figures
 %x incl
 %x lyrics
-%x lyric_quote
 %x longcomment
+%x maininput
 %x markup
 %x notes
 %x quote
+%x commandquote
 %x sourcefileline
 %x sourcefilename
 %x version
 
+/* The strategy concerning multibyte characters is to accept them but
+ * call YYText_utf8 for patterns that might contain them, in order to
+ * get a single code path responsible for flagging non-UTF-8 input:
+ * Patterns for accepting only valid UTF-8 without backing up are
+ * really hard to do and complex, and if nice error messages are
+ * wanted, one would need patterns catching the invalid input as well.
+ *
+ * Since editors and operating environments don't necessarily behave
+ * reasonably in the presence of mixed encodings, we flag encoding
+ * errors also in identifiers, comments, and strings where it would be
+ * conceivable to just transparently work with the byte string.  But
+ * the whole point of caring about UTF-8 in here at all is too avoid
+ * stranger errors later when input passes into backends or log files
+ * or console output or error messages.
+ */
+
 A              [a-zA-Z\200-\377]
 AA             {A}|_
 N              [0-9]
-AN             {AA}|{N}
 ANY_CHAR       (.|\n)
-PUNCT          [?!:'`]
-ACCENT         \\[`'"^]
-SPECIAL_CHAR           [&@]
-NATIONAL       [\001-\006\021-\027\031\036]
-TEX            {AA}|-|{PUNCT}|{ACCENT}|{NATIONAL}|{SPECIAL_CHAR}
-DASHED_WORD            {A}({AN}|-)*
-DASHED_KEY_WORD                \\{DASHED_WORD}
-
-
-
-ALPHAWORD      {A}+
+WORD           {A}([-_]{A}|{A})*
+COMMAND                \\{WORD}
+/* SPECIAL category is for every letter that needs to get passed to
+ * the parser rather than being redefinable by the user */
+SPECIAL                [-+*/=<>{}!?_^'',.:]
+SHORTHAND      (.|\\.)
 UNSIGNED       {N}+
 E_UNSIGNED     \\{N}+
 FRACTION       {N}+\/{N}+
 INT            -?{UNSIGNED}
 REAL           ({INT}\.{N}*)|(-?\.{N}+)
+STRICTREAL      {UNSIGNED}\.{UNSIGNED}
 WHITE          [ \n\t\f\r]
 HORIZONTALWHITE                [ \t]
 BLACK          [^ \n\t\f\r]
 RESTNAME       [rs]
-NOTECOMMAND    \\{A}+
-MARKUPCOMMAND  \\({A}|[-_])+
-LYRICS         ({AA}|{TEX})[^0-9 \t\n\r\f]*
-ESCAPED                [nt\\'"]
+ESCAPED                [nt\\''""]
 EXTENDER       __
 HYPHEN         --
 BOM_UTF8       \357\273\277
@@ -163,36 +185,6 @@ BOM_UTF8   \357\273\277
        // swallow and ignore carriage returns
 }
 
-<extratoken>{ANY_CHAR} {
-  /* Generate a token without swallowing anything */
-
-  /* First unswallow the eaten character */
-  add_lexed_char (-YYLeng ());
-  yyless (0);
-
-  /* produce requested token */
-  int type = scm_to_int (scm_caar (extra_tokens_));
-  yylval.scm = scm_cdar (extra_tokens_);
-  extra_tokens_ = scm_cdr (extra_tokens_);
-  if (scm_is_null (extra_tokens_))
-    yy_pop_state ();
-
-  return type;
-}
-
-<extratoken><<EOF>>    {
-  /* Generate a token without swallowing anything */
-
-  /* produce requested token */
-  int type = scm_to_int (scm_caar (extra_tokens_));
-  yylval.scm = scm_cdar (extra_tokens_);
-  extra_tokens_ = scm_cdr (extra_tokens_);
-  if (scm_is_null (extra_tokens_))
-    yy_pop_state ();
-
-  return type;
-}
-
    /* Use the trailing context feature. Otherwise, the BOM will not be
       found if the file starts with an identifier definition. */
 <INITIAL,chords,lyrics,figures,notes>{BOM_UTF8}/.* {
@@ -208,13 +200,10 @@ BOM_UTF8  \357\273\277
   "%{" {
        yy_push_state (longcomment);
   }
-  %[^{\n\r][^\n\r]*[\n\r]      {
-  }
-  %[^{\n\r]    { // backup rule
-  }
-  %[\n\r]      {
+  %[^{\n\r][^\n\r]*[\n\r]?     {
+         (void) YYText_utf8 ();
   }
-  %[^{\n\r][^\n\r]*    {
+  %[\n\r]?     {
   }
   {WHITE}+     {
 
@@ -236,8 +225,8 @@ BOM_UTF8    \357\273\277
 <INITIAL,chords,lyrics,notes,figures>\\sourcefileline{WHITE}*  {
        yy_push_state (sourcefileline);
 }
-<version>\"[^"]*\"     { /* got the version number */
-       string s (YYText () + 1);
+<version>\"[^""]*\"     { /* got the version number */
+       string s (YYText_utf8 () + 1);
        s = s.substr (0, s.rfind ('\"'));
 
        yy_pop_state ();
@@ -245,13 +234,13 @@ BOM_UTF8  \357\273\277
        SCM top_scope = scm_car (scm_last_pair (scopes_));
        scm_module_define (top_scope, ly_symbol2scm ("version-seen"), SCM_BOOL_T);
 
-       if (!is_valid_version (s))
+       if (!is_valid_version (s)) {
+                yylval = SCM_UNSPECIFIED;
                return INVALID;
-
-
+        }
 }
-<sourcefilename>\"[^"]*\"     {
-       string s (YYText () + 1);
+<sourcefilename>\"[^""]*\"     {
+       string s (YYText_utf8 () + 1);
        s = s.substr (0, s.rfind ('\"'));
 
        yy_pop_state ();
@@ -286,9 +275,10 @@ BOM_UTF8   \357\273\277
 }
 <longcomment>{
        [^\%]*          {
+               (void) YYText_utf8 ();
        }
        \%*[^}%]*               {
-
+               (void) YYText_utf8 ();
        }
        "%"+"}"         {
                yy_pop_state ();
@@ -300,24 +290,28 @@ BOM_UTF8  \357\273\277
        if (!is_main_input_)
        {
                start_main_input ();
+               main_input_level_ = include_stack_.size ();
                is_main_input_ = true;
+               int state = YYSTATE;
+               yy_push_state (maininput);
+               yy_push_state (state);
        }
        else
-               error (_ ("\\maininput not allowed outside init files"));
+               LexerError (_ ("\\maininput not allowed outside init files").c_str ());
 }
 
 <INITIAL,chords,lyrics,figures,notes>\\include           {
        yy_push_state (incl);
 }
 <incl>\"[^""]*\"   { /* got the include file name */
-       string s (YYText ()+1);
+       string s (YYText_utf8 ()+1);
        s = s.substr (0, s.rfind ('"'));
 
        new_input (s, sources_);
        yy_pop_state ();
 }
 <incl>\\{BLACK}*{WHITE}? { /* got the include identifier */
-       string s = YYText () + 1;
+       string s = YYText_utf8 () + 1;
        strip_trailing_white (s);
        if (s.length () && (s[s.length () - 1] == ';'))
          s = s.substr (0, s.length () - 1);
@@ -363,15 +357,31 @@ BOM_UTF8  \357\273\277
 }
 
 <incl,version,sourcefilename>\"[^""]*   { // backup rule
-       error (_ ("end quote missing"));
-       exit (1);
+       LexerError (_ ("end quote missing").c_str ());
+       yy_pop_state ();
 }
+
+    /* Flex picks the longest matching pattern including trailing
+     * contexts.  Without the backup pattern, r-. does not trigger the
+     * {RESTNAME} rule but rather the {WORD}/[-_] rule coming later,
+     * needed for avoiding backup states.
+     */
+
+<chords,notes,figures>{RESTNAME}/[-_]  |  // pseudo backup rule
 <chords,notes,figures>{RESTNAME}       {
        char const *s = YYText ();
-       yylval.scm = scm_from_locale_string (s);
+       yylval = scm_from_locale_string (s);
        return RESTNAME;
 }
+<chords,notes,figures>q/[-_]   | // pseudo backup rule
+<chords,notes,figures>q        {
+        yylval = SCM_UNSPECIFIED;
+       return CHORD_REPETITION;
+}
+
+<chords,notes,figures>R/[-_]   | // pseudo backup rule
 <chords,notes,figures>R                {
+        yylval = SCM_UNSPECIFIED;
        return MULTI_MEASURE_REST;
 }
 <INITIAL,chords,figures,lyrics,markup,notes>#  { //embedded scm
@@ -390,7 +400,7 @@ BOM_UTF8    \357\273\277
        }
        char_count_stack_.back () += n;
 
-       yylval.scm = sval;
+       yylval = sval;
        return SCM_TOKEN;
 }
 
@@ -407,177 +417,241 @@ BOM_UTF8        \357\273\277
        }
        char_count_stack_.back () += n;
 
-       sval = eval_scm (sval);
-               
+       sval = eval_scm (sval, '$');
+
        int token = scan_scm_id (sval);
-       if (!scm_is_eq (yylval.scm, SCM_UNSPECIFIED))
-         return token;
+       if (!scm_is_eq (yylval, SCM_UNSPECIFIED))
+               return token;
 }
 
 <INITIAL,notes,lyrics>{ 
        \<\<    {
+                yylval = SCM_UNSPECIFIED;
                return DOUBLE_ANGLE_OPEN;
        }
        \>\>    {
+                yylval = SCM_UNSPECIFIED;
                return DOUBLE_ANGLE_CLOSE;
        }
 }
 
 <INITIAL,notes>{
        \<      {
+                yylval = SCM_UNSPECIFIED;
                return ANGLE_OPEN;
        }
        \>      {
+                yylval = SCM_UNSPECIFIED;
                return ANGLE_CLOSE;
        }
 }
 
 <figures>{
        _       {
+                yylval = SCM_UNSPECIFIED;
                return FIGURE_SPACE;
        }
        \>              {
+                yylval = SCM_UNSPECIFIED;
                return FIGURE_CLOSE;
        }
        \<      {
+                yylval = SCM_UNSPECIFIED;
                return FIGURE_OPEN;
        }
+       \\\+    {
+               yylval = SCM_UNSPECIFIED;
+               return E_PLUS;
+       }
+       \\!     {
+               yylval = SCM_UNSPECIFIED;
+               return E_EXCLAMATION;
+       }
+       \\\\    {
+               yylval = SCM_UNSPECIFIED;
+               return E_BACKSLASH;
+       }
+       [][]    {
+               yylval = SCM_UNSPECIFIED;
+               return  YYText ()[0];
+       }
 }
 
 <notes,figures>{
-       {ALPHAWORD}     {
-               return scan_bare_word (YYText ());
+       {WORD}/[-_]     | // backup rule
+       {WORD}  {
+               return scan_bare_word (YYText_utf8 ());
        }
-
-       {NOTECOMMAND}   {
-               return scan_escaped_word (YYText () + 1); 
+       \\\"    {
+               start_command_quote ();
+       }
+       {COMMAND}/[-_]  | // backup rule
+       {COMMAND}       {
+               return scan_escaped_word (YYText_utf8 () + 1); 
        }
        {FRACTION}      {
-               yylval.scm =  scan_fraction (YYText ());
+               yylval =  scan_fraction (YYText ());
                return FRACTION;
        }
-       {UNSIGNED}/\/   | // backup rule
-       {UNSIGNED}              {
-               yylval.scm = scm_c_read_string (YYText ());
+       {STRICTREAL}    {
+               yylval = scm_c_read_string (YYText ());
+               return REAL;
+       }
+       {UNSIGNED}/[/.] | // backup rule
+       {UNSIGNED}      {
+               yylval = scm_c_read_string (YYText ());
                return UNSIGNED;
        }
        {E_UNSIGNED}    {
-               yylval.i = String_convert::dec2int (string (YYText () +1));
+               yylval = scm_c_read_string (YYText () + 1);
                return E_UNSIGNED;
        }
 }
 
-<quote,lyric_quote>{
+<quote,commandquote>{
        \\{ESCAPED}     {
-               *yylval.string += to_string (escaped_char (YYText ()[1]));
+                char c = escaped_char (YYText ()[1]);
+               yylval = scm_cons (scm_from_locale_stringn (&c, 1),
+                                   yylval);
        }
        [^\\""]+        {
-               *yylval.string += YYText ();
+                yylval = scm_cons (scm_from_locale_string (YYText_utf8 ()),
+                                   yylval);
        }
        \"      {
 
+               /* yylval is union. Must remember STRING before setting SCM*/
+
+                yylval = scm_string_concatenate_reverse (yylval,
+                                                         SCM_UNDEFINED,
+                                                         SCM_UNDEFINED);
+
+               if (get_state () == commandquote) {
+                       yy_pop_state ();
+                       return scan_escaped_word (ly_scm2string (yylval));
+               }
+
                yy_pop_state ();
 
-               /* yylval is union. Must remember STRING before setting SCM*/
-               string *sp = yylval.string;
-               yylval.scm = ly_string2scm (*sp);
-               delete sp;
-               return is_lyric_state () ? LYRICS_STRING : STRING;
+               return STRING;
        }
-       .       {
-               *yylval.string += YYText ();
+       \\      {
+                yylval = scm_cons (scm_from_locale_string (YYText ()),
+                                   yylval);
        }
 }
 
 <lyrics>{
        \" {
-               start_lyric_quote ();
+               start_quote ();
        }
        {FRACTION}      {
-               yylval.scm =  scan_fraction (YYText ());
+               yylval =  scan_fraction (YYText ());
                return FRACTION;
        }
-       {UNSIGNED}/\/[^0-9] { // backup rule
-               yylval.scm = scm_c_read_string (YYText ());
-               return UNSIGNED;
+       {STRICTREAL}    {
+               yylval = scm_c_read_string (YYText ());
+               return REAL;
        }
-       {UNSIGNED}/\/   | // backup rule
+       {UNSIGNED}/[/.] | // backup rule
        {UNSIGNED}              {
-               yylval.scm = scm_c_read_string (YYText ());
+               yylval = scm_c_read_string (YYText ());
                return UNSIGNED;
        }
-       {NOTECOMMAND}   {
-               return scan_escaped_word (YYText () + 1);
+       \\\"    {
+               start_command_quote ();
+       }
+       {COMMAND}/[-_]  | // backup rule
+       {COMMAND}       {
+               return scan_escaped_word (YYText_utf8 () + 1);
+       }
+       \\.|\|  {
+               // UTF-8 already covered by COMMAND
+               return scan_shorthand (YYText ());
        }
-       {LYRICS} {
+       /* Characters needed to express durations, assignments */
+       [*.=]   {
+                yylval = SCM_UNSPECIFIED;
+               return YYText ()[0];
+       }
+       [^|*.=$#{}\"\\ \t\n\r\f0-9][^$#{}\"\\ \t\n\r\f0-9]* {
                /* ugr. This sux. */
-               string s (YYText ()); 
+               string s (YYText_utf8 ());
+                yylval = SCM_UNSPECIFIED;
                if (s == "__")
-                       return yylval.i = EXTENDER;
+                       return EXTENDER;
                if (s == "--")
-                       return yylval.i = HYPHEN;
+                       return HYPHEN;
                s = lyric_fudge (s);
+               yylval = ly_string2scm (s);
 
-               char c = s[s.length () - 1];
-               if (c == '{' ||  c == '}') // brace open is for not confusing dumb tools.
-                       here_input ().warning (
-                               _ ("Brace found at end of lyric.  Did you forget a space?"));
-               yylval.scm = ly_string2scm (s);
-
-
-               return LYRICS_STRING;
+               return STRING;
        }
-       . {
+       /* This should really just cover {} */
+       [{}] {
+                yylval = SCM_UNSPECIFIED;
                return YYText ()[0];
        }
 }
 <chords>{
-       {ALPHAWORD}     {
-               return scan_bare_word (YYText ());
+       {WORD}/[-_]     | // backup rule
+       {WORD}  {
+               return scan_bare_word (YYText_utf8 ());
        }
-       {NOTECOMMAND}   {
-               return scan_escaped_word (YYText () + 1);
+       \\\"    {
+               start_command_quote ();
+       }
+       {COMMAND}/[-_]  | // backup rule
+       {COMMAND}       {
+               return scan_escaped_word (YYText_utf8 () + 1);
        }
        {FRACTION}      {
-               yylval.scm =  scan_fraction (YYText ());
+               yylval =  scan_fraction (YYText ());
                return FRACTION;
        }
-       {UNSIGNED}/\/[^0-9] { // backup rule
-               yylval.scm = scm_c_read_string (YYText ());
-               return UNSIGNED;
-       }
        {UNSIGNED}/\/   | // backup rule
        {UNSIGNED}              {
-               yylval.scm = scm_c_read_string (YYText ());
+               yylval = scm_c_read_string (YYText ());
                return UNSIGNED;
        }
        -  {
+                yylval = SCM_UNSPECIFIED;
                return CHORD_MINUS;
        }
        :  {
+                yylval = SCM_UNSPECIFIED;
                return CHORD_COLON;
        }
        \/\+ {
+                yylval = SCM_UNSPECIFIED;
                return CHORD_BASS;
        }
        \/  {
+                yylval = SCM_UNSPECIFIED;
                return CHORD_SLASH;
        }
        \^  {
+                yylval = SCM_UNSPECIFIED;
                return CHORD_CARET;
        }
-       . {
-               return YYText ()[0];
-       }
 }
 
 
 <markup>{
        \\score {
+                yylval = SCM_UNSPECIFIED;
                return SCORE;
        }
-       {MARKUPCOMMAND} {
-               string str (YYText () + 1);
+       \\score-lines {
+               yylval = SCM_UNSPECIFIED;
+               return SCORELINES;
+       }
+       \\\"    {
+               start_command_quote ();
+       }
+       {COMMAND}/[-_]  | // backup rule
+       {COMMAND} {
+               string str (YYText_utf8 () + 1);
 
                 int token_type = MARKUP_FUNCTION;
                SCM s = lookup_markup_command (str);
@@ -606,62 +680,68 @@ BOM_UTF8  \357\273\277
                // in reverse order, so the first token pushed in the
                // loop will be EXPECT_NO_MORE_ARGS.
 
-               yylval.scm = scm_car(s);
+               yylval = scm_car(s);
 
                // yylval now contains the function to call as token
                // value (for token type MARKUP_FUNCTION or
                // MARKUP_LIST_FUNCTION).
 
-               push_extra_token(EXPECT_NO_MORE_ARGS);
+               push_extra_token (here_input (), EXPECT_NO_MORE_ARGS);
                s = scm_cdr(s);
                for (; scm_is_pair(s); s = scm_cdr(s)) {
                  SCM predicate = scm_car(s);
 
                  if (predicate == ly_lily_module_constant ("markup-list?"))
-                   push_extra_token(EXPECT_MARKUP_LIST);
+                   push_extra_token (here_input (), EXPECT_MARKUP_LIST);
                  else if (predicate == ly_lily_module_constant ("markup?"))
-                   push_extra_token(EXPECT_MARKUP);
+                   push_extra_token (here_input (), EXPECT_MARKUP);
                  else
-                   push_extra_token(EXPECT_SCM, predicate);
+                   push_extra_token (here_input (), EXPECT_SCM, predicate);
                }
                return token_type;
        }
-       [{}]    {
-               return YYText ()[0];
-       }
        [^$#{}\"\\ \t\n\r\f]+ {
-               string s (YYText ()); 
-
-               char c = s[s.length () - 1];
-               /* brace open is for not confusing dumb tools.  */
-               if (c == '{' ||  c == '}')
-                       here_input ().warning (
-                               _ ("Brace found at end of markup.  Did you forget a space?"));
-               yylval.scm = ly_string2scm (s);
-
+               string s (YYText_utf8 ()); 
 
+               yylval = ly_string2scm (s);
                return STRING;
        }
-       .  {
-               return YYText()[0];
+       [{}]  {
+                yylval = SCM_UNSPECIFIED;
+               return YYText ()[0];
        }
 }
 
 <longcomment><<EOF>> {
                LexerError (_ ("EOF found inside a comment").c_str ());
-               is_main_input_ = false; // should be safe , can't have \include in --safe.
-               if (!close_input ())
-                 yyterminate (); // can't move this, since it actually rets a YY_NULL
+               yy_pop_state ();
        }
 
-<<EOF>> { if (is_main_input_)
+<quote,commandquote><<EOF>> {
+       LexerError (_ ("EOF found inside string").c_str ());
+       yy_pop_state ();
+}
+
+<<EOF>> {
+        yylval = SCM_UNSPECIFIED;
+        if (is_main_input_)
        {
-               /* 2 = init.ly + current file.
-                  > because we're before closing, but is_main_input_ should
-                  reflect after.
-               */ 
-               is_main_input_ = include_stack_.size () > 2;
-               if (!close_input ())
+               is_main_input_ = include_stack_.size () > main_input_level_;
+               if (!is_main_input_)
+               {
+                       main_input_level_ = 0;
+                       pop_state ();
+                       if (YYSTATE != maininput)
+                       {
+                               LexerError (_ ("Unfinished main input").c_str ());
+                               do {
+                                       yy_pop_state ();
+                               } while (YYSTATE != maininput);
+                       }
+                       extra_tokens_ = SCM_EOL;
+                       yy_pop_state ();
+               }
+               if (!close_input () || !is_main_input_)
                /* Returns YY_NULL */
                        yyterminate ();
        }
@@ -670,79 +750,64 @@ BOM_UTF8  \357\273\277
                yyterminate ();
 }
 
+<maininput>{ANY_CHAR} {
+       while (include_stack_.size () > main_input_level_
+              && close_input ())
+               ;
+       yyterminate ();
+}
+
 <INITIAL>{
-       {DASHED_WORD}   {
-               return scan_bare_word (YYText ());
+       {WORD}/[-_]     | // backup rule
+       {WORD}  {
+               return scan_bare_word (YYText_utf8 ());
        }
-       {DASHED_KEY_WORD}       {
-               return scan_escaped_word (YYText () + 1);
+       \\\"    {
+               start_command_quote ();
        }
+       {COMMAND}/[-_]  | // backup rule
+       {COMMAND}       {
+               return scan_escaped_word (YYText_utf8 () + 1);
+       }
+}
+
+{FRACTION}     {
+       yylval =  scan_fraction (YYText ());
+       return FRACTION;
 }
 
 -{UNSIGNED}    | // backup rule
 {REAL}         {
-       yylval.scm = scm_c_read_string (YYText ());
-       return REAL;
-}
--\.    { // backup rule
-       yylval.scm = scm_from_double (0.0);
+       yylval = scm_c_read_string (YYText ());
        return REAL;
 }
 
+{UNSIGNED}/\/  | // backup rule
 {UNSIGNED}     {
-       yylval.scm = scm_c_read_string (YYText ());
+       yylval = scm_c_read_string (YYText ());
        return UNSIGNED;
 }
 
 
-[{}]   {
-
+-/\.   { // backup rule
+        yylval = SCM_UNSPECIFIED;
        return YYText ()[0];
 }
-[*:=]          {
-       char c = YYText ()[0];
-
-       return c;
-}
 
-<INITIAL,notes,figures>.       {
+<INITIAL,chords,lyrics,figures,notes>{SPECIAL} {
+        yylval = SCM_UNSPECIFIED;
        return YYText ()[0];
 }
 
-<INITIAL,lyrics,notes,figures>\\. {
-    char c = YYText ()[1];
-
-    switch (c) {
-    case '>':
-       return E_ANGLE_CLOSE;
-    case '<':
-       return E_ANGLE_OPEN;
-    case '!':
-       return E_EXCLAMATION;
-    case '(':
-       return E_OPEN;
-    case ')':
-       return E_CLOSE;
-    case '[':
-       return E_BRACKET_OPEN;
-    case '+':
-       return E_PLUS;
-    case ']':
-       return E_BRACKET_CLOSE;
-    case '~':
-       return E_TILDE;
-    case '\\':
-       return E_BACKSLASH;
-
-    default:
-       return E_CHAR;
-    }
+<INITIAL,chords,lyrics,figures,notes>{SHORTHAND}       {
+       return scan_shorthand (YYText_utf8 ()); // should not be utf-8
 }
 
-<*>.           {
-       string msg = _f ("invalid character: `%c'", YYText ()[0]);
+<*>.[\200-\277]*       {
+       string msg = _f ("invalid character: `%s'", YYText_utf8 ());
        LexerError (msg.c_str ());
-       return YYText ()[0];
+        yylval = SCM_UNSPECIFIED;
+       return '%';  // Better not return half a utf8 character.
 }
 
 %%
@@ -750,21 +815,35 @@ BOM_UTF8  \357\273\277
 /* Make the lexer generate a token of the given type as the next token. 
  TODO: make it possible to define a value for the token as well */
 void
-Lily_lexer::push_extra_token (int token_type, SCM scm)
+Lily_lexer::push_extra_token (Input const &where, int token_type, SCM scm)
+{
+       extra_tokens_ = scm_cons (scm_cons2 (make_input (where),
+                                            scm_from_int (token_type),
+                                            scm), extra_tokens_);
+}
+
+int
+Lily_lexer::pop_extra_token ()
 {
        if (scm_is_null (extra_tokens_))
-       {
-               if (YY_START != extratoken)
-                       hidden_state_ = YY_START;
-               yy_push_state (extratoken);
-       }
-       extra_tokens_ = scm_acons (scm_from_int (token_type), scm, extra_tokens_);
+               return -1;
+
+  /* produce requested token */
+       yylloc = *unsmob_input (scm_caar (extra_tokens_));
+       int type = scm_to_int (scm_cadar (extra_tokens_));
+       yylval = scm_cddar (extra_tokens_);
+       extra_tokens_ = scm_cdr (extra_tokens_);
+       return type;
 }
 
 void
-Lily_lexer::push_chord_state (SCM tab)
+Lily_lexer::push_chord_state (SCM alist)
 {
-       pitchname_tab_stack_ = scm_cons (tab, pitchname_tab_stack_);
+       SCM p = scm_assq (alist, pitchname_tab_stack_);
+
+       if (scm_is_false (p))
+               p = scm_cons (alist, alist_to_hashq (alist));
+       pitchname_tab_stack_ = scm_cons (p, pitchname_tab_stack_);
        yy_push_state (chords);
 }
 
@@ -793,9 +872,13 @@ Lily_lexer::push_markup_state ()
 }
 
 void
-Lily_lexer::push_note_state (SCM tab)
+Lily_lexer::push_note_state (SCM alist)
 {
-       pitchname_tab_stack_ = scm_cons (tab, pitchname_tab_stack_);
+       SCM p = scm_assq (alist, pitchname_tab_stack_);
+
+       if (scm_is_false (p))
+               p = scm_cons (alist, alist_to_hashq (alist));
+       pitchname_tab_stack_ = scm_cons (p, pitchname_tab_stack_);
        yy_push_state (notes);
 }
 
@@ -805,38 +888,66 @@ Lily_lexer::pop_state ()
        if (YYSTATE == notes || YYSTATE == chords)
                pitchname_tab_stack_ = scm_cdr (pitchname_tab_stack_);
 
-       yy_pop_state ();
+       // don't cross the maininput threshold
+       if (YYSTATE != maininput)
+               yy_pop_state ();
+
 }
 
 int
 Lily_lexer::identifier_type (SCM sid)
 {
-       int k = try_special_identifiers (&yylval.scm , sid);
+       int k = try_special_identifiers (&yylval , sid);
        return k >= 0  ? k : SCM_IDENTIFIER;
 }
 
 
 int
-Lily_lexer::scan_escaped_word (string str)
+Lily_lexer::scan_escaped_word (const string &str)
 {
        // use more SCM for this.
 
 //     SCM sym = ly_symbol2scm (str.c_str ());
 
+        yylval = SCM_UNSPECIFIED;
        int i = lookup_keyword (str);
-       if (i == MARKUP && is_lyric_state ())
-               return LYRIC_MARKUP;
+
        if (i != -1)
                return i;
 
        SCM sid = lookup_identifier (str);
+       if (Music *m = unsmob_music (sid))
+       {
+               m->set_spot (override_input (here_input ()));
+       }
+
        if (sid != SCM_UNDEFINED)
                return scan_scm_id (sid);
 
        string msg (_f ("unknown escaped string: `\\%s'", str));        
        LexerError (msg.c_str ());
 
-       yylval.scm = ly_string2scm (str);
+       yylval = ly_string2scm (str);
+
+       return STRING;
+}
+
+int
+Lily_lexer::scan_shorthand (const string &str)
+{
+       SCM sid = lookup_identifier (str);
+       if (Music *m = unsmob_music (sid))
+       {
+               m->set_spot (override_input (here_input ()));
+       }
+
+       if (sid != SCM_UNDEFINED)
+               return scan_scm_id (sid);
+
+       string msg (_f ("undefined character or shorthand: %s", str));  
+       LexerError (msg.c_str ());
+
+       yylval = ly_string2scm (str);
 
        return STRING;
 }
@@ -848,9 +959,9 @@ Lily_lexer::scan_scm_id (SCM sid)
        {
                int funtype = SCM_FUNCTION;
 
-               yylval.scm = get_music_function_transform (sid);
+               yylval = sid;
 
-               SCM s = scm_object_property (yylval.scm, ly_symbol2scm ("music-function-signature"));
+               SCM s = get_music_function_signature (sid);
                SCM cs = scm_car (s);
 
                if (scm_is_pair (cs))
@@ -866,7 +977,7 @@ Lily_lexer::scan_scm_id (SCM sid)
                        funtype = SCM_FUNCTION;
                else programming_error ("Bad syntax function predicate");
 
-               push_extra_token (EXPECT_NO_MORE_ARGS);
+               push_extra_token (here_input (), EXPECT_NO_MORE_ARGS);
                for (s = scm_cdr (s); scm_is_pair (s); s = scm_cdr (s))
                {
                        SCM optional = SCM_UNDEFINED;
@@ -878,64 +989,53 @@ Lily_lexer::scan_scm_id (SCM sid)
                                cs = SCM_CAR (cs);
                        }
                        
-                       if (cs == Pitch_type_p_proc)
-                               push_extra_token (EXPECT_PITCH);
-                       else if (cs == Duration_type_p_proc)
-                               push_extra_token (EXPECT_DURATION);
-                       else if (ly_is_procedure (cs))
-                               push_extra_token (EXPECT_SCM, cs);
+                       if (ly_is_procedure (cs))
+                               push_extra_token (here_input (), EXPECT_SCM, cs);
                        else
                        {
                                programming_error ("Function parameter without type-checking predicate");
                                continue;
                        }
                        if (!scm_is_eq (optional, SCM_UNDEFINED))
-                               push_extra_token (EXPECT_OPTIONAL, optional);
+                               push_extra_token (here_input (), EXPECT_OPTIONAL, optional);
                }
                return funtype;
        }
-       yylval.scm = sid;
+       yylval = sid;
        return identifier_type (sid);
 }
 
 int
-Lily_lexer::scan_bare_word (string str)
+Lily_lexer::scan_bare_word (const string &str)
 {
        SCM sym = ly_symbol2scm (str.c_str ());
        if ((YYSTATE == notes) || (YYSTATE == chords)) {
                SCM handle = SCM_BOOL_F;
                if (scm_is_pair (pitchname_tab_stack_))
-                       handle = scm_hashq_get_handle (scm_car (pitchname_tab_stack_), sym);
+                       handle = scm_hashq_get_handle (scm_cdar (pitchname_tab_stack_), sym);
                
                if (scm_is_pair (handle)) {
-                       yylval.scm = scm_cdr (handle);
-                       if (unsmob_pitch (yylval.scm)) 
+                       yylval = scm_cdr (handle);
+                       if (unsmob_pitch (yylval))
                            return (YYSTATE == notes) ? NOTENAME_PITCH : TONICNAME_PITCH;
-                       else if (scm_is_symbol (yylval.scm))
+                       else if (scm_is_symbol (yylval))
                            return DRUM_PITCH;
                }
                else if ((YYSTATE == chords)
                        && (handle = scm_hashq_get_handle (chordmodifier_tab_, sym))!= SCM_BOOL_F)
                {
-                   yylval.scm = scm_cdr (handle);
+                   yylval = scm_cdr (handle);
                    return CHORD_MODIFIER;
                }
-               if ((chord_repetition_.repetition_symbol_ != SCM_EOL)
-                   && to_boolean (scm_equal_p (chord_repetition_.repetition_symbol_, sym)))
-                       return CHORD_REPETITION;
        }
-
-       yylval.scm = ly_string2scm (str);
+       yylval = ly_string2scm (str);
        return STRING;
 }
 
 int
 Lily_lexer::get_state () const
 {
-       if (YY_START == extratoken)
-               return hidden_state_;
-       else
-               return YY_START;
+       return YY_START;
 }
 
 bool
@@ -962,8 +1062,15 @@ Lily_lexer::is_figure_state () const
        return get_state () == figures;
 }
 
+// The extra_token parameter specifies how to convert multiple values
+// into additional tokens.  For '#', additional values get pushed as
+// SCM_IDENTIFIER.  For '$', they get checked for their type and get
+// pushed as a corresponding *_IDENTIFIER token.  Since the latter
+// tampers with yylval, it can only be done from the lexer itself, so
+// this function is private.
+
 SCM
-Lily_lexer::eval_scm (SCM readerdata)
+Lily_lexer::eval_scm (SCM readerdata, char extra_token)
 {
        SCM sval = SCM_UNDEFINED;
 
@@ -980,9 +1087,158 @@ Lily_lexer::eval_scm (SCM readerdata)
                error_level_ = 1;
                return SCM_UNSPECIFIED;
        }
+
+       if (extra_token && SCM_VALUESP (sval))
+       {
+               sval = scm_struct_ref (sval, SCM_INUM0);
+
+               if (scm_is_pair (sval)) {
+                       for (SCM p = scm_reverse (scm_cdr (sval));
+                            scm_is_pair (p);
+                            p = scm_cdr (p))
+                       {
+                               SCM v = scm_car (p);
+                               if (Music *m = unsmob_music (v))
+                               {
+                                       if (!unsmob_input (m->get_property ("origin")))
+                                               m->set_spot (override_input (here_input ()));
+                               }
+                                       
+                               int token;
+                               switch (extra_token) {
+                               case '$':
+                                       token = scan_scm_id (v);
+                                       if (!scm_is_eq (yylval, SCM_UNSPECIFIED))
+                                               push_extra_token (here_input (),
+                                                                 token, yylval);
+                                       break;
+                               case '#':
+                                       push_extra_token (here_input (),
+                                                         SCM_IDENTIFIER, v);
+                                       break;
+                               }
+                       }
+                       sval = scm_car (sval);
+               } else
+                       sval = SCM_UNSPECIFIED;
+       }
+
+       if (Music *m = unsmob_music (sval))
+       {
+               if (!unsmob_input (m->get_property ("origin")))
+                       m->set_spot (override_input (here_input ()));
+       }
+
        return sval;
 }
 
+/* Check for valid UTF-8 that has no overlong or surrogate codes and
+   is in the range 0-0x10ffff */
+
+const char *
+Lily_lexer::YYText_utf8 ()
+{
+       const char * const p =  YYText ();
+       for (int i=0; p[i];) {
+               if ((p[i] & 0xff) < 0x80) {
+                       ++i;
+                       continue;
+               }
+               int oldi = i; // start of character
+               int more = 0; // # of followup bytes, 0 if bad
+               switch (p[i++] & 0xff) {
+                       // 0xc0 and 0xc1 are overlong prefixes for
+                       // 0x00-0x3f and 0x40-0x7f respectively, bad.
+               case 0xc2:      // 0x80-0xbf
+               case 0xc3:      // 0xc0-0xff
+               case 0xc4:      // 0x100-0x13f
+               case 0xc5:      // 0x140-0x17f
+               case 0xc6:      // 0x180-0x1bf
+               case 0xc7:      // 0x1c0-0x1ff
+               case 0xc8:      // 0x200-0x23f
+               case 0xc9:      // 0x240-0x27f
+               case 0xca:      // 0x280-0x2bf
+               case 0xcb:      // 0x2c0-0x2ff
+               case 0xcc:      // 0x300-0x33f
+               case 0xcd:      // 0x340-0x37f
+               case 0xce:      // 0x380-0x3bf
+               case 0xcf:      // 0x3c0-0x3ff
+               case 0xd0:      // 0x400-0x43f
+               case 0xd1:      // 0x440-0x47f
+               case 0xd2:      // 0x480-0x4bf
+               case 0xd3:      // 0x4c0-0x4ff
+               case 0xd4:      // 0x500-0x53f
+               case 0xd5:      // 0x540-0x57f
+               case 0xd6:      // 0x580-0x5bf
+               case 0xd7:      // 0x5c0-0x5ff
+               case 0xd8:      // 0x600-0x63f
+               case 0xd9:      // 0x640-0x67f
+               case 0xda:      // 0x680-0x6bf
+               case 0xdb:      // 0x6c0-0x6ff
+               case 0xdc:      // 0x700-0x73f
+               case 0xdd:      // 0x740-0x77f
+               case 0xde:      // 0x780-0x7bf
+               case 0xdf:      // 0x7c0-0x7ff
+                       more = 1; // 2-byte sequences, 0x80-0x7ff
+                       break;
+               case 0xe0:
+                       // don't allow overlong sequences for 0-0x7ff
+                       if ((p[i] & 0xff) < 0xa0)
+                               break;
+               case 0xe1:      // 0x1000-0x1fff
+               case 0xe2:      // 0x2000-0x2fff
+               case 0xe3:      // 0x3000-0x3fff
+               case 0xe4:      // 0x4000-0x4fff
+               case 0xe5:      // 0x5000-0x5fff
+               case 0xe6:      // 0x6000-0x6fff
+               case 0xe7:      // 0x7000-0x7fff
+               case 0xe8:      // 0x8000-0x8fff
+               case 0xe9:      // 0x9000-0x9fff
+               case 0xea:      // 0xa000-0xafff
+               case 0xeb:      // 0xb000-0xbfff
+               case 0xec:      // 0xc000-0xcfff
+                       more = 2; // 3-byte sequences, 0x7ff-0xcfff
+                       break;
+               case 0xed:      // 0xd000-0xdfff
+                       // Don't allow surrogate codes 0xd800-0xdfff
+                       if ((p[i] & 0xff) >= 0xa0)
+                               break;
+               case 0xee:      // 0xe000-0xefff
+               case 0xef:      // 0xf000-0xffff
+                       more = 2; // 3-byte sequences,
+                                 // 0xd000-0xd7ff, 0xe000-0xffff
+                       break;
+               case 0xf0:
+                       // don't allow overlong sequences for 0-0xffff
+                       if ((p[i] & 0xff) < 0x90)
+                               break;
+               case 0xf1:      // 0x40000-0x7ffff
+               case 0xf2:      // 0x80000-0xbffff
+               case 0xf3:      // 0xc0000-0xfffff
+                       more = 3; // 4-byte sequences, 0x10000-0xfffff
+                       break;
+               case 0xf4:
+                       // don't allow more than 0x10ffff
+                       if ((p[i] & 0xff) >= 0x90)
+                               break;
+                       more = 3; // 4-byte sequence, 0x100000-0x10ffff
+                       break;
+               }
+               if (more) {
+                       // check that all continuation bytes are valid
+                       do {
+                               if ((p[i++] & 0xc0) != 0x80)
+                                       break;
+                       } while (--more);
+                       if (!more)
+                               continue;
+               }
+               Input h = here_input ();
+               h.set (h.get_source_file (), h.start () + oldi, h.start () + i);
+               h.warning (_ ("non-UTF-8 input").c_str ());
+       }
+       return p;
+}
 
 
 /*
@@ -1038,30 +1294,18 @@ is_valid_version (string s)
        
 
 /*
-  substitute _ and \,
+  substitute _
 */
 string
 lyric_fudge (string s)
 {
-  char *chars = string_copy (s);
+       size_t i=0;
 
-  for (char *p = chars; *p ; p++)
-    {
-      if (*p == '_' && (p == chars || *(p-1) != '\\'))
-       *p = ' ';
-    }
-  
-  s = string (chars);
-  delete[] chars;
-
-  ssize i = 0; 
-  if ((i = s.find ("\\,")) != NPOS)   // change "\," to TeX's "\c "
-    {
-      * (((char*)s.c_str ()) + i + 1) = 'c';
-      s = s.substr (0, i + 2) + " " + s.substr (i - 2);
-    }
-
-  return s;
+       while ((i = s.find ('_', i)) != string::npos)
+       {
+               s[i++] = ' ';
+       }
+       return s;
 }
 
 /*