2007-09-25 Marek Safar <marek.safar@gmail.com>
[mono.git] / mcs / gmcs / linq.cs
1 //
2 // linq.cs: support for query expressions
3 //
4 // Authors: Marek Safar (marek.safar@gmail.com)
5 //
6 // Licensed under the terms of the GNU GPL
7 //
8 // (C) 2007 Novell, Inc
9 //
10
11 using System;
12 using System.Reflection;
13 using System.Collections;
14
15 namespace Mono.CSharp.Linq
16 {
17         // NOTES:
18         // Expression should be IExpression to save some memory and make a few things
19         // easier to read
20         //
21         //
22
23         public class QueryExpression : AQueryClause
24         {
25                 LocatedToken variable;
26
27                 public QueryExpression (LocatedToken variable, AQueryClause query)
28                         : base (null, query.Location)
29                 {
30                         this.variable = variable;
31                         this.next = query;
32                 }
33
34                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
35                 {
36                         Parameter p = CreateBlockParameter (variable);
37                         return next.BuildQueryClause (ec, lSide, p, ti);
38                 }
39
40                 public override Expression DoResolve (EmitContext ec)
41                 {
42                         Expression e = BuildQueryClause (ec, null, null, null);
43                         e = e.Resolve (ec);
44                         return e;
45                 }
46
47                 protected override string MethodName {
48                         get { throw new NotSupportedException (); }
49                 }
50         }
51
52         public abstract class AQueryClause : Expression
53         {
54                 class QueryExpressionAccess : MemberAccess
55                 {
56                         public QueryExpressionAccess (Expression expr, string methodName, Location loc)
57                                 : base (expr, methodName, loc)
58                         {
59                         }
60
61                         public QueryExpressionAccess (Expression expr, string methodName, TypeArguments typeArguments, Location loc)
62                                 : base (expr, methodName, typeArguments, loc)
63                         {
64                         }
65
66                         protected override Expression Error_MemberLookupFailed (Type container_type, Type qualifier_type,
67                                 Type queried_type, string name, string class_name, MemberTypes mt, BindingFlags bf)
68                         {
69                                 Report.Error (1935, loc, "An implementation of `{0}' query expression pattern could not be found. " +
70                                         "Are you missing `System.Linq' using directive or `System.Core.dll' assembly reference?",
71                                         name);
72                                 return null;
73                         }
74                 }
75
76                 class QueryExpressionInvocation : Invocation
77                 {
78                         public QueryExpressionInvocation (QueryExpressionAccess expr, ArrayList arguments)
79                                 : base (expr, arguments)
80                         {
81                         }
82
83                         protected override MethodGroupExpr DoResolveOverload (EmitContext ec)
84                         {
85                                 int errors = Report.Errors;
86                                 MethodGroupExpr rmg = mg.OverloadResolve (ec, Arguments, true, loc);
87                                 if (rmg == null && errors == Report.Errors) {
88                                         // TODO: investigate whether would be better to re-use extension methods error handling
89                                         Report.Error (1936, loc, "An implementation of `{0}' query expression pattern for source type `{1}' could not be found",
90                                                 mg.Name, TypeManager.CSharpName (mg.Type));
91                                 }
92
93                                 return rmg;
94                         }
95                 }
96
97                 public AQueryClause next;
98                 /*protected*/ public Expression expr;
99
100                 protected AQueryClause (Expression expr, Location loc)
101                 {
102                         this.expr = expr;
103                         this.loc = loc;
104                 }
105                 
106                 protected override void CloneTo (CloneContext clonectx, Expression target)
107                 {
108                         AQueryClause t = (AQueryClause) target;
109                         if (expr != null)
110                                 t.expr = expr.Clone (clonectx);
111                         
112                         if (next != null)
113                                 t.next = (AQueryClause)next.Clone (clonectx);
114                 }
115
116                 public override Expression DoResolve (EmitContext ec)
117                 {
118                         return expr.DoResolve (ec);
119                 }
120
121                 public virtual Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
122                 {
123                         ArrayList args = new ArrayList (1);
124                         args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
125                         lSide = CreateQueryExpression (lSide, args);
126                         if (next != null) {
127                                 Select s = next as Select;
128                                 if (s == null || s.IsRequired (parameter))
129                                         return next.BuildQueryClause (ec, lSide, parameter, ti);
130                                         
131                                 // Skip transparent select clause if any clause follows
132                                 if (next.next != null)
133                                         return next.next.BuildQueryClause (ec, lSide, parameter, ti);
134                         }
135
136                         return lSide;
137                 }
138
139                 protected static Parameter CreateBlockParameter (LocatedToken li)
140                 {
141                         return new ImplicitQueryParameter (li);
142                 }
143
144                 protected Invocation CreateQueryExpression (Expression lSide, ArrayList arguments)
145                 {
146                         return new QueryExpressionInvocation (
147                                 new QueryExpressionAccess (lSide, MethodName, loc), arguments);
148                 }
149
150                 protected Invocation CreateQueryExpression (Expression lSide, TypeArguments typeArguments, ArrayList arguments)
151                 {
152                         return new QueryExpressionInvocation (
153                                 new QueryExpressionAccess (lSide, MethodName, typeArguments, loc), arguments);
154                 }
155
156                 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter parameter, TransparentIdentifiersScope ti)
157                 {
158                         return CreateSelectorArgument (ec, expr, new Parameter [] { parameter }, ti);
159                 }
160
161                 protected Argument CreateSelectorArgument (EmitContext ec, Expression expr, Parameter[] parameters, TransparentIdentifiersScope ti)
162                 {
163                         Parameters p = new Parameters (parameters);
164
165                         LambdaExpression selector = new LambdaExpression (
166                                 null, null, (TypeContainer)ec.TypeContainer, p, ec.CurrentBlock, loc);
167                         selector.Block = new SelectorBlock (ec.CurrentBlock, p, ti, loc);
168                         selector.Block.AddStatement (new Return (expr, loc));
169
170                         if (!ec.IsInProbingMode) {
171                                 selector.CreateAnonymousHelpers ();
172
173                                 // TODO: I am not sure where this should be done to work
174                                 // correctly with anonymous containerss and was called only once
175                                 // FIXME: selector.RootScope == null for nested anonymous
176                                 // methods only ?
177                                 if (selector.RootScope != null)
178                                         selector.RootScope.DefineType ();
179                         }
180                         
181                         return new Argument (selector);
182                 }
183
184                 public override void Emit (EmitContext ec)
185                 {
186                         throw new NotSupportedException ();
187                 }
188
189                 protected abstract string MethodName { get; }
190
191                 public virtual AQueryClause Next {
192                         set {
193                                 next = value;
194                         }
195                 }
196
197                 public AQueryClause Tail {
198                         get {
199                                 return next == null ? this : next.Tail;
200                         }
201                 }
202         }
203
204         //
205         // A query clause with an identifier (range variable)
206         //
207         public abstract class ARangeVariableQueryClause : AQueryClause
208         {
209                 LocatedToken variable;
210                 protected Expression element_selector;
211
212                 protected ARangeVariableQueryClause (LocatedToken variable, Expression expr, Location loc)
213                         : base (expr, loc)
214                 {
215                         this.variable = variable;
216                 }
217
218                 protected virtual void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
219                         ref Parameter parameter, TransparentIdentifiersScope ti)
220                 {
221                         args.Add (CreateSelectorArgument (ec, expr, parentParameter, ti));
222                         args.Add (CreateSelectorArgument (ec, element_selector,
223                                 new Parameter [] { parentParameter, parameter }, ti));
224                 }
225
226                 //
227                 // Customization for range variables which not only creates a lambda expression but
228                 // also builds a chain of range varible pairs
229                 //
230                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parentParameter, TransparentIdentifiersScope ti)
231                 {
232                         Parameter parameter = CreateBlockParameter (variable);
233
234                         if (next != null) {
235                                 //
236                                 // Builds transparent identifiers, each identifier includes its parent
237                                 // type at index 0, and new value at index 1. This is not valid for the
238                                 // first one which includes two values directly.
239                                 //
240                                 ArrayList transp_args = new ArrayList (2);
241                                 transp_args.Add (new AnonymousTypeParameter (parentParameter));
242                                 transp_args.Add (CreateAnonymousTypeVariable (parameter));
243                                 element_selector = new AnonymousTypeDeclaration (transp_args, (TypeContainer) ec.TypeContainer, loc);
244                         }
245
246                         ArrayList args = new ArrayList ();
247                         AddSelectorArguments (ec, args, parentParameter, ref parameter, ti);
248
249                         lSide = CreateQueryExpression (lSide, args);
250                         if (next != null) {
251                                 //
252                                 // Parameter identifiers go to the scope
253                                 //
254                                 string[] identifiers;
255                                 if (ti == null) {
256                                         identifiers = new string [] { parentParameter.Name, parameter.Name };
257                                 } else {
258                                         identifiers = new string [] { parameter.Name };
259                                 }
260
261                                 TransparentParameter tp = new TransparentParameter (loc);
262                                 return next.BuildQueryClause (ec, lSide, tp,
263                                         new TransparentIdentifiersScope (ti, tp, identifiers));
264                         }
265
266                         return lSide;
267                 }
268                 
269                 protected override void CloneTo (CloneContext clonectx, Expression target)
270                 {
271                         ARangeVariableQueryClause t = (ARangeVariableQueryClause) target;
272                         if (element_selector != null)
273                                 t.element_selector = element_selector.Clone (clonectx);
274                         base.CloneTo (clonectx, t);
275                 }
276                 
277                 //
278                 // For transparent identifiers, creates an instance of variable expression
279                 //
280                 protected virtual AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
281                 {
282                         return new AnonymousTypeParameter (parameter);
283                 }
284         }
285
286         public class QueryStartClause : AQueryClause
287         {
288                 public QueryStartClause (Expression expr)
289                         : base (expr, expr.Location)
290                 {
291                 }
292
293                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
294                 {
295                         return next.BuildQueryClause (ec, expr, parameter, ti);
296                 }
297
298                 public override Expression DoResolve (EmitContext ec)
299                 {
300                         Expression e = BuildQueryClause (ec, null, null, null);
301                         return e.Resolve (ec);
302                 }
303
304                 protected override string MethodName {
305                         get { throw new NotSupportedException (); }
306                 }
307         }
308
309         public class Cast : QueryStartClause
310         {
311                 // We don't have to clone cast type
312                 readonly Expression type_expr;
313
314                 public Cast (Expression type, Expression expr)
315                         : base (expr)
316                 {
317                         this.type_expr = type;
318                 }
319                 
320                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
321                 {
322                         lSide = CreateQueryExpression (expr, new TypeArguments (loc, type_expr), null);
323                         if (next != null)
324                                 return next.BuildQueryClause (ec, lSide, parameter, ti);
325
326                         return lSide;
327                 }
328
329                 protected override string MethodName {
330                         get { return "Cast"; }
331                 }
332         }
333
334         public class GroupBy : AQueryClause
335         {
336                 Expression element_selector;
337                 
338                 public GroupBy (Expression elementSelector, Expression keySelector, Location loc)
339                         : base (keySelector, loc)
340                 {
341                         this.element_selector = elementSelector;
342                 }
343
344                 public override Expression BuildQueryClause (EmitContext ec, Expression lSide, Parameter parameter, TransparentIdentifiersScope ti)
345                 {
346                         ArrayList args = new ArrayList (2);
347                         args.Add (CreateSelectorArgument (ec, expr, parameter, ti));
348
349                         // A query can be optimized when selector is not group by specific
350                         if (!element_selector.Equals (lSide))
351                                 args.Add (CreateSelectorArgument (ec, element_selector, parameter, ti));
352
353                         lSide = CreateQueryExpression (lSide, args);
354                         if (next != null)
355                                 return next.BuildQueryClause (ec, lSide, parameter, ti);
356
357                         return lSide;
358                 }
359         
360                 protected override void CloneTo (CloneContext clonectx, Expression target)
361                 {
362                         GroupBy t = (GroupBy) target;
363                         t.element_selector = element_selector.Clone (clonectx);
364                         base.CloneTo (clonectx, t);
365                 }
366
367                 protected override string MethodName {
368                         get { return "GroupBy"; }
369                 }
370         }
371
372         public class Join : ARangeVariableQueryClause
373         {
374                 Expression projection;
375                 Expression inner_selector, outer_selector;
376
377                 public Join (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector, Location loc)
378                         : base (variable, inner, loc)
379                 {
380                         this.outer_selector = outerSelector;
381                         this.inner_selector = innerSelector;
382                 }
383
384                 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
385                         ref Parameter parameter, TransparentIdentifiersScope ti)
386                 {
387                         args.Add (new Argument (expr));
388                         args.Add (CreateSelectorArgument (ec, outer_selector, parentParameter, ti));
389                         args.Add (CreateSelectorArgument (ec, inner_selector, parameter, ti));
390
391                         parameter = CreateResultSelectorParameter (parameter);
392                         if (projection == null) {
393                                 ArrayList join_args = new ArrayList (2);
394                                 join_args.Add (new AnonymousTypeParameter (parentParameter));
395                                 join_args.Add (new AnonymousTypeParameter (parameter));
396                                 projection = new AnonymousTypeDeclaration (join_args, (TypeContainer) ec.TypeContainer, loc);
397                         }
398
399                         args.Add (CreateSelectorArgument (ec, projection,
400                                 new Parameter [] { parentParameter, parameter }, ti));
401                 }
402         
403                 protected override void CloneTo (CloneContext clonectx, Expression target)
404                 {
405                         Join t = (Join) target;
406                         t.projection = projection.Clone (clonectx);
407                         t.inner_selector = inner_selector.Clone (clonectx);
408                         t.outer_selector = outer_selector.Clone (clonectx);
409                         base.CloneTo (clonectx, t);
410                 }       
411
412                 protected virtual Parameter CreateResultSelectorParameter (Parameter parameter)
413                 {
414                         return parameter;
415                 }
416
417                 public override AQueryClause Next {
418                         set {
419                                 // Use select as join projection
420                                 if (value is Select) {
421                                         projection = value.expr;
422                                         next = value.next;
423                                         return;
424                                 }
425
426                                 base.Next = value;
427                         }
428                 }
429
430                 protected override string MethodName {
431                         get { return "Join"; }
432                 }
433         }
434
435         public class GroupJoin : Join
436         {
437                 readonly LocatedToken into_variable;
438
439                 public GroupJoin (LocatedToken variable, Expression inner, Expression outerSelector, Expression innerSelector,
440                         LocatedToken into, Location loc)
441                         : base (variable, inner, outerSelector, innerSelector, loc)
442                 {
443                         this.into_variable = into;
444                 }
445
446                 protected override Parameter CreateResultSelectorParameter (Parameter parameter)
447                 {
448                         //
449                         // into variable is used as result selector and it's passed as
450                         // transparent identifiers to the next clause
451                         //
452                         return CreateBlockParameter (into_variable);
453                 }
454
455                 protected override string MethodName {
456                         get { return "GroupJoin"; }
457                 }
458         }
459
460         public class Let : ARangeVariableQueryClause
461         {
462                 class RangeAnonymousTypeParameter : AnonymousTypeParameter
463                 {
464                         readonly Parameter parameter;
465
466                         public RangeAnonymousTypeParameter (Expression initializer, Parameter parameter)
467                                 : base (initializer, parameter.Name, parameter.Location)
468                         {
469                                 this.parameter = parameter;
470                         }
471
472                         public override Expression DoResolve (EmitContext ec)
473                         {
474                                 Expression e = base.DoResolve (ec);
475                                 if (e != null) {
476                                         //
477                                         // Spread resolved initializer type
478                                         //
479                                         parameter.ParameterType = type;
480                                         parameter.Resolve (ec);
481                                 }
482
483                                 return e;
484                         }
485                 }
486
487                 public Let (LocatedToken variable, Expression expr, Location loc)
488                         : base (variable, expr, loc)
489                 {
490                 }
491
492                 protected override void AddSelectorArguments (EmitContext ec, ArrayList args, Parameter parentParameter,
493                         ref Parameter parameter, TransparentIdentifiersScope ti)
494                 {
495                         args.Add (CreateSelectorArgument (ec, element_selector, parentParameter, ti));
496                 }
497
498                 protected override AnonymousTypeParameter CreateAnonymousTypeVariable (Parameter parameter)
499                 {
500                         return new RangeAnonymousTypeParameter (expr, parameter);
501                 }
502
503                 protected override string MethodName {
504                         get { return "Select"; }
505                 }
506         }
507
508         public class Select : AQueryClause
509         {
510                 public Select (Expression expr, Location loc)
511                         : base (expr, loc)
512                 {
513                 }
514                 
515                 //
516                 // For queries like `from a orderby a select a'
517                 // the projection is transparent and select clause can be safely removed 
518                 //
519                 public bool IsRequired (Parameter parameter)
520                 {
521                         SimpleName sn = expr as SimpleName;
522                         if (sn == null)
523                                 return true;
524
525                         return sn.Name != parameter.Name;
526                 }
527
528                 protected override string MethodName {
529                         get { return "Select"; }
530                 }
531         }
532
533         public class SelectMany : ARangeVariableQueryClause
534         {
535                 public SelectMany (LocatedToken variable, Expression expr)
536                         : base (variable, expr, expr.Location)
537                 {
538                 }
539
540                 protected override string MethodName {
541                         get { return "SelectMany"; }
542                 }
543
544                 public override AQueryClause Next {
545                         set {
546                                 element_selector = value.expr;
547
548                                 // Can be optimized as SelectMany element selector
549                                 if (value is Select)
550                                         return;
551
552                                 next = value;
553                         }
554                 }
555         }
556
557         public class Where : AQueryClause
558         {
559                 public Where (Expression expr, Location loc)
560                         : base (expr, loc)
561                 {
562                 }
563
564                 protected override string MethodName {
565                         get { return "Where"; }
566                 }
567         }
568
569         public class OrderByAscending : AQueryClause
570         {
571                 public OrderByAscending (Expression expr)
572                         : base (expr, expr.Location)
573                 {
574                 }
575
576                 protected override string MethodName {
577                         get { return "OrderBy"; }
578                 }
579         }
580
581         public class OrderByDescending : AQueryClause
582         {
583                 public OrderByDescending (Expression expr)
584                         : base (expr, expr.Location)
585                 {
586                 }
587
588                 protected override string MethodName {
589                         get { return "OrderByDescending"; }
590                 }
591         }
592
593         public class ThenByAscending : OrderByAscending
594         {
595                 public ThenByAscending (Expression expr)
596                         : base (expr)
597                 {
598                 }
599
600                 protected override string MethodName {
601                         get { return "ThenBy"; }
602                 }
603         }
604
605         public class ThenByDescending : OrderByDescending
606         {
607                 public ThenByDescending (Expression expr)
608                         : base (expr)
609                 {
610                 }
611
612                 protected override string MethodName {
613                         get { return "ThenByDescending"; }
614                 }
615         }
616
617         class ImplicitQueryParameter : ImplicitLambdaParameter
618         {
619                 public sealed class ImplicitType : Expression
620                 {
621                         public static ImplicitType Instance = new ImplicitType ();
622
623                         private ImplicitType ()
624                         {
625                         }
626
627                         protected override void CloneTo (CloneContext clonectx, Expression target)
628                         {
629                                 // Nothing to clone
630                         }
631
632                         public override Expression DoResolve (EmitContext ec)
633                         {
634                                 throw new NotSupportedException ();
635                         }
636
637                         public override void Emit (EmitContext ec)
638                         {
639                                 throw new NotSupportedException ();
640                         }
641                 }
642
643                 public ImplicitQueryParameter (LocatedToken variable)
644                         : base (variable.Value, variable.Location)
645                 {
646                 }
647         }
648
649         //
650         // Transparent parameters are used to package up the intermediate results
651         // and pass them onto next clause
652         //
653         public class TransparentParameter : ImplicitLambdaParameter
654         {
655                 static int counter;
656                 const string ParameterNamePrefix = "<>__TranspIdent";
657
658                 public TransparentParameter (Location loc)
659                         : base (ParameterNamePrefix + counter++, loc)
660                 {
661                 }
662         }
663
664         //
665         // Transparent identifiers are stored in nested anonymous types, each type can contain
666         // up to 2 identifiers or 1 identifier and parent type.
667         //
668         public class TransparentIdentifiersScope
669         {
670                 readonly string [] identifiers;
671                 readonly TransparentIdentifiersScope parent;
672                 readonly TransparentParameter parameter;
673
674                 public TransparentIdentifiersScope (TransparentIdentifiersScope parent,
675                         TransparentParameter parameter, string [] identifiers)
676                 {
677                         this.parent = parent;
678                         this.parameter = parameter;
679                         this.identifiers = identifiers;
680                 }
681
682                 public MemberAccess GetIdentifier (string name)
683                 {
684                         TransparentIdentifiersScope ident = FindIdentifier (name);
685                         if (ident == null)
686                                 return null;
687
688                         return new MemberAccess (CreateIdentifierNestingExpression (ident), name);
689                 }
690
691                 TransparentIdentifiersScope FindIdentifier (string name)
692                 {
693                         foreach (string s in identifiers) {
694                                 if (s == name)
695                                         return this;
696                         }
697
698                         if (parent == null)
699                                 return null;
700
701                         return parent.FindIdentifier (name);
702                 }
703
704                 Expression CreateIdentifierNestingExpression (TransparentIdentifiersScope end)
705                 {
706                         Expression expr = new SimpleName (parameter.Name, parameter.Location);
707                         TransparentIdentifiersScope current = this;
708                         while (current != end)
709                         {
710                                 current = current.parent;
711                                 expr = new MemberAccess (expr, current.parameter.Name);
712                         }
713
714                         return expr;
715                 }
716         }
717
718         //
719         // Lambda expression block which contains transparent identifiers
720         //
721         class SelectorBlock : ToplevelBlock
722         {
723                 readonly TransparentIdentifiersScope transparent_identifiers;
724
725                 public SelectorBlock (Block block, Parameters parameters, 
726                         TransparentIdentifiersScope transparentIdentifiers, Location loc)
727                         : base (block, parameters, loc)
728                 {
729                         this.transparent_identifiers = transparentIdentifiers;
730                 }
731
732                 public override Expression GetTransparentIdentifier (string name)
733                 {
734                         Expression expr = null;
735                         if (transparent_identifiers != null)
736                                 expr = transparent_identifiers.GetIdentifier (name);
737
738                         if (expr != null || Container == null)
739                                 return expr;
740                         
741                         return Container.GetTransparentIdentifier (name);
742                 }
743         }
744
745         //
746         // This block is actually never used, it is used by parser only
747         //
748         public class QueryBlock : Block
749         {
750                 Hashtable range_variables = new Hashtable ();
751
752                 public QueryBlock (Block parent, Location start)
753                         : base (parent, start, Location.Null)
754                 {
755                 }
756
757                 protected override void AddVariable (LocalInfo li)
758                 {
759                         string name = li.Name;
760                         if (range_variables.Contains (name)) {
761                                 Location conflict = (Location)range_variables [name];
762                                 Report.SymbolRelatedToPreviousError (conflict, name);
763                                 Error_AlreadyDeclared (li.Location, name);
764                                 return;
765                         }
766
767                         range_variables.Add (name, li.Location);
768                 }
769                 
770                 protected override void Error_AlreadyDeclared (Location loc, string var, string reason)
771                 {
772                         Report.Error (1931, loc, "A range variable `{0}' conflicts with a previous declaration of `{0}'",
773                                 var);
774                 }
775                 
776                 protected override void Error_AlreadyDeclared (Location loc, string var)
777                 {
778                         Report.Error (1930, loc, "A range variable `{0}' has already been declared in this scope",
779                                 var);           
780                 }
781                 
782                 protected override void Error_AlreadyDeclaredTypeParameter (Location loc, string name)
783                 {
784                         Report.Error (1948, loc, "A range variable `{0}' conflicts with a method type parameter",
785                                 name);
786                 }
787         }
788 }