]> git.saurik.com Git - wxWidgets.git/blob - src/common/cmdline.cpp
Corrceted background problem with themes.
[wxWidgets.git] / src / common / cmdline.cpp
1 ///////////////////////////////////////////////////////////////////////////////
2 // Name: common/cmdline.cpp
3 // Purpose: wxCmdLineParser implementation
4 // Author: Vadim Zeitlin
5 // Modified by:
6 // Created: 05.01.00
7 // RCS-ID: $Id$
8 // Copyright: (c) 2000 Vadim Zeitlin <zeitlin@dptmaths.ens-cachan.fr>
9 // Licence: wxWindows license
10 ///////////////////////////////////////////////////////////////////////////////
11
12 // ============================================================================
13 // declarations
14 // ============================================================================
15
16 // ----------------------------------------------------------------------------
17 // headers
18 // ----------------------------------------------------------------------------
19
20 #ifdef __GNUG__
21 #pragma implementation "cmdline.h"
22 #endif
23
24 // For compilers that support precompilation, includes "wx.h".
25 #include "wx/wxprec.h"
26
27 #ifdef __BORLANDC__
28 #pragma hdrstop
29 #endif
30
31 #ifndef WX_PRECOMP
32 #include "wx/string.h"
33 #include "wx/log.h"
34 #include "wx/intl.h"
35 #include "wx/dynarray.h"
36 #endif //WX_PRECOMP
37
38 #include "wx/datetime.h"
39 #include "wx/cmdline.h"
40
41 // ----------------------------------------------------------------------------
42 // private functions
43 // ----------------------------------------------------------------------------
44
45 static wxString GetTypeName(wxCmdLineParamType type);
46
47 // ----------------------------------------------------------------------------
48 // private classes
49 // ----------------------------------------------------------------------------
50
51 // an internal representation of an option
52 struct wxCmdLineOption
53 {
54 wxCmdLineOption(wxCmdLineEntryType k,
55 const wxString& shrt,
56 const wxString& lng,
57 const wxString& desc,
58 wxCmdLineParamType typ,
59 int fl)
60 {
61 kind = k;
62
63 shortName = shrt;
64 longName = lng;
65 description = desc;
66
67 type = typ;
68 flags = fl;
69
70 m_hasVal = FALSE;
71 }
72
73 // can't use union easily here, so just store all possible data fields, we
74 // don't waste much (might still use union later if the number of supported
75 // types increases, so always use the accessor functions and don't access
76 // the fields directly!)
77
78 void Check(wxCmdLineParamType typ) const
79 {
80 wxASSERT_MSG( type == typ, _T("type mismatch in wxCmdLineOption") );
81 }
82
83 long GetLongVal() const
84 { Check(wxCMD_LINE_VAL_NUMBER); return m_longVal; }
85 const wxString& GetStrVal() const
86 { Check(wxCMD_LINE_VAL_STRING); return m_strVal; }
87 const wxDateTime& GetDateVal() const
88 { Check(wxCMD_LINE_VAL_DATE); return m_dateVal; }
89
90 void SetLongVal(long val)
91 { Check(wxCMD_LINE_VAL_NUMBER); m_longVal = val; m_hasVal = TRUE; }
92 void SetStrVal(const wxString& val)
93 { Check(wxCMD_LINE_VAL_STRING); m_strVal = val; m_hasVal = TRUE; }
94 void SetDateVal(const wxDateTime val)
95 { Check(wxCMD_LINE_VAL_DATE); m_dateVal = val; m_hasVal = TRUE; }
96
97 void SetHasValue() { m_hasVal = TRUE; }
98 bool HasValue() const { return m_hasVal; }
99
100 public:
101 wxCmdLineEntryType kind;
102 wxString shortName, longName, description;
103 wxCmdLineParamType type;
104 int flags;
105
106 private:
107 bool m_hasVal;
108
109 long m_longVal;
110 wxString m_strVal;
111 wxDateTime m_dateVal;
112 };
113
114 struct wxCmdLineParam
115 {
116 wxCmdLineParam(const wxString& desc,
117 wxCmdLineParamType typ,
118 int fl)
119 : description(desc)
120 {
121 type = typ;
122 flags = fl;
123 }
124
125 wxString description;
126 wxCmdLineParamType type;
127 int flags;
128 };
129
130 WX_DECLARE_OBJARRAY(wxCmdLineOption, wxArrayOptions);
131 WX_DECLARE_OBJARRAY(wxCmdLineParam, wxArrayParams);
132
133 #include "wx/arrimpl.cpp"
134
135 WX_DEFINE_OBJARRAY(wxArrayOptions);
136 WX_DEFINE_OBJARRAY(wxArrayParams);
137
138 // the parser internal state
139 struct wxCmdLineParserData
140 {
141 // options
142 wxString m_switchChars; // characters which may start an option
143
144 bool m_enableLongOptions; // TRUE if long options are enabled
145
146 // cmd line data
147 wxArrayString m_arguments; // == argv, argc == m_arguments.GetCount()
148 wxArrayOptions m_options; // all possible options and switchrs
149 wxArrayParams m_paramDesc; // description of all possible params
150 wxArrayString m_parameters; // all params found
151
152 // methods
153 wxCmdLineParserData();
154 void SetArguments(int argc, char **argv);
155 void SetArguments(const wxString& cmdline);
156
157 int FindOption(const wxString& name);
158 int FindOptionByLongName(const wxString& name);
159 };
160
161 // ============================================================================
162 // implementation
163 // ============================================================================
164
165 // ----------------------------------------------------------------------------
166 // wxCmdLineParserData
167 // ----------------------------------------------------------------------------
168
169 wxCmdLineParserData::wxCmdLineParserData()
170 {
171 m_enableLongOptions = TRUE;
172 #ifdef __UNIX_LIKE__
173 m_switchChars = _T("-");
174 #else // !Unix
175 m_switchChars = _T("/-");
176 #endif
177 }
178
179 void wxCmdLineParserData::SetArguments(int argc, char **argv)
180 {
181 m_arguments.Empty();
182
183 for ( int n = 0; n < argc; n++ )
184 {
185 m_arguments.Add(argv[n]);
186 }
187 }
188
189 void wxCmdLineParserData::SetArguments(const wxString& cmdline)
190 {
191 // either use wxMSW wxApp::ConvertToStandardCommandArgs() or move its logic
192 // here and use this method from it - but don't duplicate the code
193
194 wxFAIL_MSG(_T("TODO"));
195 }
196
197 int wxCmdLineParserData::FindOption(const wxString& name)
198 {
199 size_t count = m_options.GetCount();
200 for ( size_t n = 0; n < count; n++ )
201 {
202 if ( m_options[n].shortName == name )
203 {
204 // found
205 return n;
206 }
207 }
208
209 return wxNOT_FOUND;
210 }
211
212 int wxCmdLineParserData::FindOptionByLongName(const wxString& name)
213 {
214 size_t count = m_options.GetCount();
215 for ( size_t n = 0; n < count; n++ )
216 {
217 if ( m_options[n].longName == name )
218 {
219 // found
220 return n;
221 }
222 }
223
224 return wxNOT_FOUND;
225 }
226
227 // ----------------------------------------------------------------------------
228 // construction and destruction
229 // ----------------------------------------------------------------------------
230
231 void wxCmdLineParser::Init()
232 {
233 m_data = new wxCmdLineParserData;
234 }
235
236 void wxCmdLineParser::SetCmdLine(int argc, char **argv)
237 {
238 m_data->SetArguments(argc, argv);
239 }
240
241 void wxCmdLineParser::SetCmdLine(const wxString& cmdline)
242 {
243 m_data->SetArguments(cmdline);
244 }
245
246 wxCmdLineParser::~wxCmdLineParser()
247 {
248 delete m_data;
249 }
250
251 // ----------------------------------------------------------------------------
252 // options
253 // ----------------------------------------------------------------------------
254
255 void wxCmdLineParser::SetSwitchChars(const wxString& switchChars)
256 {
257 m_data->m_switchChars = switchChars;
258 }
259
260 void wxCmdLineParser::EnableLongOptions(bool enable)
261 {
262 m_data->m_enableLongOptions = enable;
263 }
264
265 // ----------------------------------------------------------------------------
266 // command line construction
267 // ----------------------------------------------------------------------------
268
269 void wxCmdLineParser::SetDesc(const wxCmdLineEntryDesc *desc)
270 {
271 for ( ;; desc++ )
272 {
273 switch ( desc->kind )
274 {
275 case wxCMD_LINE_SWITCH:
276 AddSwitch(desc->shortName, desc->longName, desc->description);
277 break;
278
279 case wxCMD_LINE_OPTION:
280 AddOption(desc->shortName, desc->longName, desc->description,
281 desc->type, desc->flags);
282 break;
283
284 case wxCMD_LINE_PARAM:
285 AddParam(desc->description, desc->type, desc->flags);
286 break;
287
288 default:
289 wxFAIL_MSG( _T("unknown command line entry type") );
290 // still fall through
291
292 case wxCMD_LINE_NONE:
293 return;
294 }
295 }
296 }
297
298 void wxCmdLineParser::AddSwitch(const wxString& shortName,
299 const wxString& longName,
300 const wxString& desc,
301 int flags)
302 {
303 wxASSERT_MSG( m_data->FindOption(shortName) == wxNOT_FOUND,
304 _T("duplicate switch") );
305
306 wxCmdLineOption *option = new wxCmdLineOption(wxCMD_LINE_SWITCH,
307 shortName, longName, desc,
308 wxCMD_LINE_VAL_NONE, flags);
309
310 m_data->m_options.Add(option);
311 }
312
313 void wxCmdLineParser::AddOption(const wxString& shortName,
314 const wxString& longName,
315 const wxString& desc,
316 wxCmdLineParamType type,
317 int flags)
318 {
319 wxASSERT_MSG( m_data->FindOption(shortName) == wxNOT_FOUND,
320 _T("duplicate option") );
321
322 wxCmdLineOption *option = new wxCmdLineOption(wxCMD_LINE_OPTION,
323 shortName, longName, desc,
324 type, flags);
325
326 m_data->m_options.Add(option);
327 }
328
329 void wxCmdLineParser::AddParam(const wxString& desc,
330 wxCmdLineParamType type,
331 int flags)
332 {
333 // do some consistency checks: a required parameter can't follow an
334 // optional one and nothing should follow a parameter with MULTIPLE flag
335 #ifdef __WXDEBUG__
336 if ( !m_data->m_paramDesc.IsEmpty() )
337 {
338 wxCmdLineParam& param = m_data->m_paramDesc.Last();
339
340 wxASSERT_MSG( !(param.flags & wxCMD_LINE_PARAM_MULTIPLE),
341 _T("all parameters after the one with "
342 "wxCMD_LINE_PARAM_MULTIPLE style will be ignored") );
343
344 if ( !(flags & wxCMD_LINE_PARAM_OPTIONAL) )
345 {
346 wxASSERT_MSG( !(param.flags & wxCMD_LINE_PARAM_OPTIONAL),
347 _T("a required parameter can't follow an "
348 "optional one") );
349 }
350 }
351 #endif // Debug
352
353 wxCmdLineParam *param = new wxCmdLineParam(desc, type, flags);
354
355 m_data->m_paramDesc.Add(param);
356 }
357
358 // ----------------------------------------------------------------------------
359 // access to parse command line
360 // ----------------------------------------------------------------------------
361
362 bool wxCmdLineParser::Found(const wxString& name) const
363 {
364 int i = m_data->FindOption(name);
365 wxCHECK_MSG( i != wxNOT_FOUND, FALSE, _T("unknown switch") );
366
367 wxCmdLineOption& opt = m_data->m_options[(size_t)i];
368 if ( !opt.HasValue() )
369 return FALSE;
370
371 return TRUE;
372 }
373
374 bool wxCmdLineParser::Found(const wxString& name, wxString *value) const
375 {
376 int i = m_data->FindOption(name);
377 wxCHECK_MSG( i != wxNOT_FOUND, FALSE, _T("unknown option") );
378
379 wxCmdLineOption& opt = m_data->m_options[(size_t)i];
380 if ( !opt.HasValue() )
381 return FALSE;
382
383 wxCHECK_MSG( value, FALSE, _T("NULL pointer in wxCmdLineOption::Found") );
384
385 *value = opt.GetStrVal();
386
387 return TRUE;
388 }
389
390 bool wxCmdLineParser::Found(const wxString& name, long *value) const
391 {
392 int i = m_data->FindOption(name);
393 wxCHECK_MSG( i != wxNOT_FOUND, FALSE, _T("unknown option") );
394
395 wxCmdLineOption& opt = m_data->m_options[(size_t)i];
396 if ( !opt.HasValue() )
397 return FALSE;
398
399 wxCHECK_MSG( value, FALSE, _T("NULL pointer in wxCmdLineOption::Found") );
400
401 *value = opt.GetLongVal();
402
403 return TRUE;
404 }
405
406 bool wxCmdLineParser::Found(const wxString& name, wxDateTime *value) const
407 {
408 int i = m_data->FindOption(name);
409 wxCHECK_MSG( i != wxNOT_FOUND, FALSE, _T("unknown option") );
410
411 wxCmdLineOption& opt = m_data->m_options[(size_t)i];
412 if ( !opt.HasValue() )
413 return FALSE;
414
415 wxCHECK_MSG( value, FALSE, _T("NULL pointer in wxCmdLineOption::Found") );
416
417 *value = opt.GetDateVal();
418
419 return TRUE;
420 }
421
422 size_t wxCmdLineParser::GetParamCount() const
423 {
424 return m_data->m_parameters.GetCount();
425 }
426
427 wxString wxCmdLineParser::GetParam(size_t n) const
428 {
429 return m_data->m_parameters[n];
430 }
431
432 // ----------------------------------------------------------------------------
433 // the real work is done here
434 // ----------------------------------------------------------------------------
435
436 int wxCmdLineParser::Parse()
437 {
438 bool maybeOption = TRUE; // can the following arg be an option?
439 bool ok = TRUE; // TRUE until an error is detected
440 bool helpRequested = FALSE; // TRUE if "-h" was given
441 bool hadRepeatableParam = FALSE; // TRUE if found param with MULTIPLE flag
442
443 size_t currentParam = 0; // the index in m_paramDesc
444
445 size_t countParam = m_data->m_paramDesc.GetCount();
446
447 // parse everything
448 wxString arg;
449 size_t count = m_data->m_arguments.GetCount();
450 for ( size_t n = 1; ok && (n < count); n++ ) // 0 is program name
451 {
452 arg = m_data->m_arguments[n];
453
454 // special case: "--" should be discarded and all following arguments
455 // should be considered as parameters, even if they start with '-' and
456 // not like options (this is POSIX-like)
457 if ( arg == _T("--") )
458 {
459 maybeOption = FALSE;
460
461 continue;
462 }
463
464 // empty argument or just '-' is not an option but a parameter
465 if ( maybeOption && arg.length() > 1 &&
466 wxStrchr(m_data->m_switchChars, arg[0u]) )
467 {
468 bool isLong;
469 wxString name;
470 int optInd = wxNOT_FOUND; // init to suppress warnings
471
472 // an option or a switch: find whether it's a long or a short one
473 if ( m_data->m_enableLongOptions &&
474 arg[0u] == _T('-') && arg[1u] == _T('-') )
475 {
476 // a long one
477 isLong = TRUE;
478
479 const wxChar *p = arg.c_str() + 2;
480 while ( wxIsalpha(*p) || (*p == _T('-')) )
481 {
482 name += *p++;
483 }
484
485 optInd = m_data->FindOptionByLongName(name);
486 if ( optInd == wxNOT_FOUND )
487 {
488 wxLogError(_("Unknown long option '%s'"), name.c_str());
489 }
490 }
491 else
492 {
493 isLong = FALSE;
494
495 // a short one: as they can be cumulated, we try to find the
496 // longest substring which is a valid option
497 const wxChar *p = arg.c_str() + 1;
498 while ( wxIsalpha(*p) )
499 {
500 name += *p++;
501 }
502
503 size_t len = name.length();
504 do
505 {
506 if ( len == 0 )
507 {
508 // we couldn't find a valid option name in the
509 // beginning of this string
510 wxLogError(_("Unknown option '%s'"), name.c_str());
511
512 break;
513 }
514 else
515 {
516 optInd = m_data->FindOption(name.Left(len));
517
518 // will try with one character less the next time
519 len--;
520 }
521 }
522 while ( optInd == wxNOT_FOUND );
523
524 if ( (optInd != wxNOT_FOUND) && (len != name.length()) )
525 {
526 // our option is only part of this argument, there is
527 // something else in it - it is either the value of this
528 // option or other switches if it is a switch
529 if ( m_data->m_options[(size_t)optInd].kind
530 == wxCMD_LINE_SWITCH )
531 {
532 // pretend that all the rest of the argument is the
533 // next argument, in fact
534 wxString arg2 = arg[0u];
535 arg2 += name.Mid(len + 1); // compensates extra --
536
537 m_data->m_arguments.Insert(arg2, n + 1);
538 count++;
539 }
540 //else: it's our value, we'll deal with it below
541 }
542 }
543
544 if ( optInd == wxNOT_FOUND )
545 {
546 ok = FALSE;
547
548 continue; // will break, in fact
549 }
550
551 wxCmdLineOption& opt = m_data->m_options[(size_t)optInd];
552 if ( opt.kind == wxCMD_LINE_SWITCH )
553 {
554 // nothing more to do
555 opt.SetHasValue();
556
557 if ( opt.flags & wxCMD_LINE_OPTION_HELP )
558 {
559 helpRequested = TRUE;
560
561 // it's not an error, but we still stop here
562 ok = FALSE;
563 }
564 }
565 else
566 {
567 // get the value
568
569 // +1 for leading '-'
570 const wxChar *p = arg.c_str() + 1 + name.length();
571 if ( isLong )
572 {
573 p++; // for another leading '-'
574
575 if ( *p++ != _T('=') )
576 {
577 wxLogError(_("Option '%s' requires a value, '=' "
578 "expected."), name.c_str());
579
580 ok = FALSE;
581 }
582 }
583 else
584 {
585 switch ( *p )
586 {
587 case _T(':'):
588 // the value follows
589 p++;
590 break;
591
592 case 0:
593 // the value is in the next argument
594 if ( ++n == count )
595 {
596 // ... but there is none
597 wxLogError(_("Option '%s' requires a value."),
598 name.c_str());
599
600 ok = FALSE;
601 }
602 else
603 {
604 // ... take it from there
605 p = m_data->m_arguments[n].c_str();
606 }
607 break;
608
609 default:
610 // the value is right here
611 ;
612 }
613 }
614
615 if ( ok )
616 {
617 wxString value = p;
618 switch ( opt.type )
619 {
620 default:
621 wxFAIL_MSG( _T("unknown option type") );
622 // still fall through
623
624 case wxCMD_LINE_VAL_STRING:
625 opt.SetStrVal(value);
626 break;
627
628 case wxCMD_LINE_VAL_NUMBER:
629 {
630 long val;
631 if ( value.ToLong(&val) )
632 {
633 opt.SetLongVal(val);
634 }
635 else
636 {
637 wxLogError(_("'%s' is not a correct "
638 "numeric value for option "
639 "'%s'."),
640 value.c_str(), name.c_str());
641
642 ok = FALSE;
643 }
644 }
645 break;
646
647 case wxCMD_LINE_VAL_DATE:
648 {
649 wxDateTime dt;
650 const wxChar *res = dt.ParseDate(value);
651 if ( !res || *res )
652 {
653 wxLogError(_("Options '%s': '%s' cannot "
654 "be converted to a date."),
655 name.c_str(), value.c_str());
656
657 ok = FALSE;
658 }
659 else
660 {
661 opt.SetDateVal(dt);
662 }
663 }
664 break;
665 }
666 }
667 }
668 }
669 else
670 {
671 // a parameter
672 if ( currentParam < countParam )
673 {
674 wxCmdLineParam& param = m_data->m_paramDesc[currentParam];
675
676 // TODO check the param type
677
678 m_data->m_parameters.Add(arg);
679
680 if ( !(param.flags & wxCMD_LINE_PARAM_MULTIPLE) )
681 {
682 currentParam++;
683 }
684 else
685 {
686 wxASSERT_MSG( currentParam == countParam - 1,
687 _T("all parameters after the one with "
688 "wxCMD_LINE_PARAM_MULTIPLE style "
689 "are ignored") );
690
691 // remember that we did have this last repeatable parameter
692 hadRepeatableParam = TRUE;
693 }
694 }
695 else
696 {
697 wxLogError(_("Unexpected parameter '%s'"), arg.c_str());
698
699 ok = FALSE;
700 }
701 }
702 }
703
704 // verify that all mandatory options were given
705 if ( ok )
706 {
707 size_t countOpt = m_data->m_options.GetCount();
708 for ( size_t n = 0; ok && (n < countOpt); n++ )
709 {
710 wxCmdLineOption& opt = m_data->m_options[n];
711 if ( (opt.flags & wxCMD_LINE_OPTION_MANDATORY) && !opt.HasValue() )
712 {
713 wxString optName;
714 if ( !opt.longName )
715 {
716 optName = opt.shortName;
717 }
718 else
719 {
720 optName.Printf(_("%s (or %s)"),
721 opt.shortName.c_str(),
722 opt.longName.c_str());
723 }
724
725 wxLogError(_("The value for the option '%s' must be specified."),
726 optName.c_str());
727
728 ok = FALSE;
729 }
730 }
731
732 for ( ; ok && (currentParam < countParam); currentParam++ )
733 {
734 wxCmdLineParam& param = m_data->m_paramDesc[currentParam];
735 if ( (currentParam == countParam - 1) &&
736 (param.flags & wxCMD_LINE_PARAM_MULTIPLE) &&
737 hadRepeatableParam )
738 {
739 // special case: currentParam wasn't incremented, but we did
740 // have it, so don't give error
741 continue;
742 }
743
744 if ( !(param.flags & wxCMD_LINE_PARAM_OPTIONAL) )
745 {
746 wxLogError(_("The required parameter '%s' was not specified."),
747 param.description.c_str());
748
749 ok = FALSE;
750 }
751 }
752 }
753
754 if ( !ok )
755 {
756 Usage();
757 }
758
759 return ok ? 0 : helpRequested ? -1 : 1;
760 }
761
762 // ----------------------------------------------------------------------------
763 // give the usage message
764 // ----------------------------------------------------------------------------
765
766 void wxCmdLineParser::Usage()
767 {
768 wxString brief, detailed;
769 brief.Printf(_("Usage: %s"), wxTheApp->GetAppName().c_str());
770
771 size_t n, count = m_data->m_options.GetCount();
772 for ( n = 0; n < count; n++ )
773 {
774 wxCmdLineOption& opt = m_data->m_options[n];
775
776 brief << _T(' ');
777 if ( !(opt.flags & wxCMD_LINE_OPTION_MANDATORY) )
778 {
779 brief << _T('[');
780 }
781
782 brief << _T('-') << opt.shortName;
783 detailed << _T(" -") << opt.shortName;
784 if ( !!opt.longName )
785 {
786 detailed << _T(" --") << opt.longName;
787 }
788
789 if ( opt.kind != wxCMD_LINE_SWITCH )
790 {
791 wxString val;
792 val << _T('<') << GetTypeName(opt.type) << _T('>');
793 brief << _T(' ') << val;
794 detailed << (!opt.longName ? _T(':') : _T('=')) << val;
795 }
796
797 if ( !(opt.flags & wxCMD_LINE_OPTION_MANDATORY) )
798 {
799 brief << _T(']');
800 }
801
802 detailed << _T('\t') << opt.description << _T('\n');
803 }
804
805 count = m_data->m_paramDesc.GetCount();
806 for ( n = 0; n < count; n++ )
807 {
808 wxCmdLineParam& param = m_data->m_paramDesc[n];
809
810 brief << _T(' ');
811 if ( param.flags & wxCMD_LINE_PARAM_OPTIONAL )
812 {
813 brief << _T('[');
814 }
815
816 brief << param.description;
817
818 if ( param.flags & wxCMD_LINE_PARAM_MULTIPLE )
819 {
820 brief << _T("...");
821 }
822
823 if ( param.flags & wxCMD_LINE_PARAM_OPTIONAL )
824 {
825 brief << _T(']');
826 }
827 }
828
829 wxLogMessage(brief);
830 wxLogMessage(detailed);
831 }
832
833 // ----------------------------------------------------------------------------
834 // global functions
835 // ----------------------------------------------------------------------------
836
837 static wxString GetTypeName(wxCmdLineParamType type)
838 {
839 wxString s;
840 switch ( type )
841 {
842 default:
843 wxFAIL_MSG( _T("unknown option type") );
844 // still fall through
845
846 case wxCMD_LINE_VAL_STRING: s = _("str"); break;
847 case wxCMD_LINE_VAL_NUMBER: s = _("num"); break;
848 case wxCMD_LINE_VAL_DATE: s = _("date"); break;
849 }
850
851 return s;
852 }