1 %top{
2 /*
3    Copyright 2021 Northern.tech AS
4 
5    This file is part of CFEngine 3 - written and maintained by Northern.tech AS.
6 
7    This program is free software; you can redistribute it and/or modify it
8    under the terms of the GNU General Public License as published by the
9    Free Software Foundation; version 3.
10 
11    This program is distributed in the hope that it will be useful,
12    but WITHOUT ANY WARRANTY; without even the implied warranty of
13    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14    GNU General Public License for more details.
15 
16   You should have received a copy of the GNU General Public License
17   along with this program; if not, write to the Free Software
18   Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA
19 
20   To the extent this program is licensed as part of the Enterprise
21   versions of CFEngine, the applicable Commercial Open Source License
22   (COSL) may apply to this file if you as a licensee so wish it. See
23   included file COSL.txt.
24 */
25 
26 /*******************************************************************/
27 /*                                                                 */
28 /*  LEXER for cfengine 3                                           */
29 /*                                                                 */
30 /*******************************************************************/
31 
32 #include <platform.h>
33 
34 #include <cf3parse.h>
35 #include <parser_state.h>
36 #include <file_lib.h>
37 #include <regex.h>
38 #include <feature.h>
39 #include <version_comparison.h>
40 }
41 
42 %{
43 /* yyinput/input are generated and unused */
44 
45 #if defined(__GNUC__)
46 # define GCC_VERSION (__GNUC__ * 10000 + __GNUC_MINOR__ * 100 + __GNUC_PATCHLEVEL__)
47 # if GCC_VERSION >= 40200
48 #  pragma GCC diagnostic ignored "-Wunused-function"
49 # endif
50 #endif
51 
52 #undef malloc
53 #undef realloc
54 #define malloc xmalloc
55 #define realloc xrealloc
56 
57 //#define ParserDebug if (LogGetGlobalLevel() >= LOG_LEVEL_DEBUG) printf
58 #define ParserDebug(...) ((void) 0)
59 #define P PARSER_STATE
60 
61 static pcre *context_expression_whitespace_rx = NULL;
62 
63 static int DeEscapeQuotedString(const char *from, char *to);
64 
yywrap(void)65 int yywrap(void)
66 {
67 return 1;
68 }
69 
yyuseraction()70 static void yyuseraction()
71 {
72 P.offsets.current += yyleng;
73 }
74 
75 #define YY_USER_ACTION yyuseraction();
76 
77 // Do not use lex - flex only
78 
79 %}
80 
81 %x if_ignore_state
82 
83 space      [ \t]+
84 
85 newline    ([\n]|[\xd][\xa])
86 
87 eat_line  ([\n]|[\xd][\xa]).*
88 
89 comment    #[^\n]*
90 
91 macro_if_minimum_version  @if\ minimum_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
92 macro_if_maximum_version  @if\ maximum_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
93 macro_if_before_version   @if\ before_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
94 macro_if_at_version       @if\ at_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
95 macro_if_after_version    @if\ after_version\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
96 macro_if_between_versions @if\ between_versions\([0-9]{1,5}(\.[0-9]{1,5}){0,2}\ *,\ *[0-9]{1,5}(\.[0-9]{1,5}){0,2}\)
97 macro_if_feature          @if\ feature\([a-zA-Z0-9_]+\)
98 macro_else  @else
99 macro_endif @endif
100 macro_line [^@\n\xd\xa].*
101 
102 bundle     bundle
103 
104 body       body
105 
106 promise    promise
107 
108 nakedvar   [$@][(][a-zA-Z0-9_\[\]\200-\377.:]+[)]|[$@][{][a-zA-Z0-9_\[\]\200-\377.:]+[}]|[$@][(][a-zA-Z0-9_\200-\377.:]+[\[][a-zA-Z0-9_$(){}\200-\377.:]+[\]]+[)]|[$@][{][a-zA-Z0-9_\200-\377.:]+[\[][a-zA-Z0-9_$(){}\200-\377.:]+[\]]+[}]
109 
110 identifier [a-zA-Z0-9_\200-\377]+
111 
112 symbol     [a-zA-Z0-9_\200-\377]+[:][a-zA-Z0-9_\200-\377]+
113 
114 fat_arrow  =>
115 
116 thin_arrow ->
117 
118 /*
119  * Three types of quoted strings:
120  *
121  * - string in double quotes, starts with double quote, runs until another
122  *   double quote, \" masks the double quote.
123  * - string in single quotes, starts with single quote, runs until another
124  *   single quote, \' masks the single quote.
125  * - string in backquotes, starts with backquote, runs until another backquote.
126  *
127  * The same rule formatted for the better readability:
128  *
129  * <qstring> := \" <dq> \" | \' <sq> \' | ` <bq> `
130  * <dq> = <dqs>*
131  * <dqs> = \\ <any> | [^"\\]
132  * <sq> = <sqs>*
133  * <sqs> = \\ <any> | [^'\\]
134  * <bq> = <bqs>*
135  * <bqs> = [^`]
136  * <any> = . | \n
137  *
138  */
139 
140 qstring        \"((\\(.|\n))|[^"\\])*\"|\'((\\(.|\n))|[^'\\])*\'|`[^`]*`
141 
142 class          [.|&!()a-zA-Z0-9_\200-\377:][\t .|&!()a-zA-Z0-9_\200-\377:]*::
143 varclass       (\"[^"\0]*\"|\'[^'\0]*\')::
144 
145 promise_guard  [a-zA-Z_]+:
146 
147 %%
148 {eat_line}             {
149                           free(P.current_line);
150                           P.current_line = xstrdup(yytext+1);
151                           ParserDebug("L:line %s\n", P.current_line);
152                           P.line_no++;
153                           P.line_pos = 1;
154                           // push back on stack and skip first char
155                           yyless(1);
156                       }
157 
158 {macro_if_minimum_version} {
159                         if (  P.line_pos != 1 )
160                         {
161                              yyerror("fatal: macro @if must be at beginning of the line.");
162                              return 0;
163                         }
164                         if (P.if_depth > 0)
165                         {
166                           yyerror("fatal: nested @if macros are not allowed");
167                           return 0;
168                         }
169 
170                         P.if_depth++;
171 
172                         const char* minimum = yytext+20;
173                         ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, minimum);
174 
175                         VersionComparison result = CompareVersion(Version(), minimum);
176                         if (result == VERSION_GREATER || result == VERSION_EQUAL)
177                         {
178                             ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
179                         }
180                         else if (result == VERSION_SMALLER)
181                         {
182                             ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
183                             BEGIN(if_ignore_state);
184                         }
185                         else
186                         {
187                             assert(result == VERSION_ERROR);
188                             yyerror("fatal: macro @if requested an unparseable version");
189                         }
190                       }
191 
192 {macro_if_maximum_version} {
193                         if (  P.line_pos != 1 )
194                         {
195                              yyerror("fatal: macro @if must be at beginning of the line.");
196                              return 0;
197                         }
198                         if (P.if_depth > 0)
199                         {
200                           yyerror("fatal: nested @if macros are not allowed");
201                           return 0;
202                         }
203 
204                         P.if_depth++;
205 
206                         const char* maximum = yytext+20;
207                         ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, maximum);
208 
209                         VersionComparison result = CompareVersion(Version(), maximum);
210                         if (result == VERSION_SMALLER || result == VERSION_EQUAL)
211                         {
212                             ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
213                         }
214                         else if (result == VERSION_GREATER)
215                         {
216                             ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
217                             BEGIN(if_ignore_state);
218                         }
219                         else
220                         {
221                             assert(result == VERSION_ERROR);
222                             yyerror("fatal: macro @if requested an unparseable version");
223                         }
224                       }
225 
226 {macro_if_before_version} {
227                         if (  P.line_pos != 1 )
228                         {
229                              yyerror("fatal: macro @if must be at beginning of the line.");
230                              return 0;
231                         }
232                         if (P.if_depth > 0)
233                         {
234                           yyerror("fatal: nested @if macros are not allowed");
235                           return 0;
236                         }
237 
238                         P.if_depth++;
239 
240                         const char* target = yytext + strlen("@if before_version(");
241                         ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target);
242 
243                         VersionComparison result = CompareVersion(Version(), target);
244                         if (result == VERSION_SMALLER)
245                         {
246                             ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
247                         }
248                         else if (result == VERSION_GREATER || result == VERSION_EQUAL)
249                         {
250                             ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
251                             BEGIN(if_ignore_state);
252                         }
253                         else
254                         {
255                             assert(result == VERSION_ERROR);
256                             yyerror("fatal: macro @if requested an unparseable version");
257                         }
258                       }
259 
260 {macro_if_at_version} {
261                         if (  P.line_pos != 1 )
262                         {
263                              yyerror("fatal: macro @if must be at beginning of the line.");
264                              return 0;
265                         }
266                         if (P.if_depth > 0)
267                         {
268                           yyerror("fatal: nested @if macros are not allowed");
269                           return 0;
270                         }
271 
272                         P.if_depth++;
273 
274                         const char* target = yytext + strlen("@if at_version(");
275                         ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target);
276 
277                         VersionComparison result = CompareVersion(Version(), target);
278                         if (result == VERSION_EQUAL)
279                         {
280                             ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
281                         }
282                         else if (result == VERSION_GREATER || result == VERSION_SMALLER)
283                         {
284                             ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
285                             BEGIN(if_ignore_state);
286                         }
287                         else
288                         {
289                             assert(result == VERSION_ERROR);
290                             yyerror("fatal: macro @if requested an unparseable version");
291                         }
292                       }
293 
294 {macro_if_after_version} {
295                         if (  P.line_pos != 1 )
296                         {
297                              yyerror("fatal: macro @if must be at beginning of the line.");
298                              return 0;
299                         }
300                         if (P.if_depth > 0)
301                         {
302                           yyerror("fatal: nested @if macros are not allowed");
303                           return 0;
304                         }
305 
306                         P.if_depth++;
307 
308                         const char* target = yytext + strlen("@if after_version(");
309                         ParserDebug("\tL:macro @if %d:version=%s\n", P.line_pos, target);
310 
311                         VersionComparison result = CompareVersion(Version(), target);
312                         if (result == VERSION_GREATER)
313                         {
314                             ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
315                         }
316                         else if (result == VERSION_EQUAL || result == VERSION_SMALLER)
317                         {
318                             ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
319                             BEGIN(if_ignore_state);
320                         }
321                         else
322                         {
323                             assert(result == VERSION_ERROR);
324                             yyerror("fatal: macro @if requested an unparseable version");
325                         }
326                       }
327 
328 {macro_if_between_versions} {
329                         if ( P.line_pos != 1 )
330                         {
331                              yyerror("fatal: macro @if must be at beginning of the line.");
332                              return 0;
333                         }
334                         if (P.if_depth > 0)
335                         {
336                           yyerror("fatal: nested @if macros are not allowed");
337                           return 0;
338                         }
339 
340                         P.if_depth++;
341 
342                         const char* from = yytext + strlen("@if between_versions(");
343 
344                         const char *to = strchr(from, ',');
345                         to += 1;
346                         while (*to == ' ')
347                         {
348                             to += 1;
349                         }
350 
351                         ParserDebug("\tL:macro @if %d:between_versions(%s\n", P.line_pos, from);
352 
353                         VersionComparison a = CompareVersion(Version(), from);
354                         VersionComparison b = CompareVersion(Version(), to);
355                         if ((a == VERSION_EQUAL || a == VERSION_GREATER)
356                             && (b == VERSION_EQUAL || b == VERSION_SMALLER))
357                         {
358                             ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
359                         }
360                         else
361                         {
362                             ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
363                             BEGIN(if_ignore_state);
364                         }
365                       }
366 
367 {macro_if_feature}    {
368                         if (  P.line_pos != 1 )
369                         {
370                           yyerror("fatal: macro @if must be at beginning of the line.");
371                           return 0;
372                         }
373 
374                         char* feature_text = yytext+12;
375                         // remove trailing ')'
376                         feature_text[strlen(feature_text)-1] = 0;
377                         ParserDebug("\tL:macro @if %d:feature=%s\n", P.line_pos, feature_text);
378                         {
379                           if (P.if_depth > 0)
380                           {
381                             yyerror("fatal: nested @if macros are not allowed");
382                             return 0;
383                           }
384 
385                           P.if_depth++;
386 
387                           if (KnownFeature(feature_text))
388                           {
389                             ParserDebug("\tL:macro @if %d:accepted to next @endif\n", P.line_pos);
390                           }
391                           else
392                           {
393                             /* ignore to the next @endif */
394                             ParserDebug("\tL:macro @if %d:ignoring to next @endif or EOF\n", P.line_pos);
395                             BEGIN(if_ignore_state);
396                           }
397                         }
398                       }
399 
400 {macro_endif}         {
401                         if (  P.line_pos != 1 )
402                         {
403                           yyerror("fatal: macro @endif must be at beginning of the line.");
404                           return 0;
405                         }
406 
407                         ParserDebug("\tL:macro @endif %d\n", P.line_pos);
408                         BEGIN(INITIAL);
409                         if (P.if_depth <= 0)
410                         {
411                           yyerror("fatal: @endif macros without a matching @if are not allowed");
412                           return 0;
413                         }
414                         P.if_depth--;
415                       }
416 
417 
418 <if_ignore_state>{macro_line}  {
419                                   ParserDebug("\tL:inside macro @if, ignoring line text:\"%s\"\n", yytext);
420                                }
421 
422 <if_ignore_state>{macro_else} {
423                                 ParserDebug("\tL:macro @else, will no longer ignore lines\n", P.line_pos);
424                                 BEGIN(INITIAL);
425                               }
426 {macro_else}                  {
427                                 if (P.if_depth <= 0)
428                                 {
429                                   yyerror("fatal: @else macro without a matching @if are not allowed");
430                                   return 0;
431                                 }
432                                 ParserDebug("\tL:macro @else, will now ignore lines\n", P.line_pos);
433                                 BEGIN(if_ignore_state);
434                               }
435 
436 <if_ignore_state>{macro_endif} {
437                                  ParserDebug("\tL:macro @endif %d\n", P.line_pos);
438                                  BEGIN(INITIAL);
439                                  if (P.if_depth <= 0)
440                                  {
441                                    yyerror("fatal: @endif macros without a matching @if are not allowed");
442                                    return 0;
443                                  }
444                                  P.if_depth--;
445                                }
446 
447 <if_ignore_state>{newline} {
448                            }
449 
450 <if_ignore_state>.    {
451                           /* eat up al unknown chars when line starts with @*/
452                           ParserDebug("\tL:inside macro @if, ignoring char text:\"%s\"\n", yytext);
453                       }
454 
455 {bundle}              {
456                           /* Note this has to come before "id" since it is a subset of id */
457 
458                           if (P.currentclasses != NULL)
459                           {
460                               free(P.currentclasses);
461                               P.currentclasses = NULL;
462                           }
463 
464                           if (P.currentvarclasses != NULL)
465                           {
466                               free(P.currentvarclasses);
467                               P.currentvarclasses = NULL;
468                           }
469 
470                           P.line_pos += yyleng;
471                           ParserDebug("\tL:bundle %d\n", P.line_pos);
472                           return BUNDLE;
473                       }
474 
475 {body}                {
476                           /* Note this has to come before "id" since it is a subset of id */
477 
478                           if (P.currentclasses != NULL)
479                           {
480                               free(P.currentclasses);
481                               P.currentclasses = NULL;
482                           }
483 
484                           if (P.currentvarclasses != NULL)
485                           {
486                               free(P.currentvarclasses);
487                               P.currentvarclasses = NULL;
488                           }
489 
490                           P.line_pos += yyleng;
491                           ParserDebug("\tL:body %d\n", P.line_pos);
492                           return BODY;
493                       }
494 
495 {promise}             {
496                           /* Note this has to come before "id" since it is a subset of id */
497 
498                           if (P.currentclasses != NULL)
499                           {
500                               free(P.currentclasses);
501                               P.currentclasses = NULL;
502                           }
503 
504                           if (P.currentvarclasses != NULL)
505                           {
506                               free(P.currentvarclasses);
507                               P.currentvarclasses = NULL;
508                           }
509 
510                           P.line_pos += yyleng;
511                           ParserDebug("\tL:promise %d\n", P.line_pos);
512                           return PROMISE;
513                       }
514 
515 {identifier}          {
516                           P.offsets.last_id = P.offsets.current - yyleng;
517                           P.line_pos += yyleng;
518                           ParserDebug("\tL:id %s %d\n", yytext, P.line_pos);
519                           if (yyleng  > CF_MAXVARSIZE-1)
520                           {
521                               yyerror("identifier too long");
522                           }
523                           strncpy(P.currentid, yytext, CF_MAXVARSIZE - 1);
524                           return IDENTIFIER;
525                       }
526 
527 
528 {symbol}              {
529                           P.offsets.last_id = P.offsets.current - yyleng;
530                           P.line_pos += yyleng;
531                           ParserDebug("\tL:symbol %s %d\n", yytext, P.line_pos);
532                           if (yyleng > CF_MAXVARSIZE-1)
533                           {
534                               yyerror("qualified identifier too long");
535                           }
536                           strncpy(P.currentid, yytext, CF_MAXVARSIZE - 1);
537                           return IDENTIFIER;
538                       }
539 
540 
541 {fat_arrow}           {
542                           P.line_pos += yyleng;
543                           ParserDebug("\tL:assign %d\n", P.line_pos);
544                           return FAT_ARROW;
545                       }
546 
547 {thin_arrow}          {
548                           P.line_pos += yyleng;
549                           ParserDebug("\tL:arrow %d\n", P.line_pos);
550                           return THIN_ARROW;
551                       }
552 
553 {varclass}            {
554                           char *tmp = NULL;
555 
556                           P.line_pos += yyleng;
557                           ParserDebug("\tL:varclass %s %d\n", yytext, P.line_pos);
558 
559                           tmp = xstrdup(yytext+1); // remove leading quote
560                           tmp[yyleng-4] = '\0'; // remove tail quote plus ::
561 
562                           if (P.currentclasses != NULL)
563                           {
564                               free(P.currentclasses);
565                               P.currentclasses = NULL;
566                           }
567 
568                           if (P.currentvarclasses != NULL)
569                           {
570                               free(P.currentvarclasses);
571                               P.currentvarclasses = NULL;
572                           }
573 
574                           P.currentvarclasses = xstrdup(tmp);
575 
576                           free(tmp);
577                           return CLASS_GUARD;
578                       }
579 
580 {class}               {
581                           char *tmp = NULL;
582 
583                           P.line_pos += yyleng;
584                           ParserDebug("\tL:class %s %d\n", yytext, P.line_pos);
585                           if (context_expression_whitespace_rx == NULL)
586                           {
587                               context_expression_whitespace_rx = CompileRegex(CFENGINE_REGEX_WHITESPACE_IN_CONTEXTS);
588                           }
589 
590                           if (context_expression_whitespace_rx == NULL)
591                           {
592                               yyerror("The context expression whitespace regular expression could not be compiled, aborting.");
593                           }
594 
595                           if (StringMatchFullWithPrecompiledRegex(context_expression_whitespace_rx, yytext))
596                           {
597                               yyerror("class names can't be separated by whitespace without an intervening operator");
598                           }
599 
600                           tmp = xstrdup(yytext);
601                           tmp[yyleng-2] = '\0';
602 
603                           if (P.currentclasses != NULL)
604                           {
605                               free(P.currentclasses);
606                               P.currentclasses = NULL;
607                           }
608 
609                           if (P.currentvarclasses != NULL)
610                           {
611                               free(P.currentvarclasses);
612                               P.currentvarclasses = NULL;
613                           }
614 
615                           P.currentclasses = xstrdup(tmp);
616 
617                           free(tmp);
618                           return CLASS_GUARD;
619                       }
620 
621 {promise_guard}       {
622                           char *tmp = NULL;
623 
624                           P.line_pos += yyleng;
625                           ParserDebug("\tL:promise_guard %s %d\n", yytext, P.line_pos);
626                           P.offsets.last_promise_guard_id = P.offsets.current - yyleng;
627 
628                           tmp = xstrdup(yytext);
629                           assert(tmp[yyleng - 1] == ':');
630                           tmp[yyleng - 1] = '\0'; // Exclude trailing colon in promise guard
631                           assert(strlen(tmp) > 0);
632                           assert(tmp[strlen(tmp) - 1] != ':');
633 
634                           strncpy(P.currenttype, tmp, CF_MAXVARSIZE - 1);
635 
636                           if (P.currentclasses != NULL)
637                           {
638                               free(P.currentclasses);
639                               P.currentclasses = NULL;
640                           }
641 
642                           if (P.currentvarclasses != NULL)
643                           {
644                               free(P.currentvarclasses);
645                               P.currentvarclasses = NULL;
646                           }
647 
648                           free(tmp);
649                           return PROMISE_GUARD;
650                       }
651 
652 {qstring}             {
653                           char *tmp = NULL;
654                           int less = 0;
655 
656                           P.offsets.last_string = P.offsets.current - yyleng;
657                           P.line_pos += yyleng;
658                           ParserDebug("\tL:qstring %s %d\n", yytext, P.line_pos);
659 
660                           for (char *c = yytext; *c; ++c)
661                           {
662                               if (*c == '\n')
663                               {
664                                   P.line_no++;
665                               }
666                           }
667 
668                           tmp = xmalloc(yyleng + 1);
669 
670                           if ((less = DeEscapeQuotedString(yytext,tmp)) > 0)
671                           {
672                               yyless(less);
673                               P.offsets.current -= less;
674                           }
675 
676                           if (P.currentstring)
677                           {
678                               free(P.currentstring);
679                           }
680 
681                           P.currentstring = xstrdup(tmp);
682 
683                           free(tmp);
684                           return QUOTED_STRING;
685                       }
686 
687 
688 {nakedvar}            {
689                           P.line_pos += yyleng;
690                           ParserDebug("\tL: %s %d\n", yytext, P.line_pos);
691                           if (P.currentstring)
692                           {
693                               free(P.currentstring);
694                           }
695                           P.currentstring = xstrdup(yytext);
696                           return NAKEDVAR;
697                       }
698 
699 {space}+              {
700                           P.line_pos += yyleng;
701                       }
702 
703 {comment}             {
704                       }
705 
706 
707 .                     {
708                           P.line_pos++;
709                           return yytext[0];
710                       }
711 
712 <if_ignore_state><<EOF>>     {
713                                if (P.if_depth > 0)
714                                {
715                                  yyerror("EOF seen while @if was waiting for @endif in ignore_state");
716                                  return 0;
717                                }
718                              }
719 
720 <<EOF>>     {
721               if (P.if_depth > 0)
722               {
723                 yyerror("EOF seen while @if was waiting for @endif without ignore_state");
724               }
725               return 0; // loops forever without this
726             }
727 
728 %%
729 
730 static int DeEscapeQuotedString(const char *from, char *to)
731 {
732     char *cp;
733     const char *sp;
734     char start = *from;
735     int len = strlen(from);
736 
737     if (len == 0)
738     {
739         return 0;
740     }
741 
742     for (sp = from + 1, cp = to; (sp - from) < len; sp++, cp++)
743     {
744         if ((*sp == start))
745         {
746             *(cp) = '\0';
747 
748             if (*(sp + 1) != '\0')
749             {
750                 return (2 + (sp - from));
751             }
752 
753             return 0;
754         }
755 
756       if (*sp == '\\')
757       {
758             switch (*(sp + 1))
759             {
760             case '\n':
761                 sp += 2;
762                 break;
763 
764             case ' ':
765                 break;
766 
767             case '\\':
768             case '\"':
769             case '\'':
770                 sp++;
771                 break;
772             }
773         }
774 
775         *cp = *sp;
776     }
777 
778     yyerror("Runaway string");
779     *(cp) = '\0';
780     return 0;
781 }
782 
783 
784 /* EOF */
785